Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F665866
line.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
30 KB
Subscribers
None
line.js
View Options
{
function
mg_line_color_text
(
elem
,
line_id
,
{
color
,
colors
})
{
elem
.
classed
(
'mg-hover-line-color'
,
color
===
null
)
.
classed
(
`mg-hover-line
${
line_id
}
-color`
,
colors
===
null
)
.
attr
(
'fill'
,
colors
===
null
?
''
:
colors
[
line_id
-
1
]);
}
function
mg_line_graph_generators
(
args
,
plot
,
svg
)
{
mg_add_line_generator
(
args
,
plot
);
mg_add_area_generator
(
args
,
plot
);
mg_add_flat_line_generator
(
args
,
plot
);
mg_add_confidence_band_generator
(
args
,
plot
,
svg
);
}
function
mg_add_confidence_band_generator
(
args
,
plot
,
svg
)
{
plot
.
existing_band
=
svg
.
selectAll
(
'.mg-confidence-band'
).
nodes
();
if
(
args
.
show_confidence_band
)
{
plot
.
confidence_area
=
d3
.
area
()
.
defined
(
plot
.
line
.
defined
())
.
x
(
args
.
scalefns
.
xf
)
.
y0
(
d
=>
{
const
l
=
args
.
show_confidence_band
[
0
];
if
(
d
[
l
]
!=
undefined
)
{
return
args
.
scales
.
Y
(
d
[
l
]);
}
else
{
return
args
.
scales
.
Y
(
d
[
args
.
y_accessor
]);
}
})
.
y1
(
d
=>
{
const
u
=
args
.
show_confidence_band
[
1
];
if
(
d
[
u
]
!=
undefined
)
{
return
args
.
scales
.
Y
(
d
[
u
]);
}
else
{
return
args
.
scales
.
Y
(
d
[
args
.
y_accessor
]);
}
})
.
curve
(
args
.
interpolate
);
}
}
function
mg_add_area_generator
({
scalefns
,
scales
,
interpolate
,
flip_area_under_y_value
},
plot
)
{
const
areaBaselineValue
=
(
Number
.
isFinite
(
flip_area_under_y_value
))
?
scales
.
Y
(
flip_area_under_y_value
)
:
scales
.
Y
.
range
()[
0
];
plot
.
area
=
d3
.
area
()
.
defined
(
plot
.
line
.
defined
())
.
x
(
scalefns
.
xf
)
.
y0
(()
=>
{
return
areaBaselineValue
;
})
.
y1
(
scalefns
.
yf
)
.
curve
(
interpolate
);
}
function
mg_add_flat_line_generator
({
y_accessor
,
scalefns
,
scales
,
interpolate
},
plot
)
{
plot
.
flat_line
=
d3
.
line
()
.
defined
(
d
=>
(
d
[
'_missing'
]
===
undefined
||
d
[
'_missing'
]
!==
true
)
&&
d
[
y_accessor
]
!==
null
)
.
x
(
scalefns
.
xf
)
.
y
(()
=>
scales
.
Y
(
plot
.
data_median
))
.
curve
(
interpolate
);
}
function
mg_add_line_generator
({
scalefns
,
interpolate
,
missing_is_zero
,
y_accessor
},
plot
)
{
plot
.
line
=
d3
.
line
()
.
x
(
scalefns
.
xf
)
.
y
(
scalefns
.
yf
)
.
curve
(
interpolate
);
// if missing_is_zero is not set, then hide data points that fall in missing
// data ranges or that have been explicitly identified as missing in the
// data source.
if
(
!
missing_is_zero
)
{
// a line is defined if the _missing attrib is not set to true
// and the y-accessor is not null
plot
.
line
=
plot
.
line
.
defined
(
d
=>
(
d
[
'_missing'
]
===
undefined
||
d
[
'_missing'
]
!==
true
)
&&
d
[
y_accessor
]
!==
null
);
}
}
function
mg_add_confidence_band
(
{
show_confidence_band
,
transition_on_update
,
data
,
target
},
plot
,
svg
,
which_line
)
{
if
(
show_confidence_band
)
{
let
confidenceBand
;
if
(
svg
.
select
(
`.mg-confidence-band-
${
which_line
}
`
).
empty
())
{
svg
.
append
(
'path'
)
.
attr
(
'class'
,
`mg-confidence-band mg-confidence-band-
${
which_line
}
`
);
}
// transition this line's confidence band
confidenceBand
=
svg
.
select
(
`.mg-confidence-band-
${
which_line
}
`
);
confidenceBand
.
transition
()
.
duration
(()
=>
(
transition_on_update
)
?
1000
:
0
)
.
attr
(
'd'
,
plot
.
confidence_area
(
data
[
which_line
-
1
]))
.
attr
(
'clip-path'
,
`url(#mg-plot-window-
${
mg_target_ref
(
target
)
}
)`
);
}
}
function
mg_add_area
({
data
,
target
,
colors
},
plot
,
svg
,
which_line
,
line_id
)
{
const
areas
=
svg
.
selectAll
(
`.mg-main-area.mg-area
${
line_id
}
`
);
if
(
plot
.
display_area
)
{
// if area already exists, transition it
if
(
!
areas
.
empty
())
{
svg
.
node
().
appendChild
(
areas
.
node
());
areas
.
transition
()
.
duration
(
plot
.
update_transition_duration
)
.
attr
(
'd'
,
plot
.
area
(
data
[
which_line
]))
.
attr
(
'clip-path'
,
`url(#mg-plot-window-
${
mg_target_ref
(
target
)
}
)`
);
}
else
{
// otherwise, add the area
svg
.
append
(
'path'
)
.
classed
(
'mg-main-area'
,
true
)
.
classed
(
`mg-area
${
line_id
}
`
,
true
)
.
classed
(
'mg-area-color'
,
colors
===
null
)
.
classed
(
`mg-area
${
line_id
}
-color`
,
colors
===
null
)
.
attr
(
'd'
,
plot
.
area
(
data
[
which_line
]))
.
attr
(
'fill'
,
colors
===
null
?
''
:
colors
[
line_id
-
1
])
.
attr
(
'clip-path'
,
`url(#mg-plot-window-
${
mg_target_ref
(
target
)
}
)`
);
}
}
else
if
(
!
areas
.
empty
())
{
areas
.
remove
();
}
}
function
mg_default_color_for_path
(
this_path
,
line_id
)
{
this_path
.
classed
(
'mg-line-color'
,
true
)
.
classed
(
`mg-line
${
line_id
}
-color`
,
true
);
}
function
mg_color_line
({
colors
},
this_path
,
which_line
,
line_id
)
{
if
(
colors
)
{
// for now, if args.colors is not an array, then keep moving as if nothing happened.
// if args.colors is not long enough, default to the usual line_id color.
if
(
colors
.
constructor
===
Array
)
{
this_path
.
attr
(
'stroke'
,
colors
[
which_line
]);
if
(
colors
.
length
<
which_line
+
1
)
{
// Go with default coloring.
// this_path.classed('mg-line' + (line_id) + '-color', true);
mg_default_color_for_path
(
this_path
,
line_id
);
}
}
else
{
// this_path.classed('mg-line' + (line_id) + '-color', true);
mg_default_color_for_path
(
this_path
,
line_id
);
}
}
else
{
// this is the typical workflow
// this_path.classed('mg-line' + (line_id) + '-color', true);
mg_default_color_for_path
(
this_path
,
line_id
);
}
}
function
mg_add_line_element
({
animate_on_load
,
data
,
y_accessor
,
target
},
plot
,
this_path
,
which_line
)
{
if
(
animate_on_load
)
{
plot
.
data_median
=
d3
.
median
(
data
[
which_line
],
d
=>
d
[
y_accessor
]);
this_path
.
attr
(
'd'
,
plot
.
flat_line
(
data
[
which_line
]))
.
transition
()
.
duration
(
1000
)
.
attr
(
'd'
,
plot
.
line
(
data
[
which_line
]))
.
attr
(
'clip-path'
,
`url(#mg-plot-window-
${
mg_target_ref
(
target
)
}
)`
);
}
else
{
// or just add the line
this_path
.
attr
(
'd'
,
plot
.
line
(
data
[
which_line
]))
.
attr
(
'clip-path'
,
`url(#mg-plot-window-
${
mg_target_ref
(
target
)
}
)`
);
}
}
function
mg_add_line
(
args
,
plot
,
svg
,
existing_line
,
which_line
,
line_id
)
{
if
(
!
existing_line
.
empty
())
{
svg
.
node
().
appendChild
(
existing_line
.
node
());
const
lineTransition
=
existing_line
.
transition
()
.
duration
(
plot
.
update_transition_duration
);
if
(
!
plot
.
display_area
&&
args
.
transition_on_update
&&
!
args
.
missing_is_hidden
)
{
lineTransition
.
attrTween
(
'd'
,
path_tween
(
plot
.
line
(
args
.
data
[
which_line
]),
4
));
}
else
{
lineTransition
.
attr
(
'd'
,
plot
.
line
(
args
.
data
[
which_line
]));
}
}
else
{
// otherwise...
// if we're animating on load, animate the line from its median value
const
this_path
=
svg
.
append
(
'path'
)
.
attr
(
'class'
,
`mg-main-line mg-line
${
line_id
}
`
);
mg_color_line
(
args
,
this_path
,
which_line
,
line_id
);
mg_add_line_element
(
args
,
plot
,
this_path
,
which_line
);
}
}
function
mg_add_legend_element
(
args
,
plot
,
which_line
,
line_id
)
{
let
this_legend
;
if
(
args
.
legend
)
{
if
(
is_array
(
args
.
legend
))
{
this_legend
=
args
.
legend
[
which_line
];
}
else
if
(
is_function
(
args
.
legend
))
{
this_legend
=
args
.
legend
(
args
.
data
[
which_line
]);
}
if
(
args
.
legend_target
)
{
if
(
args
.
colors
&&
args
.
colors
.
constructor
===
Array
)
{
plot
.
legend_text
=
`<span style='color:
${
args
.
colors
[
which_line
]
}
'>—
${
this_legend
}
</span>
${
plot
.
legend_text
}
`
;
}
else
{
plot
.
legend_text
=
`<span class='mg-line
${
line_id
}
-legend-color'>—
${
this_legend
}
</span>
${
plot
.
legend_text
}
`
;
}
}
else
{
let
anchor_point
,
anchor_orientation
,
dx
;
if
(
args
.
y_axis_position
===
'left'
)
{
anchor_point
=
args
.
data
[
which_line
][
args
.
data
[
which_line
].
length
-
1
];
anchor_orientation
=
'start'
;
dx
=
args
.
buffer
;
}
else
{
anchor_point
=
args
.
data
[
which_line
][
0
];
anchor_orientation
=
'end'
;
dx
=
-
args
.
buffer
;
}
const
legend_text
=
plot
.
legend_group
.
append
(
'svg:text'
)
.
attr
(
'x'
,
args
.
scalefns
.
xf
(
anchor_point
))
.
attr
(
'dx'
,
dx
)
.
attr
(
'y'
,
args
.
scalefns
.
yf
(
anchor_point
))
.
attr
(
'dy'
,
'.35em'
)
.
attr
(
'font-size'
,
10
)
.
attr
(
'text-anchor'
,
anchor_orientation
)
.
attr
(
'font-weight'
,
'300'
)
.
text
(
this_legend
);
if
(
args
.
colors
&&
args
.
colors
.
constructor
===
Array
)
{
if
(
args
.
colors
.
length
<
which_line
+
1
)
{
legend_text
.
classed
(
`mg-line
${
line_id
}
-legend-color`
,
true
);
}
else
{
legend_text
.
attr
(
'fill'
,
args
.
colors
[
which_line
]);
}
}
else
{
legend_text
.
classed
(
'mg-line-legend-color'
,
true
)
.
classed
(
`mg-line
${
line_id
}
-legend-color`
,
true
);
}
mg_prevent_vertical_overlap
(
plot
.
legend_group
.
selectAll
(
'.mg-line-legend text'
).
nodes
(),
args
);
}
}
}
function
mg_plot_legend_if_legend_target
(
target
,
legend
)
{
if
(
target
)
d3
.
select
(
target
).
html
(
legend
);
}
function
mg_add_legend_group
({
legend
},
plot
,
svg
)
{
if
(
legend
)
plot
.
legend_group
=
mg_add_g
(
svg
,
'mg-line-legend'
);
}
function
mg_remove_existing_line_rollover_elements
(
svg
)
{
// remove the old rollovers if they already exist
mg_selectAll_and_remove
(
svg
,
'.mg-rollover-rect'
);
mg_selectAll_and_remove
(
svg
,
'.mg-voronoi'
);
// remove the old rollover text and circle if they already exist
mg_selectAll_and_remove
(
svg
,
'.mg-active-datapoint'
);
mg_selectAll_and_remove
(
svg
,
'.mg-line-rollover-circle'
);
//mg_selectAll_and_remove(svg, '.mg-active-datapoint-container');
}
function
mg_add_rollover_circle
({
data
,
colors
},
svg
)
{
// append circle
const
circle
=
svg
.
selectAll
(
'.mg-line-rollover-circle'
)
.
data
(
data
)
.
enter
().
append
(
'circle'
)
.
attr
(
'cx'
,
0
)
.
attr
(
'cy'
,
0
)
.
attr
(
'r'
,
0
);
if
(
colors
&&
colors
.
constructor
===
Array
)
{
circle
.
attr
(
'class'
,
({
__line_id__
})
=>
`mg-line
${
__line_id__
}
`
)
.
attr
(
'fill'
,
(
d
,
i
)
=>
colors
[
i
])
.
attr
(
'stroke'
,
(
d
,
i
)
=>
colors
[
i
]);
}
else
{
circle
.
attr
(
'class'
,
({
__line_id__
},
i
)
=>
[
`mg-line
${
__line_id__
}
`
,
`mg-line
${
__line_id__
}
-color`
,
`mg-area
${
__line_id__
}
-color`
].
join
(
' '
));
}
circle
.
classed
(
'mg-line-rollover-circle'
,
true
);
}
function
mg_set_unique_line_id_for_each_series
({
data
,
custom_line_color_map
})
{
// update our data by setting a unique line id for each series
// increment from 1... unless we have a custom increment series
for
(
let
i
=
0
;
i
<
data
.
length
;
i
++
)
{
data
[
i
].
forEach
(
datum
=>
{
datum
.
__index__
=
i
+
1
;
datum
.
__line_id__
=
(
custom_line_color_map
.
length
>
0
)
?
custom_line_color_map
[
i
]
:
i
+
1
;
});
}
}
function
mg_nest_data_for_voronoi
({
data
})
{
return
d3
.
merge
(
data
);
}
function
mg_line_class_string
(
args
)
{
return
d
=>
{
let
class_string
;
if
(
args
.
linked
)
{
const
v
=
d
[
args
.
x_accessor
];
const
formatter
=
MG
.
time_format
(
args
.
utc_time
,
args
.
linked_format
);
// only format when x-axis is date
const
id
=
(
typeof
v
===
'number'
)
?
(
d
.
__line_id__
-
1
)
:
formatter
(
v
);
class_string
=
`roll_
${
id
}
mg-line
${
d
.
__line_id__
}
`
;
if
(
args
.
color
===
null
)
{
class_string
+=
` mg-line
${
d
.
__line_id__
}
-color`
;
}
return
class_string
;
}
else
{
class_string
=
`mg-line
${
d
.
__line_id__
}
`
;
if
(
args
.
color
===
null
)
class_string
+=
` mg-line
${
d
.
__line_id__
}
-color`
;
return
class_string
;
}
};
}
function
mg_add_voronoi_rollover
(
args
,
svg
,
rollover_on
,
rollover_off
,
rollover_move
,
rollover_click
)
{
const
voronoi
=
d3
.
voronoi
()
.
x
(
d
=>
args
.
scales
.
X
(
d
[
args
.
x_accessor
]).
toFixed
(
2
))
.
y
(
d
=>
args
.
scales
.
Y
(
d
[
args
.
y_accessor
]).
toFixed
(
2
))
.
extent
([
[
args
.
buffer
,
args
.
buffer
+
(
args
.
title
?
args
.
title_y_position
:
0
)],
[
args
.
width
-
args
.
buffer
,
args
.
height
-
args
.
buffer
]
]);
const
g
=
mg_add_g
(
svg
,
'mg-voronoi'
);
g
.
selectAll
(
'path'
)
.
data
(
voronoi
.
polygons
(
mg_nest_data_for_voronoi
(
args
)))
.
enter
()
.
append
(
'path'
)
.
filter
(
d
=>
d
!==
undefined
&&
d
.
length
>
0
)
.
attr
(
'd'
,
d
=>
d
==
null
?
null
:
`M
${
d
.
join
(
'L'
)
}
Z`
)
.
datum
(
d
=>
d
==
null
?
null
:
d
.
data
)
// because of d3.voronoi, reassign d
.
attr
(
'class'
,
mg_line_class_string
(
args
))
.
on
(
'click'
,
rollover_click
)
.
on
(
'mouseover'
,
rollover_on
)
.
on
(
'mouseout'
,
rollover_off
)
.
on
(
'mousemove'
,
rollover_move
);
mg_configure_voronoi_rollover
(
args
,
svg
);
}
function
nest_data_for_aggregate_rollover
({
x_accessor
,
data
,
x_sort
})
{
const
data_nested
=
d3
.
nest
()
.
key
(
d
=>
d
[
x_accessor
])
.
entries
(
d3
.
merge
(
data
));
data_nested
.
forEach
(
entry
=>
{
const
datum
=
entry
.
values
[
0
];
entry
.
key
=
datum
[
x_accessor
];
});
if
(
x_sort
)
{
return
data_nested
.
sort
((
a
,
b
)
=>
new
Date
(
a
.
key
)
-
new
Date
(
b
.
key
));
}
else
{
return
data_nested
;
}
}
function
mg_add_aggregate_rollover
(
args
,
svg
,
rollover_on
,
rollover_off
,
rollover_move
,
rollover_click
)
{
// Undo the keys getting coerced to strings, by setting the keys from the values
// This is necessary for when we have X axis keys that are things like
const
data_nested
=
nest_data_for_aggregate_rollover
(
args
);
const
xf
=
data_nested
.
map
(({
key
})
=>
args
.
scales
.
X
(
key
));
const
g
=
svg
.
append
(
'g'
)
.
attr
(
'class'
,
'mg-rollover-rect'
);
g
.
selectAll
(
'.mg-rollover-rects'
)
.
data
(
data_nested
).
enter
()
.
append
(
'rect'
)
.
attr
(
'x'
,
(
d
,
i
)
=>
{
if
(
xf
.
length
===
1
)
return
mg_get_plot_left
(
args
);
else
if
(
i
===
0
)
return
xf
[
i
].
toFixed
(
2
);
else
return
((
xf
[
i
-
1
]
+
xf
[
i
])
/
2
).
toFixed
(
2
);
})
.
attr
(
'y'
,
args
.
top
)
.
attr
(
'width'
,
(
d
,
i
)
=>
{
if
(
xf
.
length
===
1
)
return
mg_get_plot_right
(
args
);
else
if
(
i
===
0
)
return
((
xf
[
i
+
1
]
-
xf
[
i
])
/
2
).
toFixed
(
2
);
else
if
(
i
===
xf
.
length
-
1
)
return
((
xf
[
i
]
-
xf
[
i
-
1
])
/
2
).
toFixed
(
2
);
else
return
((
xf
[
i
+
1
]
-
xf
[
i
-
1
])
/
2
).
toFixed
(
2
);
})
.
attr
(
'class'
,
({
values
})
=>
{
let
line_classes
=
values
.
map
(({
__line_id__
})
=>
{
let
lc
=
mg_line_class
(
__line_id__
);
if
(
args
.
colors
===
null
)
lc
+=
`
${
mg_line_color_class
(
__line_id__
)
}
`
;
return
lc
;
}).
join
(
' '
);
if
(
args
.
linked
&&
values
.
length
>
0
)
{
line_classes
+=
`
${
mg_rollover_id_class
(
mg_rollover_format_id
(
values
[
0
],
args
))
}
`
;
}
return
line_classes
;
})
.
attr
(
'height'
,
args
.
height
-
args
.
bottom
-
args
.
top
-
args
.
buffer
)
.
attr
(
'opacity'
,
0
)
.
on
(
'click'
,
rollover_click
)
.
on
(
'mouseover'
,
rollover_on
)
.
on
(
'mouseout'
,
rollover_off
)
.
on
(
'mousemove'
,
rollover_move
);
mg_configure_aggregate_rollover
(
args
,
svg
);
}
function
mg_configure_singleton_rollover
({
data
},
svg
)
{
svg
.
select
(
'.mg-rollover-rect rect'
)
.
on
(
'mouseover'
)(
data
[
0
][
0
],
0
);
}
function
mg_configure_voronoi_rollover
({
data
,
custom_line_color_map
},
svg
)
{
for
(
let
i
=
0
;
i
<
data
.
length
;
i
++
)
{
let
j
=
i
+
1
;
if
(
custom_line_color_map
.
length
>
0
&&
custom_line_color_map
[
i
]
!==
undefined
)
{
j
=
custom_line_color_map
[
i
];
}
if
(
data
[
i
].
length
===
1
&&
!
svg
.
selectAll
(
`.mg-voronoi .mg-line
${
j
}
`
).
empty
())
{
svg
.
selectAll
(
`.mg-voronoi .mg-line
${
j
}
`
)
.
on
(
'mouseover'
)(
data
[
i
][
0
],
0
);
svg
.
selectAll
(
`.mg-voronoi .mg-line
${
j
}
`
)
.
on
(
'mouseout'
)(
data
[
i
][
0
],
0
);
}
}
}
function
mg_line_class
(
line_id
)
{
return
`mg-line
${
line_id
}
`
;
}
function
mg_line_color_class
(
line_id
)
{
return
`mg-line
${
line_id
}
-color`
;
}
function
mg_rollover_id_class
(
id
)
{
return
`roll_
${
id
}
`
;
}
function
mg_rollover_format_id
(
d
,
{
x_accessor
,
utc_time
,
linked_format
})
{
const
v
=
d
[
x_accessor
];
const
formatter
=
MG
.
time_format
(
utc_time
,
linked_format
);
// only format when x-axis is date
return
(
typeof
v
===
'number'
)
?
v
.
toString
().
replace
(
'.'
,
'_'
)
:
formatter
(
v
);
}
function
mg_add_single_line_rollover
(
args
,
svg
,
rollover_on
,
rollover_off
,
rollover_move
,
rollover_click
)
{
// set to 1 unless we have a custom increment series
let
line_id
=
1
;
if
(
args
.
custom_line_color_map
.
length
>
0
)
{
line_id
=
args
.
custom_line_color_map
[
0
];
}
const
g
=
svg
.
append
(
'g'
)
.
attr
(
'class'
,
'mg-rollover-rect'
);
const
xf
=
args
.
data
[
0
].
map
(
args
.
scalefns
.
xf
);
g
.
selectAll
(
'.mg-rollover-rects'
)
.
data
(
args
.
data
[
0
]).
enter
()
.
append
(
'rect'
)
.
attr
(
'class'
,
(
d
,
i
)
=>
{
let
cl
=
`
${
mg_line_color_class
(
line_id
)
}
${
mg_line_class
(
d
.
__line_id__
)
}
`
;
if
(
args
.
linked
)
cl
+=
`
${
cl
}
${
mg_rollover_id_class
(
mg_rollover_format_id
(
d
,
args
))
}
`
;
return
cl
;
})
.
attr
(
'x'
,
(
d
,
i
)
=>
{
// if data set is of length 1
if
(
xf
.
length
===
1
)
return
mg_get_plot_left
(
args
);
else
if
(
i
===
0
)
return
xf
[
i
].
toFixed
(
2
);
else
return
((
xf
[
i
-
1
]
+
xf
[
i
])
/
2
).
toFixed
(
2
);
})
.
attr
(
'y'
,
(
d
,
i
)
=>
(
args
.
data
.
length
>
1
)
?
args
.
scalefns
.
yf
(
d
)
-
6
// multi-line chart sensitivity
:
args
.
top
)
.
attr
(
'width'
,
(
d
,
i
)
=>
{
// if data set is of length 1
if
(
xf
.
length
===
1
)
return
mg_get_plot_right
(
args
);
else
if
(
i
===
0
)
return
((
xf
[
i
+
1
]
-
xf
[
i
])
/
2
).
toFixed
(
2
);
else
if
(
i
===
xf
.
length
-
1
)
return
((
xf
[
i
]
-
xf
[
i
-
1
])
/
2
).
toFixed
(
2
);
else
return
((
xf
[
i
+
1
]
-
xf
[
i
-
1
])
/
2
).
toFixed
(
2
);
})
.
attr
(
'height'
,
(
d
,
i
)
=>
(
args
.
data
.
length
>
1
)
?
12
// multi-line chart sensitivity
:
args
.
height
-
args
.
bottom
-
args
.
top
-
args
.
buffer
)
.
attr
(
'opacity'
,
0
)
.
on
(
'click'
,
rollover_click
)
.
on
(
'mouseover'
,
rollover_on
)
.
on
(
'mouseout'
,
rollover_off
)
.
on
(
'mousemove'
,
rollover_move
);
if
(
mg_is_singleton
(
args
))
{
mg_configure_singleton_rollover
(
args
,
svg
);
}
}
function
mg_configure_aggregate_rollover
({
data
},
svg
)
{
const
rect
=
svg
.
selectAll
(
'.mg-rollover-rect rect'
);
const
rect_first
=
rect
.
nodes
()[
0
][
0
]
||
rect
.
nodes
()[
0
];
if
(
data
.
filter
(({
length
})
=>
length
===
1
).
length
>
0
)
{
rect
.
on
(
'mouseover'
)(
rect_first
.
__data__
,
0
);
}
}
function
mg_is_standard_multiline
({
data
,
aggregate_rollover
})
{
return
data
.
length
>
1
&&
!
aggregate_rollover
;
}
function
mg_is_aggregated_rollover
({
data
,
aggregate_rollover
})
{
return
data
.
length
>
1
&&
aggregate_rollover
;
}
function
mg_is_singleton
({
data
})
{
return
data
.
length
===
1
&&
data
[
0
].
length
===
1
;
}
function
mg_draw_all_line_elements
(
args
,
plot
,
svg
)
{
mg_remove_dangling_bands
(
plot
,
svg
);
// If option activated, remove existing active points if exists
if
(
args
.
active_point_on_lines
)
{
svg
.
selectAll
(
'circle.mg-shown-active-point'
).
remove
();
}
for
(
let
i
=
args
.
data
.
length
-
1
;
i
>=
0
;
i
--
)
{
const
this_data
=
args
.
data
[
i
];
// passing the data for the current line
MG
.
call_hook
(
'line.before_each_series'
,
[
this_data
,
args
]);
// override increment if we have a custom increment series
let
line_id
=
i
+
1
;
if
(
args
.
custom_line_color_map
.
length
>
0
)
{
line_id
=
args
.
custom_line_color_map
[
i
];
}
args
.
data
[
i
].
__line_id__
=
line_id
;
// If option activated, add active points for each lines
if
(
args
.
active_point_on_lines
)
{
svg
.
selectAll
(
'circle-'
+
line_id
)
.
data
(
args
.
data
[
i
])
.
enter
()
.
filter
((
d
)
=>
{
return
d
[
args
.
active_point_accessor
];
})
.
append
(
'circle'
)
.
attr
(
'class'
,
'mg-area'
+
(
line_id
)
+
'-color mg-shown-active-point'
)
.
attr
(
'cx'
,
args
.
scalefns
.
xf
)
.
attr
(
'cy'
,
args
.
scalefns
.
yf
)
.
attr
(
'r'
,
()
=>
{
return
args
.
active_point_size
;
});
}
const
existing_line
=
svg
.
select
(
`path.mg-main-line.mg-line
${
line_id
}
`
);
if
(
this_data
.
length
===
0
)
{
existing_line
.
remove
();
continue
;
}
mg_add_confidence_band
(
args
,
plot
,
svg
,
line_id
);
if
(
Array
.
isArray
(
args
.
area
))
{
if
(
args
.
area
[
line_id
-
1
])
{
mg_add_area
(
args
,
plot
,
svg
,
i
,
line_id
);
}
}
else
{
mg_add_area
(
args
,
plot
,
svg
,
i
,
line_id
);
}
mg_add_line
(
args
,
plot
,
svg
,
existing_line
,
i
,
line_id
);
mg_add_legend_element
(
args
,
plot
,
i
,
line_id
);
// passing the data for the current line
MG
.
call_hook
(
'line.after_each_series'
,
[
this_data
,
existing_line
,
args
]);
}
}
function
mg_remove_dangling_bands
({
existing_band
},
svg
)
{
if
(
existing_band
[
0
]
&&
existing_band
[
0
].
length
>
svg
.
selectAll
(
'.mg-main-line'
).
node
().
length
)
{
svg
.
selectAll
(
'.mg-confidence-band'
).
remove
();
}
}
function
mg_line_main_plot
(
args
)
{
const
plot
=
{};
const
svg
=
mg_get_svg_child_of
(
args
.
target
);
// remove any old legends if they exist
mg_selectAll_and_remove
(
svg
,
'.mg-line-legend'
);
mg_add_legend_group
(
args
,
plot
,
svg
);
plot
.
data_median
=
0
;
plot
.
update_transition_duration
=
(
args
.
transition_on_update
)
?
1000
:
0
;
plot
.
display_area
=
(
args
.
area
&&
!
args
.
use_data_y_min
&&
args
.
data
.
length
<=
1
&&
args
.
aggregate_rollover
===
false
)
||
(
Array
.
isArray
(
args
.
area
)
&&
args
.
area
.
length
>
0
);
plot
.
legend_text
=
''
;
mg_line_graph_generators
(
args
,
plot
,
svg
);
plot
.
existing_band
=
svg
.
selectAll
(
'.mg-confidence-band'
).
nodes
();
// should we continue with the default line render? A `line.all_series` hook should return false to prevent the default.
const
continueWithDefault
=
MG
.
call_hook
(
'line.before_all_series'
,
[
args
]);
if
(
continueWithDefault
!==
false
)
{
mg_draw_all_line_elements
(
args
,
plot
,
svg
);
}
mg_plot_legend_if_legend_target
(
args
.
legend_target
,
plot
.
legend_text
);
}
function
mg_line_rollover_setup
(
args
,
graph
)
{
const
svg
=
mg_get_svg_child_of
(
args
.
target
);
if
(
args
.
showActivePoint
&&
svg
.
selectAll
(
'.mg-active-datapoint-container'
).
nodes
().
length
===
0
)
{
mg_add_g
(
svg
,
'mg-active-datapoint-container'
);
}
mg_remove_existing_line_rollover_elements
(
svg
);
mg_add_rollover_circle
(
args
,
svg
);
mg_set_unique_line_id_for_each_series
(
args
);
if
(
mg_is_standard_multiline
(
args
))
{
mg_add_voronoi_rollover
(
args
,
svg
,
graph
.
rolloverOn
(
args
),
graph
.
rolloverOff
(
args
),
graph
.
rolloverMove
(
args
),
graph
.
rolloverClick
(
args
));
}
else
if
(
mg_is_aggregated_rollover
(
args
))
{
mg_add_aggregate_rollover
(
args
,
svg
,
graph
.
rolloverOn
(
args
),
graph
.
rolloverOff
(
args
),
graph
.
rolloverMove
(
args
),
graph
.
rolloverClick
(
args
));
}
else
{
mg_add_single_line_rollover
(
args
,
svg
,
graph
.
rolloverOn
(
args
),
graph
.
rolloverOff
(
args
),
graph
.
rolloverMove
(
args
),
graph
.
rolloverClick
(
args
));
}
}
function
mg_update_rollover_circle
(
args
,
svg
,
d
)
{
if
(
args
.
aggregate_rollover
&&
args
.
data
.
length
>
1
)
{
// hide the circles in case a non-contiguous series is present
svg
.
selectAll
(
'circle.mg-line-rollover-circle'
)
.
style
(
'opacity'
,
0
);
d
.
values
.
forEach
((
datum
,
index
,
list
)
=>
{
if
(
args
.
missing_is_hidden
&&
list
[
index
][
'_missing'
])
{
return
;
}
if
(
mg_data_in_plot_bounds
(
datum
,
args
))
mg_update_aggregate_rollover_circle
(
args
,
svg
,
datum
);
});
}
else
if
((
args
.
missing_is_hidden
&&
d
[
'_missing'
])
||
d
[
args
.
y_accessor
]
===
null
)
{
// disable rollovers for hidden parts of the line
// recall that hidden parts are missing data ranges and possibly also
// data points that have been explicitly identified as missing
return
;
}
else
{
// show circle on mouse-overed rect
if
(
mg_data_in_plot_bounds
(
d
,
args
))
{
mg_update_generic_rollover_circle
(
args
,
svg
,
d
);
}
}
}
function
mg_update_aggregate_rollover_circle
({
scales
,
x_accessor
,
y_accessor
,
point_size
},
svg
,
datum
)
{
svg
.
select
(
`circle.mg-line-rollover-circle.mg-line
${
datum
.
__line_id__
}
`
)
.
attr
(
'cx'
,
scales
.
X
(
datum
[
x_accessor
]).
toFixed
(
2
))
.
attr
(
'cy'
,
scales
.
Y
(
datum
[
y_accessor
]).
toFixed
(
2
))
.
attr
(
'r'
,
point_size
)
.
style
(
'opacity'
,
1
);
}
function
mg_update_generic_rollover_circle
({
scales
,
x_accessor
,
y_accessor
,
point_size
},
svg
,
d
)
{
svg
.
selectAll
(
`circle.mg-line-rollover-circle.mg-line
${
d
.
__line_id__
}
`
)
.
classed
(
'mg-line-rollover-circle'
,
true
)
.
attr
(
'cx'
,
()
=>
scales
.
X
(
d
[
x_accessor
]).
toFixed
(
2
))
.
attr
(
'cy'
,
()
=>
scales
.
Y
(
d
[
y_accessor
]).
toFixed
(
2
))
.
attr
(
'r'
,
point_size
)
.
style
(
'opacity'
,
1
);
}
function
mg_trigger_linked_mouseovers
(
args
,
d
,
i
)
{
if
(
args
.
linked
&&
!
MG
.
globals
.
link
)
{
MG
.
globals
.
link
=
true
;
if
(
!
args
.
aggregate_rollover
||
d
[
args
.
y_accessor
]
!==
undefined
||
(
d
.
values
&&
d
.
values
.
length
>
0
))
{
const
datum
=
d
.
values
?
d
.
values
[
0
]
:
d
;
const
id
=
mg_rollover_format_id
(
datum
,
args
);
// trigger mouseover on matching line in .linked charts
d3
.
selectAll
(
`.
${
mg_line_class
(
datum
.
__line_id__
)
}
.
${
mg_rollover_id_class
(
id
)
}
`
)
.
each
(
function
(
d
)
{
d3
.
select
(
this
)
.
on
(
'mouseover'
)(
d
,
i
);
});
}
}
}
function
mg_trigger_linked_mouseouts
({
linked
,
utc_time
,
linked_format
,
x_accessor
},
d
,
i
)
{
if
(
linked
&&
MG
.
globals
.
link
)
{
MG
.
globals
.
link
=
false
;
const
formatter
=
MG
.
time_format
(
utc_time
,
linked_format
);
const
datums
=
d
.
values
?
d
.
values
:
[
d
];
datums
.
forEach
(
datum
=>
{
const
v
=
datum
[
x_accessor
];
const
id
=
(
typeof
v
===
'number'
)
?
i
:
formatter
(
v
);
// trigger mouseout on matching line in .linked charts
d3
.
selectAll
(
`.roll_
${
id
}
`
)
.
each
(
function
(
d
)
{
d3
.
select
(
this
)
.
on
(
'mouseout'
)(
d
);
});
});
}
}
function
mg_remove_active_data_points_for_aggregate_rollover
(
args
,
svg
)
{
svg
.
selectAll
(
'circle.mg-line-rollover-circle'
).
filter
(({
length
})
=>
length
>
1
)
.
style
(
'opacity'
,
0
);
}
function
mg_remove_active_data_points_for_generic_rollover
({
custom_line_color_map
,
data
},
svg
,
line_id
)
{
svg
.
selectAll
(
`circle.mg-line-rollover-circle.mg-line
${
line_id
}
`
)
.
style
(
'opacity'
,
()
=>
{
let
id
=
line_id
-
1
;
if
(
custom_line_color_map
.
length
>
0
&&
custom_line_color_map
.
indexOf
(
line_id
)
!==
undefined
)
{
id
=
custom_line_color_map
.
indexOf
(
line_id
);
}
if
(
data
[
id
].
length
===
1
)
{
return
1
;
}
else
{
return
0
;
}
});
}
function
mg_remove_active_text
(
svg
)
{
svg
.
select
(
'.mg-active-datapoint'
).
text
(
''
);
}
function
lineChart
(
args
)
{
this
.
init
=
function
(
args
)
{
this
.
args
=
args
;
if
(
!
args
.
data
||
args
.
data
.
length
===
0
)
{
args
.
internal_error
=
'No data was supplied'
;
internal_error
(
args
);
return
this
;
}
else
{
args
.
internal_error
=
undefined
;
}
raw_data_transformation
(
args
);
process_line
(
args
);
MG
.
call_hook
(
'line.before_destroy'
,
this
);
init
(
args
);
// TODO incorporate markers into calculation of x scales
new
MG
.
scale_factory
(
args
)
.
namespace
(
'x'
)
.
numericalDomainFromData
()
.
numericalRange
(
'bottom'
);
const
baselines
=
(
args
.
baselines
||
[]).
map
(
d
=>
d
[
args
.
y_accessor
]);
new
MG
.
scale_factory
(
args
)
.
namespace
(
'y'
)
.
zeroBottom
(
true
)
.
inflateDomain
(
true
)
.
numericalDomainFromData
(
baselines
)
.
numericalRange
(
'left'
);
if
(
args
.
x_axis
)
{
new
MG
.
axis_factory
(
args
)
.
namespace
(
'x'
)
.
type
(
'numerical'
)
.
position
(
args
.
x_axis_position
)
.
rug
(
x_rug
(
args
))
.
label
(
mg_add_x_label
)
.
draw
();
}
if
(
args
.
y_axis
)
{
new
MG
.
axis_factory
(
args
)
.
namespace
(
'y'
)
.
type
(
'numerical'
)
.
position
(
args
.
y_axis_position
)
.
rug
(
y_rug
(
args
))
.
label
(
mg_add_y_label
)
.
draw
();
}
this
.
markers
();
this
.
mainPlot
();
this
.
rollover
();
this
.
windowListeners
();
if
(
args
.
brush
)
MG
.
add_brush_function
(
args
);
MG
.
call_hook
(
'line.after_init'
,
this
);
return
this
;
};
this
.
mainPlot
=
function
()
{
mg_line_main_plot
(
args
);
return
this
;
};
this
.
markers
=
function
()
{
markers
(
args
);
return
this
;
};
this
.
rollover
=
function
()
{
mg_line_rollover_setup
(
args
,
this
);
MG
.
call_hook
(
'line.after_rollover'
,
args
);
return
this
;
};
this
.
rolloverClick
=
args
=>
(
d
,
i
)
=>
{
if
(
args
.
click
)
{
args
.
click
(
d
,
i
);
}
};
this
.
rolloverOn
=
args
=>
{
const
svg
=
mg_get_svg_child_of
(
args
.
target
);
return
(
d
,
i
)
=>
{
mg_update_rollover_circle
(
args
,
svg
,
d
);
mg_trigger_linked_mouseovers
(
args
,
d
,
i
);
svg
.
selectAll
(
'text'
)
.
filter
((
g
,
j
)
=>
d
===
g
)
.
attr
(
'opacity'
,
0.3
);
// update rollover text except for missing data points
if
(
args
.
show_rollover_text
&&
!
((
args
.
missing_is_hidden
&&
d
[
'_missing'
])
||
d
[
args
.
y_accessor
]
===
null
)
)
{
const
mouseover
=
mg_mouseover_text
(
args
,
{
svg
});
let
row
=
mouseover
.
mouseover_row
();
if
(
args
.
aggregate_rollover
)
{
row
.
text
((
args
.
aggregate_rollover
&&
args
.
data
.
length
>
1
?
mg_format_x_aggregate_mouseover
:
mg_format_x_mouseover
)(
args
,
d
));
}
const
pts
=
args
.
aggregate_rollover
&&
args
.
data
.
length
>
1
?
d
.
values
:
[
d
];
pts
.
forEach
(
di
=>
{
if
(
args
.
aggregate_rollover
)
{
row
=
mouseover
.
mouseover_row
();
}
if
(
args
.
legend
)
{
mg_line_color_text
(
row
.
text
(
`
${
args
.
legend
[
di
.
__index__
-
1
]
}
`
).
bold
(),
di
.
__line_id__
,
args
);
}
mg_line_color_text
(
row
.
text
(
'\u2014 '
).
elem
,
di
.
__line_id__
,
args
);
if
(
!
args
.
aggregate_rollover
)
{
row
.
text
(
mg_format_x_mouseover
(
args
,
di
));
}
row
.
text
(
mg_format_y_mouseover
(
args
,
di
,
args
.
time_series
===
false
));
});
}
if
(
args
.
mouseover
)
{
args
.
mouseover
(
d
,
i
);
}
};
};
this
.
rolloverOff
=
args
=>
{
const
svg
=
mg_get_svg_child_of
(
args
.
target
);
return
(
d
,
i
)
=>
{
mg_trigger_linked_mouseouts
(
args
,
d
,
i
);
if
(
args
.
aggregate_rollover
)
{
mg_remove_active_data_points_for_aggregate_rollover
(
args
,
svg
);
}
else
{
mg_remove_active_data_points_for_generic_rollover
(
args
,
svg
,
d
.
__line_id__
);
}
if
(
args
.
data
[
0
].
length
>
1
)
{
mg_clear_mouseover_container
(
svg
);
}
if
(
args
.
mouseout
)
{
args
.
mouseout
(
d
,
i
);
}
};
};
this
.
rolloverMove
=
args
=>
(
d
,
i
)
=>
{
if
(
args
.
mousemove
)
{
args
.
mousemove
(
d
,
i
);
}
};
this
.
windowListeners
=
function
()
{
mg_window_listeners
(
this
.
args
);
return
this
;
};
this
.
init
(
args
);
}
MG
.
register
(
'line'
,
lineChart
);
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Feb 28, 9:53 AM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
79536
Default Alt Text
line.js (30 KB)
Attached To
rNOLA Nola
Event Timeline
Log In to Comment