UNPKG

monte

Version:

A visualization framework for D3.js and SVG. Ships with prebuilt charts and components.

148 lines (128 loc) 4.74 kB
import { AXIS_SHIFT, EXIT, UPDATE } from '../const/d3'; import { INTERACTION_HIDE_EVENTS, INTERACTION_SHOW_EVENTS } from '../const/events'; import { isArray, isDefined, isFunc } from '../tools/is'; import { Extension } from './Extension'; import { noop } from '../tools/noop'; // TODO: Add support for "always active" (i.e chart:mousemove) const CROSSHAIR_DEFAULTS = { featurePrefix: 'point', eventPrefix: 'crosshair', crosshairCss: '', lines: ['bottom', 'left'], alignmentShift: AXIS_SHIFT, // Use a slight shift to match default d3-axis drawing. showBindings: INTERACTION_SHOW_EVENTS, hideBindings: INTERACTION_HIDE_EVENTS, topCustomize: noop, leftCustomize: noop, rightCustomize: noop, bottomCustomize: noop, }; // Draws lines connecting a chart feature to axes and/or chart edges. export class Crosshair extends Extension { _initOptions(...options) { super._initOptions(...options, CROSSHAIR_DEFAULTS); const showBindings = this.tryInvoke(this.opts.showBindings); const hideBindings = this.tryInvoke(this.opts.hideBindings); let featurePrefix = this.tryInvoke(this.opts.featurePrefix); if (!isArray(featurePrefix)) { featurePrefix = [featurePrefix]; } this.showEvents = []; this.hideEvents = []; featurePrefix.forEach((fp) => { showBindings.forEach((ev) => { const fev = Extension.featureEventName(fp, ev); this.opts.binding.push(fev); this.showEvents.push(fev); }); hideBindings.forEach((ev) => { const fev = Extension.featureEventName(fp, ev); this.opts.binding.push(fev); this.hideEvents.push(fev); }); }); } _update(...args) { const ext = this; const css = this.tryInvoke(this.opts.crosshairCss); const linesToDraw = this.tryInvoke(this.opts.lines); const lines = this._extCreateSelection().data(linesToDraw).order(); const shift = this.tryInvoke(this.opts.alignmentShift); const coords = { top: { x1: (d) => this.chart.getScaledProp('x', d) + shift, y1: 0, x2: (d) => this.chart.getScaledProp('x', d) + shift, y2: (d) => this.chart.getScaledProp('y', d), }, left: { x1: 0, y1: (d) => this.chart.getScaledProp('y', d) + shift, x2: (d) => this.chart.getScaledProp('x', d), y2: (d) => this.chart.getScaledProp('y', d) + shift, }, right: { x1: (d) => this.chart.getScaledProp('x', d), y1: (d) => this.chart.getScaledProp('y', d) + shift, x2: this.chart.width, y2: (d) => this.chart.getScaledProp('y', d) + shift, }, bottom: { x1: (d) => this.chart.getScaledProp('x', d) + shift, y1: (d) => this.chart.getScaledProp('y', d), x2: (d) => this.chart.getScaledProp('x', d) + shift, y2: this.chart.height, }, }; if (this.lastUpdateEvent === 'updated') { lines.enter().append('line') .call(this._setExtAttrs.bind(this)) .attr('opacity', 0) .attr('class', css) .classed('monte-ext-crosshair', true); if (this.activeFeatureDatum) { lines.transition() .call(this.chart._transitionSetup('extCrosshair', UPDATE)) .each(setLinePoints); } lines.exit() .transition() .call(this.chart._transitionSetup('extCrosshair', EXIT)) .attr('opacity', 0) .remove(); } else if (this.isShowEvent(this.lastUpdateEvent)) { const featureDatum = args[0]; this.activeFeatureDatum = featureDatum; lines.interrupt() .attr('opacity', 1) .each(setLinePoints); } else if (this.isHideEvent(this.lastUpdateEvent)) { this.activeFeatureDatum = null; lines.interrupt().attr('opacity', 0); } // Function is invoked in the `d3.each` context (i.e. `this` is the current element) function setLinePoints(d) { const node = d3.select(this); const customize = ext.opts[d + 'Customize']; let points = { x1: ext.tryInvoke(coords[d].x1, ext.activeFeatureDatum), y1: ext.tryInvoke(coords[d].y1, ext.activeFeatureDatum), x2: ext.tryInvoke(coords[d].x2, ext.activeFeatureDatum), y2: ext.tryInvoke(coords[d].y2, ext.activeFeatureDatum), }; if (isDefined(customize) && isFunc(customize) && customize !== noop) { points = ext.tryInvoke(customize, points); } node.attr('x1', points.x1) .attr('y1', points.y1) .attr('x2', points.x2) .attr('y2', points.y2); } } isShowEvent(event) { return this.showEvents.indexOf(event) > -1; } isHideEvent(event) { return this.hideEvents.indexOf(event) > -1; } }