{ 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 = `— ${this_legend}  ${plot.legend_text}`; } else { plot.legend_text = `— ${this_legend}  ${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); }