UNPKG

veritatisquam

Version:
179 lines (152 loc) 5.56 kB
// Internal Imports import { Component } from '../component'; import { Events, ScaleTypes } from '../../interfaces'; import { DOMUtils } from '../../services'; // D3 Imports import { brushX } from 'd3-brush'; import { event, mouse } from 'd3-selection'; import { scaleTime } from 'd3-scale'; // This class is used for handle brush events in chart export class ChartBrush extends Component { static DASH_LENGTH = 4; type = 'grid-brush'; selectionSelector = 'rect.selection'; // needs to match the class name in d3.brush frontSelectionSelector = 'rect.frontSelection'; // needs to match the class name in _grid-brush.scss render(animate = true) { const svg = this.parent; // use this area to display selection above all graphs const frontSelectionArea = this.getContainerSVG(); const backdrop = DOMUtils.appendOrSelect( svg, 'svg.chart-grid-backdrop' ); // use this area to handle d3 brush events const brushArea = DOMUtils.appendOrSelect(backdrop, `g.${this.type}`); // set an id for rect.selection to be referred const d3Selection = DOMUtils.appendOrSelect( brushArea, this.selectionSelector ); const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true, }); const { cartesianScales } = this.services; const mainXScaleType = cartesianScales.getMainXScaleType(); const mainXScale = cartesianScales.getMainXScale(); const [xScaleStart, xScaleEnd] = mainXScale.range(); frontSelectionArea.attr('transform', `translate(${xScaleStart},0)`); const frontSelection = DOMUtils.appendOrSelect( frontSelectionArea, this.frontSelectionSelector ); if (mainXScale && mainXScaleType === ScaleTypes.TIME) { // get current zoomDomain let zoomDomain = this.model.get('zoomDomain'); if (zoomDomain === undefined) { // default to full range with extended domain zoomDomain = this.services.zoom.getDefaultZoomBarDomain(); if (zoomDomain) { this.model.set( { zoomDomain: zoomDomain }, { animate: false } ); } } const updateSelectionDash = (selection) => { // set end drag point to dash const selectionWidth = selection[1] - selection[0]; let dashArray = '0,' + selectionWidth.toString(); // top (invisible) // right const dashCount = Math.floor(height / ChartBrush.DASH_LENGTH); const totalRightDash = dashCount * ChartBrush.DASH_LENGTH; for (let i = 0; i < dashCount; i++) { dashArray += ',' + ChartBrush.DASH_LENGTH; // for each full length dash } dashArray += ',' + (height - totalRightDash); // for rest of the right height // if dash count is even, one more ",0" is needed to make total right dash pattern even if (dashCount % 2 === 1) { dashArray += ',0'; } dashArray += ',' + selectionWidth.toString(); // bottom (invisible) dashArray += ',' + height.toString(); // left frontSelection.attr('stroke-dasharray', dashArray); }; const brushEventHandler = () => { // selection range: [0, width] const selection = event.selection; if (selection === null || selection[0] === selection[1]) { return; } // copy the d3 selection attrs to front selection element frontSelection .attr('x', d3Selection.attr('x')) .attr('y', d3Selection.attr('y')) .attr('width', d3Selection.attr('width')) .attr('height', d3Selection.attr('height')) .style('cursor', 'pointer') .style('display', null); updateSelectionDash(selection); }; // assume max range is [0, width] const updateZoomDomain = (startPoint, endPoint) => { // create xScale based on current zoomDomain const xScale = scaleTime().range([0, width]).domain(zoomDomain); let newDomain = [ xScale.invert(startPoint), xScale.invert(endPoint), ]; // if selected start time and end time are the same // reset to default full range if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoom bar behavior: set to default full range newDomain = this.services.zoom.getDefaultZoomBarDomain(); } // only if zoomDomain needs update if ( zoomDomain[0].valueOf() !== newDomain[0].valueOf() || zoomDomain[1].valueOf() !== newDomain[1].valueOf() ) { this.services.zoom.handleDomainChange(newDomain); } }; const brushed = () => { // max selection range: [0, width] const selection = event.selection; if (selection !== null) { // updateZoomDomain assumes max range is [0, width] updateZoomDomain(selection[0], selection[1]); // clear brush selection brushArea.call(brush.move, null); // hide frontSelection frontSelection.style('display', 'none'); } }; // leave some space to display selection strokes besides axis const brush = brushX() .extent([ [0, 0], [width - 1, height], ]) .on('start brush end', brushEventHandler) .on('end.brushed', brushed); brushArea.call(brush); const zoomRatio = this.services.zoom.getZoomRatio(); backdrop.on('click', function () { if (event.shiftKey) { // clickedX range: [0, width] const clickedX = mouse(brushArea.node())[0]; let leftPoint = clickedX - (width * zoomRatio) / 2; if (leftPoint < 0) { leftPoint = 0; } let rightPoint = clickedX + (width * zoomRatio) / 2; if (rightPoint > width) { rightPoint = width; } // updateZoomDomain assumes max range is [0, width] updateZoomDomain(leftPoint, rightPoint); } }); } } }