From fd481a47c44d874d85024e4e68a43340ba344b0e Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 27 May 2020 15:44:15 +0800 Subject: [PATCH 001/510] feat: initial zoom bar implementation from PR 511 --- packages/core/src/axis-chart.ts | 16 +- packages/core/src/chart.ts | 9 +- packages/core/src/components/axes/axis.ts | 9 +- packages/core/src/components/axes/zoom-bar.ts | 216 ++++++++++++++++++ packages/core/src/components/index.ts | 1 + packages/core/src/model.ts | 10 +- packages/core/src/services/axis-zoom.ts | 60 +++++ packages/core/src/services/index.ts | 1 + .../core/src/services/scales-cartesian.ts | 6 + .../core/src/styles/components/_zoom-bar.scss | 11 + .../core/src/styles/components/index.scss | 1 + packages/core/src/styles/graphs/index.scss | 4 + 12 files changed, 333 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/axes/zoom-bar.ts create mode 100644 packages/core/src/services/axis-zoom.ts create mode 100644 packages/core/src/styles/components/_zoom-bar.scss diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index b9751b3a31..e4d105905f 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -12,7 +12,8 @@ import { Legend, Title, AxisChartsTooltip, - Spacer + Spacer, + ZoomBar } from "./components/index"; import { Tools } from "./tools"; @@ -123,6 +124,17 @@ export class AxisChart extends Chart { } }; + const zoomBarComponent = { + id: "zoom-bar", + components: [ + new ZoomBar(this.model, this.services) + ], + growth: { + x: LayoutGrowth.PREFERRED, + y: LayoutGrowth.FIXED + } + }; + // Add chart title if it exists const topLevelLayoutComponents = []; if (this.model.getOptions().title) { @@ -139,6 +151,8 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } + + topLevelLayoutComponents.push(zoomBarComponent); topLevelLayoutComponents.push(fullFrameComponent); return [ diff --git a/packages/core/src/chart.ts b/packages/core/src/chart.ts index ea65e9f3c3..4e86305488 100644 --- a/packages/core/src/chart.ts +++ b/packages/core/src/chart.ts @@ -37,7 +37,7 @@ export class Chart { // Contains the code that uses properties that are overridable by the super-class init(holder: Element, chartConfigs: ChartConfig) { // Store the holder in the model - this.model.set({ holder }, true); + this.model.set({ holder }, { skipUpdate: true }); // Initialize all services Object.keys(this.services).forEach((serviceName) => { @@ -49,8 +49,9 @@ export class Chart { }); // Call update() when model has been updated - this.services.events.addEventListener(ChartEvents.Model.UPDATE, () => { - this.update(true); + this.services.events.addEventListener(ChartEvents.Model.UPDATE, (e) => { + const animate = !!Tools.getProperty(e, "detail", "animate"); + this.update(animate); }); // Set model data & options @@ -110,7 +111,7 @@ export class Chart { // Remove the chart holder this.services.domUtils.getHolder().remove(); - this.model.set({ destroyed: true }, true); + this.model.set({ destroyed: true }, { skipUpdate: true }); } protected getChartComponents(graphFrameComponents: any[]) { diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index c810af5994..04433e6399 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -106,6 +106,13 @@ export class Axis extends Component { scale.range([startPosition, endPosition]); } + // if zoomDomain is available, update scale domain to Date array. + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { + scale.domain(zoomDomain.map(d => new Date(d))); + } + + // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -405,7 +412,7 @@ export class Axis extends Component { const ticksNumber = isTimeScaleType ? axis.tickValues().length : scale.ticks().length; - const estimatedTickSize = width / ticksNumber / 2; + const estimatedTickSize = width / ticksNumber / 1.6; rotateTicks = estimatedTickSize < minTickSize; } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts new file mode 100644 index 0000000000..dee5a24f55 --- /dev/null +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -0,0 +1,216 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { area, line } from "d3-shape"; +import { extent } from "d3-array"; +import { drag } from "d3-drag"; +import { event, select, selectAll } from "d3-selection"; + +export class ZoomBar extends Component { + type = "zoom-bar"; + + ogXScale: any; + + dragged = Tools.debounce((element, d, e) => { + element = select(element); + const startingHandle = element.attr("class").indexOf("start") !== -1; + + let domain; + if (startingHandle) { + domain = [ + this.ogXScale.invert(e.x), + this.ogXScale.domain()[1] + // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) + ]; + } else { + domain = [ + this.ogXScale.domain()[0], + this.ogXScale.invert(e.x) + // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) + ]; + } + this.model.set({ zoomDomain: domain }, { animate: false }); + }, 2.5); + + render(animate = true) { + const svg = this.getContainerSVG(); + + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); + const mainXScaleType = cartesianScales.getScaleTypeByPosition(mainXAxisPosition); + const mainYScaleType = cartesianScales.getScaleTypeByPosition(mainYAxisPosition); + + const height = 32; + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") + // .attr("transform", "translateX(10)") + .attr("width", "100%") + .attr("height", height) + .attr("opacity", 1); + + const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") + .attr("x", 0) + .attr("y", 32) + .attr("width", "100%") + .attr("height", 20) + .attr("opacity", 1) + .attr("fill", "none"); + + const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") + .attr("x", 0) + .attr("y", 0) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "white") + .attr("stroke", "#e8e8e8"); + + if (mainXScale) { + const displayData = this.model.getDisplayData(); + + if (mainXScaleType === ScaleTypes.TIME) { + // Get all date values provided in data + // TODO - Could be re-used through the model + let allDates = []; + displayData.datasets.forEach(dataset => { + allDates = allDates.concat(dataset.data.map(datum => Number(datum.date))); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + + // Go through all date values + // And get corresponding data from each dataset + const stackDataArray = allDates.map((date) => { + let count = 0; + let correspondingSum = 0; + const correspondingData = {}; + + displayData.datasets.forEach(dataset => { + const correspondingDatum = dataset.data.find(datum => Number(datum.date) === Number(date)); + if (correspondingDatum) { + ++count; + correspondingSum += correspondingDatum.value; + } + }); + correspondingData["label"] = date; + correspondingData["value"] = correspondingSum; + + return correspondingData; + }); + + const xScale = mainXScale.copy(); + if (!this.ogXScale) { + this.ogXScale = xScale; + } + + const yScale = mainYScale.copy(); + + const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); + + xScale.range([0, +width]) + .domain(extent(stackDataArray, (d: any) => d.label)); + + yScale.range([0, height - 6]) + .domain(extent(stackDataArray, (d: any) => d.value)); + + const zoomDomain = this.model.get("zoomDomain"); + + // D3 line generator function + const lineGenerator = line() + .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .y((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)) + .curve(this.services.curves.getD3Curve()); + // .defined((d: any, i) => { + // if (zoomDomain) { + // const dTimestamp = +d.label; + + // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; + // } + + // return true; + // }); + + const lineGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-line") + .attr("stroke", "#8e8e8e") + .attr("stroke-width", 3) + .attr("fill", "none") + .datum(stackDataArray) + .transition(this.services.transitions.getTransition("zoom-pan-line-update", animate)) + .attr("d", lineGenerator); + + const areaGenerator = area() + .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .y0(height) + .y1((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)); + + const areaGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-area") + .attr("fill", "#e0e0e0") + .datum(stackDataArray) + .transition(this.services.transitions.getTransition("zoom-pan-area-update", animate)) + .attr("d", areaGenerator); + + const startHandlePosition = zoomDomain ? xScale(+zoomDomain[0]) : 0; + // Handle #1 + const startHandle = DOMUtils.appendOrSelect(container, "rect.zoom-handle.start") + .attr("x", startHandlePosition) + .attr("width", 5) + .attr("height", "100%") + .attr("fill", "#525252"); + + DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.start") + .attr("x", startHandlePosition + 2) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + const endHandlePosition = zoomDomain ? xScale(+zoomDomain[1]) : xScale.range()[1]; + // console.log("endHandlePosition", endHandlePosition) + + // Handle #2 + const handle2 = DOMUtils.appendOrSelect(container, "rect.zoom-handle.end") + .attr("x", endHandlePosition - 5) + .attr("width", 5) + .attr("height", "100%") + .attr("fill", "#525252"); + + DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.end") + .attr("x", endHandlePosition - 5 + 2) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + + const outboundRangeRight = DOMUtils.appendOrSelect(container, "rect.outbound-range.right") + .attr("x", endHandlePosition) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "#fff") + .attr("fill-opacity", 0.85); + + const self = this; + // handle2.on("click", this.zoomIn.bind(this)); + selectAll("rect.zoom-handle").call( + drag() + .on("start", function() { + select(this).classed("dragging", true); + }) + .on("drag", function(d) { + self.dragged(this, d, event); + }) + .on("end", function() { + select(this).classed("dragging", false); + }) + ); + } + } + } + + zoomIn() { + const mainXScale = this.services.cartesianScales.getMainXScale(); + console.log("zoom in", mainXScale.domain()); + } +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 9c38ec5ef1..8ea6485eaf 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -33,3 +33,4 @@ export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; +export * from "./axes/zoom-bar"; diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index ea1b449b08..c2fba1cd4b 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -267,11 +267,11 @@ export class ChartModel { return this.state.options; } - set(newState: any, skipUpdate = false) { + set(newState: any, configs?: any) { this.state = Object.assign({}, this.state, newState); - if (!skipUpdate) { - this.update(); + if (!configs || !configs.skipUpdate) { + this.update(configs ? configs.animate : true); } } @@ -298,7 +298,7 @@ export class ChartModel { * Updates miscellanous information within the model * such as the color scales, or the legend data labels */ - update() { + update(animate = true) { if (!this.getDisplayData()) { return; } @@ -306,7 +306,7 @@ export class ChartModel { this.updateAllDataGroups(); this.setColorScale(); - this.services.events.dispatchEvent(Events.Model.UPDATE); + this.services.events.dispatchEvent(Events.Model.UPDATE, { animate }); } setUpdateCallback(cb: Function) { diff --git a/packages/core/src/services/axis-zoom.ts b/packages/core/src/services/axis-zoom.ts new file mode 100644 index 0000000000..8dbb2b3d80 --- /dev/null +++ b/packages/core/src/services/axis-zoom.ts @@ -0,0 +1,60 @@ +// Internal Imports +import { Service } from "./service"; +import { Tools } from "../tools"; + +// D3 Imports +import { brushX } from "d3-brush"; +import { event } from "d3-selection"; + +export class AxisZoom extends Service { + brush: any; + + brushed(e) { + if (event) { + const selectedRange = event.selection; + + const mainXAxis = this.services.axes.getMainXAxis(); + const mainXAxisRangeStart = mainXAxis.scale.range()[0]; + + const newDomain = selectedRange.map(d => d + mainXAxisRangeStart) + .map(mainXAxis.scale.invert, mainXAxis.scale); + + this.model.set({ + zoomDomain: newDomain.map(d => new Date(+d)) + }); + } + } + + // We need a custom debounce function here + // because of the async nature of d3.event + debounceWithD3Event(func, wait) { + let timeout; + return function() { + const e = Object.assign({}, event); + const context = this; + const later = function() { + timeout = null; + func.apply(context, [e]); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } + + getZoomInstance() { + const mainXAxis = this.services.axes.getMainXAxis(); + const mainYAxis = this.services.axes.getMainYAxis(); + const xMaxRange = mainXAxis.scale.range()[1]; + const yMaxRange = mainYAxis.scale.range()[0]; + + this.brush = brushX() + .extent([ + [0, 0], + [xMaxRange, yMaxRange] + ]) + .on("end", this.brushed.bind(this)); + + + return this.brush; + } +} diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index d999a07207..4e9fcc0e17 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -4,5 +4,6 @@ export * from "./essentials/events"; export * from "./essentials/transitions"; // MISC +export * from "./axis-zoom"; export * from "./scales-cartesian"; export * from "./curves"; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 45a2bd142b..e75d6f14e7 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -382,6 +382,12 @@ export class CartesianScales extends Service { return map(displayData, (d) => d[mapsTo]).keys(); } + // If scale is a TIME scale and zoomDomain is available, return Date array as the domain + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && axisOptions && scaleType === ScaleTypes.TIME) { + return zoomDomain.map(d => new Date(d)); + } + // Get the extent of the domain let domain; let allDataValues; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss new file mode 100644 index 0000000000..02fcf7a55f --- /dev/null +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -0,0 +1,11 @@ +g.#{$prefix}--#{$charts-prefix}--zoom-bar { + rect.zoom-handle.dragging, + rect.zoom-handle:hover { + fill: black; + cursor: col-resize; + } + + rect.zoom-handle-bar { + pointer-events: none; + } +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index 06f043776c..b7e21a8cfd 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -9,3 +9,4 @@ @import "./title"; @import "./tooltip"; @import "./threshold"; +@import "./zoom-bar"; diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index 426d88265c..ca80344db3 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -5,3 +5,7 @@ @import "./scatter-stacked"; @import "./radar"; @import "./gauge"; + +svg.#{$prefix}--#{$charts-prefix}--chart-svg svg.graph-frame { + overflow-x: hidden; +} From e4621944133b85f7421de7dea7d29851e4bbd1e3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 2 Jun 2020 16:33:50 +0800 Subject: [PATCH 002/510] fix: update ZoomBar to match new data format - fix trailing-comma - update scales-cartesian.getValueFromScale() - update line time-series 15 seconds demo data --- packages/core/demo/data/time-series-axis.ts | 12 +- packages/core/src/components/axes/zoom-bar.ts | 165 +++++++++++++----- .../core/src/services/scales-cartesian.ts | 165 ++++++++++-------- packages/core/tslint.json | 2 +- 4 files changed, 216 insertions(+), 128 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e3a3502c92..c1c7183571 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -8,12 +8,12 @@ export const lineTimeSeriesData15seconds = { label: "Dataset 1", data: [ { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 30), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 45), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 0), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 15), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 30), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 10 } + { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, + { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, + { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, + { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, + { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 }, ] } ] diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index dee5a24f55..f6ac815a7b 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -23,13 +23,13 @@ export class ZoomBar extends Component { if (startingHandle) { domain = [ this.ogXScale.invert(e.x), - this.ogXScale.domain()[1] + this.ogXScale.domain()[1], // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } else { domain = [ this.ogXScale.domain()[0], - this.ogXScale.invert(e.x) + this.ogXScale.invert(e.x), // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } @@ -44,8 +44,12 @@ export class ZoomBar extends Component { const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition(mainXAxisPosition); - const mainYScaleType = cartesianScales.getScaleTypeByPosition(mainYAxisPosition); + const mainXScaleType = cartesianScales.getScaleTypeByPosition( + mainXAxisPosition + ); + const mainYScaleType = cartesianScales.getScaleTypeByPosition( + mainYAxisPosition + ); const height = 32; const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") @@ -77,8 +81,8 @@ export class ZoomBar extends Component { // Get all date values provided in data // TODO - Could be re-used through the model let allDates = []; - displayData.datasets.forEach(dataset => { - allDates = allDates.concat(dataset.data.map(datum => Number(datum.date))); + displayData.forEach((data) => { + allDates = allDates.concat(Number(data.date)); }); allDates = Tools.removeArrayDuplicates(allDates).sort(); @@ -89,14 +93,13 @@ export class ZoomBar extends Component { let correspondingSum = 0; const correspondingData = {}; - displayData.datasets.forEach(dataset => { - const correspondingDatum = dataset.data.find(datum => Number(datum.date) === Number(date)); - if (correspondingDatum) { + displayData.forEach((data) => { + if (Number(data.date) === Number(date)) { ++count; - correspondingSum += correspondingDatum.value; + correspondingSum += data.value; } }); - correspondingData["label"] = date; + correspondingData["date"] = date; correspondingData["value"] = correspondingSum; return correspondingData; @@ -109,53 +112,113 @@ export class ZoomBar extends Component { const yScale = mainYScale.copy(); - const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true, + }); - xScale.range([0, +width]) - .domain(extent(stackDataArray, (d: any) => d.label)); + xScale + .range([0, width]) + .domain(extent(stackDataArray, (d: any) => d.date)); - yScale.range([0, height - 6]) + yScale + .range([0, height - 6]) .domain(extent(stackDataArray, (d: any) => d.value)); const zoomDomain = this.model.get("zoomDomain"); // D3 line generator function const lineGenerator = line() - .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) - .y((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)) + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) + .y( + (d, i) => + height - + cartesianScales.getValueFromScale( + yScale, + mainYScaleType, + mainYAxisPosition, + d, + i + ) + ) .curve(this.services.curves.getD3Curve()); - // .defined((d: any, i) => { - // if (zoomDomain) { - // const dTimestamp = +d.label; - - // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; - // } - - // return true; - // }); - - const lineGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-line") + // .defined((d: any, i) => { + // if (zoomDomain) { + // const dTimestamp = +d.label; + + // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; + // } + + // return true; + // }); + const lineGraph = DOMUtils.appendOrSelect( + container, + "path.zoom-graph-line" + ) .attr("stroke", "#8e8e8e") .attr("stroke-width", 3) .attr("fill", "none") .datum(stackDataArray) - .transition(this.services.transitions.getTransition("zoom-pan-line-update", animate)) + .transition( + this.services.transitions.getTransition( + "zoom-pan-line-update", + animate + ) + ) .attr("d", lineGenerator); const areaGenerator = area() - .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) .y0(height) - .y1((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)); - - const areaGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-area") + .y1( + (d, i) => + height - + cartesianScales.getValueFromScale( + yScale, + mainYScaleType, + mainYAxisPosition, + d, + i + ) + ); + + const areaGraph = DOMUtils.appendOrSelect( + container, + "path.zoom-graph-area" + ) .attr("fill", "#e0e0e0") .datum(stackDataArray) - .transition(this.services.transitions.getTransition("zoom-pan-area-update", animate)) + .transition( + this.services.transitions.getTransition( + "zoom-pan-area-update", + animate + ) + ) .attr("d", areaGenerator); - const startHandlePosition = zoomDomain ? xScale(+zoomDomain[0]) : 0; + const startHandlePosition = zoomDomain + ? xScale(+zoomDomain[0]) + : 0; // Handle #1 - const startHandle = DOMUtils.appendOrSelect(container, "rect.zoom-handle.start") + const startHandle = DOMUtils.appendOrSelect( + container, + "rect.zoom-handle.start" + ) .attr("x", startHandlePosition) .attr("width", 5) .attr("height", "100%") @@ -167,11 +230,16 @@ export class ZoomBar extends Component { .attr("width", 1) .attr("height", 12) .attr("fill", "#fff"); - const endHandlePosition = zoomDomain ? xScale(+zoomDomain[1]) : xScale.range()[1]; + const endHandlePosition = zoomDomain + ? xScale(+zoomDomain[1]) + : xScale.range()[1]; // console.log("endHandlePosition", endHandlePosition) // Handle #2 - const handle2 = DOMUtils.appendOrSelect(container, "rect.zoom-handle.end") + const endHandle = DOMUtils.appendOrSelect( + container, + "rect.zoom-handle.end" + ) .attr("x", endHandlePosition - 5) .attr("width", 5) .attr("height", "100%") @@ -184,24 +252,27 @@ export class ZoomBar extends Component { .attr("height", 12) .attr("fill", "#fff"); - const outboundRangeRight = DOMUtils.appendOrSelect(container, "rect.outbound-range.right") - .attr("x", endHandlePosition) - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "#fff") - .attr("fill-opacity", 0.85); + const outboundRangeRight = DOMUtils.appendOrSelect( + container, + "rect.outbound-range.right" + ) + .attr("x", endHandlePosition) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "#fff") + .attr("fill-opacity", 0.85); const self = this; // handle2.on("click", this.zoomIn.bind(this)); selectAll("rect.zoom-handle").call( drag() - .on("start", function() { + .on("start", function () { select(this).classed("dragging", true); }) - .on("drag", function(d) { + .on("drag", function (d) { self.dragged(this, d, event); }) - .on("end", function() { + .on("end", function () { select(this).classed("dragging", false); }) ); diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index e75d6f14e7..162b0c833f 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -181,33 +181,50 @@ export class CartesianScales extends Service { return this.scales[this.getMainYAxisPosition()]; } - getValueFromScale(axisPosition: AxisPositions, datum: any, index?: number) { + getValueFromScale(scale: any, scaleType: ScaleTypes, axisPosition: AxisPositions, datum: any, index?: number) { const options = this.model.getOptions(); - const axisOptions = Tools.getProperty(options, "axes", axisPosition); - - const scaleType = this.scaleTypes[axisPosition]; - const scale = this.scales[axisPosition]; - + const axesOptions = Tools.getProperty(options, "axes"); + const axisOptions = axesOptions[axisPosition]; const { mapsTo } = axisOptions; const value = datum[mapsTo] !== undefined ? datum[mapsTo] : datum; - - if (scaleType === ScaleTypes.LABELS) { - return scale(value) + scale.step() / 2; + let scaledValue; + switch (scaleType) { + case ScaleTypes.LABELS: + scaledValue = scale(value) + scale.step() / 2; + break; + case ScaleTypes.TIME: + scaledValue = scale(new Date(value)); + break; + default: + scaledValue = scale(value); } + return scaledValue; + } - if (scaleType === ScaleTypes.TIME) { - return scale(new Date(value)); - } + getValueThroughAxisPosition(axisPosition: AxisPositions, datum: any, index?: number) { + const scaleType = this.scaleTypes[axisPosition]; + const scale = this.scales[axisPosition]; - return scale(value); + return this.getValueFromScale(scale, scaleType, axisPosition, datum, index); } + getDomainValue(d, i) { - return this.getValueFromScale(this.domainAxisPosition, d, i); + return this.getValueThroughAxisPosition(this.domainAxisPosition, d, i); } getRangeValue(d, i) { - return this.getValueFromScale(this.rangeAxisPosition, d, i); + return this.getValueThroughAxisPosition(this.rangeAxisPosition, d, i); + } + + getXValue(d, i) { + const mainXAxisPosition = this.getMainXAxisPosition(); + return this.getValueThroughAxisPosition(mainXAxisPosition, d, i); + } + + getYValue(d, i) { + const mainYAxisPosition = this.getMainYAxisPosition(); + return this.getValueThroughAxisPosition(mainYAxisPosition, d, i); } getDomainIdentifier() { @@ -271,6 +288,65 @@ export class CartesianScales extends Service { } } + getHighestDomainThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const domainAxisPosition = this.getDomainAxisPosition(); + + const { thresholds } = axesOptions[domainAxisPosition]; + + if (!thresholds) { + return null; + } + + const domainScale = this.getDomainScale(); + // Find the highest threshold for the domain + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value, + )[0]; + + const scaleType = this.getScaleTypeByPosition(domainAxisPosition); + if ( + scaleType === ScaleTypes.TIME && + (typeof highestThreshold.value === "string" || + highestThreshold.value.getTime === undefined) + ) { + highestThreshold.value = new Date(highestThreshold.value); + } + + return { + threshold: highestThreshold, + scaleValue: domainScale(highestThreshold.value), + }; + } + + getHighestRangeThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const rangeAxisPosition = this.getRangeAxisPosition(); + + const { thresholds } = axesOptions[rangeAxisPosition]; + + if (!thresholds) { + return null; + } + + const rangeScale = this.getRangeScale(); + // Find the highest threshold for the range + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value, + )[0]; + + return { + threshold: highestThreshold, + scaleValue: rangeScale(highestThreshold.value), + }; + } + protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); @@ -438,65 +514,6 @@ export class CartesianScales extends Service { return scale; } - - getHighestDomainThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const domainAxisPosition = this.getDomainAxisPosition(); - - const { thresholds } = axesOptions[domainAxisPosition]; - - if (!thresholds) { - return null; - } - - const domainScale = this.getDomainScale(); - // Find the highest threshold for the domain - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - const scaleType = this.getScaleTypeByPosition(domainAxisPosition); - if ( - scaleType === ScaleTypes.TIME && - (typeof highestThreshold.value === "string" || - highestThreshold.value.getTime === undefined) - ) { - highestThreshold.value = new Date(highestThreshold.value); - } - - return { - threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value) - }; - } - - getHighestRangeThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const rangeAxisPosition = this.getRangeAxisPosition(); - - const { thresholds } = axesOptions[rangeAxisPosition]; - - if (!thresholds) { - return null; - } - - const rangeScale = this.getRangeScale(); - // Find the highest threshold for the range - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - return { - threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value) - }; - } } function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { diff --git a/packages/core/tslint.json b/packages/core/tslint.json index a5a0970b82..91271d283a 100644 --- a/packages/core/tslint.json +++ b/packages/core/tslint.json @@ -92,7 +92,7 @@ "trailing-comma": [ true, { - "multiline": "never", + "multiline": "always", "singleline": "never" } ] From 0a15537dfc29a4bb8e67665079954dbf394ac4b0 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 3 Jun 2020 21:56:58 +0800 Subject: [PATCH 003/510] feat: use d3-brush to implement selection --- packages/core/src/components/axes/zoom-bar.ts | 184 +++++++++--------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index f6ac815a7b..8066d0aa08 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -5,20 +5,30 @@ import { ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports -import { area, line } from "d3-shape"; import { extent } from "d3-array"; +import { brushX } from "d3-brush"; import { drag } from "d3-drag"; -import { event, select, selectAll } from "d3-selection"; +import { area, line } from "d3-shape"; +import { event, select, selectAll, BaseType } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + height = 32; + ogXScale: any; dragged = Tools.debounce((element, d, e) => { + console.log("dragged"); + console.log(this.ogXScale.invert(100)); + console.log(d); + console.log(e); element = select(element); const startingHandle = element.attr("class").indexOf("start") !== -1; - + console.log("startingHandle?:" + startingHandle); + const oldDomain = this.model.get("zoomDomain"); + console.log("old domain"); + console.log(oldDomain); let domain; if (startingHandle) { domain = [ @@ -33,9 +43,13 @@ export class ZoomBar extends Component { // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } + console.log("new domain"); + console.log(domain); this.model.set({ zoomDomain: domain }, { animate: false }); }, 2.5); + + render(animate = true) { const svg = this.getContainerSVG(); @@ -45,19 +59,69 @@ export class ZoomBar extends Component { const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition + mainXAxisPosition, ); const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition + mainYAxisPosition, ); - const height = 32; + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") // .attr("transform", "translateX(10)") .attr("width", "100%") - .attr("height", height) + .attr("height", this.height) .attr("opacity", 1); + const brushHandle = (g, selection) => { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{type: "w"}, {type: "e"}]) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32) + .attr("fill", "#525252"); + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{type: "w"}, {type: "e"}]) + .join("rect") + .attr("class", function(d) { return "handle-bar handle-bar--" + d.type; }) + .attr("x", function(d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + + }; + + const brushed = () => { + const selection = event.selection; + if (selection === null) { + // do nothing + console.log("selection === null"); + } else { + // TODO - pass selection to callback function or update scaleDomain + } + // update brush handle position + select(svg).call(brushHandle, selection); + }; + + const brush = brushX() + .extent([[0, 0], [700, this.height]]) + .on("start brush end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") + .call(brush) + .call(brush.move, mainXScale.range()); // TODO -- mainXScale.range() incorrect + const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) .attr("y", 32) @@ -105,11 +169,13 @@ export class ZoomBar extends Component { return correspondingData; }); + // if (!this.ogXScale) { + // this.ogXScale = cartesianScales.getDomainScale(); + // } const xScale = mainXScale.copy(); if (!this.ogXScale) { this.ogXScale = xScale; } - const yScale = mainYScale.copy(); const { width } = DOMUtils.getSVGElementSize(this.parent, { @@ -121,7 +187,7 @@ export class ZoomBar extends Component { .domain(extent(stackDataArray, (d: any) => d.date)); yScale - .range([0, height - 6]) + .range([0, this.height - 6]) .domain(extent(stackDataArray, (d: any) => d.value)); const zoomDomain = this.model.get("zoomDomain"); @@ -134,19 +200,19 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i - ) + i, + ), ) .y( (d, i) => - height - + this.height - cartesianScales.getValueFromScale( yScale, mainYScaleType, mainYAxisPosition, d, - i - ) + i, + ), ) .curve(this.services.curves.getD3Curve()); // .defined((d: any, i) => { @@ -160,7 +226,7 @@ export class ZoomBar extends Component { // }); const lineGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-line" + "path.zoom-graph-line", ) .attr("stroke", "#8e8e8e") .attr("stroke-width", 3) @@ -169,8 +235,8 @@ export class ZoomBar extends Component { .transition( this.services.transitions.getTransition( "zoom-pan-line-update", - animate - ) + animate, + ), ) .attr("d", lineGenerator); @@ -181,101 +247,36 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i - ) + i, + ), ) - .y0(height) + .y0(this.height) .y1( (d, i) => - height - + this.height - cartesianScales.getValueFromScale( yScale, mainYScaleType, mainYAxisPosition, d, - i - ) + i, + ), ); const areaGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-area" + "path.zoom-graph-area", ) .attr("fill", "#e0e0e0") .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-area-update", - animate - ) + animate, + ), ) .attr("d", areaGenerator); - const startHandlePosition = zoomDomain - ? xScale(+zoomDomain[0]) - : 0; - // Handle #1 - const startHandle = DOMUtils.appendOrSelect( - container, - "rect.zoom-handle.start" - ) - .attr("x", startHandlePosition) - .attr("width", 5) - .attr("height", "100%") - .attr("fill", "#525252"); - - DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.start") - .attr("x", startHandlePosition + 2) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - const endHandlePosition = zoomDomain - ? xScale(+zoomDomain[1]) - : xScale.range()[1]; - // console.log("endHandlePosition", endHandlePosition) - - // Handle #2 - const endHandle = DOMUtils.appendOrSelect( - container, - "rect.zoom-handle.end" - ) - .attr("x", endHandlePosition - 5) - .attr("width", 5) - .attr("height", "100%") - .attr("fill", "#525252"); - - DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.end") - .attr("x", endHandlePosition - 5 + 2) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - - const outboundRangeRight = DOMUtils.appendOrSelect( - container, - "rect.outbound-range.right" - ) - .attr("x", endHandlePosition) - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "#fff") - .attr("fill-opacity", 0.85); - - const self = this; - // handle2.on("click", this.zoomIn.bind(this)); - selectAll("rect.zoom-handle").call( - drag() - .on("start", function () { - select(this).classed("dragging", true); - }) - .on("drag", function (d) { - self.dragged(this, d, event); - }) - .on("end", function () { - select(this).classed("dragging", false); - }) - ); } } } @@ -284,4 +285,5 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } + } From ef9a45819ed1596b8683e7b39382bd1745ed5687 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 8 Jun 2020 16:04:58 +0800 Subject: [PATCH 004/510] fix: add left margin to align to chart - using .scss to set fill and stroke color --- packages/core/src/components/axes/zoom-bar.ts | 162 +++++++----------- .../core/src/styles/components/_zoom-bar.scss | 26 ++- 2 files changed, 86 insertions(+), 102 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 8066d0aa08..b200d522b2 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -7,9 +7,8 @@ import { DOMUtils } from "../../services"; // D3 Imports import { extent } from "d3-array"; import { brushX } from "d3-brush"; -import { drag } from "d3-drag"; import { area, line } from "d3-shape"; -import { event, select, selectAll, BaseType } from "d3-selection"; +import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; @@ -18,39 +17,9 @@ export class ZoomBar extends Component { ogXScale: any; - dragged = Tools.debounce((element, d, e) => { - console.log("dragged"); - console.log(this.ogXScale.invert(100)); - console.log(d); - console.log(e); - element = select(element); - const startingHandle = element.attr("class").indexOf("start") !== -1; - console.log("startingHandle?:" + startingHandle); - const oldDomain = this.model.get("zoomDomain"); - console.log("old domain"); - console.log(oldDomain); - let domain; - if (startingHandle) { - domain = [ - this.ogXScale.invert(e.x), - this.ogXScale.domain()[1], - // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) - ]; - } else { - domain = [ - this.ogXScale.domain()[0], - this.ogXScale.invert(e.x), - // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) - ]; - } - console.log("new domain"); - console.log(domain); - this.model.set({ zoomDomain: domain }, { animate: false }); - }, 2.5); - - - render(animate = true) { + // TODO - get correct axis left width + const axisLeftWidth = 23; const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -65,63 +34,11 @@ export class ZoomBar extends Component { mainYAxisPosition, ); - const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") - // .attr("transform", "translateX(10)") .attr("width", "100%") .attr("height", this.height) .attr("opacity", 1); - const brushHandle = (g, selection) => { - const handleSize = 5; - // handle - svg.select("g.brush") - .selectAll("rect.handle") - .data([{type: "w"}, {type: "e"}]) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) - .attr("fill", "#525252"); - // handle-bar - svg.select("g.brush") - .selectAll("rect.handle-bar") - .data([{type: "w"}, {type: "e"}]) - .join("rect") - .attr("class", function(d) { return "handle-bar handle-bar--" + d.type; }) - .attr("x", function(d) { - if (d.type === "w") { - return selection[0] - 1; - } else if (d.type === "e") { - return selection[1] - 1; - } - }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - - }; - - const brushed = () => { - const selection = event.selection; - if (selection === null) { - // do nothing - console.log("selection === null"); - } else { - // TODO - pass selection to callback function or update scaleDomain - } - // update brush handle position - select(svg).call(brushHandle, selection); - }; - - const brush = brushX() - .extent([[0, 0], [700, this.height]]) - .on("start brush end", brushed); - - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") - .call(brush) - .call(brush.move, mainXScale.range()); // TODO -- mainXScale.range() incorrect - const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) .attr("y", 32) @@ -131,7 +48,7 @@ export class ZoomBar extends Component { .attr("fill", "none"); const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") - .attr("x", 0) + .attr("x", axisLeftWidth) .attr("y", 0) .attr("width", "100%") .attr("height", "100%") @@ -169,9 +86,9 @@ export class ZoomBar extends Component { return correspondingData; }); - // if (!this.ogXScale) { - // this.ogXScale = cartesianScales.getDomainScale(); - // } + if (!this.ogXScale) { + this.ogXScale = cartesianScales.getDomainScale(); + } const xScale = mainXScale.copy(); if (!this.ogXScale) { this.ogXScale = xScale; @@ -183,7 +100,7 @@ export class ZoomBar extends Component { }); xScale - .range([0, width]) + .range([axisLeftWidth, width]) .domain(extent(stackDataArray, (d: any) => d.date)); yScale @@ -228,9 +145,6 @@ export class ZoomBar extends Component { container, "path.zoom-graph-line", ) - .attr("stroke", "#8e8e8e") - .attr("stroke-width", 3) - .attr("fill", "none") .datum(stackDataArray) .transition( this.services.transitions.getTransition( @@ -267,7 +181,6 @@ export class ZoomBar extends Component { container, "path.zoom-graph-area", ) - .attr("fill", "#e0e0e0") .datum(stackDataArray) .transition( this.services.transitions.getTransition( @@ -277,6 +190,64 @@ export class ZoomBar extends Component { ) .attr("d", areaGenerator); + const updateBrushHandle = (g, selection) => { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{ type: "w" }, { type: "e" }]) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32); + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{ type: "w" }, { type: "e" }]) + .join("rect") + .attr("class", function (d) { + return "handle-bar handle-bar--" + d.type; + }) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12); + }; + + const brushed = () => { + const selection = event.selection; + if (selection === null) { + // do nothing + console.log("selection === null"); + } else { + // TODO - pass selection to callback function or update scaleDomain + } + // update brush handle position + select(svg).call(updateBrushHandle, selection); + + const domain = [ + mainXScale.invert(selection[0]), + mainXScale.invert(selection[1]), + ]; + // TODO -- update zoomDomain in model + // this.model.set({zoomDomain: domain}, {animate: false}); + }; + + const brush = brushX() + .extent([ + [0 + axisLeftWidth, 0], + [700, this.height], + ]) + .on("start brush end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") + .call(brush) + .call(brush.move, xScale.range()); } } } @@ -285,5 +256,4 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } - } diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 02fcf7a55f..59019956a8 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -1,11 +1,25 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { - rect.zoom-handle.dragging, - rect.zoom-handle:hover { - fill: black; - cursor: col-resize; + rect.zoom-bg { + fill: $ui-background; + stroke: $ui-01; } - rect.zoom-handle-bar { - pointer-events: none; + path.zoom-graph-line { + stroke: $ui-04; + stroke-width: 3; + fill: none; + } + + path.zoom-graph-area { + fill: $ui-03; + stroke: $ui-04; + } + + g.brush rect.handle { + fill: $icon-02; + } + + g.brush rect.handle-bar { + fill: $ui-02; } } From 13b3e2797424e516e4ad345c82f6f06f4d573391 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 16:03:16 +0800 Subject: [PATCH 005/510] feat: set brush selection to zoomDomain in Model - use flag to avoid recursive update event --- packages/core/src/components/axes/zoom-bar.ts | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index b200d522b2..12b7b356be 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,7 +17,14 @@ export class ZoomBar extends Component { ogXScale: any; + // use this flag to avoid recursive update events + skipNextUpdate = false; + render(animate = true) { + if (this.skipNextUpdate === true) { + this.skipNextUpdate = false; + return; + } // TODO - get correct axis left width const axisLeftWidth = 23; const svg = this.getContainerSVG(); @@ -28,10 +35,10 @@ export class ZoomBar extends Component { const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition, + mainXAxisPosition ); const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition, + mainYAxisPosition ); const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") @@ -96,7 +103,7 @@ export class ZoomBar extends Component { const yScale = mainYScale.copy(); const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true, + useAttrs: true }); xScale @@ -117,8 +124,8 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i, - ), + i + ) ) .y( (d, i) => @@ -128,8 +135,8 @@ export class ZoomBar extends Component { mainYScaleType, mainYAxisPosition, d, - i, - ), + i + ) ) .curve(this.services.curves.getD3Curve()); // .defined((d: any, i) => { @@ -143,14 +150,14 @@ export class ZoomBar extends Component { // }); const lineGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-line", + "path.zoom-graph-line" ) .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-line-update", - animate, - ), + animate + ) ) .attr("d", lineGenerator); @@ -161,8 +168,8 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i, - ), + i + ) ) .y0(this.height) .y1( @@ -173,20 +180,20 @@ export class ZoomBar extends Component { mainYScaleType, mainYAxisPosition, d, - i, - ), + i + ) ); const areaGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-area", + "path.zoom-graph-area" ) .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-area-update", - animate, - ), + animate + ) ) .attr("d", areaGenerator); @@ -196,9 +203,17 @@ export class ZoomBar extends Component { svg.select("g.brush") .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 3; + } else if (d.type === "e") { + return selection[1] - 3; + } + }) .attr("y", 0) .attr("width", handleSize) - .attr("height", 32); + .attr("height", 32) + .style("display", null); // always display // handle-bar svg.select("g.brush") .selectAll("rect.handle-bar") @@ -220,10 +235,10 @@ export class ZoomBar extends Component { }; const brushed = () => { - const selection = event.selection; + let selection = event.selection; if (selection === null) { - // do nothing - console.log("selection === null"); + // set to default full range + selection = xScale.range(); } else { // TODO - pass selection to callback function or update scaleDomain } @@ -231,23 +246,29 @@ export class ZoomBar extends Component { select(svg).call(updateBrushHandle, selection); const domain = [ - mainXScale.invert(selection[0]), - mainXScale.invert(selection[1]), + xScale.invert(selection[0]), + xScale.invert(selection[1]) ]; - // TODO -- update zoomDomain in model - // this.model.set({zoomDomain: domain}, {animate: false}); + // only if the brush event comes from mouseup event + if (event.sourceEvent != null && event.type === "end") { + this.skipNextUpdate = true; // avoid recursive update + this.model.set( + { zoomDomain: domain }, + { animate: false } + ); + } }; const brush = brushX() .extent([ [0 + axisLeftWidth, 0], - [700, this.height], + [700, this.height] ]) .on("start brush end", brushed); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") .call(brush) - .call(brush.move, xScale.range()); + .call(brush.move, xScale.range()); // default to full range } } } From 973ff9d38a1e9dcaefbd0b8e6b80f86025acc2de Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:42:26 +0800 Subject: [PATCH 006/510] feat: create ZoomBarOptions and default configuration - default to disable zoombar --- packages/core/src/axis-chart.ts | 9 ++++----- packages/core/src/configuration.ts | 13 +++++++++++-- packages/core/src/interfaces/charts.ts | 7 ++++++- packages/core/src/interfaces/components.ts | 10 ++++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index e4d105905f..04f9bdeeb5 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -126,9 +126,7 @@ export class AxisChart extends Chart { const zoomBarComponent = { id: "zoom-bar", - components: [ - new ZoomBar(this.model, this.services) - ], + components: [new ZoomBar(this.model, this.services)], growth: { x: LayoutGrowth.PREFERRED, y: LayoutGrowth.FIXED @@ -151,8 +149,9 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } - - topLevelLayoutComponents.push(zoomBarComponent); + if (this.model.getOptions().zoomBar.enabled === true) { + topLevelLayoutComponents.push(zoomBarComponent); + } topLevelLayoutComponents.push(fullFrameComponent); return [ diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index b68790dfd6..866d815d6f 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -21,7 +21,8 @@ import { LegendPositions, TruncationTypes, StackedBarOptions, - GaugeTypes + GaugeTypes, + ZoomBarOptions } from "./interfaces"; import enUSLocaleObject from "date-fns/locale/en-US/index"; @@ -124,6 +125,13 @@ export const timeScale: TimeScaleOptions = { } }; +/** + * ZoomBar options + */ +export const zoomBar: ZoomBarOptions = { + enabled: false +}; + /** * Base chart options common to any chart */ @@ -152,7 +160,8 @@ const chart: BaseChartOptions = { const axisChart: AxisChartOptions = Tools.merge({}, chart, { axes, timeScale, - grid + grid, + zoomBar } as AxisChartOptions); /** diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 3ec6ec6f1f..a96642bb6a 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -3,7 +3,8 @@ import { LegendOptions, TooltipOptions, GridOptions, - AxesOptions + AxesOptions, + ZoomBarOptions } from "./index"; import { BarOptions, StackedBarOptions } from "./components"; import { TimeScaleOptions } from "./axis-scales"; @@ -40,6 +41,10 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index f99b45b5e7..5ca9008aab 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -114,3 +114,13 @@ export interface BarOptions { export interface StackedBarOptions extends BarOptions { dividerSize?: number; } + +/** + * customize the ZoomBar component + */ +export interface ZoomBarOptions { + /** + * is the zoom-bar visible or not + */ + enabled?: boolean; +} From 23d8a7b1b70da8e5e6a0ed3b5e200d55d8f19904 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:44:58 +0800 Subject: [PATCH 007/510] feat: create zoombar story in storybook --- packages/core/demo/data/time-series-axis.ts | 34 ++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index c1c7183571..e9519866dc 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -13,7 +13,7 @@ export const lineTimeSeriesData15seconds = { { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } ] } ] @@ -418,3 +418,35 @@ export const lineTimeSeriesAllLabelsInPrimaryFormatOptions = { addSpaceOnEdges: 0 } }; + +// ZoomBar +export const lineTimeSeriesDataZoomBar = { + labels: ["Qty"], + datasets: [ + { + label: "Dataset 1", + data: [ + { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, + { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, + { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, + { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, + { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, + { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } + ] + } + ] +}; + +export const lineTimeSeriesZoomBarOptions = { + title: "Line (time series) - zoom-bar enabled", + axes: { + left: {}, + bottom: { + scaleType: "time" + } + }, + zoomBar: { + enabled: true + } +}; From a6d9bc62638e249c055342b89afedb07f4b4b5cc Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 11:17:12 +0800 Subject: [PATCH 008/510] fix: set tslint trailing-comma to never --- packages/core/tslint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/tslint.json b/packages/core/tslint.json index 91271d283a..a5a0970b82 100644 --- a/packages/core/tslint.json +++ b/packages/core/tslint.json @@ -92,7 +92,7 @@ "trailing-comma": [ true, { - "multiline": "always", + "multiline": "never", "singleline": "never" } ] From bd8d77ef4020067e643444901ccdd96be4e419a4 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 12:23:10 +0800 Subject: [PATCH 009/510] fix: allow zoomDomain to update for every brush event --- packages/core/src/components/axes/zoom-bar.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 12b7b356be..37c68ab870 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,14 +17,7 @@ export class ZoomBar extends Component { ogXScale: any; - // use this flag to avoid recursive update events - skipNextUpdate = false; - render(animate = true) { - if (this.skipNextUpdate === true) { - this.skipNextUpdate = false; - return; - } // TODO - get correct axis left width const axisLeftWidth = 23; const svg = this.getContainerSVG(); @@ -245,17 +238,24 @@ export class ZoomBar extends Component { // update brush handle position select(svg).call(updateBrushHandle, selection); - const domain = [ + const newDomain = [ xScale.invert(selection[0]), xScale.invert(selection[1]) ]; + // only if the brush event comes from mouseup event - if (event.sourceEvent != null && event.type === "end") { - this.skipNextUpdate = true; // avoid recursive update - this.model.set( - { zoomDomain: domain }, - { animate: false } - ); + if (event.sourceEvent != null) { + // only if zoomDomain is never set or needs update + if ( + zoomDomain === undefined || + zoomDomain[0] !== newDomain[0] || + zoomDomain[1] !== newDomain[1] + ) { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + } } }; @@ -266,9 +266,12 @@ export class ZoomBar extends Component { ]) .on("start brush end", brushed); - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") - .call(brush) - .call(brush.move, xScale.range()); // default to full range + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( + brush + ); + if (zoomDomain === undefined) { + brushArea.call(brush.move, xScale.range()); // default to full range + } } } } From 03972614e6ebb317d5b87d7e51b457d74e838d6d Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 23:06:38 +0800 Subject: [PATCH 010/510] feat: provide ZoomBar options for brush event callback - selected x range and domain as callback parameters --- packages/core/demo/data/time-series-axis.ts | 20 +++++++++++++++- packages/core/src/components/axes/zoom-bar.ts | 23 +++++++++++++++++++ packages/core/src/configuration.ts | 5 +++- packages/core/src/interfaces/components.ts | 12 ++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e9519866dc..e79de136b6 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -437,6 +437,21 @@ export const lineTimeSeriesDataZoomBar = { } ] }; +const selectionStartFun = (selection, domain) => { + console.log("ZoomBar SelectionStart callback!"); + console.log(selection); + console.log(domain); +}; +const selectionInProgressFun = (selection, domain) => { + console.log("ZoomBar SelectionInProgress callback!"); + console.log(selection); + console.log(domain); +}; +const selectionEndFun = (selection, domain) => { + console.log("ZoomBar SelectionEnd callback!"); + console.log(selection); + console.log(domain); +}; export const lineTimeSeriesZoomBarOptions = { title: "Line (time series) - zoom-bar enabled", @@ -447,6 +462,9 @@ export const lineTimeSeriesZoomBarOptions = { } }, zoomBar: { - enabled: true + enabled: true, + selectionStart: selectionStartFun, + selectionInProgress: selectionInProgressFun, + selectionEnd: selectionEndFun } }; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 37c68ab870..4fdfa2fec5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -256,6 +256,29 @@ export class ZoomBar extends Component { { animate: false } ); } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress( + selection, + newDomain + ); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } } }; diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 866d815d6f..a21b09d82c 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -129,7 +129,10 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false + enabled: false, + selectionStart: undefined, + selectionInProgress: undefined, + selectionEnd: undefined }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 5ca9008aab..eeac8dbb6e 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -123,4 +123,16 @@ export interface ZoomBarOptions { * is the zoom-bar visible or not */ enabled?: boolean; + /** + * a function to handle selection start event + */ + selectionStart?: Function; + /** + * a function to handle selection in progress event + */ + selectionInProgress?: Function; + /** + * a function to handle selection end event + */ + selectionEnd?: Function; } From 1ac8810f9f74c97c62a831674b4ec032daa97ae9 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Mon, 15 Jun 2020 18:46:54 +0800 Subject: [PATCH 011/510] feat: apply clipPath to line chart - v1 --- packages/core/src/charts/line.ts | 2 + packages/core/src/components/axes/cover.ts | 64 +++++++++++++++++++ packages/core/src/components/component.ts | 25 ++++++-- packages/core/src/components/graphs/line.ts | 3 +- packages/core/src/components/index.ts | 1 + .../core/src/services/essentials/dom-utils.ts | 30 +++++++++ 6 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/components/axes/cover.ts diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index 469bf7a28e..d766c7ad5a 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -11,6 +11,7 @@ import { Line, Ruler, Scatter, + Cover, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) Tooltip, @@ -40,6 +41,7 @@ export class LineChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts new file mode 100644 index 0000000000..20b4083e09 --- /dev/null +++ b/packages/core/src/components/axes/cover.ts @@ -0,0 +1,64 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { axisBottom, axisLeft } from "d3-axis"; +import { mouse, select } from "d3-selection"; +import { TooltipTypes, Events } from "../../interfaces"; + +export class Cover extends Component { + type = "cover"; + + coverClipPath: any; + + render(animate = true) { + // Create the cover + this.createCover(); + } + + + createCover() { + const svg = this.parent; + console.log("!!! cover svg: ", svg); + const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainYScale = this.services.cartesianScales.getMainYScale(); + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + // Get height + this.coverClipPath = DOMUtils.appendOrSelect(svg, `clipPath.${this.type}`); + this.coverClipPath + .attr("id", `${this.type}Clip`); + const coverRect = DOMUtils.appendOrSelect( + this.coverClipPath, + "rect.cover" + ); + coverRect + .attr("x", xScaleStart) + .attr("y", yScaleStart) + .attr("width", xScaleEnd - xScaleStart) + .attr("height", yScaleEnd - yScaleStart); + + this.coverClipPath + .merge(coverRect) + .lower(); + + const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); + coverG + .attr("clip-path", `url(#${this.type}Clip)`) + .attr("id", `g-${this.type}Clip`); + + } + + cleanCover(g) { + const options = this.model.getOptions(); + g.selectAll("line").attr("stroke", options.grid.strokeColor); + + // Remove extra elements + g.selectAll("text").remove(); + g.select(".domain").remove(); + } +} diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 7530628ac1..2b1d8d84d0 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -90,10 +90,27 @@ export class Component { "style", "prefix" ); - return DOMUtils.appendOrSelect( - this.parent, - `g.${settings.prefix}--${chartprefix}--${this.type}` - ); + if (this.type === "line" || this.type === "scatter") { + const { width, height } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + return DOMUtils.appendOrSelectForAxisChart( + this.parent, + `clipPath.cover`, + this.type, + 23, + 0, + (width - 23), + height, + ); + + } else { + return DOMUtils.appendOrSelect( + this.parent, + `g.${settings.prefix}--${chartprefix}--${this.type}` + ); + } + } return this.parent; diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 2d2c5c36c7..2be9c949a9 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -131,6 +131,7 @@ export class Line extends Component { this.parent .selectAll("path.line") + .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-hover-line") ) @@ -145,7 +146,7 @@ export class Line extends Component { handleLegendMouseOut = (event: CustomEvent) => { this.parent - .selectAll("path.line") + .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-mouseout-line") ) diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 8ea6485eaf..d51b27574d 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -34,3 +34,4 @@ export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; +export * from "./axes/cover"; diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index 2cb6fcdb75..92620122f4 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -149,6 +149,36 @@ export class DOMUtils extends Service { return selection; } + static appendOrSelectForAxisChart(parent, query, type, x, y, width, height) { + + const querySections = query.split("."); + const elementToAppend = querySections[0]; + + const parentOfSelection = parent.select(`${elementToAppend}.${querySections.slice(1).join(" ")}`); + const selection = parent.select(`g#g-coverClip`); + if (parentOfSelection.empty() && parent) { + parent + .append(elementToAppend) + .attr("id", `coverClip`) + .append("svg:rect") + .attr("x", x) + .attr("y", y) + .attr("width", width) + .attr("height", height) + .attr("class", querySections.slice(1).join(" ")); + } + if (selection.empty() && parent) { + parent + .append("g") + .attr("clip-path", `url(#coverClip)`) + .attr("id", `g-coverClip`); + + return parent; + } + + return selection; + } + protected svg: Element; protected width: string; protected height: string; From 8183536adf747ca868b408cc60fa22438eaa502c Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 13:37:58 +0800 Subject: [PATCH 012/510] feat: apply cover to axis chart --- packages/core/src/charts/area-stacked.ts | 2 ++ packages/core/src/charts/area.ts | 2 ++ packages/core/src/charts/bar-grouped.ts | 2 ++ packages/core/src/charts/bar-simple.ts | 2 ++ packages/core/src/charts/bar-stacked.ts | 2 ++ packages/core/src/charts/bubble.ts | 4 ++- packages/core/src/charts/scatter.ts | 2 ++ packages/core/src/components/component.ts | 26 +++++++++---------- .../core/src/services/essentials/dom-utils.ts | 8 +++--- 9 files changed, 31 insertions(+), 19 deletions(-) diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index c89a4f132d..8f18b69cf5 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -7,6 +7,7 @@ import { Tools } from "../tools"; // Components import { Grid, + Cover, StackedArea, TwoDimensionalAxes, Line, @@ -35,6 +36,7 @@ export class StackedAreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new StackedArea(this.model, this.services), diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index aaf6cbad0a..a61e3f669c 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -7,6 +7,7 @@ import { Tools } from "../tools"; // Components import { Grid, + Cover, Area, Line, Ruler, @@ -39,6 +40,7 @@ export class AreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index bba066a001..6a2be76a82 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, GroupedBar, TwoDimensionalAxes, ZeroLine, @@ -39,6 +40,7 @@ export class GroupedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new GroupedBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 0115bd6959..bf2da6772b 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, SimpleBar, TwoDimensionalAxes, ZeroLine, @@ -39,6 +40,7 @@ export class SimpleBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new SimpleBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 5edcdfc9b3..443f483d33 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, StackedBar, StackedBarRuler, TwoDimensionalAxes, @@ -42,6 +43,7 @@ export class StackedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new StackedBarRuler(this.model, this.services), new StackedBar(this.model, this.services), diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 05f1f5e4f1..dcdb8fcced 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -15,7 +15,8 @@ import { Tooltip, Legend, LayoutComponent, - Skeleton + Skeleton, + Cover } from "../components/index"; export class BubbleChart extends AxisChart { @@ -42,6 +43,7 @@ export class BubbleChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Bubble(this.model, this.services), diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 5d2a20c6cb..f3cda814cb 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -9,6 +9,7 @@ import { Skeletons } from "../interfaces/enums"; import { Grid, Ruler, + Cover, Scatter, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) @@ -42,6 +43,7 @@ export class ScatterChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Scatter(this.model, this.services), diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 2b1d8d84d0..c49dbd79fc 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -90,19 +90,19 @@ export class Component { "style", "prefix" ); - if (this.type === "line" || this.type === "scatter") { - const { width, height } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); - return DOMUtils.appendOrSelectForAxisChart( - this.parent, - `clipPath.cover`, - this.type, - 23, - 0, - (width - 23), - height, - ); + + + if ( + this.type === "line" || + this.type === "scatter" || + this.type === "area" || + this.type === "bubble" || + this.type === "area-stacked" || + this.type === "grouped-bar" || + this.type === "simple-bar" || + this.type === "scatter-stacked" + ) { + return DOMUtils.appendOrSelectForAxisChart(this.parent,`clipPath.cover`); } else { return DOMUtils.appendOrSelect( diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index 92620122f4..efc52c7355 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -149,22 +149,19 @@ export class DOMUtils extends Service { return selection; } - static appendOrSelectForAxisChart(parent, query, type, x, y, width, height) { + static appendOrSelectForAxisChart(parent, query) { const querySections = query.split("."); const elementToAppend = querySections[0]; const parentOfSelection = parent.select(`${elementToAppend}.${querySections.slice(1).join(" ")}`); const selection = parent.select(`g#g-coverClip`); + /* if (parentOfSelection.empty() && parent) { parent .append(elementToAppend) .attr("id", `coverClip`) .append("svg:rect") - .attr("x", x) - .attr("y", y) - .attr("width", width) - .attr("height", height) .attr("class", querySections.slice(1).join(" ")); } if (selection.empty() && parent) { @@ -175,6 +172,7 @@ export class DOMUtils extends Service { return parent; } + */ return selection; } From 0fc01ea54134a691a2eddfe130802f8cb83bd773 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 13:57:19 +0800 Subject: [PATCH 013/510] refactor: clean code --- packages/core/src/charts/area-stacked.ts | 2 +- packages/core/src/charts/area.ts | 2 +- packages/core/src/charts/bar-grouped.ts | 2 +- packages/core/src/charts/bar-simple.ts | 2 +- packages/core/src/charts/bar-stacked.ts | 2 +- packages/core/src/charts/bubble.ts | 4 ++-- packages/core/src/charts/line.ts | 2 +- packages/core/src/charts/scatter.ts | 2 +- packages/core/src/components/axes/cover.ts | 10 ---------- packages/core/src/components/index.ts | 3 ++- packages/core/src/services/essentials/dom-utils.ts | 4 ---- 11 files changed, 11 insertions(+), 24 deletions(-) diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 8f18b69cf5..71cc66c34d 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,8 +6,8 @@ import { Tools } from "../tools"; // Components import { - Grid, Cover, + Grid, StackedArea, TwoDimensionalAxes, Line, diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index a61e3f669c..6d808c893c 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,8 +6,8 @@ import { Tools } from "../tools"; // Components import { - Grid, Cover, + Grid, Area, Line, Ruler, diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 6a2be76a82..63f82294ca 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, GroupedBar, TwoDimensionalAxes, ZeroLine, diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index bf2da6772b..21a1ccecc0 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, SimpleBar, TwoDimensionalAxes, ZeroLine, diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 443f483d33..5816fd62e8 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, StackedBar, StackedBarRuler, TwoDimensionalAxes, diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index dcdb8fcced..11469b0355 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Ruler, Bubble, @@ -15,8 +16,7 @@ import { Tooltip, Legend, LayoutComponent, - Skeleton, - Cover + Skeleton } from "../components/index"; export class BubbleChart extends AxisChart { diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index d766c7ad5a..943686c6c2 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,11 +7,11 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Line, Ruler, Scatter, - Cover, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) Tooltip, diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index f3cda814cb..e38fc66d51 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,9 +7,9 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Ruler, - Cover, Scatter, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts index 20b4083e09..16179b45c5 100644 --- a/packages/core/src/components/axes/cover.ts +++ b/packages/core/src/components/axes/cover.ts @@ -21,7 +21,6 @@ export class Cover extends Component { createCover() { const svg = this.parent; - console.log("!!! cover svg: ", svg); const mainXScale = this.services.cartesianScales.getMainXScale(); const mainYScale = this.services.cartesianScales.getMainYScale(); @@ -52,13 +51,4 @@ export class Cover extends Component { .attr("id", `g-${this.type}Clip`); } - - cleanCover(g) { - const options = this.model.getOptions(); - g.selectAll("line").attr("stroke", options.grid.strokeColor); - - // Remove extra elements - g.selectAll("text").remove(); - g.select(".domain").remove(); - } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index d51b27574d..5b14964da6 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -29,9 +29,10 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; +export * from "./axes/cover"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; -export * from "./axes/cover"; + diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index efc52c7355..181695bcb8 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -156,7 +156,6 @@ export class DOMUtils extends Service { const parentOfSelection = parent.select(`${elementToAppend}.${querySections.slice(1).join(" ")}`); const selection = parent.select(`g#g-coverClip`); - /* if (parentOfSelection.empty() && parent) { parent .append(elementToAppend) @@ -169,11 +168,8 @@ export class DOMUtils extends Service { .append("g") .attr("clip-path", `url(#coverClip)`) .attr("id", `g-coverClip`); - return parent; } - */ - return selection; } From 31b796a946b0487038d213586327aa7f3189fc5d Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 16 Jun 2020 14:57:41 +0800 Subject: [PATCH 014/510] feat: set axes margins as zoom bar left margin --- .../components/axes/two-dimensional-axes.ts | 3 +++ packages/core/src/components/axes/zoom-bar.ts | 20 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/axes/two-dimensional-axes.ts b/packages/core/src/components/axes/two-dimensional-axes.ts index ec843b20ab..d95e70e557 100644 --- a/packages/core/src/components/axes/two-dimensional-axes.ts +++ b/packages/core/src/components/axes/two-dimensional-axes.ts @@ -114,6 +114,9 @@ export class TwoDimensionalAxes extends Component { if (isNotEqual) { this.margins = Object.assign(this.margins, margins); + // also set new margins to model to allow external components to access + this.model.set({ axesMargins: this.margins }, { animate: false }); + Object.keys(this.children).forEach((childKey) => { const child = this.children[childKey]; child.margins = this.margins; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 4fdfa2fec5..0bf64460d9 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -18,10 +18,7 @@ export class ZoomBar extends Component { ogXScale: any; render(animate = true) { - // TODO - get correct axis left width - const axisLeftWidth = 23; const svg = this.getContainerSVG(); - const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); @@ -34,6 +31,13 @@ export class ZoomBar extends Component { mainYAxisPosition ); + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") .attr("width", "100%") .attr("height", this.height) @@ -48,7 +52,7 @@ export class ZoomBar extends Component { .attr("fill", "none"); const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") - .attr("x", axisLeftWidth) + .attr("x", axesLeftMargin) .attr("y", 0) .attr("width", "100%") .attr("height", "100%") @@ -100,7 +104,7 @@ export class ZoomBar extends Component { }); xScale - .range([axisLeftWidth, width]) + .range([axesLeftMargin, width]) .domain(extent(stackDataArray, (d: any) => d.date)); yScale @@ -232,8 +236,6 @@ export class ZoomBar extends Component { if (selection === null) { // set to default full range selection = xScale.range(); - } else { - // TODO - pass selection to callback function or update scaleDomain } // update brush handle position select(svg).call(updateBrushHandle, selection); @@ -284,8 +286,8 @@ export class ZoomBar extends Component { const brush = brushX() .extent([ - [0 + axisLeftWidth, 0], - [700, this.height] + [axesLeftMargin, 0], + [width, this.height] ]) .on("start brush end", brushed); From fde62aaa6cf28455ee8a9ba2dfbc62615735d30b Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 16:04:29 +0800 Subject: [PATCH 015/510] refactor: add todo --- packages/core/src/components/component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index c49dbd79fc..5664f29ec8 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -91,18 +91,18 @@ export class Component { "prefix" ); - + // @todo Chart type equals to axis-chart if ( - this.type === "line" || - this.type === "scatter" || - this.type === "area" || + this.type === "line" || + this.type === "scatter" || + this.type === "area" || this.type === "bubble" || this.type === "area-stacked" || this.type === "grouped-bar" || this.type === "simple-bar" || this.type === "scatter-stacked" ) { - return DOMUtils.appendOrSelectForAxisChart(this.parent,`clipPath.cover`); + return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); } else { return DOMUtils.appendOrSelect( From 8977bcf8c68101ef3649c26a30429cd430398495 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 16 Jun 2020 16:54:51 +0800 Subject: [PATCH 016/510] fix: double the minTickSize for datetime ticks --- packages/core/src/components/axes/axis.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 04433e6399..484106f8fa 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -109,10 +109,9 @@ export class Axis extends Component { // if zoomDomain is available, update scale domain to Date array. const zoomDomain = this.model.get("zoomDomain"); if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain.map(d => new Date(d))); + scale.domain(zoomDomain.map((d) => new Date(d))); } - // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -412,11 +411,11 @@ export class Axis extends Component { const ticksNumber = isTimeScaleType ? axis.tickValues().length : scale.ticks().length; - const estimatedTickSize = width / ticksNumber / 1.6; - - rotateTicks = estimatedTickSize < minTickSize; + const estimatedTickSize = width / ticksNumber / 2; + rotateTicks = isTimeScaleType + ? estimatedTickSize < minTickSize * 2 // datetime tick could be very long + : estimatedTickSize < minTickSize; } - if (rotateTicks) { if (!isNumberOfTicksProvided) { axis.ticks( From 68817030f215275bb8df9edeae59ec89e98d9bb9 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 18 Jun 2020 13:42:33 +0800 Subject: [PATCH 017/510] feat: add chart brush --- packages/core/src/charts/area-stacked.ts | 5 +- packages/core/src/charts/area.ts | 5 +- packages/core/src/charts/bar-grouped.ts | 5 +- packages/core/src/charts/bar-simple.ts | 5 +- packages/core/src/charts/bar-stacked.ts | 5 +- packages/core/src/charts/bubble.ts | 5 +- packages/core/src/charts/donut.ts | 5 +- packages/core/src/charts/line.ts | 5 +- packages/core/src/charts/pie.ts | 5 +- packages/core/src/charts/radar.ts | 4 +- packages/core/src/charts/scatter.ts | 5 +- packages/core/src/components/axes/brush.ts | 142 +++++++++++++++++++++ packages/core/src/components/index.ts | 1 + 13 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/axes/brush.ts diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 71cc66c34d..b87365a97b 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, Cover, Grid, StackedArea, @@ -34,7 +35,7 @@ export class StackedAreaChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -48,6 +49,8 @@ export class StackedAreaChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 6d808c893c..8d5b94aa49 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, Cover, Grid, Area, @@ -38,7 +39,7 @@ export class AreaChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -51,6 +52,8 @@ export class AreaChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 63f82294ca..5668d302eb 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, GroupedBar, @@ -38,7 +39,7 @@ export class GroupedBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -49,6 +50,8 @@ export class GroupedBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 21a1ccecc0..951cd4509f 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, SimpleBar, @@ -38,7 +39,7 @@ export class SimpleBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -49,6 +50,8 @@ export class SimpleBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 5816fd62e8..e5684fb737 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, StackedBar, @@ -41,7 +42,7 @@ export class StackedBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class StackedBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 11469b0355..fd1c03a354 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Ruler, @@ -41,7 +42,7 @@ export class BubbleChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class BubbleChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/donut.ts b/packages/core/src/charts/donut.ts index 100faa5fac..18e00d2c36 100644 --- a/packages/core/src/charts/donut.ts +++ b/packages/core/src/charts/donut.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Donut, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -33,13 +34,15 @@ export class DonutChart extends PieChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new Donut(this.model, this.services), new Skeleton(this.model, this.services, { skeleton: Skeletons.DONUT }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getChartComponents(graphFrameComponents); return components; } diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index 943686c6c2..e950b1c4af 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Line, @@ -39,7 +40,7 @@ export class LineChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -51,6 +52,8 @@ export class LineChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 0fc4429807..9631b459a8 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Pie, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -46,13 +47,15 @@ export class PieChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new Pie(this.model, this.services), new Skeleton(this.model, this.services, { skeleton: Skeletons.PIE }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 8758bf1591..6a79b1a13b 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, // the imports below are needed because of typescript bug (error TS4029) Legend, LayoutComponent @@ -41,7 +42,8 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [new Radar(this.model, this.services)]; + const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index e38fc66d51..4c5b92d89b 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Ruler, @@ -41,7 +42,7 @@ export class ScatterChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class ScatterChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts new file mode 100644 index 0000000000..60dc0a8945 --- /dev/null +++ b/packages/core/src/components/axes/brush.ts @@ -0,0 +1,142 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { extent } from "d3-array"; +import { brushX } from "d3-brush"; +import { event } from "d3-selection"; +import { scaleTime } from "d3-scale"; + +export class Brush extends Component { + type = "brush"; + + render(animate = true) { + const svg = this.parent; + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const mainXScaleType = cartesianScales.getScaleTypeByPosition( + mainXAxisPosition + ); + + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + + const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainYScale = this.services.cartesianScales.getMainYScale(); + + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + const container = DOMUtils.appendOrSelect(svg, "svg.brush-container") + .attr("width", "100%") + .attr("height", "100%") + .attr("opacity", 1); + + if (mainXScale) { + const displayData = this.model.getDisplayData(); + + if (mainXScaleType === ScaleTypes.TIME) { + // Get all date values provided in data + // TODO - Could be re-used through the model + let allDates = []; + displayData.forEach((data) => { + allDates = allDates.concat(Number(data.date)); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + + // Go through all date values + // And get corresponding data from each dataset + const stackDataArray = allDates.map((date) => { + let count = 0; + let correspondingSum = 0; + const correspondingData = {}; + + displayData.forEach((data) => { + if (Number(data.date) === Number(date)) { + ++count; + correspondingSum += data.value; + } + }); + correspondingData["date"] = date; + correspondingData["value"] = correspondingSum; + + return correspondingData; + }); + + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + + let zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain === undefined) { + zoomDomain = extent(stackDataArray, (d: any) => d.date); // default to full range + this.model.set( + { zoomDomain: zoomDomain }, + { animate: false } + ); + } + + const brushed = () => { + let selection = event.selection; + if (selection !== null) { + // update zoombar handle position + // select(svg).call(updateBrushHandle, selection); + + // get current zoomDomain + zoomDomain = this.model.get("zoomDomain"); + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([axesLeftMargin, width]) + .domain(zoomDomain); + + const newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + // only if the brush event comes from mouseup event + if (event.sourceEvent != null) { + // only if zoomDomain needs update + if (zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1]) { + this.model.set( + { zoomDomain: newDomain, zoomDomainForZoomBar: newDomain }, + { animate: false } + ); + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + } + }; + + const brush = brushX() + .extent([ + [xScaleStart, 0], + [width, yScaleEnd] + ]) + .on("end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.chart-brush").call( + brush + ); + // no need for having default brush selection + // @todo try to hide brush after selection + setTimeout(()=> {brushArea.call(brush.move);}, 0); + } + } + } +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 5b14964da6..aff7968ac5 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -29,6 +29,7 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; +export * from "./axes/brush"; export * from "./axes/cover"; export * from "./axes/grid"; export * from "./axes/ruler"; From 6f83f7b77b045df7b445f38f8ff799867249345e Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 18 Jun 2020 14:19:35 +0800 Subject: [PATCH 018/510] refactor: add brush in axis chart component --- packages/core/src/axis-chart.ts | 3 +++ packages/core/src/charts/area-stacked.ts | 3 --- packages/core/src/charts/area.ts | 3 --- packages/core/src/charts/bar-grouped.ts | 3 --- packages/core/src/charts/bar-simple.ts | 3 --- packages/core/src/charts/bar-stacked.ts | 3 --- packages/core/src/charts/bubble.ts | 3 --- packages/core/src/charts/donut.ts | 3 --- packages/core/src/charts/line.ts | 3 --- packages/core/src/charts/pie.ts | 5 +---- packages/core/src/charts/radar.ts | 4 +--- packages/core/src/charts/scatter.ts | 3 --- 12 files changed, 5 insertions(+), 34 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 04f9bdeeb5..543d0ac6d4 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -8,6 +8,7 @@ import { AxisChartOptions } from "./interfaces/index"; import { + Brush, LayoutComponent, Legend, Title, @@ -48,6 +49,8 @@ export class AxisChart extends Chart { } }; + !!this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ? + graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index b87365a97b..42895bdc6b 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, Cover, Grid, StackedArea, @@ -49,8 +48,6 @@ export class StackedAreaChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 8d5b94aa49..2b313f6cc7 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, Cover, Grid, Area, @@ -52,8 +51,6 @@ export class AreaChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 5668d302eb..59354739e6 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, GroupedBar, @@ -50,8 +49,6 @@ export class GroupedBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 951cd4509f..5709ea0e9b 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, SimpleBar, @@ -50,8 +49,6 @@ export class SimpleBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index e5684fb737..c2510fa7da 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, StackedBar, @@ -53,8 +52,6 @@ export class StackedBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index fd1c03a354..7238de0aa3 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Ruler, @@ -53,8 +52,6 @@ export class BubbleChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/donut.ts b/packages/core/src/charts/donut.ts index 18e00d2c36..a3c9a33a61 100644 --- a/packages/core/src/charts/donut.ts +++ b/packages/core/src/charts/donut.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Donut, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -41,8 +40,6 @@ export class DonutChart extends PieChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getChartComponents(graphFrameComponents); return components; } diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index e950b1c4af..ee4e07f163 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Line, @@ -52,8 +51,6 @@ export class LineChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 9631b459a8..4642ea8f2d 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -8,7 +8,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Pie, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -53,9 +52,7 @@ export class PieChart extends Chart { skeleton: Skeletons.PIE }) ]; - - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 6a79b1a13b..80b8e7888c 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, // the imports below are needed because of typescript bug (error TS4029) Legend, LayoutComponent @@ -43,8 +42,7 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 4c5b92d89b..2acacdf9df 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Ruler, @@ -53,8 +52,6 @@ export class ScatterChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); From 5d7056d70d3a02ee120631ed2ca21b937404e4a8 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 18 Jun 2020 22:32:35 +0800 Subject: [PATCH 019/510] refactor: move brush functions to ZoomBar class function --- packages/core/src/components/axes/zoom-bar.ts | 180 +++++++++--------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0bf64460d9..8f5d2587e7 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -194,94 +194,8 @@ export class ZoomBar extends Component { ) .attr("d", areaGenerator); - const updateBrushHandle = (g, selection) => { - const handleSize = 5; - // handle - svg.select("g.brush") - .selectAll("rect.handle") - .data([{ type: "w" }, { type: "e" }]) - .attr("x", function (d) { - if (d.type === "w") { - return selection[0] - 3; - } else if (d.type === "e") { - return selection[1] - 3; - } - }) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) - .style("display", null); // always display - // handle-bar - svg.select("g.brush") - .selectAll("rect.handle-bar") - .data([{ type: "w" }, { type: "e" }]) - .join("rect") - .attr("class", function (d) { - return "handle-bar handle-bar--" + d.type; - }) - .attr("x", function (d) { - if (d.type === "w") { - return selection[0] - 1; - } else if (d.type === "e") { - return selection[1] - 1; - } - }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12); - }; - - const brushed = () => { - let selection = event.selection; - if (selection === null) { - // set to default full range - selection = xScale.range(); - } - // update brush handle position - select(svg).call(updateBrushHandle, selection); - - const newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { - // only if zoomDomain is never set or needs update - if ( - zoomDomain === undefined || - zoomDomain[0] !== newDomain[0] || - zoomDomain[1] !== newDomain[1] - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress( - selection, - newDomain - ); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); - } - } + const brushEventListener = () => { + this.brushed(zoomDomain, xScale, event.selection); }; const brush = brushX() @@ -289,13 +203,18 @@ export class ZoomBar extends Component { [axesLeftMargin, 0], [width, this.height] ]) - .on("start brush end", brushed); + .on("start brush end", brushEventListener); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( brush ); if (zoomDomain === undefined) { brushArea.call(brush.move, xScale.range()); // default to full range + } else { + // brushArea.call( + // brush.move, + // zoomDomain.map((domain) => xScale(domain)) //set brush to correct position + // ); } } } @@ -305,4 +224,87 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } + + // brush event listener + brushed(zoomDomain, scale, selection) { + if (selection === null) { + // set to default full range + selection = scale.range(); + } + // update brush handle position + this.updateBrushHandle(this.getContainerSVG(), selection); + + const newDomain = [ + scale.invert(selection[0]), + scale.invert(selection[1]) + ]; + // only if the brush event comes from mouseup event + if (event.sourceEvent != null) { + // only if zoomDomain is never set or needs update + if ( + zoomDomain === undefined || + zoomDomain[0] !== newDomain[0] || + zoomDomain[1] !== newDomain[1] + ) { + this.model.set({ zoomDomain: newDomain }, { animate: false }); + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + } + + updateBrushHandle(svg, selection) { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{ type: "w" }, { type: "e" }]) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 3; + } else if (d.type === "e") { + return selection[1] - 3; + } + }) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32) + .style("display", null); // always display + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{ type: "w" }, { type: "e" }]) + .join("rect") + .attr("class", function (d) { + return "handle-bar handle-bar--" + d.type; + }) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12); + } } From 1f5a6b855d42cc85a0077adb61ea157fc16d8991 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 19 Jun 2020 11:57:00 +0800 Subject: [PATCH 020/510] fix: handel situation of zoom bar selection[0] equals selection[1] --- packages/core/src/components/axes/zoom-bar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 8f5d2587e7..d7aff2470a 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -227,7 +227,9 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - if (selection === null) { + // follow d3 behavior: when selection[0] === selection[1], reset default full range + // @todo find a better way to handel the situation when selection[0] === selection[1] + if (selection === null || selection[0] === selection[1]) { // set to default full range selection = scale.range(); } From fcb4c44cacf7094ff482873709ab0820006e60d0 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 12:37:08 +0800 Subject: [PATCH 021/510] feat: ZoomBar could update brush based on zoomDomain - fix some brush handle UI bugs --- packages/core/src/components/axes/zoom-bar.ts | 104 +++++++++++------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d7aff2470a..3c26a897bd 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,6 +17,8 @@ export class ZoomBar extends Component { ogXScale: any; + brush = brushX(); + render(animate = true) { const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -198,23 +200,30 @@ export class ZoomBar extends Component { this.brushed(zoomDomain, xScale, event.selection); }; - const brush = brushX() + this.brush .extent([ [axesLeftMargin, 0], [width, this.height] ]) + .on("start brush end", null) // remove old listener first .on("start brush end", brushEventListener); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( - brush + this.brush ); - if (zoomDomain === undefined) { - brushArea.call(brush.move, xScale.range()); // default to full range + if ( + zoomDomain === undefined || + zoomDomain[0].valueOf() === zoomDomain[1].valueOf() + ) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range() + ); } else { - // brushArea.call( - // brush.move, - // zoomDomain.map((domain) => xScale(domain)) //set brush to correct position - // ); + const selected = zoomDomain.map((domain) => xScale(domain)); + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle(this.getContainerSVG(), selected); } } } @@ -240,8 +249,13 @@ export class ZoomBar extends Component { scale.invert(selection[0]), scale.invert(selection[1]) ]; - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { + + // be aware that the value of d3.event changes during an event! + // update zoomDomain only if the event comes from mousemove event + if ( + event.sourceEvent != null && + event.sourceEvent.type === "mousemove" + ) { // only if zoomDomain is never set or needs update if ( zoomDomain === undefined || @@ -250,45 +264,53 @@ export class ZoomBar extends Component { ) { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); - } + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if (zoomBarOptions.selectionEnd !== undefined && event.type === "end") { + zoomBarOptions.selectionEnd(selection, newDomain); } } updateBrushHandle(svg, selection) { - const handleSize = 5; + // @todo the handle size, height are calculated by d3 library + // need to figure out how to override the value + const handleWidth = 6; + const handleHeight = 38; + const handleXDiff = -handleWidth / 2; + const handleYDiff = -(handleHeight - this.height) / 2; + + const handleBarWidth = 2; + const handleBarHeight = 12; + const handleBarXDiff = -handleBarWidth / 2; + const handleYBarDiff = + (handleHeight - handleBarHeight) / 2 + handleYDiff; // handle svg.select("g.brush") .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { if (d.type === "w") { - return selection[0] - 3; + return selection[0] + handleXDiff; } else if (d.type === "e") { - return selection[1] - 3; + return selection[1] + handleXDiff; } }) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) + .attr("y", handleYDiff) + .attr("width", handleWidth) + .attr("height", handleHeight) .style("display", null); // always display // handle-bar svg.select("g.brush") @@ -300,13 +322,13 @@ export class ZoomBar extends Component { }) .attr("x", function (d) { if (d.type === "w") { - return selection[0] - 1; + return selection[0] + handleBarXDiff; } else if (d.type === "e") { - return selection[1] - 1; + return selection[1] + handleBarXDiff; } }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12); + .attr("y", handleYBarDiff) + .attr("width", handleBarWidth) + .attr("height", handleBarHeight); } } From 5ae26bbcfd1f0fd74d6f02810c360a0467977a17 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 12:51:20 +0800 Subject: [PATCH 022/510] fix: handle empty selection --- packages/core/src/components/axes/zoom-bar.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 3c26a897bd..04507c90a3 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -197,7 +197,21 @@ export class ZoomBar extends Component { .attr("d", areaGenerator); const brushEventListener = () => { - this.brushed(zoomDomain, xScale, event.selection); + const selection = event.selection; + // follow d3 behavior: when selection is null, reset default full range + // @todo find a better way to handel the situation when selection is null + // select behavior is completed, but nothing selected + if (selection === null) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range() + ); + } else if (selection[0] === selection[1]) { + // select behavior is not completed yet, do nothing + } else { + this.brushed(zoomDomain, xScale, selection); + } }; this.brush @@ -236,12 +250,6 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - // follow d3 behavior: when selection[0] === selection[1], reset default full range - // @todo find a better way to handel the situation when selection[0] === selection[1] - if (selection === null || selection[0] === selection[1]) { - // set to default full range - selection = scale.range(); - } // update brush handle position this.updateBrushHandle(this.getContainerSVG(), selection); From 0dab26aa012d3d329cb3f4de348aed96b39434a8 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 19 Jun 2020 14:13:17 +0800 Subject: [PATCH 023/510] fix: set to default full range when same selection of brush --- packages/core/src/components/axes/brush.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 60dc0a8945..0a22fe0d79 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -45,7 +45,7 @@ export class Brush extends Component { if (mainXScaleType === ScaleTypes.TIME) { // Get all date values provided in data - // TODO - Could be re-used through the model + // @todo - Could be re-used through the model let allDates = []; displayData.forEach((data) => { allDates = allDates.concat(Number(data.date)); @@ -86,10 +86,8 @@ export class Brush extends Component { const brushed = () => { let selection = event.selection; - if (selection !== null) { - // update zoombar handle position - // select(svg).call(updateBrushHandle, selection); + if (selection !== null) { // get current zoomDomain zoomDomain = this.model.get("zoomDomain"); // create xScale based on current zoomDomain @@ -97,17 +95,26 @@ export class Brush extends Component { .range([axesLeftMargin, width]) .domain(zoomDomain); - const newDomain = [ + let newDomain = [ xScale.invert(selection[0]), xScale.invert(selection[1]) ]; + + // check if slected start time and end time are the same + if(newDomain[0].valueOf() === newDomain[1].valueOf()) { + // same as d3 behavior and zoombar behavior: set to default full range + newDomain = extent(stackDataArray, (d: any) => d.date); + } // only if the brush event comes from mouseup event if (event.sourceEvent != null) { // only if zoomDomain needs update - if (zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1]) { + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { this.model.set( - { zoomDomain: newDomain, zoomDomainForZoomBar: newDomain }, + { zoomDomain: newDomain }, { animate: false } ); } From eb5f9698df8a146761a396361d16e584f7ff7d2a Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 13:59:36 +0800 Subject: [PATCH 024/510] fix: fix bug when selection === null --- packages/core/src/components/axes/zoom-bar.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 04507c90a3..f7894985c5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -202,11 +202,7 @@ export class ZoomBar extends Component { // @todo find a better way to handel the situation when selection is null // select behavior is completed, but nothing selected if (selection === null) { - brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle( - this.getContainerSVG(), - xScale.range() - ); + this.brushed(zoomDomain, xScale, xScale.range()); } else if (selection[0] === selection[1]) { // select behavior is not completed yet, do nothing } else { @@ -259,10 +255,12 @@ export class ZoomBar extends Component { ]; // be aware that the value of d3.event changes during an event! - // update zoomDomain only if the event comes from mousemove event + // update zoomDomain only if the event comes from mouse event if ( event.sourceEvent != null && - event.sourceEvent.type === "mousemove" + (event.sourceEvent.type === "mousemove" || + event.sourceEvent.type === "mouseup" || + event.sourceEvent.type === "mousedown") ) { // only if zoomDomain is never set or needs update if ( From 7aaabea60bc97cf3f1042673af4ce7ea177ace67 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 14:41:24 +0800 Subject: [PATCH 025/510] feat: add ZoomBarOptions.initZoomDomain - update storybook - add init() and destroy() in ZoomBar --- packages/core/demo/data/time-series-axis.ts | 5 ++++ packages/core/src/components/axes/zoom-bar.ts | 25 ++++++++++++++++--- packages/core/src/configuration.ts | 1 + packages/core/src/interfaces/components.ts | 6 +++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e79de136b6..845e1bd1a7 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -453,6 +453,10 @@ const selectionEndFun = (selection, domain) => { console.log(domain); }; +const initZoomDomain = [ + new Date(2020, 11, 10, 23, 59, 25), + new Date(2020, 11, 11, 0, 0, 25) +]; export const lineTimeSeriesZoomBarOptions = { title: "Line (time series) - zoom-bar enabled", axes: { @@ -463,6 +467,7 @@ export const lineTimeSeriesZoomBarOptions = { }, zoomBar: { enabled: true, + initZoomDomain: initZoomDomain, selectionStart: selectionStartFun, selectionInProgress: selectionInProgressFun, selectionEnd: selectionEndFun diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index f7894985c5..0137960f8d 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -19,6 +19,17 @@ export class ZoomBar extends Component { brush = brushX(); + init() { + // get initZoomDomain + const zoomBarOptions = this.model.getOptions().zoomBar; + if (zoomBarOptions.initZoomDomain !== undefined) { + this.model.set( + { zoomDomain: zoomBarOptions.initZoomDomain }, + { skipUpdate: true } + ); + } + } + render(animate = true) { const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -221,6 +232,7 @@ export class ZoomBar extends Component { const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( this.brush ); + if ( zoomDomain === undefined || zoomDomain[0].valueOf() === zoomDomain[1].valueOf() @@ -239,10 +251,11 @@ export class ZoomBar extends Component { } } - zoomIn() { - const mainXScale = this.services.cartesianScales.getMainXScale(); - console.log("zoom in", mainXScale.domain()); - } + // could be used by Toolbar + // zoomIn() { + // const mainXScale = this.services.cartesianScales.getMainXScale(); + // console.log("zoom in", mainXScale.domain()); + // } // brush event listener brushed(zoomDomain, scale, selection) { @@ -337,4 +350,8 @@ export class ZoomBar extends Component { .attr("width", handleBarWidth) .attr("height", handleBarHeight); } + + destroy() { + this.brush.on("start brush end", null); // remove event listener + } } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index a21b09d82c..99f3a7dd68 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -130,6 +130,7 @@ export const timeScale: TimeScaleOptions = { */ export const zoomBar: ZoomBarOptions = { enabled: false, + initZoomDomain: undefined, selectionStart: undefined, selectionInProgress: undefined, selectionEnd: undefined diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index eeac8dbb6e..e01eba594c 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -123,6 +123,12 @@ export interface ZoomBarOptions { * is the zoom-bar visible or not */ enabled?: boolean; + + /** + * an two element array which represents the initial zoom domain + */ + initZoomDomain?: Object[]; + /** * a function to handle selection start event */ From 68e095c4f045f554484eb662925f500e9dd84f57 Mon Sep 17 00:00:00 2001 From: EricYang Date: Sat, 20 Jun 2020 18:03:35 +0800 Subject: [PATCH 026/510] feat: create Zoombar storybook demo group --- packages/core/demo/data/index.ts | 54 +++++++++++++ packages/core/demo/data/time-series-axis.ts | 55 ------------- packages/core/demo/data/zoom-bar.ts | 88 +++++++++++++++++++++ 3 files changed, 142 insertions(+), 55 deletions(-) create mode 100644 packages/core/demo/data/zoom-bar.ts diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 46f6be24ee..35d1f640b9 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -9,6 +9,7 @@ import * as scatterDemos from "./scatter"; import * as stepDemos from "./step"; import * as timeSeriesAxisDemos from "./time-series-axis"; import * as radarDemos from "./radar"; +import * as zoomBarDemos from "./zoom-bar"; export * from "./area"; export * from "./bar"; @@ -662,6 +663,59 @@ let allDemoGroups = [ chartType: chartTypes.RadarChart } ] + }, + { + title: "Zoom bar", + demos: [ + { + options: zoomBarDemos.zoomBarSimpleBarTimeSeriesOptions, + data: zoomBarDemos.zoomBarSimpleBarTimeSeriesData, + chartType: chartTypes.SimpleBarChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarStackedBarTimeSeriesOptions, + data: zoomBarDemos.zoomBarStackedBarTimeSeriesData, + chartType: chartTypes.StackedBarChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarBubbleTimeSeriesOptions, + data: zoomBarDemos.zoomBarBubbleTimeSeriesData, + chartType: chartTypes.BubbleChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeriesOptions, + data: zoomBarDemos.zoomBarLineTimeSeriesData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarScatterTimeSeriesOptions, + data: zoomBarDemos.zoomBarScatterTimeSeriesData, + chartType: chartTypes.ScatterChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarStepTimeSeriesOptions, + data: zoomBarDemos.zoomBarStepTimeSeriesData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeries15secondsOptions, + data: zoomBarDemos.zoomBarLineTimeSeries15secondsData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeriesInitDomainOptions, + data: zoomBarDemos.zoomBarLineTimeSeriesInitDomainData, + chartType: chartTypes.LineChart, + isDemoExample: false + } + ] } ] as any; diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index 845e1bd1a7..4ac497de89 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -418,58 +418,3 @@ export const lineTimeSeriesAllLabelsInPrimaryFormatOptions = { addSpaceOnEdges: 0 } }; - -// ZoomBar -export const lineTimeSeriesDataZoomBar = { - labels: ["Qty"], - datasets: [ - { - label: "Dataset 1", - data: [ - { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, - { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, - { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, - { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, - { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } - ] - } - ] -}; -const selectionStartFun = (selection, domain) => { - console.log("ZoomBar SelectionStart callback!"); - console.log(selection); - console.log(domain); -}; -const selectionInProgressFun = (selection, domain) => { - console.log("ZoomBar SelectionInProgress callback!"); - console.log(selection); - console.log(domain); -}; -const selectionEndFun = (selection, domain) => { - console.log("ZoomBar SelectionEnd callback!"); - console.log(selection); - console.log(domain); -}; - -const initZoomDomain = [ - new Date(2020, 11, 10, 23, 59, 25), - new Date(2020, 11, 11, 0, 0, 25) -]; -export const lineTimeSeriesZoomBarOptions = { - title: "Line (time series) - zoom-bar enabled", - axes: { - left: {}, - bottom: { - scaleType: "time" - } - }, - zoomBar: { - enabled: true, - initZoomDomain: initZoomDomain, - selectionStart: selectionStartFun, - selectionInProgress: selectionInProgressFun, - selectionEnd: selectionEndFun - } -}; diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts new file mode 100644 index 0000000000..5c97e33973 --- /dev/null +++ b/packages/core/demo/data/zoom-bar.ts @@ -0,0 +1,88 @@ +import * as timeSeriesAxisChart from "./time-series-axis"; +import * as barChart from "./bar"; +import * as bubbleChart from "./bubble"; +import * as lineChart from "./line"; +import * as scatterChart from "./scatter"; +import * as stepChart from "./step"; + +// default function for selection callback +const selectionStartFun = (selection, domain) => { + console.log("ZoomBar SelectionStart callback!"); + console.log(selection); + console.log(domain); +}; +const selectionInProgressFun = (selection, domain) => { + console.log("ZoomBar SelectionInProgress callback!"); + console.log(selection); + console.log(domain); +}; +const selectionEndFun = (selection, domain) => { + console.log("ZoomBar SelectionEnd callback!"); + console.log(selection); + console.log(domain); +}; + +const initZoomDomain = [ + new Date(2020, 11, 10, 23, 59, 25), + new Date(2020, 11, 11, 0, 0, 25) +]; + +const defaultZoomBarOptions = { + enabled: true, + initZoomDomain: undefined, + selectionStart: selectionStartFun, + selectionInProgress: selectionInProgressFun, + selectionEnd: selectionEndFun +}; + +// utility function to update title and enable zoomBar option +const updateOptions = (options) => { + options["title"] = options["title"] + " - Zoom bar enabled"; + options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); + return options; +}; + +export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; +export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( + Object.assign({}, barChart.simpleBarTimeSeriesOptions) +); + +export const zoomBarStackedBarTimeSeriesData = + barChart.stackedBarTimeSeriesData; +export const zoomBarStackedBarTimeSeriesOptions = updateOptions( + Object.assign({}, barChart.stackedBarTimeSeriesOptions) +); + +export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; +export const zoomBarBubbleTimeSeriesOptions = updateOptions( + Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) +); + +export const zoomBarLineTimeSeriesData = lineChart.lineTimeSeriesData; +export const zoomBarLineTimeSeriesOptions = updateOptions( + Object.assign({}, lineChart.lineTimeSeriesOptions) +); + +export const zoomBarScatterTimeSeriesData = scatterChart.scatterTimeSeriesData; +export const zoomBarScatterTimeSeriesOptions = updateOptions( + Object.assign({}, scatterChart.scatterTimeSeriesOptions) +); + +export const zoomBarStepTimeSeriesData = stepChart.stepTimeSeriesData; +export const zoomBarStepTimeSeriesOptions = updateOptions( + Object.assign({}, stepChart.stepTimeSeriesOptions) +); + +export const zoomBarLineTimeSeries15secondsData = + timeSeriesAxisChart.lineTimeSeriesData15seconds; +export const zoomBarLineTimeSeries15secondsOptions = updateOptions( + Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) +); + +export const zoomBarLineTimeSeriesInitDomainData = + timeSeriesAxisChart.lineTimeSeriesData15seconds; +export const zoomBarLineTimeSeriesInitDomainOptions = updateOptions( + Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) +); +zoomBarLineTimeSeriesInitDomainOptions["title"] += " with initial zoom domain"; +zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initZoomDomain = initZoomDomain; From e4c976a0e32355f6414743cb91aa8ab41571becb Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 23 Jun 2020 10:08:11 +0800 Subject: [PATCH 027/510] fix: apply cover to stacked bar --- packages/core/src/components/component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 5664f29ec8..6aa5e17ee0 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -100,6 +100,7 @@ export class Component { this.type === "area-stacked" || this.type === "grouped-bar" || this.type === "simple-bar" || + this.type === "stacked-bar" || this.type === "scatter-stacked" ) { return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); From cbec6805c83dac0e0335bd6a4c8e56f112b067da Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 24 Jun 2020 12:23:11 +0800 Subject: [PATCH 028/510] feat: create ZoomBar event and handler - use Events.ZoomBar.UPDATE to handle axesMargins update --- .../core/src/components/axes/two-dimensional-axes.ts | 4 +++- packages/core/src/components/axes/zoom-bar.ts | 9 ++++++++- packages/core/src/interfaces/events.ts | 7 +++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/axes/two-dimensional-axes.ts b/packages/core/src/components/axes/two-dimensional-axes.ts index d95e70e557..aa8b1cccb3 100644 --- a/packages/core/src/components/axes/two-dimensional-axes.ts +++ b/packages/core/src/components/axes/two-dimensional-axes.ts @@ -5,6 +5,7 @@ import { Axis } from "./axis"; import { Tools } from "../../tools"; import { DOMUtils } from "../../services"; import { Threshold } from "../essentials/threshold"; +import { Events } from "./../../interfaces"; export class TwoDimensionalAxes extends Component { type = "2D-axes"; @@ -115,7 +116,8 @@ export class TwoDimensionalAxes extends Component { this.margins = Object.assign(this.margins, margins); // also set new margins to model to allow external components to access - this.model.set({ axesMargins: this.margins }, { animate: false }); + this.model.set({ axesMargins: this.margins }, { skipUpdate: true }); + this.services.events.dispatchEvent(Events.ZoomBar.UPDATE); Object.keys(this.children).forEach((childKey) => { const child = this.children[childKey]; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0137960f8d..a6d85ed844 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -1,7 +1,7 @@ // Internal Imports import { Component } from "../component"; import { Tools } from "../../tools"; -import { ScaleTypes } from "../../interfaces"; +import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports @@ -20,6 +20,10 @@ export class ZoomBar extends Component { brush = brushX(); init() { + this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); + // get initZoomDomain const zoomBarOptions = this.model.getOptions().zoomBar; if (zoomBarOptions.initZoomDomain !== undefined) { @@ -353,5 +357,8 @@ export class ZoomBar extends Component { destroy() { this.brush.on("start brush end", null); // remove event listener + this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); } } diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 2850204dc5..2a25fd5992 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -17,6 +17,13 @@ export enum Model { UPDATE = "model-update" } +/** + * enum of all events related to the zoom-bar + */ +export enum ZoomBar { + UPDATE = "zoom-bar-update" +} + /** * enum of all axis-related events */ From 87e30679bf1392e7d5a15b0a447435e9ec9b608e Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 24 Jun 2020 12:48:50 +0800 Subject: [PATCH 029/510] fix: fix merge error - move ZoomBarOptions from BaseChartOptions to AxisChartOptions --- packages/core/src/interfaces/charts.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index a96642bb6a..41bb3d794e 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -41,10 +41,6 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; - /** - * zoombar configuration - */ - zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ @@ -117,6 +113,10 @@ export interface AxisChartOptions extends BaseChartOptions { axes?: AxesOptions; grid?: GridOptions; timeScale?: TimeScaleOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; } /** @@ -213,7 +213,7 @@ export interface StackedAreaChartOptions extends ScatterChartOptions { | string | { name: string; - }; + }; } /** From a28b3994277119c1152c6d8c2cac5a1029691c01 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 14:26:51 +0800 Subject: [PATCH 030/510] feat: update color for non-highlighted areas - remove zoombar graph line - add zoombar unselected graph area with clip - add zoombar baseline - clear d3.brush selection style --- packages/core/src/components/axes/zoom-bar.ts | 142 +++++++++++------- .../core/src/styles/components/_zoom-bar.scss | 16 +- 2 files changed, 97 insertions(+), 61 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index a6d85ed844..d04c7a4eeb 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,6 +13,8 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + clipId = "zoomBarClip"; + height = 32; ogXScale: any; @@ -72,9 +74,7 @@ export class ZoomBar extends Component { .attr("x", axesLeftMargin) .attr("y", 0) .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "white") - .attr("stroke", "#e8e8e8"); + .attr("height", "100%"); if (mainXScale) { const displayData = this.model.getDisplayData(); @@ -153,63 +153,44 @@ export class ZoomBar extends Component { ) ) .curve(this.services.curves.getD3Curve()); - // .defined((d: any, i) => { - // if (zoomDomain) { - // const dTimestamp = +d.label; - - // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; - // } - - // return true; - // }); - const lineGraph = DOMUtils.appendOrSelect( - container, - "path.zoom-graph-line" - ) - .datum(stackDataArray) - .transition( - this.services.transitions.getTransition( - "zoom-pan-line-update", - animate - ) - ) - .attr("d", lineGenerator); - - const areaGenerator = area() - .x((d, i) => - cartesianScales.getValueFromScale( - xScale, - mainXScaleType, - mainXAxisPosition, + const accessorFunc = (scale, scaleType, axisPosition) => { + return (d, i) => { + return cartesianScales.getValueFromScale( + scale, + scaleType, + axisPosition, d, i - ) - ) - .y0(this.height) - .y1( - (d, i) => - this.height - - cartesianScales.getValueFromScale( - yScale, - mainYScaleType, - mainYAxisPosition, - d, - i - ) - ); - - const areaGraph = DOMUtils.appendOrSelect( + ); + }; + }; + this.renderZoomBarArea( container, - "path.zoom-graph-area" - ) - .datum(stackDataArray) - .transition( - this.services.transitions.getTransition( - "zoom-pan-area-update", - animate - ) - ) - .attr("d", areaGenerator); + "path.zoom-graph-area-unselected", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + stackDataArray, + animate, + undefined + ); + this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); + this.renderZoomBarArea( + container, + "path.zoom-graph-area", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + stackDataArray, + animate, + "zoomBarClip" + ); + const baselineGenerator = line()([ + [axesLeftMargin, this.height], + [width, this.height] + ]); + const zoomBaseline = DOMUtils.appendOrSelect( + container, + "path.zoom-bg-baseline" + ).attr("d", baselineGenerator); const brushEventListener = () => { const selection = event.selection; @@ -352,7 +333,52 @@ export class ZoomBar extends Component { }) .attr("y", handleYBarDiff) .attr("width", handleBarWidth) - .attr("height", handleBarHeight); + .attr("height", handleBarHeight) + .attr("cursor", "ew-resize"); + + this.updateClipPath( + svg, + this.clipId, + selection[0], + 0, + selection[1] - selection[0], + this.height + ); + } + + renderZoomBarArea( + container, + querySelector, + xFunc, + y1Func, + datum, + animate, + clipId + ) { + const areaGenerator = area() + .x((d, i) => xFunc(d, i)) + .y0(this.height) + .y1((d, i) => this.height - y1Func(d, i)); + + const areaGraph = DOMUtils.appendOrSelect(container, querySelector) + .datum(datum) + .attr("d", areaGenerator); + + if (clipId) { + areaGraph.attr("clip-path", `url(#${clipId})`); + } + } + + updateClipPath(svg, clipId, x, y, width, height) { + const zoomBarClipPath = DOMUtils.appendOrSelect(svg, `clipPath`).attr( + "id", + clipId + ); + DOMUtils.appendOrSelect(zoomBarClipPath, "rect") + .attr("x", x) + .attr("y", y) + .attr("width", width) + .attr("height", height); } destroy() { diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 59019956a8..32dfc32842 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -4,15 +4,19 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: $ui-01; } - path.zoom-graph-line { + path.zoom-bg-baseline { stroke: $ui-04; - stroke-width: 3; - fill: none; + stroke-width: 2; } path.zoom-graph-area { fill: $ui-03; stroke: $ui-04; + stroke-width: 1; + } + path.zoom-graph-area-unselected { + fill: $ui-01; + stroke: none; } g.brush rect.handle { @@ -22,4 +26,10 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { g.brush rect.handle-bar { fill: $ui-02; } + + // clear d3.brush selection style + g.brush rect.selection { + fill: none; + stroke: none; + } } From ecf6774d0637979105fbe888185893678f47bdf0 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 15:01:58 +0800 Subject: [PATCH 031/510] fix: avoid extra brush handle update --- packages/core/src/components/axes/zoom-bar.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d04c7a4eeb..cc5fd6bfe3 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -218,8 +218,10 @@ export class ZoomBar extends Component { this.brush ); - if ( - zoomDomain === undefined || + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + } else if ( zoomDomain[0].valueOf() === zoomDomain[1].valueOf() ) { brushArea.call(this.brush.move, xScale.range()); // default to full range @@ -229,8 +231,16 @@ export class ZoomBar extends Component { ); } else { const selected = zoomDomain.map((domain) => xScale(domain)); - brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle(this.getContainerSVG(), selected); + if (selected[1] - selected[0] < 1) { + // initialization not completed yet + // don't update brushHandle to avoid flash + } else { + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle( + this.getContainerSVG(), + selected + ); + } } } } From d9cfaf91cffb895a41b6fad3a68e89df1bcfe867 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 15:34:34 +0800 Subject: [PATCH 032/510] fix: fix the brush handle style --- packages/core/src/components/axes/zoom-bar.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index cc5fd6bfe3..840d15198f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -299,18 +299,14 @@ export class ZoomBar extends Component { } updateBrushHandle(svg, selection) { - // @todo the handle size, height are calculated by d3 library - // need to figure out how to override the value - const handleWidth = 6; - const handleHeight = 38; + const handleWidth = 5; + const handleHeight = this.height; const handleXDiff = -handleWidth / 2; - const handleYDiff = -(handleHeight - this.height) / 2; - const handleBarWidth = 2; + const handleBarWidth = 1; const handleBarHeight = 12; const handleBarXDiff = -handleBarWidth / 2; - const handleYBarDiff = - (handleHeight - handleBarHeight) / 2 + handleYDiff; + const handleYBarDiff = (handleHeight - handleBarHeight) / 2; // handle svg.select("g.brush") .selectAll("rect.handle") @@ -322,7 +318,7 @@ export class ZoomBar extends Component { return selection[1] + handleXDiff; } }) - .attr("y", handleYDiff) + .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) .style("display", null); // always display From 242c956573c8354680286642c7fc953c1da4b24c Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 14:00:53 +0800 Subject: [PATCH 033/510] refactor: remove unnecessary svg.brush-container --- packages/core/src/components/axes/brush.ts | 42 ++++++++++++---------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 0a22fe0d79..517a73eb80 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -30,16 +30,10 @@ export class Brush extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); const mainYScale = this.services.cartesianScales.getMainYScale(); - const [xScaleStart, xScaleEnd] = mainXScale.range(); const [yScaleEnd, yScaleStart] = mainYScale.range(); - const container = DOMUtils.appendOrSelect(svg, "svg.brush-container") - .attr("width", "100%") - .attr("height", "100%") - .attr("opacity", 1); - if (mainXScale) { const displayData = this.model.getDisplayData(); @@ -85,7 +79,7 @@ export class Brush extends Component { } const brushed = () => { - let selection = event.selection; + const selection = event.selection; if (selection !== null) { // get current zoomDomain @@ -99,19 +93,24 @@ export class Brush extends Component { xScale.invert(selection[0]), xScale.invert(selection[1]) ]; - + // check if slected start time and end time are the same - if(newDomain[0].valueOf() === newDomain[1].valueOf()) { + if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoombar behavior: set to default full range - newDomain = extent(stackDataArray, (d: any) => d.date); + newDomain = extent( + stackDataArray, + (d: any) => d.date + ); } // only if the brush event comes from mouseup event if (event.sourceEvent != null) { // only if zoomDomain needs update if ( - zoomDomain[0].valueOf() !== newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== newDomain[1].valueOf() + zoomDomain[0].valueOf() !== + newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== + newDomain[1].valueOf() ) { this.model.set( { zoomDomain: newDomain }, @@ -119,12 +118,16 @@ export class Brush extends Component { ); } // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; + const zoomBarOptions = this.model.getOptions() + .zoomBar; if ( zoomBarOptions.selectionEnd !== undefined && event.type === "end" ) { - zoomBarOptions.selectionEnd(selection, newDomain); + zoomBarOptions.selectionEnd( + selection, + newDomain + ); } } } @@ -137,12 +140,15 @@ export class Brush extends Component { ]) .on("end", brushed); - const brushArea = DOMUtils.appendOrSelect(svg, "g.chart-brush").call( - brush - ); + const brushArea = DOMUtils.appendOrSelect( + svg, + "g.chart-brush" + ).call(brush); // no need for having default brush selection // @todo try to hide brush after selection - setTimeout(()=> {brushArea.call(brush.move);}, 0); + setTimeout(() => { + brushArea.call(brush.move); + }, 0); } } } From f2edf4ceacbf854db822b27945bc8190b3731a9f Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 14:17:17 +0800 Subject: [PATCH 034/510] fix: move brush layer to back to allow tooltips in graph --- packages/core/src/axis-chart.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 543d0ac6d4..3690c7b924 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -6,7 +6,7 @@ import { LegendPositions, ChartConfig, AxisChartOptions -} from "./interfaces/index"; +} from "./interfaces"; import { Brush, LayoutComponent, @@ -15,10 +15,10 @@ import { AxisChartsTooltip, Spacer, ZoomBar -} from "./components/index"; +} from "./components"; import { Tools } from "./tools"; -import { CartesianScales, Curves } from "./services/index"; +import { CartesianScales, Curves } from "./services"; export class AxisChart extends Chart { services: any = Object.assign(this.services, { @@ -49,8 +49,16 @@ export class AxisChart extends Chart { } }; - !!this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ? - graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + if ( + this.model.getOptions().zoomBar && + this.model.getOptions().zoomBar.enabled + ) { + graphFrameComponents.splice( + 1, + 0, + new Brush(this.model, this.services) + ); + } const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, From 621b271aec9e43beca4ea937be82a49a89e6a0b3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 16:18:27 +0800 Subject: [PATCH 035/510] feat: add stacked-area chart with zoom bar in storybook --- packages/core/demo/data/index.ts | 6 ++++++ packages/core/demo/data/zoom-bar.ts | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 35d1f640b9..1ac03c84d0 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -667,6 +667,12 @@ let allDemoGroups = [ { title: "Zoom bar", demos: [ + { + options: zoomBarDemos.zoomBarStackedAreaTimeSeriesOptions, + data: zoomBarDemos.zoomBarStackedAreaTimeSeriesData, + chartType: chartTypes.StackedAreaChart, + isDemoExample: false + }, { options: zoomBarDemos.zoomBarSimpleBarTimeSeriesOptions, data: zoomBarDemos.zoomBarSimpleBarTimeSeriesData, diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 5c97e33973..0016bbcad7 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -1,9 +1,10 @@ -import * as timeSeriesAxisChart from "./time-series-axis"; +import * as areaChart from "./area"; import * as barChart from "./bar"; import * as bubbleChart from "./bubble"; import * as lineChart from "./line"; import * as scatterChart from "./scatter"; import * as stepChart from "./step"; +import * as timeSeriesAxisChart from "./time-series-axis"; // default function for selection callback const selectionStartFun = (selection, domain) => { @@ -42,6 +43,12 @@ const updateOptions = (options) => { return options; }; +export const zoomBarStackedAreaTimeSeriesData = + areaChart.stackedAreaTimeSeriesData; +export const zoomBarStackedAreaTimeSeriesOptions = updateOptions( + Object.assign({}, areaChart.stackedAreaTimeSeriesOptions) +); + export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( Object.assign({}, barChart.simpleBarTimeSeriesOptions) From a06e3e390fa5fa8e72704a886db1c965794dcb4b Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 14:28:08 +0800 Subject: [PATCH 036/510] refactor: delete unused axis-zoom service --- packages/core/src/services/axis-zoom.ts | 60 ------------------------- packages/core/src/services/index.ts | 1 - 2 files changed, 61 deletions(-) delete mode 100644 packages/core/src/services/axis-zoom.ts diff --git a/packages/core/src/services/axis-zoom.ts b/packages/core/src/services/axis-zoom.ts deleted file mode 100644 index 8dbb2b3d80..0000000000 --- a/packages/core/src/services/axis-zoom.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Internal Imports -import { Service } from "./service"; -import { Tools } from "../tools"; - -// D3 Imports -import { brushX } from "d3-brush"; -import { event } from "d3-selection"; - -export class AxisZoom extends Service { - brush: any; - - brushed(e) { - if (event) { - const selectedRange = event.selection; - - const mainXAxis = this.services.axes.getMainXAxis(); - const mainXAxisRangeStart = mainXAxis.scale.range()[0]; - - const newDomain = selectedRange.map(d => d + mainXAxisRangeStart) - .map(mainXAxis.scale.invert, mainXAxis.scale); - - this.model.set({ - zoomDomain: newDomain.map(d => new Date(+d)) - }); - } - } - - // We need a custom debounce function here - // because of the async nature of d3.event - debounceWithD3Event(func, wait) { - let timeout; - return function() { - const e = Object.assign({}, event); - const context = this; - const later = function() { - timeout = null; - func.apply(context, [e]); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - } - - getZoomInstance() { - const mainXAxis = this.services.axes.getMainXAxis(); - const mainYAxis = this.services.axes.getMainYAxis(); - const xMaxRange = mainXAxis.scale.range()[1]; - const yMaxRange = mainYAxis.scale.range()[0]; - - this.brush = brushX() - .extent([ - [0, 0], - [xMaxRange, yMaxRange] - ]) - .on("end", this.brushed.bind(this)); - - - return this.brush; - } -} diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index 4e9fcc0e17..d999a07207 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -4,6 +4,5 @@ export * from "./essentials/events"; export * from "./essentials/transitions"; // MISC -export * from "./axis-zoom"; export * from "./scales-cartesian"; export * from "./curves"; From edbb9d951a0f55b2c4bb90f50b5282bd97123666 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 14:41:59 +0800 Subject: [PATCH 037/510] feat: allow addSpaceOnEdges to work with zoom bar - extends domain in zoom bar and brush --- packages/core/src/components/axes/brush.ts | 6 ++- packages/core/src/components/axes/zoom-bar.ts | 13 +++++-- .../core/src/services/scales-cartesian.ts | 39 +++++++++++-------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 517a73eb80..efc35a735d 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -71,7 +71,11 @@ export class Brush extends Component { let zoomDomain = this.model.get("zoomDomain"); if (zoomDomain === undefined) { - zoomDomain = extent(stackDataArray, (d: any) => d.date); // default to full range + // default to full range with extended domain + zoomDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) + ); this.model.set( { zoomDomain: zoomDomain }, { animate: false } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 840d15198f..b3b4b2b571 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -120,9 +120,16 @@ export class ZoomBar extends Component { useAttrs: true }); - xScale - .range([axesLeftMargin, width]) - .domain(extent(stackDataArray, (d: any) => d.date)); + // @todo could be a better function to extend domain with default value + const xDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) + ); + // add value 0 to the extended domain for zoom bar area graph + stackDataArray.unshift({ date: xDomain[0], value: 0 }); + stackDataArray.push({ date: xDomain[1], value: 0 }); + + xScale.range([axesLeftMargin, width]).domain(xDomain); yScale .range([0, this.height - 6]) diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 162b0c833f..9614fea1c2 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -5,7 +5,6 @@ import { AxisPositions, CartesianOrientations, ScaleTypes, - AxesOptions, ThresholdOptions } from "../interfaces"; import { Tools } from "../tools"; @@ -181,7 +180,13 @@ export class CartesianScales extends Service { return this.scales[this.getMainYAxisPosition()]; } - getValueFromScale(scale: any, scaleType: ScaleTypes, axisPosition: AxisPositions, datum: any, index?: number) { + getValueFromScale( + scale: any, + scaleType: ScaleTypes, + axisPosition: AxisPositions, + datum: any, + index?: number + ) { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); const axisOptions = axesOptions[axisPosition]; @@ -201,14 +206,23 @@ export class CartesianScales extends Service { return scaledValue; } - getValueThroughAxisPosition(axisPosition: AxisPositions, datum: any, index?: number) { + getValueThroughAxisPosition( + axisPosition: AxisPositions, + datum: any, + index?: number + ) { const scaleType = this.scaleTypes[axisPosition]; const scale = this.scales[axisPosition]; - return this.getValueFromScale(scale, scaleType, axisPosition, datum, index); + return this.getValueFromScale( + scale, + scaleType, + axisPosition, + datum, + index + ); } - getDomainValue(d, i) { return this.getValueThroughAxisPosition(this.domainAxisPosition, d, i); } @@ -304,7 +318,7 @@ export class CartesianScales extends Service { const domainScale = this.getDomainScale(); // Find the highest threshold for the domain const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value, + (a, b) => b.value - a.value )[0]; const scaleType = this.getScaleTypeByPosition(domainAxisPosition); @@ -318,7 +332,7 @@ export class CartesianScales extends Service { return { threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value), + scaleValue: domainScale(highestThreshold.value) }; } @@ -338,12 +352,12 @@ export class CartesianScales extends Service { const rangeScale = this.getRangeScale(); // Find the highest threshold for the range const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value, + (a, b) => b.value - a.value )[0]; return { threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value), + scaleValue: rangeScale(highestThreshold.value) }; } @@ -458,12 +472,6 @@ export class CartesianScales extends Service { return map(displayData, (d) => d[mapsTo]).keys(); } - // If scale is a TIME scale and zoomDomain is available, return Date array as the domain - const zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain && axisOptions && scaleType === ScaleTypes.TIME) { - return zoomDomain.map(d => new Date(d)); - } - // Get the extent of the domain let domain; let allDataValues; @@ -483,7 +491,6 @@ export class CartesianScales extends Service { domain = extent(allDataValues); domain = this.extendsDomain(axisPosition, domain); - return domain; } From 3e56c09a32032a3939c9509e13ce2241c8ec2aa5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 16:12:13 +0800 Subject: [PATCH 038/510] refactor: code refactoring - reduce code differences from master branch --- .../core/src/services/scales-cartesian.ts | 119 +++++++++--------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 9614fea1c2..11be646307 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -302,65 +302,6 @@ export class CartesianScales extends Service { } } - getHighestDomainThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const domainAxisPosition = this.getDomainAxisPosition(); - - const { thresholds } = axesOptions[domainAxisPosition]; - - if (!thresholds) { - return null; - } - - const domainScale = this.getDomainScale(); - // Find the highest threshold for the domain - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - const scaleType = this.getScaleTypeByPosition(domainAxisPosition); - if ( - scaleType === ScaleTypes.TIME && - (typeof highestThreshold.value === "string" || - highestThreshold.value.getTime === undefined) - ) { - highestThreshold.value = new Date(highestThreshold.value); - } - - return { - threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value) - }; - } - - getHighestRangeThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const rangeAxisPosition = this.getRangeAxisPosition(); - - const { thresholds } = axesOptions[rangeAxisPosition]; - - if (!thresholds) { - return null; - } - - const rangeScale = this.getRangeScale(); - // Find the highest threshold for the range - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - return { - threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value) - }; - } - protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); @@ -491,6 +432,7 @@ export class CartesianScales extends Service { domain = extent(allDataValues); domain = this.extendsDomain(axisPosition, domain); + return domain; } @@ -521,6 +463,65 @@ export class CartesianScales extends Service { return scale; } + + protected getHighestDomainThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const domainAxisPosition = this.getDomainAxisPosition(); + + const { thresholds } = axesOptions[domainAxisPosition]; + + if (!thresholds) { + return null; + } + + const domainScale = this.getDomainScale(); + // Find the highest threshold for the domain + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value + )[0]; + + const scaleType = this.getScaleTypeByPosition(domainAxisPosition); + if ( + scaleType === ScaleTypes.TIME && + (typeof highestThreshold.value === "string" || + highestThreshold.value.getTime === undefined) + ) { + highestThreshold.value = new Date(highestThreshold.value); + } + + return { + threshold: highestThreshold, + scaleValue: domainScale(highestThreshold.value) + }; + } + + protected getHighestRangeThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const rangeAxisPosition = this.getRangeAxisPosition(); + + const { thresholds } = axesOptions[rangeAxisPosition]; + + if (!thresholds) { + return null; + } + + const rangeScale = this.getRangeScale(); + // Find the highest threshold for the range + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value + )[0]; + + return { + threshold: highestThreshold, + scaleValue: rangeScale(highestThreshold.value) + }; + } } function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { From ec58dbc7ad27701ebb5698d4cfc7798cd15921b4 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 23:25:56 +0800 Subject: [PATCH 039/510] fix: display multiline tooltip with zoom bar - use ruler backdrop as brush area - svg.chart-grid-backdrop --- packages/core/src/axis-chart.ts | 6 +----- packages/core/src/components/axes/brush.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 3690c7b924..f5559f6f56 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -53,11 +53,7 @@ export class AxisChart extends Chart { this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ) { - graphFrameComponents.splice( - 1, - 0, - new Brush(this.model, this.services) - ); + graphFrameComponents.push(new Brush(this.model, this.services)); } const graphFrameComponent = { id: "graph-frame", diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index efc35a735d..35f12159b9 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -146,7 +146,7 @@ export class Brush extends Component { const brushArea = DOMUtils.appendOrSelect( svg, - "g.chart-brush" + "svg.chart-grid-backdrop" ).call(brush); // no need for having default brush selection // @todo try to hide brush after selection From 50494dddec6bfc92075966fddaa77cc697a88079 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:42:26 +0800 Subject: [PATCH 040/510] feat: create ZoomBarOptions and default configuration - default to disable zoombar --- packages/core/src/interfaces/charts.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 41bb3d794e..5255f98a24 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -41,6 +41,10 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ From 200db8e6e873e8c2b1713d0f9fe80d9e2b8fa1a3 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 2 Jul 2020 14:21:12 +0800 Subject: [PATCH 041/510] fix: chart brush with correct range --- packages/core/src/components/axes/brush.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 35f12159b9..c06fdac4c0 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -90,7 +90,7 @@ export class Brush extends Component { zoomDomain = this.model.get("zoomDomain"); // create xScale based on current zoomDomain const xScale = scaleTime() - .range([axesLeftMargin, width]) + .range([0, width]) .domain(zoomDomain); let newDomain = [ @@ -101,9 +101,9 @@ export class Brush extends Component { // check if slected start time and end time are the same if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoombar behavior: set to default full range - newDomain = extent( - stackDataArray, - (d: any) => d.date + newDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) ); } @@ -139,15 +139,19 @@ export class Brush extends Component { const brush = brushX() .extent([ - [xScaleStart, 0], + [0, 0], [width, yScaleEnd] ]) .on("end", brushed); - - const brushArea = DOMUtils.appendOrSelect( + const backdrop = DOMUtils.appendOrSelect( svg, "svg.chart-grid-backdrop" + ); + const brushArea = DOMUtils.appendOrSelect( + backdrop, + "g.chart-brush" ).call(brush); + // no need for having default brush selection // @todo try to hide brush after selection setTimeout(() => { From e78055c887d4df3b46faa619ad73a2510d426913 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 2 Jul 2020 15:59:02 +0800 Subject: [PATCH 042/510] fix: remove graph out of zoom domain --- .../core/src/components/graphs/bar-grouped.ts | 4 ++++ .../core/src/components/graphs/bar-simple.ts | 5 +++++ .../core/src/components/graphs/bar-stacked.ts | 4 ++++ packages/core/src/components/graphs/bar.ts | 12 ++++++++++++ packages/core/src/components/graphs/scatter.ts | 16 ++++++++++++++++ 5 files changed, 41 insertions(+) diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 48377b5957..07f12e8c64 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -123,6 +123,10 @@ export class GroupedBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(0); const y1 = this.services.cartesianScales.getRangeValue(d.value); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } return Tools.generateSVGPathString( { x0, x1, y0, y1 }, this.services.cartesianScales.getOrientation() diff --git a/packages/core/src/components/graphs/bar-simple.ts b/packages/core/src/components/graphs/bar-simple.ts index 2cbf168934..aeb72950d1 100644 --- a/packages/core/src/components/graphs/bar-simple.ts +++ b/packages/core/src/components/graphs/bar-simple.ts @@ -70,6 +70,11 @@ export class SimpleBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(0); const y1 = this.services.cartesianScales.getRangeValue(d, i); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } + return Tools.generateSVGPathString( { x0, x1, y0, y1 }, this.services.cartesianScales.getOrientation() diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index 3c9bcbdb9c..55d376f835 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -97,6 +97,10 @@ export class StackedBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(d[0], i); let y1 = this.services.cartesianScales.getRangeValue(d[1], i); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } // Add the divider gap if ( Math.abs(y1 - y0) > 0 && diff --git a/packages/core/src/components/graphs/bar.ts b/packages/core/src/components/graphs/bar.ts index dff83021d5..59c18f5685 100644 --- a/packages/core/src/components/graphs/bar.ts +++ b/packages/core/src/components/graphs/bar.ts @@ -16,4 +16,16 @@ export class Bar extends Component { return Math.min(options.bars.maxWidth, mainXScale.step() / 2); } + + protected isOutOfZoomDomain(x0: number, x1: number) { + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain !== undefined) { + const domainScale = this.services.cartesianScales.getDomainScale(); + return ( + x0 < domainScale(zoomDomain[0]) || + x1 > domainScale(zoomDomain[1]) + ); + } + return false; + } } diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index 0ceab80518..2d8074dc49 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -37,6 +37,19 @@ export class Scatter extends Component { } } + filterOutOfDomain(data) { + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain !== undefined) { + return data.filter( + (d) => + d[domainIdentifier] > zoomDomain[0] && + d[domainIdentifier] < zoomDomain[1] + ); + } + return data; + } + render(animate: boolean) { // Grab container SVG const svg = this.getContainerSVG(); @@ -64,6 +77,9 @@ export class Scatter extends Component { ); } + // filter out of domain data + scatterData = this.filterOutOfDomain(scatterData); + // Update data on dot groups const circles = svg .selectAll("circle.dot") From 70ad5b9f512c7880f549f3a46472b18cd1f56a60 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 12:44:35 +0800 Subject: [PATCH 043/510] refactor: code refactoring - create getZoomBarData(), getDefaultZoomBarDomain() in model - remove unused code --- packages/core/src/components/axes/brush.ts | 193 ++++------- packages/core/src/components/axes/cover.ts | 25 +- packages/core/src/components/axes/zoom-bar.ts | 312 ++++++++---------- packages/core/src/model.ts | 48 ++- 4 files changed, 257 insertions(+), 321 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index c06fdac4c0..16f21dce44 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -15,149 +15,80 @@ export class Brush extends Component { render(animate = true) { const svg = this.parent; + const backdrop = DOMUtils.appendOrSelect( + svg, + "svg.chart-grid-backdrop" + ); + const { width, height } = DOMUtils.getSVGElementSize(backdrop, { + useAttrs: true + }); + const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( mainXAxisPosition ); - // get axes margins - let axesLeftMargin = 0; - const axesMargins = this.model.get("axesMargins"); - if (axesMargins && axesMargins.left) { - axesLeftMargin = axesMargins.left; - } - const mainXScale = this.services.cartesianScales.getMainXScale(); - const mainYScale = this.services.cartesianScales.getMainYScale(); - - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const [yScaleEnd, yScaleStart] = mainYScale.range(); - - if (mainXScale) { - const displayData = this.model.getDisplayData(); - - if (mainXScaleType === ScaleTypes.TIME) { - // Get all date values provided in data - // @todo - Could be re-used through the model - let allDates = []; - displayData.forEach((data) => { - allDates = allDates.concat(Number(data.date)); - }); - allDates = Tools.removeArrayDuplicates(allDates).sort(); - - // Go through all date values - // And get corresponding data from each dataset - const stackDataArray = allDates.map((date) => { - let count = 0; - let correspondingSum = 0; - const correspondingData = {}; - - displayData.forEach((data) => { - if (Number(data.date) === Number(date)) { - ++count; - correspondingSum += data.value; - } - }); - correspondingData["date"] = date; - correspondingData["value"] = correspondingSum; - - return correspondingData; - }); - const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); - - let zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain === undefined) { - // default to full range with extended domain - zoomDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - this.model.set( - { zoomDomain: zoomDomain }, - { animate: false } - ); - } - - const brushed = () => { - const selection = event.selection; - - if (selection !== null) { - // get current zoomDomain - zoomDomain = this.model.get("zoomDomain"); - // create xScale based on current zoomDomain - const xScale = scaleTime() - .range([0, width]) - .domain(zoomDomain); - - let newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // check if slected start time and end time are the same - if (newDomain[0].valueOf() === newDomain[1].valueOf()) { - // same as d3 behavior and zoombar behavior: set to default full range - newDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - } + 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.model.getDefaultZoomBarDomain(); + this.model.set({ zoomDomain: zoomDomain }, { animate: false }); + } - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { - // only if zoomDomain needs update - if ( - zoomDomain[0].valueOf() !== - newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== - newDomain[1].valueOf() - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } - // call external callback - const zoomBarOptions = this.model.getOptions() - .zoomBar; - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd( - selection, - newDomain - ); - } - } + const brushed = () => { + const selection = event.selection; + + if (selection !== null) { + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([0, width]) + .domain(zoomDomain); + + let newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + // 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.model.getDefaultZoomBarDomain(); } - }; - const brush = brushX() - .extent([ - [0, 0], - [width, yScaleEnd] - ]) - .on("end", brushed); - const backdrop = DOMUtils.appendOrSelect( - svg, - "svg.chart-grid-backdrop" - ); - const brushArea = DOMUtils.appendOrSelect( - backdrop, - "g.chart-brush" - ).call(brush); + // only if zoomDomain needs update + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + } - // no need for having default brush selection - // @todo try to hide brush after selection - setTimeout(() => { - brushArea.call(brush.move); - }, 0); - } + // clear brush selection + brushArea.call(brush.move, null); + } + }; + + // leave some space to display selection strokes besides axis + const brush = brushX() + .extent([ + [2, 0], + [width - 1, height - 1] + ]) + .on("end", brushed); + + const brushArea = DOMUtils.appendOrSelect( + backdrop, + "g.chart-brush" + ).call(brush); } } } diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts index 16179b45c5..2536c9ef40 100644 --- a/packages/core/src/components/axes/cover.ts +++ b/packages/core/src/components/axes/cover.ts @@ -1,13 +1,7 @@ // Internal Imports import { Component } from "../component"; -import { Tools } from "../../tools"; import { DOMUtils } from "../../services"; -// D3 Imports -import { axisBottom, axisLeft } from "d3-axis"; -import { mouse, select } from "d3-selection"; -import { TooltipTypes, Events } from "../../interfaces"; - export class Cover extends Component { type = "cover"; @@ -18,19 +12,21 @@ export class Cover extends Component { this.createCover(); } - createCover() { const svg = this.parent; - const mainXScale = this.services.cartesianScales.getMainXScale(); - const mainYScale = this.services.cartesianScales.getMainYScale(); + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); const [xScaleStart, xScaleEnd] = mainXScale.range(); const [yScaleEnd, yScaleStart] = mainYScale.range(); // Get height - this.coverClipPath = DOMUtils.appendOrSelect(svg, `clipPath.${this.type}`); - this.coverClipPath - .attr("id", `${this.type}Clip`); + this.coverClipPath = DOMUtils.appendOrSelect( + svg, + `clipPath.${this.type}` + ); + this.coverClipPath.attr("id", `${this.type}Clip`); const coverRect = DOMUtils.appendOrSelect( this.coverClipPath, "rect.cover" @@ -41,14 +37,11 @@ export class Cover extends Component { .attr("width", xScaleEnd - xScaleStart) .attr("height", yScaleEnd - yScaleStart); - this.coverClipPath - .merge(coverRect) - .lower(); + this.coverClipPath.merge(coverRect).lower(); const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); coverG .attr("clip-path", `url(#${this.type}Clip)`) .attr("id", `g-${this.type}Clip`); - } } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index b3b4b2b571..6e3d8efe91 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,11 +13,13 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + brushSelector = "g.brush"; // needs to be this value for d3.brush API + clipId = "zoomBarClip"; height = 32; - ogXScale: any; + spacerHeight = 20; brush = brushX(); @@ -64,9 +66,9 @@ export class ZoomBar extends Component { const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) - .attr("y", 32) + .attr("y", this.height) .attr("width", "100%") - .attr("height", 20) + .attr("height", this.spacerHeight) .attr("opacity", 1) .attr("fill", "none"); @@ -76,189 +78,134 @@ export class ZoomBar extends Component { .attr("width", "100%") .attr("height", "100%"); - if (mainXScale) { - const displayData = this.model.getDisplayData(); - - if (mainXScaleType === ScaleTypes.TIME) { - // Get all date values provided in data - // TODO - Could be re-used through the model - let allDates = []; - displayData.forEach((data) => { - allDates = allDates.concat(Number(data.date)); - }); - allDates = Tools.removeArrayDuplicates(allDates).sort(); - - // Go through all date values - // And get corresponding data from each dataset - const stackDataArray = allDates.map((date) => { - let count = 0; - let correspondingSum = 0; - const correspondingData = {}; - - displayData.forEach((data) => { - if (Number(data.date) === Number(date)) { - ++count; - correspondingSum += data.value; - } - }); - correspondingData["date"] = date; - correspondingData["value"] = correspondingSum; - - return correspondingData; - }); - - if (!this.ogXScale) { - this.ogXScale = cartesianScales.getDomainScale(); - } - const xScale = mainXScale.copy(); - if (!this.ogXScale) { - this.ogXScale = xScale; - } - const yScale = mainYScale.copy(); + if (mainXScale && mainXScaleType === ScaleTypes.TIME) { + const zoomBarData = this.model.getZoomBarData(); + const xScale = mainXScale.copy(); + const yScale = mainYScale.copy(); - const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); - // @todo could be a better function to extend domain with default value - const xDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - // add value 0 to the extended domain for zoom bar area graph - stackDataArray.unshift({ date: xDomain[0], value: 0 }); - stackDataArray.push({ date: xDomain[1], value: 0 }); + const defaultDomain = this.model.getDefaultZoomBarDomain(); + // add value 0 to the extended domain for zoom bar area graph + this.compensateDataForDefaultDomain(zoomBarData, defaultDomain, 0); - xScale.range([axesLeftMargin, width]).domain(xDomain); + xScale.range([axesLeftMargin, width]).domain(defaultDomain); - yScale - .range([0, this.height - 6]) - .domain(extent(stackDataArray, (d: any) => d.value)); + yScale + .range([0, this.height - 6]) + .domain(extent(zoomBarData, (d: any) => d.value)); - const zoomDomain = this.model.get("zoomDomain"); + const zoomDomain = this.model.get("zoomDomain"); - // D3 line generator function - const lineGenerator = line() - .x((d, i) => + // D3 line generator function + const lineGenerator = line() + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) + .y( + (d, i) => + this.height - cartesianScales.getValueFromScale( - xScale, - mainXScaleType, - mainXAxisPosition, + yScale, + mainYScaleType, + mainYAxisPosition, d, i ) - ) - .y( - (d, i) => - this.height - - cartesianScales.getValueFromScale( - yScale, - mainYScaleType, - mainYAxisPosition, - d, - i - ) - ) - .curve(this.services.curves.getD3Curve()); - const accessorFunc = (scale, scaleType, axisPosition) => { - return (d, i) => { - return cartesianScales.getValueFromScale( - scale, - scaleType, - axisPosition, - d, - i - ); - }; - }; - this.renderZoomBarArea( - container, - "path.zoom-graph-area-unselected", - accessorFunc(xScale, mainXScaleType, mainXAxisPosition), - accessorFunc(yScale, mainYScaleType, mainYAxisPosition), - stackDataArray, - animate, - undefined - ); - this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); - this.renderZoomBarArea( - container, - "path.zoom-graph-area", - accessorFunc(xScale, mainXScaleType, mainXAxisPosition), - accessorFunc(yScale, mainYScaleType, mainYAxisPosition), - stackDataArray, - animate, - "zoomBarClip" - ); - const baselineGenerator = line()([ - [axesLeftMargin, this.height], - [width, this.height] - ]); - const zoomBaseline = DOMUtils.appendOrSelect( - container, - "path.zoom-bg-baseline" - ).attr("d", baselineGenerator); - - const brushEventListener = () => { - const selection = event.selection; - // follow d3 behavior: when selection is null, reset default full range - // @todo find a better way to handel the situation when selection is null - // select behavior is completed, but nothing selected - if (selection === null) { - this.brushed(zoomDomain, xScale, xScale.range()); - } else if (selection[0] === selection[1]) { - // select behavior is not completed yet, do nothing - } else { - this.brushed(zoomDomain, xScale, selection); - } + ) + .curve(this.services.curves.getD3Curve()); + const accessorFunc = (scale, scaleType, axisPosition) => { + return (d, i) => { + return cartesianScales.getValueFromScale( + scale, + scaleType, + axisPosition, + d, + i + ); }; + }; + this.renderZoomBarArea( + container, + "path.zoom-graph-area-unselected", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + zoomBarData, + animate, + undefined + ); + this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); + this.renderZoomBarArea( + container, + "path.zoom-graph-area", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + zoomBarData, + animate, + this.clipId + ); + const baselineGenerator = line()([ + [axesLeftMargin, this.height], + [width, this.height] + ]); + const zoomBaseline = DOMUtils.appendOrSelect( + container, + "path.zoom-bg-baseline" + ).attr("d", baselineGenerator); + + const brushEventListener = () => { + const selection = event.selection; + // follow d3 behavior: when selection is null, reset default full range + // select behavior is completed, but nothing selected + if (selection === null) { + this.brushed(zoomDomain, xScale, xScale.range()); + } else if (selection[0] === selection[1]) { + // select behavior is not completed yet, do nothing + } else { + this.brushed(zoomDomain, xScale, selection); + } + }; - this.brush - .extent([ - [axesLeftMargin, 0], - [width, this.height] - ]) - .on("start brush end", null) // remove old listener first - .on("start brush end", brushEventListener); - - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( - this.brush - ); - - if (zoomDomain === undefined) { - // do nothing, initialization not completed yet + this.brush + .extent([ + [axesLeftMargin, 0], + [width, this.height] + ]) + .on("start brush end", null) // remove old listener first + .on("start brush end", brushEventListener); + + const brushArea = DOMUtils.appendOrSelect( + svg, + this.brushSelector + ).call(this.brush); + + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + } else if (zoomDomain[0].valueOf() === zoomDomain[1].valueOf()) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle(this.getContainerSVG(), xScale.range()); + } else { + const selected = zoomDomain.map((domain) => xScale(domain)); + if (selected[1] - selected[0] < 1) { + // initialization not completed yet // don't update brushHandle to avoid flash - } else if ( - zoomDomain[0].valueOf() === zoomDomain[1].valueOf() - ) { - brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle( - this.getContainerSVG(), - xScale.range() - ); } else { - const selected = zoomDomain.map((domain) => xScale(domain)); - if (selected[1] - selected[0] < 1) { - // initialization not completed yet - // don't update brushHandle to avoid flash - } else { - brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle( - this.getContainerSVG(), - selected - ); - } + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle(this.getContainerSVG(), selected); } } } } - // could be used by Toolbar - // zoomIn() { - // const mainXScale = this.services.cartesianScales.getMainXScale(); - // console.log("zoom in", mainXScale.domain()); - // } - // brush event listener brushed(zoomDomain, scale, selection) { // update brush handle position @@ -315,7 +262,7 @@ export class ZoomBar extends Component { const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; // handle - svg.select("g.brush") + svg.select(this.brushSelector) .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { @@ -330,7 +277,7 @@ export class ZoomBar extends Component { .attr("height", handleHeight) .style("display", null); // always display // handle-bar - svg.select("g.brush") + svg.select(this.brushSelector) .selectAll("rect.handle-bar") .data([{ type: "w" }, { type: "e" }]) .join("rect") @@ -364,7 +311,7 @@ export class ZoomBar extends Component { querySelector, xFunc, y1Func, - datum, + data, animate, clipId ) { @@ -374,7 +321,7 @@ export class ZoomBar extends Component { .y1((d, i) => this.height - y1Func(d, i)); const areaGraph = DOMUtils.appendOrSelect(container, querySelector) - .datum(datum) + .datum(data) .attr("d", areaGenerator); if (clipId) { @@ -394,6 +341,29 @@ export class ZoomBar extends Component { .attr("height", height); } + // assume the domains in data are already sorted + compensateDataForDefaultDomain(data, defaultDomain, value) { + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); + // if min domain is extended + if (Number(defaultDomain[0]) < Number(data[0][domainIdentifier])) { + const newDatum = {}; + newDatum[domainIdentifier] = defaultDomain[0]; + newDatum[rangeIdentifier] = value; + data.unshift(newDatum); + } + // if max domain is extended + if ( + Number(defaultDomain[1]) > + Number(data[data.length - 1][domainIdentifier]) + ) { + const newDatum = {}; + newDatum[domainIdentifier] = defaultDomain[1]; + newDatum[rangeIdentifier] = value; + data.push(newDatum); + } + } + destroy() { this.brush.on("start brush end", null); // remove event listener this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index c2fba1cd4b..185f334848 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -5,8 +5,9 @@ import * as colorPalettes from "./services/colorPalettes"; import { Events, ScaleTypes } from "./interfaces"; // D3 -import { scaleOrdinal } from "d3-scale"; +import { extent } from "d3-array"; import { map } from "d3-collection"; +import { scaleOrdinal } from "d3-scale"; import { stack } from "d3-shape"; /** The charting model layer which includes mainly the chart data and options, @@ -38,7 +39,48 @@ export class ChartModel { constructor(services: any) { this.services = services; } + // get display data for zoom bar + // basically it's sum of value grouped by time + getZoomBarData() { + const { cartesianScales } = this.services; + const domainIdentifier = cartesianScales.getDomainIdentifier(); + const rangeIdentifier = cartesianScales.getRangeIdentifier(); + + const displayData = this.getDisplayData(); + // get all dates (Number) in displayData + let allDates = []; + displayData.forEach((data) => { + allDates = allDates.concat(Number(data[domainIdentifier])); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + // Go through all date values + // And get corresponding data from each dataset + return allDates.map((date) => { + let sum = 0; + const datum = {}; + + displayData.forEach((data) => { + if (Number(data[domainIdentifier]) === date) { + sum += data[rangeIdentifier]; + } + }); + datum[domainIdentifier] = new Date(date); + datum[rangeIdentifier] = sum; + return datum; + }); + } + getDefaultZoomBarDomain() { + const zoomBarData = this.getZoomBarData(); + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const domainIdentifier = cartesianScales.getDomainIdentifier(); + // default to full range with extended domain + return cartesianScales.extendsDomain( + mainXAxisPosition, + extent(zoomBarData, (d: any) => d[domainIdentifier]) + ); + } getAllDataFromDomain() { if (!this.get("data")) { return null; @@ -95,7 +137,7 @@ export class ChartModel { return allDataFromDomain.filter((datum) => { const group = dataGroups.find( - (group) => group.name === datum[groupMapsTo] + (g) => g.name === datum[groupMapsTo] ); return group.status === ACTIVE; @@ -551,7 +593,7 @@ export class ChartModel { * Fill scales */ protected setColorScale() { - let defaultColors = colorPalettes.DEFAULT; + const defaultColors = colorPalettes.DEFAULT; const options = this.getOptions(); const userProvidedScale = Tools.getProperty(options, "color", "scale"); From c853eeec4795647805adb844d4b11476c0329d4b Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 16:13:14 +0800 Subject: [PATCH 044/510] fix: set min selection difference threshold --- packages/core/src/components/axes/zoom-bar.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6e3d8efe91..6c1dd80fdb 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,6 +13,11 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + // The minimum selection x range to trigger handler update + // Smaller number may introduce a handler flash during initialization + // Bigger number may not trigger handler update while selection area on chart is very small + MIN_SELECTION_DIFF = 9e-10; + brushSelector = "g.brush"; // needs to be this value for d3.brush API clipId = "zoomBarClip"; @@ -195,7 +200,7 @@ export class ZoomBar extends Component { this.updateBrushHandle(this.getContainerSVG(), xScale.range()); } else { const selected = zoomDomain.map((domain) => xScale(domain)); - if (selected[1] - selected[0] < 1) { + if (selected[1] - selected[0] < this.MIN_SELECTION_DIFF) { // initialization not completed yet // don't update brushHandle to avoid flash } else { From 99cc49e5adab0e4a8e02ccd381fe25b49893c882 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 16:46:44 +0800 Subject: [PATCH 045/510] refactor: code refactoring - zoom-bar.scss - remove unused scss settings --- packages/core/src/components/axes/axis.ts | 2 +- packages/core/src/components/axes/zoom-bar.ts | 2 +- .../core/src/styles/components/_zoom-bar.scss | 24 +++++++++---------- packages/core/src/styles/graphs/index.scss | 4 ---- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 484106f8fa..38f4c2b408 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -109,7 +109,7 @@ export class Axis extends Component { // if zoomDomain is available, update scale domain to Date array. const zoomDomain = this.model.get("zoomDomain"); if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain.map((d) => new Date(d))); + scale.domain(zoomDomain); } // Identify the corresponding d3 axis function diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6c1dd80fdb..edcb1f792b 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -18,7 +18,7 @@ export class ZoomBar extends Component { // Bigger number may not trigger handler update while selection area on chart is very small MIN_SELECTION_DIFF = 9e-10; - brushSelector = "g.brush"; // needs to be this value for d3.brush API + brushSelector = "g.zoom-bar-brush"; // needs to match the style in _zoom-bar.scss clipId = "zoomBarClip"; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 32dfc32842..13c20ecc30 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -19,17 +19,17 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: none; } - g.brush rect.handle { - fill: $icon-02; - } - - g.brush rect.handle-bar { - fill: $ui-02; - } - - // clear d3.brush selection style - g.brush rect.selection { - fill: none; - stroke: none; + g.zoom-bar-brush { + rect.handle { + fill: $icon-02; + } + rect.handle-bar { + fill: $ui-02; + } + // clear d3.brush selection style + rect.selection { + fill: none; + stroke: none; + } } } diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index ca80344db3..426d88265c 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -5,7 +5,3 @@ @import "./scatter-stacked"; @import "./radar"; @import "./gauge"; - -svg.#{$prefix}--#{$charts-prefix}--chart-svg svg.graph-frame { - overflow-x: hidden; -} From 43b8f9e7d142f0263edffc3680b222755207fc6d Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 6 Jul 2020 17:09:13 +0800 Subject: [PATCH 046/510] fix: avoid extra/duplicate external callback --- packages/core/src/components/axes/brush.ts | 44 ++++++++++++++++++- packages/core/src/components/axes/zoom-bar.ts | 38 +++++++++------- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 16f21dce44..8067e6aaa8 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -40,6 +40,47 @@ export class Brush extends Component { this.model.set({ zoomDomain: zoomDomain }, { animate: false }); } + const eventHandler = () => { + const selection = event.selection; + const xScale = scaleTime().range([0, width]).domain(zoomDomain); + + const newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + if ( + selection != null && + event.sourceEvent != null && + (event.sourceEvent.type === "mousemove" || + event.sourceEvent.type === "mouseup" || + event.sourceEvent.type === "mousedown") + ) { + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress( + selection, + newDomain + ); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + }; const brushed = () => { const selection = event.selection; @@ -83,7 +124,8 @@ export class Brush extends Component { [2, 0], [width - 1, height - 1] ]) - .on("end", brushed); + .on("start brush end", eventHandler) + .on("end.brushed", brushed); const brushArea = DOMUtils.appendOrSelect( backdrop, diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index edcb1f792b..05722a4116 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -237,23 +237,27 @@ export class ZoomBar extends Component { ) { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if (zoomBarOptions.selectionEnd !== undefined && event.type === "end") { - zoomBarOptions.selectionEnd(selection, newDomain); + + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } } } From 2bf6944a4075372a09db874c55c6c26eaad5700d Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 7 Jul 2020 12:33:39 +0800 Subject: [PATCH 047/510] fix: remove ZoomBarOptions in BaseChartOptions --- packages/core/src/interfaces/charts.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 5255f98a24..77b986896a 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -41,10 +41,6 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; - /** - * zoombar configuration - */ - zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ @@ -217,7 +213,7 @@ export interface StackedAreaChartOptions extends ScatterChartOptions { | string | { name: string; - }; + }; } /** From f34336dd72aa58f2359b268cf9bad8811fc8f28c Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 11:28:33 +0800 Subject: [PATCH 048/510] refactor: change initZoomDomain to initialZoomDomain - remove unnecessary undefined setting --- packages/core/demo/data/zoom-bar.ts | 27 +++++++++---------- packages/core/src/components/axes/zoom-bar.ts | 4 +-- packages/core/src/configuration.ts | 1 - packages/core/src/interfaces/components.ts | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 0016bbcad7..fbe74fba2a 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -23,21 +23,20 @@ const selectionEndFun = (selection, domain) => { console.log(domain); }; -const initZoomDomain = [ +const initialZoomDomain = [ new Date(2020, 11, 10, 23, 59, 25), new Date(2020, 11, 11, 0, 0, 25) ]; const defaultZoomBarOptions = { enabled: true, - initZoomDomain: undefined, selectionStart: selectionStartFun, selectionInProgress: selectionInProgressFun, selectionEnd: selectionEndFun }; // utility function to update title and enable zoomBar option -const updateOptions = (options) => { +const addZoomBarToOptions = (options) => { options["title"] = options["title"] + " - Zoom bar enabled"; options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); return options; @@ -45,51 +44,51 @@ const updateOptions = (options) => { export const zoomBarStackedAreaTimeSeriesData = areaChart.stackedAreaTimeSeriesData; -export const zoomBarStackedAreaTimeSeriesOptions = updateOptions( +export const zoomBarStackedAreaTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, areaChart.stackedAreaTimeSeriesOptions) ); export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; -export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( +export const zoomBarSimpleBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.simpleBarTimeSeriesOptions) ); export const zoomBarStackedBarTimeSeriesData = barChart.stackedBarTimeSeriesData; -export const zoomBarStackedBarTimeSeriesOptions = updateOptions( +export const zoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.stackedBarTimeSeriesOptions) ); export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; -export const zoomBarBubbleTimeSeriesOptions = updateOptions( +export const zoomBarBubbleTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) ); export const zoomBarLineTimeSeriesData = lineChart.lineTimeSeriesData; -export const zoomBarLineTimeSeriesOptions = updateOptions( +export const zoomBarLineTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, lineChart.lineTimeSeriesOptions) ); export const zoomBarScatterTimeSeriesData = scatterChart.scatterTimeSeriesData; -export const zoomBarScatterTimeSeriesOptions = updateOptions( +export const zoomBarScatterTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, scatterChart.scatterTimeSeriesOptions) ); export const zoomBarStepTimeSeriesData = stepChart.stepTimeSeriesData; -export const zoomBarStepTimeSeriesOptions = updateOptions( +export const zoomBarStepTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, stepChart.stepTimeSeriesOptions) ); export const zoomBarLineTimeSeries15secondsData = timeSeriesAxisChart.lineTimeSeriesData15seconds; -export const zoomBarLineTimeSeries15secondsOptions = updateOptions( +export const zoomBarLineTimeSeries15secondsOptions = addZoomBarToOptions( Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) ); export const zoomBarLineTimeSeriesInitDomainData = timeSeriesAxisChart.lineTimeSeriesData15seconds; -export const zoomBarLineTimeSeriesInitDomainOptions = updateOptions( +export const zoomBarLineTimeSeriesInitDomainOptions = addZoomBarToOptions( Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) ); -zoomBarLineTimeSeriesInitDomainOptions["title"] += " with initial zoom domain"; -zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initZoomDomain = initZoomDomain; +zoomBarLineTimeSeriesInitDomainOptions["title"] += " zoomed domain"; +zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initialZoomDomain = initialZoomDomain; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 05722a4116..2bbd6ad4ea 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -35,9 +35,9 @@ export class ZoomBar extends Component { // get initZoomDomain const zoomBarOptions = this.model.getOptions().zoomBar; - if (zoomBarOptions.initZoomDomain !== undefined) { + if (zoomBarOptions.initialZoomDomain !== undefined) { this.model.set( - { zoomDomain: zoomBarOptions.initZoomDomain }, + { zoomDomain: zoomBarOptions.initialZoomDomain }, { skipUpdate: true } ); } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 99f3a7dd68..a21b09d82c 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -130,7 +130,6 @@ export const timeScale: TimeScaleOptions = { */ export const zoomBar: ZoomBarOptions = { enabled: false, - initZoomDomain: undefined, selectionStart: undefined, selectionInProgress: undefined, selectionEnd: undefined diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index e01eba594c..440433d76b 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -127,7 +127,7 @@ export interface ZoomBarOptions { /** * an two element array which represents the initial zoom domain */ - initZoomDomain?: Object[]; + initialZoomDomain?: Object[]; /** * a function to handle selection start event From 1cf733a877838b4119540448f8a01c4dfad29a23 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 12:40:27 +0800 Subject: [PATCH 049/510] refactor: use Tools.getProperty to load zoomBarOptions --- packages/core/src/axis-chart.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index f5559f6f56..c7feb56bae 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -31,6 +31,11 @@ export class AxisChart extends Chart { } protected getAxisChartComponents(graphFrameComponents: any[]) { + const zoomBarEnabled = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "enabled" + ); const titleComponent = { id: "title", components: [new Title(this.model, this.services)], @@ -49,12 +54,10 @@ export class AxisChart extends Chart { } }; - if ( - this.model.getOptions().zoomBar && - this.model.getOptions().zoomBar.enabled - ) { + if (zoomBarEnabled) { graphFrameComponents.push(new Brush(this.model, this.services)); } + const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, @@ -156,7 +159,7 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } - if (this.model.getOptions().zoomBar.enabled === true) { + if (zoomBarEnabled) { topLevelLayoutComponents.push(zoomBarComponent); } topLevelLayoutComponents.push(fullFrameComponent); From 954a13f58cc189ef9950c3d3ecd71cba691744fd Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 13:37:33 +0800 Subject: [PATCH 050/510] refactor: update code format --- packages/core/src/charts/pie.ts | 2 +- packages/core/src/charts/radar.ts | 6 ++++-- packages/core/src/components/component.ts | 7 ++++--- packages/core/src/components/index.ts | 1 - packages/core/src/model.ts | 5 ++++- packages/core/src/services/essentials/dom-utils.ts | 5 +++-- packages/core/src/styles/components/_zoom-bar.scss | 3 +++ 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 4642ea8f2d..87b74217aa 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -52,7 +52,7 @@ export class PieChart extends Chart { skeleton: Skeletons.PIE }) ]; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 80b8e7888c..c9b3d67e6e 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -41,8 +41,10 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; - + const graphFrameComponents: any[] = [ + new Radar(this.model, this.services) + ]; + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 6aa5e17ee0..b5996e519c 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -103,15 +103,16 @@ export class Component { this.type === "stacked-bar" || this.type === "scatter-stacked" ) { - return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); - + return DOMUtils.appendOrSelectForAxisChart( + this.parent, + `clipPath.cover` + ); } else { return DOMUtils.appendOrSelect( this.parent, `g.${settings.prefix}--${chartprefix}--${this.type}` ); } - } return this.parent; diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index aff7968ac5..8ecae0d828 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -36,4 +36,3 @@ export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; - diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 185f334848..e45f0a4741 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -70,17 +70,20 @@ export class ChartModel { return datum; }); } + getDefaultZoomBarDomain() { const zoomBarData = this.getZoomBarData(); const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const domainIdentifier = cartesianScales.getDomainIdentifier(); + // default to full range with extended domain return cartesianScales.extendsDomain( mainXAxisPosition, extent(zoomBarData, (d: any) => d[domainIdentifier]) ); } + getAllDataFromDomain() { if (!this.get("data")) { return null; @@ -137,7 +140,7 @@ export class ChartModel { return allDataFromDomain.filter((datum) => { const group = dataGroups.find( - (g) => g.name === datum[groupMapsTo] + (dataGroup) => dataGroup.name === datum[groupMapsTo] ); return group.status === ACTIVE; diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index 181695bcb8..e2e0db7740 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -150,11 +150,12 @@ export class DOMUtils extends Service { } static appendOrSelectForAxisChart(parent, query) { - const querySections = query.split("."); const elementToAppend = querySections[0]; - const parentOfSelection = parent.select(`${elementToAppend}.${querySections.slice(1).join(" ")}`); + const parentOfSelection = parent.select( + `${elementToAppend}.${querySections.slice(1).join(" ")}` + ); const selection = parent.select(`g#g-coverClip`); if (parentOfSelection.empty() && parent) { parent diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 13c20ecc30..d52d6c7978 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -14,6 +14,7 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: $ui-04; stroke-width: 1; } + path.zoom-graph-area-unselected { fill: $ui-01; stroke: none; @@ -23,9 +24,11 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { rect.handle { fill: $icon-02; } + rect.handle-bar { fill: $ui-02; } + // clear d3.brush selection style rect.selection { fill: none; From 8f9bcc436ad38737cf88a43fa858c7362b08e6b9 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 15:38:10 +0800 Subject: [PATCH 051/510] refactor: use Tools.getProperty to get initialZoomDomain --- packages/core/src/components/axes/zoom-bar.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 2bbd6ad4ea..32c4cbb61f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -34,10 +34,14 @@ export class ZoomBar extends Component { }); // get initZoomDomain - const zoomBarOptions = this.model.getOptions().zoomBar; - if (zoomBarOptions.initialZoomDomain !== undefined) { + const initialZoomDomain = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "initialZoomDomain" + ); + if (initialZoomDomain !== null) { this.model.set( - { zoomDomain: zoomBarOptions.initialZoomDomain }, + { zoomDomain: initialZoomDomain }, { skipUpdate: true } ); } From a7ba53fecfafb50119674b64ddaf179bc26e3f43 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 12:13:31 +0800 Subject: [PATCH 052/510] refactor: Change Cover to ChartClip --- packages/core/src/charts/area-stacked.ts | 4 +- packages/core/src/charts/area.ts | 4 +- packages/core/src/charts/bar-grouped.ts | 4 +- packages/core/src/charts/bar-simple.ts | 4 +- packages/core/src/charts/bar-stacked.ts | 4 +- packages/core/src/charts/bubble.ts | 4 +- packages/core/src/charts/line.ts | 4 +- packages/core/src/charts/scatter.ts | 4 +- .../core/src/components/axes/chart-clip.ts | 50 +++++++++++++++++++ packages/core/src/components/axes/cover.ts | 47 ----------------- packages/core/src/components/component.ts | 2 +- packages/core/src/components/index.ts | 2 +- .../core/src/services/essentials/dom-utils.ts | 8 +-- 13 files changed, 72 insertions(+), 69 deletions(-) create mode 100644 packages/core/src/components/axes/chart-clip.ts delete mode 100644 packages/core/src/components/axes/cover.ts diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 42895bdc6b..8104c10ef0 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,7 +6,7 @@ import { Tools } from "../tools"; // Components import { - Cover, + ChartClip, Grid, StackedArea, TwoDimensionalAxes, @@ -36,7 +36,7 @@ export class StackedAreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new StackedArea(this.model, this.services), diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 2b313f6cc7..23959cce54 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,7 +6,7 @@ import { Tools } from "../tools"; // Components import { - Cover, + ChartClip, Grid, Area, Line, @@ -40,7 +40,7 @@ export class AreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 59354739e6..0c259fb01e 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, GroupedBar, TwoDimensionalAxes, @@ -40,7 +40,7 @@ export class GroupedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new GroupedBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 5709ea0e9b..4d6d17345a 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, SimpleBar, TwoDimensionalAxes, @@ -40,7 +40,7 @@ export class SimpleBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new SimpleBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index c2510fa7da..a680c9402b 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, StackedBar, StackedBarRuler, @@ -43,7 +43,7 @@ export class StackedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new StackedBarRuler(this.model, this.services), new StackedBar(this.model, this.services), diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 7238de0aa3..090a72ec29 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Ruler, Bubble, @@ -43,7 +43,7 @@ export class BubbleChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Bubble(this.model, this.services), diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index ee4e07f163..3e0c2959b2 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Line, Ruler, @@ -41,7 +41,7 @@ export class LineChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 2acacdf9df..6eebc8a9e0 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Ruler, Scatter, @@ -43,7 +43,7 @@ export class ScatterChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Scatter(this.model, this.services), diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts new file mode 100644 index 0000000000..f7bb54fc21 --- /dev/null +++ b/packages/core/src/components/axes/chart-clip.ts @@ -0,0 +1,50 @@ +// Internal Imports +import { Component } from "../component"; +import { DOMUtils } from "../../services"; + +// This class is used to create the clipPath to clip the chart graphs +// It's necessary for zoom in/out behavior +export class ChartClip extends Component { + type = "chart-clip"; + + chartClipPath: any; + + clipPathId = "id-" + this.type; + + render(animate = true) { + // Create the clipPath + this.createClipPath(); + } + + createClipPath() { + const svg = this.parent; + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + // Get height + this.chartClipPath = DOMUtils.appendOrSelect( + svg, + `clipPath.${this.type}` + ).attr("id", this.clipPathId); + const clipRect = DOMUtils.appendOrSelect( + this.chartClipPath, + `rect.${this.type}` + ); + clipRect + .attr("x", xScaleStart) + .attr("y", yScaleStart) + .attr("width", xScaleEnd - xScaleStart) + .attr("height", yScaleEnd - yScaleStart); + + this.chartClipPath.merge(clipRect).lower(); + + const clipG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); + clipG + .attr("clip-path", `url(#${this.clipPathId})`) + .attr("id", `g-${this.type}`); + } +} diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts deleted file mode 100644 index 2536c9ef40..0000000000 --- a/packages/core/src/components/axes/cover.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Internal Imports -import { Component } from "../component"; -import { DOMUtils } from "../../services"; - -export class Cover extends Component { - type = "cover"; - - coverClipPath: any; - - render(animate = true) { - // Create the cover - this.createCover(); - } - - createCover() { - const svg = this.parent; - const { cartesianScales } = this.services; - const mainXScale = cartesianScales.getMainXScale(); - const mainYScale = cartesianScales.getMainYScale(); - - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const [yScaleEnd, yScaleStart] = mainYScale.range(); - - // Get height - this.coverClipPath = DOMUtils.appendOrSelect( - svg, - `clipPath.${this.type}` - ); - this.coverClipPath.attr("id", `${this.type}Clip`); - const coverRect = DOMUtils.appendOrSelect( - this.coverClipPath, - "rect.cover" - ); - coverRect - .attr("x", xScaleStart) - .attr("y", yScaleStart) - .attr("width", xScaleEnd - xScaleStart) - .attr("height", yScaleEnd - yScaleStart); - - this.coverClipPath.merge(coverRect).lower(); - - const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); - coverG - .attr("clip-path", `url(#${this.type}Clip)`) - .attr("id", `g-${this.type}Clip`); - } -} diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index b5996e519c..f79a5e347f 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -105,7 +105,7 @@ export class Component { ) { return DOMUtils.appendOrSelectForAxisChart( this.parent, - `clipPath.cover` + `clipPath.chart-clip` ); } else { return DOMUtils.appendOrSelect( diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 8ecae0d828..608c04018d 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,7 +30,7 @@ export * from "./layout/layout"; export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; export * from "./axes/brush"; -export * from "./axes/cover"; +export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index e2e0db7740..73313f18ca 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -156,19 +156,19 @@ export class DOMUtils extends Service { const parentOfSelection = parent.select( `${elementToAppend}.${querySections.slice(1).join(" ")}` ); - const selection = parent.select(`g#g-coverClip`); + const selection = parent.select(`g#g-chart-clip`); if (parentOfSelection.empty() && parent) { parent .append(elementToAppend) - .attr("id", `coverClip`) + .attr("id", `chart-clip`) .append("svg:rect") .attr("class", querySections.slice(1).join(" ")); } if (selection.empty() && parent) { parent .append("g") - .attr("clip-path", `url(#coverClip)`) - .attr("id", `g-coverClip`); + .attr("clip-path", `url(#id-chart-clip)`) + .attr("id", `g-chart-clip`); return parent; } return selection; From 75eff19e257f620989accde5798fd80dc0b77f41 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 12:29:05 +0800 Subject: [PATCH 053/510] refactor: Change Brush to ChartBrush --- packages/core/src/axis-chart.ts | 6 ++++-- .../src/components/axes/{brush.ts => chart-brush.ts} | 9 ++++----- packages/core/src/components/index.ts | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) rename packages/core/src/components/axes/{brush.ts => chart-brush.ts} (95%) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index c7feb56bae..b79ca91dfa 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -8,7 +8,7 @@ import { AxisChartOptions } from "./interfaces"; import { - Brush, + ChartBrush, LayoutComponent, Legend, Title, @@ -55,7 +55,9 @@ export class AxisChart extends Chart { }; if (zoomBarEnabled) { - graphFrameComponents.push(new Brush(this.model, this.services)); + graphFrameComponents.push( + new ChartBrush(this.model, this.services) + ); } const graphFrameComponent = { diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/chart-brush.ts similarity index 95% rename from packages/core/src/components/axes/brush.ts rename to packages/core/src/components/axes/chart-brush.ts index 8067e6aaa8..7fa5a1caf3 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -1,17 +1,16 @@ // Internal Imports import { Component } from "../component"; -import { Tools } from "../../tools"; import { ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports -import { extent } from "d3-array"; import { brushX } from "d3-brush"; import { event } from "d3-selection"; import { scaleTime } from "d3-scale"; -export class Brush extends Component { - type = "brush"; +// This class is used for handle brush events in chart +export class ChartBrush extends Component { + type = "chart-brush"; render(animate = true) { const svg = this.parent; @@ -129,7 +128,7 @@ export class Brush extends Component { const brushArea = DOMUtils.appendOrSelect( backdrop, - "g.chart-brush" + `g.${this.type}` ).call(brush); } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 608c04018d..5c58ed83e8 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -29,7 +29,7 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; -export * from "./axes/brush"; +export * from "./axes/chart-brush"; export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; From fb60a4e4297cf39354dc13b37be549343642037e Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 14:18:36 +0800 Subject: [PATCH 054/510] refactor: set model.set() function default config --- packages/core/src/model.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index e45f0a4741..343e4abdfb 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -314,9 +314,12 @@ export class ChartModel { set(newState: any, configs?: any) { this.state = Object.assign({}, this.state, newState); - - if (!configs || !configs.skipUpdate) { - this.update(configs ? configs.animate : true); + const newConfig = Object.assign( + { skipUpdate: false, animate: true }, // default configs + configs + ); + if (!newConfig.skipUpdate) { + this.update(newConfig.animate); } } From 06332ef3d1f47f124967a0f1e4050374a3806ae9 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 14:30:32 +0800 Subject: [PATCH 055/510] fix: remove unnecessary selector --- packages/core/src/components/graphs/line.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 2be9c949a9..02caebb0be 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -131,7 +131,6 @@ export class Line extends Component { this.parent .selectAll("path.line") - .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-hover-line") ) @@ -142,16 +141,16 @@ export class Line extends Component { return Configuration.lines.opacity.selected; }); - }; + } handleLegendMouseOut = (event: CustomEvent) => { this.parent - .selectAll(`g#coverClip`) + .selectAll("path.line") .transition( this.services.transitions.getTransition("legend-mouseout-line") ) .attr("opacity", Configuration.lines.opacity.selected); - }; + } destroy() { // Remove event listeners From 4c2ed51a1271d0f6daa9a43d44690653b85eeca3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 15:22:22 +0800 Subject: [PATCH 056/510] refactor: create reusable getMainXScaleType() --- packages/core/src/components/axes/chart-brush.ts | 8 ++------ packages/core/src/components/axes/zoom-bar.ts | 8 ++------ packages/core/src/services/scales-cartesian.ts | 10 ++++------ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 7fa5a1caf3..19b9ee14b9 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -23,12 +23,8 @@ export class ChartBrush extends Component { }); const { cartesianScales } = this.services; - const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition - ); - - const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainXScaleType = cartesianScales.getMainXScaleType(); + const mainXScale = cartesianScales.getMainXScale(); if (mainXScale && mainXScaleType === ScaleTypes.TIME) { // get current zoomDomain diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 32c4cbb61f..897a1b9417 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -54,12 +54,8 @@ export class ZoomBar extends Component { const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition - ); - const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition - ); + const mainXScaleType = cartesianScales.getMainXScaleType(); + const mainYScaleType = cartesianScales.getMainYScaleType(); // get axes margins let axesLeftMargin = 0; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 11be646307..4904006ba5 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -231,14 +231,12 @@ export class CartesianScales extends Service { return this.getValueThroughAxisPosition(this.rangeAxisPosition, d, i); } - getXValue(d, i) { - const mainXAxisPosition = this.getMainXAxisPosition(); - return this.getValueThroughAxisPosition(mainXAxisPosition, d, i); + getMainXScaleType() { + return this.getScaleTypeByPosition(this.getMainXAxisPosition()); } - getYValue(d, i) { - const mainYAxisPosition = this.getMainYAxisPosition(); - return this.getValueThroughAxisPosition(mainYAxisPosition, d, i); + getMainYScaleType() { + return this.getScaleTypeByPosition(this.getMainYAxisPosition()); } getDomainIdentifier() { From 4d25064ee6548e1f6beb09734e4f90bb9fc92f3e Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 16:22:13 +0800 Subject: [PATCH 057/510] refactor: make sure zoom bar only shows with supported options - zoomBar is available when -- zoomBar is enabled -- main X axis position is bottom -- main X axis scale type is time --- packages/core/src/axis-chart.ts | 21 +++++++++++++++++++-- packages/core/src/components/axes/axis.ts | 13 +++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index b79ca91dfa..6d4d515cba 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -5,7 +5,9 @@ import { LegendOrientations, LegendPositions, ChartConfig, - AxisChartOptions + AxisChartOptions, + AxisPositions, + ScaleTypes } from "./interfaces"; import { ChartBrush, @@ -31,11 +33,26 @@ export class AxisChart extends Chart { } protected getAxisChartComponents(graphFrameComponents: any[]) { - const zoomBarEnabled = Tools.getProperty( + const isZoomBarEnabled = Tools.getProperty( this.model.getOptions(), "zoomBar", "enabled" ); + + this.services.cartesianScales.findDomainAndRangeAxes(); // need to do this before getMainXAxisPosition() + const mainXAxisPosition = this.services.cartesianScales.getMainXAxisPosition(); + const mainXScaleType = Tools.getProperty( + this.model.getOptions(), + "axes", + mainXAxisPosition, + "scaleType" + ); + // @todo - Zoom Bar only supports main axis at BOTTOM axis and time scale for now + const zoomBarEnabled = + isZoomBarEnabled && + mainXAxisPosition === AxisPositions.BOTTOM && + mainXScaleType === ScaleTypes.TIME; + const titleComponent = { id: "title", components: [new Title(this.model, this.services)], diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 38f4c2b408..bf0e0e92a1 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -106,12 +106,6 @@ export class Axis extends Component { scale.range([startPosition, endPosition]); } - // if zoomDomain is available, update scale domain to Date array. - const zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain); - } - // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -170,6 +164,13 @@ export class Axis extends Component { const scaleType = this.scaleType || axisOptions.scaleType || ScaleTypes.LINEAR; + // if zoomDomain is available, scale type is time, and axis position isBOTTOM or TOP + // update scale domain to zoomDomain. + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && isTimeScaleType && !isVerticalAxis) { + scale.domain(zoomDomain); + } + // Initialize axis object const axis = axisFunction(scale).tickSizeOuter(0); From 35b83afe4c8dc9507d3922cdf05f1e9e4fa3c7e7 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 22:15:30 +0800 Subject: [PATCH 058/510] refactor: set clip-path url in containerSVG --- .../core/src/components/axes/chart-clip.ts | 11 ++---- packages/core/src/components/component.ts | 34 +++++++------------ .../src/components/graphs/area-stacked.ts | 2 +- packages/core/src/components/graphs/area.ts | 2 +- .../core/src/components/graphs/bar-grouped.ts | 2 +- .../core/src/components/graphs/bar-simple.ts | 2 +- .../core/src/components/graphs/bar-stacked.ts | 2 +- packages/core/src/components/graphs/line.ts | 6 ++-- .../src/components/graphs/scatter-stacked.ts | 2 +- .../core/src/components/graphs/scatter.ts | 2 +- .../core/src/services/essentials/dom-utils.ts | 25 -------------- 11 files changed, 24 insertions(+), 66 deletions(-) diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts index f7bb54fc21..321db115e5 100644 --- a/packages/core/src/components/axes/chart-clip.ts +++ b/packages/core/src/components/axes/chart-clip.ts @@ -2,15 +2,13 @@ import { Component } from "../component"; import { DOMUtils } from "../../services"; -// This class is used to create the clipPath to clip the chart graphs +// This class is used to create the clipPath to clip the chart components // It's necessary for zoom in/out behavior export class ChartClip extends Component { type = "chart-clip"; chartClipPath: any; - clipPathId = "id-" + this.type; - render(animate = true) { // Create the clipPath this.createClipPath(); @@ -29,7 +27,7 @@ export class ChartClip extends Component { this.chartClipPath = DOMUtils.appendOrSelect( svg, `clipPath.${this.type}` - ).attr("id", this.clipPathId); + ).attr("id", this.chartClipId); const clipRect = DOMUtils.appendOrSelect( this.chartClipPath, `rect.${this.type}` @@ -41,10 +39,5 @@ export class ChartClip extends Component { .attr("height", yScaleEnd - yScaleStart); this.chartClipPath.merge(clipRect).lower(); - - const clipG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); - clipG - .attr("clip-path", `url(#${this.clipPathId})`) - .attr("id", `g-${this.type}`); } } diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index f79a5e347f..91ccf82ef8 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -19,6 +19,8 @@ export class Component { protected model: ChartModel; protected services: any; + protected chartClipId = "chart-clip-id"; + constructor(model: ChartModel, services: any, configs?: any) { this.model = model; this.services = services; @@ -83,7 +85,7 @@ export class Component { return this.parent; } - getContainerSVG() { + getContainerSVG(withinChartClip = false) { if (this.type) { const chartprefix = Tools.getProperty( this.model.getOptions(), @@ -91,28 +93,16 @@ export class Component { "prefix" ); - // @todo Chart type equals to axis-chart - if ( - this.type === "line" || - this.type === "scatter" || - this.type === "area" || - this.type === "bubble" || - this.type === "area-stacked" || - this.type === "grouped-bar" || - this.type === "simple-bar" || - this.type === "stacked-bar" || - this.type === "scatter-stacked" - ) { - return DOMUtils.appendOrSelectForAxisChart( - this.parent, - `clipPath.chart-clip` - ); - } else { - return DOMUtils.appendOrSelect( - this.parent, - `g.${settings.prefix}--${chartprefix}--${this.type}` - ); + const svg = DOMUtils.appendOrSelect( + this.parent, + `g.${settings.prefix}--${chartprefix}--${this.type}` + ); + + if (withinChartClip) { + svg.attr("clip-path", `url(#${this.chartClipId})`); } + + return svg; } return this.parent; diff --git a/packages/core/src/components/graphs/area-stacked.ts b/packages/core/src/components/graphs/area-stacked.ts index 5f6609df63..2e086cd958 100644 --- a/packages/core/src/components/graphs/area-stacked.ts +++ b/packages/core/src/components/graphs/area-stacked.ts @@ -28,7 +28,7 @@ export class StackedArea extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const self = this; const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/components/graphs/area.ts b/packages/core/src/components/graphs/area.ts index cd94376aaf..127dab7ac0 100644 --- a/packages/core/src/components/graphs/area.ts +++ b/packages/core/src/components/graphs/area.ts @@ -26,7 +26,7 @@ export class Area extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const { cartesianScales } = this.services; const orientation = cartesianScales.getOrientation(); diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 07f12e8c64..85217ba207 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -44,7 +44,7 @@ export class GroupedBar extends Bar { this.setGroupScale(); // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const allDataLabels = map( displayData, diff --git a/packages/core/src/components/graphs/bar-simple.ts b/packages/core/src/components/graphs/bar-simple.ts index aeb72950d1..2f74a5c922 100644 --- a/packages/core/src/components/graphs/bar-simple.ts +++ b/packages/core/src/components/graphs/bar-simple.ts @@ -31,7 +31,7 @@ export class SimpleBar extends Bar { const { groupMapsTo } = options.data; // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); // Update data on all bars const bars = svg diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index 55d376f835..a4cce26d0f 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -28,7 +28,7 @@ export class StackedBar extends Bar { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); // Chart options mixed with the internal configurations const displayData = this.model.getDisplayData(); diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 02caebb0be..3668bc294c 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -25,7 +25,7 @@ export class Line extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const { cartesianScales, curves } = this.services; const getDomainValue = (d, i) => cartesianScales.getDomainValue(d, i); @@ -141,7 +141,7 @@ export class Line extends Component { return Configuration.lines.opacity.selected; }); - } + }; handleLegendMouseOut = (event: CustomEvent) => { this.parent @@ -150,7 +150,7 @@ export class Line extends Component { this.services.transitions.getTransition("legend-mouseout-line") ) .attr("opacity", Configuration.lines.opacity.selected); - } + }; destroy() { // Remove event listeners diff --git a/packages/core/src/components/graphs/scatter-stacked.ts b/packages/core/src/components/graphs/scatter-stacked.ts index e477182786..ca5ad2a4c1 100644 --- a/packages/core/src/components/graphs/scatter-stacked.ts +++ b/packages/core/src/components/graphs/scatter-stacked.ts @@ -7,7 +7,7 @@ export class StackedScatter extends Scatter { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index 2d8074dc49..539d61b7e3 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -52,7 +52,7 @@ export class Scatter extends Component { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index 73313f18ca..2cb6fcdb75 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -149,31 +149,6 @@ export class DOMUtils extends Service { return selection; } - static appendOrSelectForAxisChart(parent, query) { - const querySections = query.split("."); - const elementToAppend = querySections[0]; - - const parentOfSelection = parent.select( - `${elementToAppend}.${querySections.slice(1).join(" ")}` - ); - const selection = parent.select(`g#g-chart-clip`); - if (parentOfSelection.empty() && parent) { - parent - .append(elementToAppend) - .attr("id", `chart-clip`) - .append("svg:rect") - .attr("class", querySections.slice(1).join(" ")); - } - if (selection.empty() && parent) { - parent - .append("g") - .attr("clip-path", `url(#id-chart-clip)`) - .attr("id", `g-chart-clip`); - return parent; - } - return selection; - } - protected svg: Element; protected width: string; protected height: string; From 0559702759a36b93c8810917250ae67cc7b6d3e6 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 11:52:45 +0800 Subject: [PATCH 059/510] refactor: dispatch zoombar selection events instead of callback --- packages/core/demo/data/zoom-bar.ts | 26 +------------- .../core/src/components/axes/chart-brush.ts | 36 +++++++------------ packages/core/src/components/axes/zoom-bar.ts | 33 +++++++---------- packages/core/src/configuration.ts | 5 +-- packages/core/src/interfaces/components.ts | 13 ------- packages/core/src/interfaces/events.ts | 5 ++- 6 files changed, 32 insertions(+), 86 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index fbe74fba2a..2dc1b076e1 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -6,39 +6,15 @@ import * as scatterChart from "./scatter"; import * as stepChart from "./step"; import * as timeSeriesAxisChart from "./time-series-axis"; -// default function for selection callback -const selectionStartFun = (selection, domain) => { - console.log("ZoomBar SelectionStart callback!"); - console.log(selection); - console.log(domain); -}; -const selectionInProgressFun = (selection, domain) => { - console.log("ZoomBar SelectionInProgress callback!"); - console.log(selection); - console.log(domain); -}; -const selectionEndFun = (selection, domain) => { - console.log("ZoomBar SelectionEnd callback!"); - console.log(selection); - console.log(domain); -}; - const initialZoomDomain = [ new Date(2020, 11, 10, 23, 59, 25), new Date(2020, 11, 11, 0, 0, 25) ]; -const defaultZoomBarOptions = { - enabled: true, - selectionStart: selectionStartFun, - selectionInProgress: selectionInProgressFun, - selectionEnd: selectionEndFun -}; - // utility function to update title and enable zoomBar option const addZoomBarToOptions = (options) => { options["title"] = options["title"] + " - Zoom bar enabled"; - options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); + options["zoomBar"] = { enabled: true }; return options; }; diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 19b9ee14b9..e8ba9b3f7d 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -1,6 +1,6 @@ // Internal Imports import { Component } from "../component"; -import { ScaleTypes } from "../../interfaces"; +import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports @@ -51,29 +51,19 @@ export class ChartBrush extends Component { event.sourceEvent.type === "mouseup" || event.sourceEvent.type === "mousedown") ) { - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress( - selection, - newDomain - ); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); + // dispatch selection events + let zoomBarEventType; + if (event.type === "start") { + zoomBarEventType = Events.ZoomBar.SELECTION_START; + } else if (event.type === "brush") { + zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; + } else if (event.type === "end") { + zoomBarEventType = Events.ZoomBar.SELECTION_END; } + this.services.events.dispatchEvent(zoomBarEventType, { + selection, + newDomain + }); } }; const brushed = () => { diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 897a1b9417..258d60ea1a 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -8,7 +8,7 @@ import { DOMUtils } from "../../services"; import { extent } from "d3-array"; import { brushX } from "d3-brush"; import { area, line } from "d3-shape"; -import { event, select, selectAll } from "d3-selection"; +import { event } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; @@ -238,26 +238,19 @@ export class ZoomBar extends Component { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); + // dispatch selection events + let zoomBarEventType; + if (event.type === "start") { + zoomBarEventType = Events.ZoomBar.SELECTION_START; + } else if (event.type === "brush") { + zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; + } else if (event.type === "end") { + zoomBarEventType = Events.ZoomBar.SELECTION_END; } + this.services.events.dispatchEvent(zoomBarEventType, { + selection, + newDomain + }); } } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index a21b09d82c..866d815d6f 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -129,10 +129,7 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false, - selectionStart: undefined, - selectionInProgress: undefined, - selectionEnd: undefined + enabled: false }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 440433d76b..00ef8fb98a 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -128,17 +128,4 @@ export interface ZoomBarOptions { * an two element array which represents the initial zoom domain */ initialZoomDomain?: Object[]; - - /** - * a function to handle selection start event - */ - selectionStart?: Function; - /** - * a function to handle selection in progress event - */ - selectionInProgress?: Function; - /** - * a function to handle selection end event - */ - selectionEnd?: Function; } diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 2a25fd5992..1da2416ead 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -21,7 +21,10 @@ export enum Model { * enum of all events related to the zoom-bar */ export enum ZoomBar { - UPDATE = "zoom-bar-update" + UPDATE = "zoom-bar-update", + SELECTION_START = "zoom-bar-selection-start", + SELECTION_IN_PROGRESS = "zoom-bar-selection-in-progress", + SELECTION_END = "zoom-bar-selection-end" } /** From 78cf4ae734bef70f8b50c769ca56e32b9bd51046 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 15:48:41 +0800 Subject: [PATCH 060/510] feat: update chart brush selection storke to dash --- .../core/src/components/axes/chart-brush.ts | 36 +++++++++++++++++-- .../src/styles/components/_chart-brush.scss | 9 +++++ .../core/src/styles/components/index.scss | 1 + 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/styles/components/_chart-brush.scss diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e8ba9b3f7d..e267ae0bda 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -10,8 +10,12 @@ 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 = "chart-brush"; + selectionSelector = "rect.selection"; // needs to match the class name in d3.brush + render(animate = true) { const svg = this.parent; const backdrop = DOMUtils.appendOrSelect( @@ -35,8 +39,36 @@ export class ChartBrush extends Component { 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 + + brushArea + .select(this.selectionSelector) + .attr("stroke-dasharray", dashArray); + }; + const eventHandler = () => { const selection = event.selection; + + updateSelectionDash(selection); + const xScale = scaleTime().range([0, width]).domain(zoomDomain); const newDomain = [ @@ -106,8 +138,8 @@ export class ChartBrush extends Component { // leave some space to display selection strokes besides axis const brush = brushX() .extent([ - [2, 0], - [width - 1, height - 1] + [0, 0], + [width - 1, height] ]) .on("start brush end", eventHandler) .on("end.brushed", brushed); diff --git a/packages/core/src/styles/components/_chart-brush.scss b/packages/core/src/styles/components/_chart-brush.scss new file mode 100644 index 0000000000..f316c7d486 --- /dev/null +++ b/packages/core/src/styles/components/_chart-brush.scss @@ -0,0 +1,9 @@ +.#{$prefix}--#{$charts-prefix}--chart-brush { + g.chart-brush { + rect.selection { + fill: $ui-03; + fill-opacity: 0.3; + stroke: $interactive-03; + } + } +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index b7e21a8cfd..bb63189e7f 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -1,5 +1,6 @@ @import "./axis"; @import "./callouts"; +@import "./chart-brush"; @import "./grid"; @import "./ruler"; @import "./skeleton"; From a8c7db541acebffbfc048d201b8e6729e7a85595 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 17:33:44 +0800 Subject: [PATCH 061/510] feat: show tooltip when mouseover zoombar handle --- packages/core/src/components/axes/zoom-bar.ts | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 258d60ea1a..4498bb214f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -197,7 +197,11 @@ export class ZoomBar extends Component { // don't update brushHandle to avoid flash } else if (zoomDomain[0].valueOf() === zoomDomain[1].valueOf()) { brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle(this.getContainerSVG(), xScale.range()); + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range(), + xScale.domain() + ); } else { const selected = zoomDomain.map((domain) => xScale(domain)); if (selected[1] - selected[0] < this.MIN_SELECTION_DIFF) { @@ -205,7 +209,11 @@ export class ZoomBar extends Component { // don't update brushHandle to avoid flash } else { brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle(this.getContainerSVG(), selected); + this.updateBrushHandle( + this.getContainerSVG(), + selected, + zoomDomain + ); } } } @@ -213,14 +221,14 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - // update brush handle position - this.updateBrushHandle(this.getContainerSVG(), selection); - const newDomain = [ scale.invert(selection[0]), scale.invert(selection[1]) ]; + // update brush handle position + this.updateBrushHandle(this.getContainerSVG(), selection, newDomain); + // be aware that the value of d3.event changes during an event! // update zoomDomain only if the event comes from mouse event if ( @@ -254,7 +262,20 @@ export class ZoomBar extends Component { } } - updateBrushHandle(svg, selection) { + updateBrushHandleTooltip(svg, domain) { + // remove old handle tooltip + svg.select("title").remove(); + // add new handle tooltip + svg.append("title").text((d) => { + if (d.type === "w") { + return domain[0]; + } else if (d.type === "e") { + return domain[1]; + } + }); + } + + updateBrushHandle(svg, selection, domain) { const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -263,6 +284,7 @@ export class ZoomBar extends Component { const handleBarHeight = 12; const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; + // handle svg.select(this.brushSelector) .selectAll("rect.handle") @@ -277,7 +299,10 @@ export class ZoomBar extends Component { .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) - .style("display", null); // always display + .attr("cursor", "pointer") + .style("display", null) // always display + .call(this.updateBrushHandleTooltip, domain); + // handle-bar svg.select(this.brushSelector) .selectAll("rect.handle-bar") @@ -296,7 +321,8 @@ export class ZoomBar extends Component { .attr("y", handleYBarDiff) .attr("width", handleBarWidth) .attr("height", handleBarHeight) - .attr("cursor", "ew-resize"); + .attr("cursor", "pointer") + .call(this.updateBrushHandleTooltip, domain); this.updateClipPath( svg, From 06f02c39dc17f257f193db082786d0a1efb63767 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 11:54:28 +0800 Subject: [PATCH 062/510] fix: avoid tick rotation flip during zoom domain changing - always rotate ticks during zoom domain changing --- packages/core/src/components/axes/axis.ts | 33 ++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index bf0e0e92a1..9650d9217f 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -29,6 +29,8 @@ export class Axis extends Component { scale: any; scaleType: ScaleTypes; + zoomDomainChanging = false; + constructor(model: ChartModel, services: any, configs?: any) { super(model, services, configs); @@ -37,6 +39,25 @@ export class Axis extends Component { } this.margins = this.configs.margins; + this.init(); + } + + init() { + this.services.events.addEventListener( + Events.ZoomBar.SELECTION_START, + () => { + this.zoomDomainChanging = true; + } + ); + this.services.events.addEventListener( + Events.ZoomBar.SELECTION_END, + () => { + this.zoomDomainChanging = false; + // need another update after zoom bar selection is completed + // to make sure the tick rotation is calculated correctly + this.services.events.dispatchEvent(Events.Model.UPDATE, {}); + } + ); } render(animate = true) { @@ -417,7 +438,9 @@ export class Axis extends Component { ? estimatedTickSize < minTickSize * 2 // datetime tick could be very long : estimatedTickSize < minTickSize; } - if (rotateTicks) { + + // always rotate ticks if zoomDomain is changing to avoid rotation flips during zoomDomain changing + if (rotateTicks || this.zoomDomainChanging) { if (!isNumberOfTicksProvided) { axis.ticks( this.getNumberOfFittingTicks( @@ -635,5 +658,13 @@ export class Axis extends Component { .on("mouseover", null) .on("mousemove", null) .on("mouseout", null); + this.services.events.removeEventListener( + Events.ZoomBar.SELECTION_START, + {} + ); + this.services.events.removeEventListener( + Events.ZoomBar.SELECTION_END, + {} + ); } } From e2535d651478c65e7057adc167b12a648ad87db2 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 18:19:27 +0800 Subject: [PATCH 063/510] fix: move chart brush selection above all graphs --- .../core/src/components/axes/chart-brush.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e267ae0bda..944e2245e6 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -16,6 +16,8 @@ export class ChartBrush extends Component { selectionSelector = "rect.selection"; // needs to match the class name in d3.brush + selectionElementId = "ChartBrushSelectionId"; + render(animate = true) { const svg = this.parent; const backdrop = DOMUtils.appendOrSelect( @@ -66,6 +68,9 @@ export class ChartBrush extends Component { const eventHandler = () => { const selection = event.selection; + if (selection === null) { + return; + } updateSelectionDash(selection); @@ -148,6 +153,26 @@ export class ChartBrush extends Component { backdrop, `g.${this.type}` ).call(brush); + + // set an id for rect.selection to be referred + brushArea + .select(this.selectionSelector) + .attr("id", this.selectionElementId); + + // create the chart brush group + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const selectionArea = this.getContainerSVG().attr( + "transform", + `translate(${xScaleStart},0)` + ); + // clear old svg + selectionArea.selectAll("svg").remove(); + // create a svg referring to d3 brush rect.selection + // this is to draw the selection above all graphs + selectionArea + .append("svg") + .append("use") + .attr("xlink:href", `#${this.selectionElementId}`); } } } From 8f4a6eade4d7012c18175cc5cc1411ba7c87afbe Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 20:47:11 +0800 Subject: [PATCH 064/510] fix: use another svg to display front selection --- .../core/src/components/axes/chart-brush.ts | 55 +++++++++---------- .../src/styles/components/_chart-brush.scss | 15 ++++- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 944e2245e6..d07edd0c4e 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -16,14 +16,22 @@ export class ChartBrush extends Component { selectionSelector = "rect.selection"; // needs to match the class name in d3.brush - selectionElementId = "ChartBrushSelectionId"; + frontSelectionSelector = "rect.frontSelection"; // needs to match the class name in _chart-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 = brushArea.select(this.selectionSelector); + const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true }); @@ -31,6 +39,12 @@ export class ChartBrush extends Component { 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 @@ -61,9 +75,7 @@ export class ChartBrush extends Component { dashArray += "," + selectionWidth.toString(); // bottom (invisible) dashArray += "," + height.toString(); // left - brushArea - .select(this.selectionSelector) - .attr("stroke-dasharray", dashArray); + frontSelection.attr("stroke-dasharray", dashArray); }; const eventHandler = () => { @@ -72,6 +84,14 @@ export class ChartBrush extends Component { 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("display", null); + updateSelectionDash(selection); const xScale = scaleTime().range([0, width]).domain(zoomDomain); @@ -137,6 +157,8 @@ export class ChartBrush extends Component { // clear brush selection brushArea.call(brush.move, null); + // hide frontSelection + frontSelection.style("display", "none"); } }; @@ -149,30 +171,7 @@ export class ChartBrush extends Component { .on("start brush end", eventHandler) .on("end.brushed", brushed); - const brushArea = DOMUtils.appendOrSelect( - backdrop, - `g.${this.type}` - ).call(brush); - - // set an id for rect.selection to be referred - brushArea - .select(this.selectionSelector) - .attr("id", this.selectionElementId); - - // create the chart brush group - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const selectionArea = this.getContainerSVG().attr( - "transform", - `translate(${xScaleStart},0)` - ); - // clear old svg - selectionArea.selectAll("svg").remove(); - // create a svg referring to d3 brush rect.selection - // this is to draw the selection above all graphs - selectionArea - .append("svg") - .append("use") - .attr("xlink:href", `#${this.selectionElementId}`); + brushArea.call(brush); } } } diff --git a/packages/core/src/styles/components/_chart-brush.scss b/packages/core/src/styles/components/_chart-brush.scss index f316c7d486..d458f791d4 100644 --- a/packages/core/src/styles/components/_chart-brush.scss +++ b/packages/core/src/styles/components/_chart-brush.scss @@ -1,9 +1,18 @@ .#{$prefix}--#{$charts-prefix}--chart-brush { + // disable default d3 brush selection g.chart-brush { rect.selection { - fill: $ui-03; - fill-opacity: 0.3; - stroke: $interactive-03; + fill: none; + fill-opacity: 0; + stroke: none; } } } + +g.#{$prefix}--#{$charts-prefix}--chart-brush { + rect.frontSelection { + fill: $ui-03; + fill-opacity: 0.3; + stroke: $interactive-03; + } +} From d6c6194eb4d4d4850e3dd9cfa222a8cf3a9c7335 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 20:59:47 +0800 Subject: [PATCH 065/510] fix: set cursor for front selection --- packages/core/src/components/axes/chart-brush.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index d07edd0c4e..85e48b3d08 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -90,6 +90,7 @@ export class ChartBrush extends Component { .attr("y", d3Selection.attr("y")) .attr("width", d3Selection.attr("width")) .attr("height", d3Selection.attr("height")) + .style("cursor", "pointer") .style("display", null); updateSelectionDash(selection); From 9e2e6997a9f1c0f782429b26430eb247b51b4e5e Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 16 Jul 2020 15:07:40 +0800 Subject: [PATCH 066/510] fix: use unique chartClipId for each chart --- packages/core/src/components/axes/chart-clip.ts | 15 +++++++++++++++ packages/core/src/components/component.ts | 8 +++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts index 321db115e5..4a8a206c2f 100644 --- a/packages/core/src/components/axes/chart-clip.ts +++ b/packages/core/src/components/axes/chart-clip.ts @@ -1,14 +1,29 @@ // Internal Imports import { Component } from "../component"; import { DOMUtils } from "../../services"; +import { ChartModel } from "../../model"; // This class is used to create the clipPath to clip the chart components // It's necessary for zoom in/out behavior export class ChartClip extends Component { type = "chart-clip"; + // Give every chart-clip a distinct ID + // so they don't interfere each other in a page with multiple charts + chartClipId = "chart-clip-id-" + Math.floor(Math.random() * 99999999999); + chartClipPath: any; + constructor(model: ChartModel, services: any, configs?: any) { + super(model, services, configs); + this.init(); + } + + init() { + // set unique chartClipId in this chart to model + this.model.set({ chartClipId: this.chartClipId }, { skipUpdate: true }); + } + render(animate = true) { // Create the clipPath this.createClipPath(); diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 91ccf82ef8..87d55fba62 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -19,8 +19,6 @@ export class Component { protected model: ChartModel; protected services: any; - protected chartClipId = "chart-clip-id"; - constructor(model: ChartModel, services: any, configs?: any) { this.model = model; this.services = services; @@ -99,7 +97,11 @@ export class Component { ); if (withinChartClip) { - svg.attr("clip-path", `url(#${this.chartClipId})`); + // get unique chartClipId int this chart from model + const chartClipId = this.model.get("chartClipId"); + if (chartClipId) { + svg.attr("clip-path", `url(#${chartClipId})`); + } } return svg; From 7510e59161aa6a2462070d87626957b4096fe426 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 16 Jul 2020 16:17:58 +0800 Subject: [PATCH 067/510] fix: keep zoom bar handle inside zoom bar range --- packages/core/src/components/axes/zoom-bar.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 4498bb214f..adc38f9bc9 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -20,7 +20,9 @@ export class ZoomBar extends Component { brushSelector = "g.zoom-bar-brush"; // needs to match the style in _zoom-bar.scss - clipId = "zoomBarClip"; + // Give every zoomBarClip a distinct ID + // so they don't interfere the other zoom bars in a page + clipId = "zoomBarClip-" + Math.floor(Math.random() * 99999999999); height = 32; @@ -28,6 +30,9 @@ export class ZoomBar extends Component { brush = brushX(); + // The max allowed selection ragne, will be updated soon in render() + maxSelectionRange: [0, 0]; + init() { this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { this.render(); @@ -97,6 +102,8 @@ export class ZoomBar extends Component { this.compensateDataForDefaultDomain(zoomBarData, defaultDomain, 0); xScale.range([axesLeftMargin, width]).domain(defaultDomain); + // keep max selection range + this.maxSelectionRange = xScale.range(); yScale .range([0, this.height - 6]) @@ -276,6 +283,7 @@ export class ZoomBar extends Component { } updateBrushHandle(svg, selection, domain) { + const self = this; const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -291,9 +299,17 @@ export class ZoomBar extends Component { .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { if (d.type === "w") { - return selection[0] + handleXDiff; + // handle should not exceed zoom bar range + return Math.max( + selection[0] + handleXDiff, + self.maxSelectionRange[0] + ); } else if (d.type === "e") { - return selection[1] + handleXDiff; + // handle should not exceed zoom bar range + return Math.min( + selection[1] + handleXDiff, + self.maxSelectionRange[1] - handleWidth + ); } }) .attr("y", 0) @@ -313,9 +329,15 @@ export class ZoomBar extends Component { }) .attr("x", function (d) { if (d.type === "w") { - return selection[0] + handleBarXDiff; + return Math.max( + selection[0] + handleBarXDiff, + self.maxSelectionRange[0] - handleXDiff + handleBarXDiff + ); } else if (d.type === "e") { - return selection[1] + handleBarXDiff; + return Math.min( + selection[1] + handleBarXDiff, + self.maxSelectionRange[1] + handleXDiff + handleBarXDiff + ); } }) .attr("y", handleYBarDiff) From b0caf5cdd4b372ddd9898d81414da26e21fc29e0 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 17 Jul 2020 11:37:27 +0800 Subject: [PATCH 068/510] feat: add zoom ratio for config --- packages/core/src/axis-chart.ts | 15 +- packages/core/src/components/axes/tool-bar.ts | 195 ++++++++++++++++++ packages/core/src/components/axes/zoom-bar.ts | 2 +- packages/core/src/components/index.ts | 1 + packages/core/src/configuration.ts | 4 +- packages/core/src/interfaces/components.ts | 14 +- .../core/src/styles/components/_tool-bar.scss | 25 +++ .../core/src/styles/components/index.scss | 1 + 8 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 packages/core/src/components/axes/tool-bar.ts create mode 100644 packages/core/src/styles/components/_tool-bar.scss diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 6d4d515cba..9ae572e767 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -16,7 +16,8 @@ import { Title, AxisChartsTooltip, Spacer, - ZoomBar + ZoomBar, + ToolBar, } from "./components"; import { Tools } from "./tools"; @@ -162,6 +163,15 @@ export class AxisChart extends Chart { } }; + const toolBarComponent = { + id: "tool-bar", + components: [new ToolBar(this.model, this.services)], + growth: { + x: LayoutGrowth.PREFERRED, + y: LayoutGrowth.FIXED + } + }; + // Add chart title if it exists const topLevelLayoutComponents = []; if (this.model.getOptions().title) { @@ -179,6 +189,9 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } if (zoomBarEnabled) { + if (this.model.getOptions().zoomBar.showToolBar === true) { + topLevelLayoutComponents.push(toolBarComponent); + } topLevelLayoutComponents.push(zoomBarComponent); } topLevelLayoutComponents.push(fullFrameComponent); diff --git a/packages/core/src/components/axes/tool-bar.ts b/packages/core/src/components/axes/tool-bar.ts new file mode 100644 index 0000000000..84194a25ce --- /dev/null +++ b/packages/core/src/components/axes/tool-bar.ts @@ -0,0 +1,195 @@ +// Internal Imports +import { Component } from "../component"; +import { Events, ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { extent } from "d3-array"; +import { line } from "d3-shape"; +import { event, select, selectAll } from "d3-selection"; +import { scaleTime } from "d3-scale"; + +// Carbon component import + +export class ToolBar extends Component { + type = "tool-bar"; + // The minimum selection x range to trigger handler update + // Smaller number may introduce a handler flash during initialization + // Bigger number may not trigger handler update while selection area on chart is very small + MIN_SELECTION_DIFF = 9e-10; + + height = 20; + + spacerHeight = 10; + + zoomInStart = 700; + + zoomOutStart = 0; + + previousZoomDomain; + + zoomRatio; + + init() { + this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); + this.zoomRatio = this.model.getOptions().zoomBar.zoomRatio; + + } + + render(animate = true) { + const svg = this.getContainerSVG(); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + // get current zoomDomain + let zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + return; + } + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const xScale = mainXScale.copy(); + + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + + this.zoomOutStart = width - (axesLeftMargin + 70); + this.zoomInStart = this.zoomOutStart - 25; + + const container = DOMUtils.appendOrSelect(svg, "svg.toolbar-container") + .attr("width", "100%") + .attr("height", this.height) + .attr("opacity", 1); + + const spacer = DOMUtils.appendOrSelect(svg, "rect.toolbar-spacer") + .attr("x", 0) + .attr("y", this.height) + .attr("width", "100%") + .attr("height", this.spacerHeight) + .attr("opacity", 1) + .attr("fill", "none"); + + const zoomInContainer = DOMUtils.appendOrSelect(container, "g.toolbar-zoomIn") + const zoomInGroup = zoomInContainer.html(this.getZoomInIcon()); + const zoomIn = zoomInGroup.select("svg#icon-zoomIn"); + var self = this; + zoomIn.on("click", function () { + let selectionRange = self.model.get("selectionRange"); + if (!selectionRange) { + selectionRange = [axesLeftMargin, width]; + } + xScale.range([axesLeftMargin, width]).domain(zoomDomain); + let startPoint = selectionRange[0] + ((width - axesLeftMargin)/2) * (self.zoomRatio / 2); + let endPoint = selectionRange[1] - ((width - axesLeftMargin)/2) * (self.zoomRatio / 2); + 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 = self.model.getDefaultZoomBarDomain(); + startPoint = axesLeftMargin; + endPoint = width; + } + + // only if zoomDomain needs update + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { + self.model.set( + { zoomDomain: newDomain, selectionRange: [startPoint, endPoint] }, + { animate: false } + ); + } + }); + const zoomOutContainer = DOMUtils.appendOrSelect(container, "g.toolbar-zoomOut") + const zoomOutGroup = zoomOutContainer.html(this.getZoomOutIcon()); + const zoomOut = zoomOutGroup.select("svg#icon-zoomOut"); + zoomOut.on("click", function () { + let currentSelection = self.model.get("selectionRange"); + if (!currentSelection) { + currentSelection = [axesLeftMargin, width]; + } + let startPoint = currentSelection[0] - ((width - axesLeftMargin) / 2) * (self.zoomRatio / 2); + let endPoint = currentSelection[1] + ((width - axesLeftMargin) / 2) * (self.zoomRatio / 2); + zoomDomain = self.model.getDefaultZoomBarDomain(); + xScale.range([axesLeftMargin, 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 = self.model.getDefaultZoomBarDomain(); + } + + if (newDomain[0] <= zoomDomain[0]) { + newDomain[0] = zoomDomain[0] + startPoint = axesLeftMargin; + } + if (newDomain[1] >= zoomDomain[1]) { + newDomain[1] = zoomDomain[1]; + endPoint = width; + } + + self.model.set( + { zoomDomain: newDomain, selectionRange: [startPoint, endPoint] }, + { animate: false } + ); + + }); + + + } + + getZoomInIcon() { + return ` + + + + Zoom in + + + + `; + } + + getZoomOutIcon() { + return ` + + + + Zoom out + + + + `; + } + + + + destroy() { + this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); + } + +} diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index adc38f9bc9..1c10223964 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -250,7 +250,7 @@ export class ZoomBar extends Component { zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1] ) { - this.model.set({ zoomDomain: newDomain }, { animate: false }); + this.model.set({ zoomDomain: newDomain, selectionRange: selection }, { animate: false }); } // dispatch selection events diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 5c58ed83e8..7a4148bc57 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -34,5 +34,6 @@ export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; +export * from "./axes/tool-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 866d815d6f..34f5fab800 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -129,7 +129,9 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false + enabled: false, + zoomRatio: 0.4, + showToolBar: true, }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 00ef8fb98a..d7fe4aa477 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -127,5 +127,17 @@ export interface ZoomBarOptions { /** * an two element array which represents the initial zoom domain */ - initialZoomDomain?: Object[]; + initZoomDomain?: Object[]; + + /** + * a vaiable to handle zoom in ratio + */ + zoomRatio?: number; + /** + * is the tool-bar visible or not + */ + showToolBar?: boolean; + + + } diff --git a/packages/core/src/styles/components/_tool-bar.scss b/packages/core/src/styles/components/_tool-bar.scss new file mode 100644 index 0000000000..fed7f5e0f4 --- /dev/null +++ b/packages/core/src/styles/components/_tool-bar.scss @@ -0,0 +1,25 @@ +g.#{$prefix}--#{$charts-prefix}--tool-bar { + rect.icon-zoom { + fill: transparent; + cursor: pointer; + } + + rect.icon-zoom:hover { + cursor: pointer; + pointer-events: visible; + } + + polygon { + fill: $icon-02; + } + + svg#icon-zoomOut rect.icon-zoomOut { + fill: $icon-02; + } + + path { + fill: $icon-02; + } + + +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index bb63189e7f..42d8384af5 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -8,6 +8,7 @@ @import "./layout"; @import "./legend"; @import "./title"; +@import "./tool-bar"; @import "./tooltip"; @import "./threshold"; @import "./zoom-bar"; From e2a399c2b4b11b4e129d45aeb798a3a6e0d8df63 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 27 May 2020 15:44:15 +0800 Subject: [PATCH 069/510] feat: initial zoom bar implementation from PR 511 --- packages/core/src/axis-chart.ts | 16 +- packages/core/src/chart.ts | 9 +- packages/core/src/components/axes/axis.ts | 9 +- packages/core/src/components/axes/zoom-bar.ts | 216 ++++++++++++++++++ packages/core/src/components/index.ts | 1 + packages/core/src/model.ts | 10 +- packages/core/src/services/axis-zoom.ts | 60 +++++ packages/core/src/services/index.ts | 1 + .../core/src/services/scales-cartesian.ts | 6 + .../core/src/styles/components/_zoom-bar.scss | 11 + .../core/src/styles/components/index.scss | 1 + packages/core/src/styles/graphs/index.scss | 4 + 12 files changed, 333 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/axes/zoom-bar.ts create mode 100644 packages/core/src/services/axis-zoom.ts create mode 100644 packages/core/src/styles/components/_zoom-bar.scss diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index b9751b3a31..e4d105905f 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -12,7 +12,8 @@ import { Legend, Title, AxisChartsTooltip, - Spacer + Spacer, + ZoomBar } from "./components/index"; import { Tools } from "./tools"; @@ -123,6 +124,17 @@ export class AxisChart extends Chart { } }; + const zoomBarComponent = { + id: "zoom-bar", + components: [ + new ZoomBar(this.model, this.services) + ], + growth: { + x: LayoutGrowth.PREFERRED, + y: LayoutGrowth.FIXED + } + }; + // Add chart title if it exists const topLevelLayoutComponents = []; if (this.model.getOptions().title) { @@ -139,6 +151,8 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } + + topLevelLayoutComponents.push(zoomBarComponent); topLevelLayoutComponents.push(fullFrameComponent); return [ diff --git a/packages/core/src/chart.ts b/packages/core/src/chart.ts index ea65e9f3c3..4e86305488 100644 --- a/packages/core/src/chart.ts +++ b/packages/core/src/chart.ts @@ -37,7 +37,7 @@ export class Chart { // Contains the code that uses properties that are overridable by the super-class init(holder: Element, chartConfigs: ChartConfig) { // Store the holder in the model - this.model.set({ holder }, true); + this.model.set({ holder }, { skipUpdate: true }); // Initialize all services Object.keys(this.services).forEach((serviceName) => { @@ -49,8 +49,9 @@ export class Chart { }); // Call update() when model has been updated - this.services.events.addEventListener(ChartEvents.Model.UPDATE, () => { - this.update(true); + this.services.events.addEventListener(ChartEvents.Model.UPDATE, (e) => { + const animate = !!Tools.getProperty(e, "detail", "animate"); + this.update(animate); }); // Set model data & options @@ -110,7 +111,7 @@ export class Chart { // Remove the chart holder this.services.domUtils.getHolder().remove(); - this.model.set({ destroyed: true }, true); + this.model.set({ destroyed: true }, { skipUpdate: true }); } protected getChartComponents(graphFrameComponents: any[]) { diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index c810af5994..04433e6399 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -106,6 +106,13 @@ export class Axis extends Component { scale.range([startPosition, endPosition]); } + // if zoomDomain is available, update scale domain to Date array. + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { + scale.domain(zoomDomain.map(d => new Date(d))); + } + + // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -405,7 +412,7 @@ export class Axis extends Component { const ticksNumber = isTimeScaleType ? axis.tickValues().length : scale.ticks().length; - const estimatedTickSize = width / ticksNumber / 2; + const estimatedTickSize = width / ticksNumber / 1.6; rotateTicks = estimatedTickSize < minTickSize; } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts new file mode 100644 index 0000000000..dee5a24f55 --- /dev/null +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -0,0 +1,216 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { area, line } from "d3-shape"; +import { extent } from "d3-array"; +import { drag } from "d3-drag"; +import { event, select, selectAll } from "d3-selection"; + +export class ZoomBar extends Component { + type = "zoom-bar"; + + ogXScale: any; + + dragged = Tools.debounce((element, d, e) => { + element = select(element); + const startingHandle = element.attr("class").indexOf("start") !== -1; + + let domain; + if (startingHandle) { + domain = [ + this.ogXScale.invert(e.x), + this.ogXScale.domain()[1] + // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) + ]; + } else { + domain = [ + this.ogXScale.domain()[0], + this.ogXScale.invert(e.x) + // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) + ]; + } + this.model.set({ zoomDomain: domain }, { animate: false }); + }, 2.5); + + render(animate = true) { + const svg = this.getContainerSVG(); + + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); + const mainXScaleType = cartesianScales.getScaleTypeByPosition(mainXAxisPosition); + const mainYScaleType = cartesianScales.getScaleTypeByPosition(mainYAxisPosition); + + const height = 32; + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") + // .attr("transform", "translateX(10)") + .attr("width", "100%") + .attr("height", height) + .attr("opacity", 1); + + const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") + .attr("x", 0) + .attr("y", 32) + .attr("width", "100%") + .attr("height", 20) + .attr("opacity", 1) + .attr("fill", "none"); + + const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") + .attr("x", 0) + .attr("y", 0) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "white") + .attr("stroke", "#e8e8e8"); + + if (mainXScale) { + const displayData = this.model.getDisplayData(); + + if (mainXScaleType === ScaleTypes.TIME) { + // Get all date values provided in data + // TODO - Could be re-used through the model + let allDates = []; + displayData.datasets.forEach(dataset => { + allDates = allDates.concat(dataset.data.map(datum => Number(datum.date))); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + + // Go through all date values + // And get corresponding data from each dataset + const stackDataArray = allDates.map((date) => { + let count = 0; + let correspondingSum = 0; + const correspondingData = {}; + + displayData.datasets.forEach(dataset => { + const correspondingDatum = dataset.data.find(datum => Number(datum.date) === Number(date)); + if (correspondingDatum) { + ++count; + correspondingSum += correspondingDatum.value; + } + }); + correspondingData["label"] = date; + correspondingData["value"] = correspondingSum; + + return correspondingData; + }); + + const xScale = mainXScale.copy(); + if (!this.ogXScale) { + this.ogXScale = xScale; + } + + const yScale = mainYScale.copy(); + + const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); + + xScale.range([0, +width]) + .domain(extent(stackDataArray, (d: any) => d.label)); + + yScale.range([0, height - 6]) + .domain(extent(stackDataArray, (d: any) => d.value)); + + const zoomDomain = this.model.get("zoomDomain"); + + // D3 line generator function + const lineGenerator = line() + .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .y((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)) + .curve(this.services.curves.getD3Curve()); + // .defined((d: any, i) => { + // if (zoomDomain) { + // const dTimestamp = +d.label; + + // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; + // } + + // return true; + // }); + + const lineGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-line") + .attr("stroke", "#8e8e8e") + .attr("stroke-width", 3) + .attr("fill", "none") + .datum(stackDataArray) + .transition(this.services.transitions.getTransition("zoom-pan-line-update", animate)) + .attr("d", lineGenerator); + + const areaGenerator = area() + .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .y0(height) + .y1((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)); + + const areaGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-area") + .attr("fill", "#e0e0e0") + .datum(stackDataArray) + .transition(this.services.transitions.getTransition("zoom-pan-area-update", animate)) + .attr("d", areaGenerator); + + const startHandlePosition = zoomDomain ? xScale(+zoomDomain[0]) : 0; + // Handle #1 + const startHandle = DOMUtils.appendOrSelect(container, "rect.zoom-handle.start") + .attr("x", startHandlePosition) + .attr("width", 5) + .attr("height", "100%") + .attr("fill", "#525252"); + + DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.start") + .attr("x", startHandlePosition + 2) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + const endHandlePosition = zoomDomain ? xScale(+zoomDomain[1]) : xScale.range()[1]; + // console.log("endHandlePosition", endHandlePosition) + + // Handle #2 + const handle2 = DOMUtils.appendOrSelect(container, "rect.zoom-handle.end") + .attr("x", endHandlePosition - 5) + .attr("width", 5) + .attr("height", "100%") + .attr("fill", "#525252"); + + DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.end") + .attr("x", endHandlePosition - 5 + 2) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + + const outboundRangeRight = DOMUtils.appendOrSelect(container, "rect.outbound-range.right") + .attr("x", endHandlePosition) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "#fff") + .attr("fill-opacity", 0.85); + + const self = this; + // handle2.on("click", this.zoomIn.bind(this)); + selectAll("rect.zoom-handle").call( + drag() + .on("start", function() { + select(this).classed("dragging", true); + }) + .on("drag", function(d) { + self.dragged(this, d, event); + }) + .on("end", function() { + select(this).classed("dragging", false); + }) + ); + } + } + } + + zoomIn() { + const mainXScale = this.services.cartesianScales.getMainXScale(); + console.log("zoom in", mainXScale.domain()); + } +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 4142972992..60daf43009 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -34,3 +34,4 @@ export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; +export * from "./axes/zoom-bar"; diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 67f15f296a..999d689680 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -267,11 +267,11 @@ export class ChartModel { return this.state.options; } - set(newState: any, skipUpdate = false) { + set(newState: any, configs?: any) { this.state = Object.assign({}, this.state, newState); - if (!skipUpdate) { - this.update(); + if (!configs || !configs.skipUpdate) { + this.update(configs ? configs.animate : true); } } @@ -298,7 +298,7 @@ export class ChartModel { * Updates miscellanous information within the model * such as the color scales, or the legend data labels */ - update() { + update(animate = true) { if (!this.getDisplayData()) { return; } @@ -306,7 +306,7 @@ export class ChartModel { this.updateAllDataGroups(); this.setColorScale(); - this.services.events.dispatchEvent(Events.Model.UPDATE); + this.services.events.dispatchEvent(Events.Model.UPDATE, { animate }); } setUpdateCallback(cb: Function) { diff --git a/packages/core/src/services/axis-zoom.ts b/packages/core/src/services/axis-zoom.ts new file mode 100644 index 0000000000..8dbb2b3d80 --- /dev/null +++ b/packages/core/src/services/axis-zoom.ts @@ -0,0 +1,60 @@ +// Internal Imports +import { Service } from "./service"; +import { Tools } from "../tools"; + +// D3 Imports +import { brushX } from "d3-brush"; +import { event } from "d3-selection"; + +export class AxisZoom extends Service { + brush: any; + + brushed(e) { + if (event) { + const selectedRange = event.selection; + + const mainXAxis = this.services.axes.getMainXAxis(); + const mainXAxisRangeStart = mainXAxis.scale.range()[0]; + + const newDomain = selectedRange.map(d => d + mainXAxisRangeStart) + .map(mainXAxis.scale.invert, mainXAxis.scale); + + this.model.set({ + zoomDomain: newDomain.map(d => new Date(+d)) + }); + } + } + + // We need a custom debounce function here + // because of the async nature of d3.event + debounceWithD3Event(func, wait) { + let timeout; + return function() { + const e = Object.assign({}, event); + const context = this; + const later = function() { + timeout = null; + func.apply(context, [e]); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } + + getZoomInstance() { + const mainXAxis = this.services.axes.getMainXAxis(); + const mainYAxis = this.services.axes.getMainYAxis(); + const xMaxRange = mainXAxis.scale.range()[1]; + const yMaxRange = mainYAxis.scale.range()[0]; + + this.brush = brushX() + .extent([ + [0, 0], + [xMaxRange, yMaxRange] + ]) + .on("end", this.brushed.bind(this)); + + + return this.brush; + } +} diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index d999a07207..4e9fcc0e17 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -4,5 +4,6 @@ export * from "./essentials/events"; export * from "./essentials/transitions"; // MISC +export * from "./axis-zoom"; export * from "./scales-cartesian"; export * from "./curves"; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 45a2bd142b..e75d6f14e7 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -382,6 +382,12 @@ export class CartesianScales extends Service { return map(displayData, (d) => d[mapsTo]).keys(); } + // If scale is a TIME scale and zoomDomain is available, return Date array as the domain + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && axisOptions && scaleType === ScaleTypes.TIME) { + return zoomDomain.map(d => new Date(d)); + } + // Get the extent of the domain let domain; let allDataValues; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss new file mode 100644 index 0000000000..02fcf7a55f --- /dev/null +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -0,0 +1,11 @@ +g.#{$prefix}--#{$charts-prefix}--zoom-bar { + rect.zoom-handle.dragging, + rect.zoom-handle:hover { + fill: black; + cursor: col-resize; + } + + rect.zoom-handle-bar { + pointer-events: none; + } +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index 3fff851a8b..0b96507fbe 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -10,3 +10,4 @@ @import "./meter-title"; @import "./tooltip"; @import "./threshold"; +@import "./zoom-bar"; diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index 5530eba1a0..bebafaffc3 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -6,3 +6,7 @@ @import "./scatter-stacked"; @import "./radar"; @import "./gauge"; + +svg.#{$prefix}--#{$charts-prefix}--chart-svg svg.graph-frame { + overflow-x: hidden; +} From 6cee2b22b9577b6c7caf0b9d63dcf88055975076 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 2 Jun 2020 16:33:50 +0800 Subject: [PATCH 070/510] fix: update ZoomBar to match new data format - fix trailing-comma - update scales-cartesian.getValueFromScale() - update line time-series 15 seconds demo data --- packages/core/demo/data/time-series-axis.ts | 12 +- packages/core/src/components/axes/zoom-bar.ts | 165 +++++++++++++----- .../core/src/services/scales-cartesian.ts | 165 ++++++++++-------- packages/core/tslint.json | 2 +- 4 files changed, 216 insertions(+), 128 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e3a3502c92..c1c7183571 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -8,12 +8,12 @@ export const lineTimeSeriesData15seconds = { label: "Dataset 1", data: [ { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 30), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 45), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 0), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 15), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 30), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 10 } + { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, + { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, + { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, + { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, + { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 }, ] } ] diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index dee5a24f55..f6ac815a7b 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -23,13 +23,13 @@ export class ZoomBar extends Component { if (startingHandle) { domain = [ this.ogXScale.invert(e.x), - this.ogXScale.domain()[1] + this.ogXScale.domain()[1], // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } else { domain = [ this.ogXScale.domain()[0], - this.ogXScale.invert(e.x) + this.ogXScale.invert(e.x), // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } @@ -44,8 +44,12 @@ export class ZoomBar extends Component { const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition(mainXAxisPosition); - const mainYScaleType = cartesianScales.getScaleTypeByPosition(mainYAxisPosition); + const mainXScaleType = cartesianScales.getScaleTypeByPosition( + mainXAxisPosition + ); + const mainYScaleType = cartesianScales.getScaleTypeByPosition( + mainYAxisPosition + ); const height = 32; const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") @@ -77,8 +81,8 @@ export class ZoomBar extends Component { // Get all date values provided in data // TODO - Could be re-used through the model let allDates = []; - displayData.datasets.forEach(dataset => { - allDates = allDates.concat(dataset.data.map(datum => Number(datum.date))); + displayData.forEach((data) => { + allDates = allDates.concat(Number(data.date)); }); allDates = Tools.removeArrayDuplicates(allDates).sort(); @@ -89,14 +93,13 @@ export class ZoomBar extends Component { let correspondingSum = 0; const correspondingData = {}; - displayData.datasets.forEach(dataset => { - const correspondingDatum = dataset.data.find(datum => Number(datum.date) === Number(date)); - if (correspondingDatum) { + displayData.forEach((data) => { + if (Number(data.date) === Number(date)) { ++count; - correspondingSum += correspondingDatum.value; + correspondingSum += data.value; } }); - correspondingData["label"] = date; + correspondingData["date"] = date; correspondingData["value"] = correspondingSum; return correspondingData; @@ -109,53 +112,113 @@ export class ZoomBar extends Component { const yScale = mainYScale.copy(); - const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true, + }); - xScale.range([0, +width]) - .domain(extent(stackDataArray, (d: any) => d.label)); + xScale + .range([0, width]) + .domain(extent(stackDataArray, (d: any) => d.date)); - yScale.range([0, height - 6]) + yScale + .range([0, height - 6]) .domain(extent(stackDataArray, (d: any) => d.value)); const zoomDomain = this.model.get("zoomDomain"); // D3 line generator function const lineGenerator = line() - .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) - .y((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)) + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) + .y( + (d, i) => + height - + cartesianScales.getValueFromScale( + yScale, + mainYScaleType, + mainYAxisPosition, + d, + i + ) + ) .curve(this.services.curves.getD3Curve()); - // .defined((d: any, i) => { - // if (zoomDomain) { - // const dTimestamp = +d.label; - - // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; - // } - - // return true; - // }); - - const lineGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-line") + // .defined((d: any, i) => { + // if (zoomDomain) { + // const dTimestamp = +d.label; + + // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; + // } + + // return true; + // }); + const lineGraph = DOMUtils.appendOrSelect( + container, + "path.zoom-graph-line" + ) .attr("stroke", "#8e8e8e") .attr("stroke-width", 3) .attr("fill", "none") .datum(stackDataArray) - .transition(this.services.transitions.getTransition("zoom-pan-line-update", animate)) + .transition( + this.services.transitions.getTransition( + "zoom-pan-line-update", + animate + ) + ) .attr("d", lineGenerator); const areaGenerator = area() - .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) .y0(height) - .y1((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)); - - const areaGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-area") + .y1( + (d, i) => + height - + cartesianScales.getValueFromScale( + yScale, + mainYScaleType, + mainYAxisPosition, + d, + i + ) + ); + + const areaGraph = DOMUtils.appendOrSelect( + container, + "path.zoom-graph-area" + ) .attr("fill", "#e0e0e0") .datum(stackDataArray) - .transition(this.services.transitions.getTransition("zoom-pan-area-update", animate)) + .transition( + this.services.transitions.getTransition( + "zoom-pan-area-update", + animate + ) + ) .attr("d", areaGenerator); - const startHandlePosition = zoomDomain ? xScale(+zoomDomain[0]) : 0; + const startHandlePosition = zoomDomain + ? xScale(+zoomDomain[0]) + : 0; // Handle #1 - const startHandle = DOMUtils.appendOrSelect(container, "rect.zoom-handle.start") + const startHandle = DOMUtils.appendOrSelect( + container, + "rect.zoom-handle.start" + ) .attr("x", startHandlePosition) .attr("width", 5) .attr("height", "100%") @@ -167,11 +230,16 @@ export class ZoomBar extends Component { .attr("width", 1) .attr("height", 12) .attr("fill", "#fff"); - const endHandlePosition = zoomDomain ? xScale(+zoomDomain[1]) : xScale.range()[1]; + const endHandlePosition = zoomDomain + ? xScale(+zoomDomain[1]) + : xScale.range()[1]; // console.log("endHandlePosition", endHandlePosition) // Handle #2 - const handle2 = DOMUtils.appendOrSelect(container, "rect.zoom-handle.end") + const endHandle = DOMUtils.appendOrSelect( + container, + "rect.zoom-handle.end" + ) .attr("x", endHandlePosition - 5) .attr("width", 5) .attr("height", "100%") @@ -184,24 +252,27 @@ export class ZoomBar extends Component { .attr("height", 12) .attr("fill", "#fff"); - const outboundRangeRight = DOMUtils.appendOrSelect(container, "rect.outbound-range.right") - .attr("x", endHandlePosition) - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "#fff") - .attr("fill-opacity", 0.85); + const outboundRangeRight = DOMUtils.appendOrSelect( + container, + "rect.outbound-range.right" + ) + .attr("x", endHandlePosition) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "#fff") + .attr("fill-opacity", 0.85); const self = this; // handle2.on("click", this.zoomIn.bind(this)); selectAll("rect.zoom-handle").call( drag() - .on("start", function() { + .on("start", function () { select(this).classed("dragging", true); }) - .on("drag", function(d) { + .on("drag", function (d) { self.dragged(this, d, event); }) - .on("end", function() { + .on("end", function () { select(this).classed("dragging", false); }) ); diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index e75d6f14e7..162b0c833f 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -181,33 +181,50 @@ export class CartesianScales extends Service { return this.scales[this.getMainYAxisPosition()]; } - getValueFromScale(axisPosition: AxisPositions, datum: any, index?: number) { + getValueFromScale(scale: any, scaleType: ScaleTypes, axisPosition: AxisPositions, datum: any, index?: number) { const options = this.model.getOptions(); - const axisOptions = Tools.getProperty(options, "axes", axisPosition); - - const scaleType = this.scaleTypes[axisPosition]; - const scale = this.scales[axisPosition]; - + const axesOptions = Tools.getProperty(options, "axes"); + const axisOptions = axesOptions[axisPosition]; const { mapsTo } = axisOptions; const value = datum[mapsTo] !== undefined ? datum[mapsTo] : datum; - - if (scaleType === ScaleTypes.LABELS) { - return scale(value) + scale.step() / 2; + let scaledValue; + switch (scaleType) { + case ScaleTypes.LABELS: + scaledValue = scale(value) + scale.step() / 2; + break; + case ScaleTypes.TIME: + scaledValue = scale(new Date(value)); + break; + default: + scaledValue = scale(value); } + return scaledValue; + } - if (scaleType === ScaleTypes.TIME) { - return scale(new Date(value)); - } + getValueThroughAxisPosition(axisPosition: AxisPositions, datum: any, index?: number) { + const scaleType = this.scaleTypes[axisPosition]; + const scale = this.scales[axisPosition]; - return scale(value); + return this.getValueFromScale(scale, scaleType, axisPosition, datum, index); } + getDomainValue(d, i) { - return this.getValueFromScale(this.domainAxisPosition, d, i); + return this.getValueThroughAxisPosition(this.domainAxisPosition, d, i); } getRangeValue(d, i) { - return this.getValueFromScale(this.rangeAxisPosition, d, i); + return this.getValueThroughAxisPosition(this.rangeAxisPosition, d, i); + } + + getXValue(d, i) { + const mainXAxisPosition = this.getMainXAxisPosition(); + return this.getValueThroughAxisPosition(mainXAxisPosition, d, i); + } + + getYValue(d, i) { + const mainYAxisPosition = this.getMainYAxisPosition(); + return this.getValueThroughAxisPosition(mainYAxisPosition, d, i); } getDomainIdentifier() { @@ -271,6 +288,65 @@ export class CartesianScales extends Service { } } + getHighestDomainThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const domainAxisPosition = this.getDomainAxisPosition(); + + const { thresholds } = axesOptions[domainAxisPosition]; + + if (!thresholds) { + return null; + } + + const domainScale = this.getDomainScale(); + // Find the highest threshold for the domain + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value, + )[0]; + + const scaleType = this.getScaleTypeByPosition(domainAxisPosition); + if ( + scaleType === ScaleTypes.TIME && + (typeof highestThreshold.value === "string" || + highestThreshold.value.getTime === undefined) + ) { + highestThreshold.value = new Date(highestThreshold.value); + } + + return { + threshold: highestThreshold, + scaleValue: domainScale(highestThreshold.value), + }; + } + + getHighestRangeThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const rangeAxisPosition = this.getRangeAxisPosition(); + + const { thresholds } = axesOptions[rangeAxisPosition]; + + if (!thresholds) { + return null; + } + + const rangeScale = this.getRangeScale(); + // Find the highest threshold for the range + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value, + )[0]; + + return { + threshold: highestThreshold, + scaleValue: rangeScale(highestThreshold.value), + }; + } + protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); @@ -438,65 +514,6 @@ export class CartesianScales extends Service { return scale; } - - getHighestDomainThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const domainAxisPosition = this.getDomainAxisPosition(); - - const { thresholds } = axesOptions[domainAxisPosition]; - - if (!thresholds) { - return null; - } - - const domainScale = this.getDomainScale(); - // Find the highest threshold for the domain - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - const scaleType = this.getScaleTypeByPosition(domainAxisPosition); - if ( - scaleType === ScaleTypes.TIME && - (typeof highestThreshold.value === "string" || - highestThreshold.value.getTime === undefined) - ) { - highestThreshold.value = new Date(highestThreshold.value); - } - - return { - threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value) - }; - } - - getHighestRangeThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const rangeAxisPosition = this.getRangeAxisPosition(); - - const { thresholds } = axesOptions[rangeAxisPosition]; - - if (!thresholds) { - return null; - } - - const rangeScale = this.getRangeScale(); - // Find the highest threshold for the range - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - return { - threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value) - }; - } } function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { diff --git a/packages/core/tslint.json b/packages/core/tslint.json index a5a0970b82..91271d283a 100644 --- a/packages/core/tslint.json +++ b/packages/core/tslint.json @@ -92,7 +92,7 @@ "trailing-comma": [ true, { - "multiline": "never", + "multiline": "always", "singleline": "never" } ] From b3ac50f1ba7fed55869e04eff4976ae1bbc6b1fb Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 3 Jun 2020 21:56:58 +0800 Subject: [PATCH 071/510] feat: use d3-brush to implement selection --- packages/core/src/components/axes/zoom-bar.ts | 184 +++++++++--------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index f6ac815a7b..8066d0aa08 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -5,20 +5,30 @@ import { ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports -import { area, line } from "d3-shape"; import { extent } from "d3-array"; +import { brushX } from "d3-brush"; import { drag } from "d3-drag"; -import { event, select, selectAll } from "d3-selection"; +import { area, line } from "d3-shape"; +import { event, select, selectAll, BaseType } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + height = 32; + ogXScale: any; dragged = Tools.debounce((element, d, e) => { + console.log("dragged"); + console.log(this.ogXScale.invert(100)); + console.log(d); + console.log(e); element = select(element); const startingHandle = element.attr("class").indexOf("start") !== -1; - + console.log("startingHandle?:" + startingHandle); + const oldDomain = this.model.get("zoomDomain"); + console.log("old domain"); + console.log(oldDomain); let domain; if (startingHandle) { domain = [ @@ -33,9 +43,13 @@ export class ZoomBar extends Component { // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } + console.log("new domain"); + console.log(domain); this.model.set({ zoomDomain: domain }, { animate: false }); }, 2.5); + + render(animate = true) { const svg = this.getContainerSVG(); @@ -45,19 +59,69 @@ export class ZoomBar extends Component { const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition + mainXAxisPosition, ); const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition + mainYAxisPosition, ); - const height = 32; + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") // .attr("transform", "translateX(10)") .attr("width", "100%") - .attr("height", height) + .attr("height", this.height) .attr("opacity", 1); + const brushHandle = (g, selection) => { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{type: "w"}, {type: "e"}]) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32) + .attr("fill", "#525252"); + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{type: "w"}, {type: "e"}]) + .join("rect") + .attr("class", function(d) { return "handle-bar handle-bar--" + d.type; }) + .attr("x", function(d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + + }; + + const brushed = () => { + const selection = event.selection; + if (selection === null) { + // do nothing + console.log("selection === null"); + } else { + // TODO - pass selection to callback function or update scaleDomain + } + // update brush handle position + select(svg).call(brushHandle, selection); + }; + + const brush = brushX() + .extent([[0, 0], [700, this.height]]) + .on("start brush end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") + .call(brush) + .call(brush.move, mainXScale.range()); // TODO -- mainXScale.range() incorrect + const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) .attr("y", 32) @@ -105,11 +169,13 @@ export class ZoomBar extends Component { return correspondingData; }); + // if (!this.ogXScale) { + // this.ogXScale = cartesianScales.getDomainScale(); + // } const xScale = mainXScale.copy(); if (!this.ogXScale) { this.ogXScale = xScale; } - const yScale = mainYScale.copy(); const { width } = DOMUtils.getSVGElementSize(this.parent, { @@ -121,7 +187,7 @@ export class ZoomBar extends Component { .domain(extent(stackDataArray, (d: any) => d.date)); yScale - .range([0, height - 6]) + .range([0, this.height - 6]) .domain(extent(stackDataArray, (d: any) => d.value)); const zoomDomain = this.model.get("zoomDomain"); @@ -134,19 +200,19 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i - ) + i, + ), ) .y( (d, i) => - height - + this.height - cartesianScales.getValueFromScale( yScale, mainYScaleType, mainYAxisPosition, d, - i - ) + i, + ), ) .curve(this.services.curves.getD3Curve()); // .defined((d: any, i) => { @@ -160,7 +226,7 @@ export class ZoomBar extends Component { // }); const lineGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-line" + "path.zoom-graph-line", ) .attr("stroke", "#8e8e8e") .attr("stroke-width", 3) @@ -169,8 +235,8 @@ export class ZoomBar extends Component { .transition( this.services.transitions.getTransition( "zoom-pan-line-update", - animate - ) + animate, + ), ) .attr("d", lineGenerator); @@ -181,101 +247,36 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i - ) + i, + ), ) - .y0(height) + .y0(this.height) .y1( (d, i) => - height - + this.height - cartesianScales.getValueFromScale( yScale, mainYScaleType, mainYAxisPosition, d, - i - ) + i, + ), ); const areaGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-area" + "path.zoom-graph-area", ) .attr("fill", "#e0e0e0") .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-area-update", - animate - ) + animate, + ), ) .attr("d", areaGenerator); - const startHandlePosition = zoomDomain - ? xScale(+zoomDomain[0]) - : 0; - // Handle #1 - const startHandle = DOMUtils.appendOrSelect( - container, - "rect.zoom-handle.start" - ) - .attr("x", startHandlePosition) - .attr("width", 5) - .attr("height", "100%") - .attr("fill", "#525252"); - - DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.start") - .attr("x", startHandlePosition + 2) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - const endHandlePosition = zoomDomain - ? xScale(+zoomDomain[1]) - : xScale.range()[1]; - // console.log("endHandlePosition", endHandlePosition) - - // Handle #2 - const endHandle = DOMUtils.appendOrSelect( - container, - "rect.zoom-handle.end" - ) - .attr("x", endHandlePosition - 5) - .attr("width", 5) - .attr("height", "100%") - .attr("fill", "#525252"); - - DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.end") - .attr("x", endHandlePosition - 5 + 2) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - - const outboundRangeRight = DOMUtils.appendOrSelect( - container, - "rect.outbound-range.right" - ) - .attr("x", endHandlePosition) - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "#fff") - .attr("fill-opacity", 0.85); - - const self = this; - // handle2.on("click", this.zoomIn.bind(this)); - selectAll("rect.zoom-handle").call( - drag() - .on("start", function () { - select(this).classed("dragging", true); - }) - .on("drag", function (d) { - self.dragged(this, d, event); - }) - .on("end", function () { - select(this).classed("dragging", false); - }) - ); } } } @@ -284,4 +285,5 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } + } From d63af6938770bc6dc795ecfcd88e1730ea7b13f9 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 8 Jun 2020 16:04:58 +0800 Subject: [PATCH 072/510] fix: add left margin to align to chart - using .scss to set fill and stroke color --- packages/core/src/components/axes/zoom-bar.ts | 162 +++++++----------- .../core/src/styles/components/_zoom-bar.scss | 26 ++- 2 files changed, 86 insertions(+), 102 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 8066d0aa08..b200d522b2 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -7,9 +7,8 @@ import { DOMUtils } from "../../services"; // D3 Imports import { extent } from "d3-array"; import { brushX } from "d3-brush"; -import { drag } from "d3-drag"; import { area, line } from "d3-shape"; -import { event, select, selectAll, BaseType } from "d3-selection"; +import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; @@ -18,39 +17,9 @@ export class ZoomBar extends Component { ogXScale: any; - dragged = Tools.debounce((element, d, e) => { - console.log("dragged"); - console.log(this.ogXScale.invert(100)); - console.log(d); - console.log(e); - element = select(element); - const startingHandle = element.attr("class").indexOf("start") !== -1; - console.log("startingHandle?:" + startingHandle); - const oldDomain = this.model.get("zoomDomain"); - console.log("old domain"); - console.log(oldDomain); - let domain; - if (startingHandle) { - domain = [ - this.ogXScale.invert(e.x), - this.ogXScale.domain()[1], - // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) - ]; - } else { - domain = [ - this.ogXScale.domain()[0], - this.ogXScale.invert(e.x), - // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) - ]; - } - console.log("new domain"); - console.log(domain); - this.model.set({ zoomDomain: domain }, { animate: false }); - }, 2.5); - - - render(animate = true) { + // TODO - get correct axis left width + const axisLeftWidth = 23; const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -65,63 +34,11 @@ export class ZoomBar extends Component { mainYAxisPosition, ); - const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") - // .attr("transform", "translateX(10)") .attr("width", "100%") .attr("height", this.height) .attr("opacity", 1); - const brushHandle = (g, selection) => { - const handleSize = 5; - // handle - svg.select("g.brush") - .selectAll("rect.handle") - .data([{type: "w"}, {type: "e"}]) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) - .attr("fill", "#525252"); - // handle-bar - svg.select("g.brush") - .selectAll("rect.handle-bar") - .data([{type: "w"}, {type: "e"}]) - .join("rect") - .attr("class", function(d) { return "handle-bar handle-bar--" + d.type; }) - .attr("x", function(d) { - if (d.type === "w") { - return selection[0] - 1; - } else if (d.type === "e") { - return selection[1] - 1; - } - }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - - }; - - const brushed = () => { - const selection = event.selection; - if (selection === null) { - // do nothing - console.log("selection === null"); - } else { - // TODO - pass selection to callback function or update scaleDomain - } - // update brush handle position - select(svg).call(brushHandle, selection); - }; - - const brush = brushX() - .extent([[0, 0], [700, this.height]]) - .on("start brush end", brushed); - - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") - .call(brush) - .call(brush.move, mainXScale.range()); // TODO -- mainXScale.range() incorrect - const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) .attr("y", 32) @@ -131,7 +48,7 @@ export class ZoomBar extends Component { .attr("fill", "none"); const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") - .attr("x", 0) + .attr("x", axisLeftWidth) .attr("y", 0) .attr("width", "100%") .attr("height", "100%") @@ -169,9 +86,9 @@ export class ZoomBar extends Component { return correspondingData; }); - // if (!this.ogXScale) { - // this.ogXScale = cartesianScales.getDomainScale(); - // } + if (!this.ogXScale) { + this.ogXScale = cartesianScales.getDomainScale(); + } const xScale = mainXScale.copy(); if (!this.ogXScale) { this.ogXScale = xScale; @@ -183,7 +100,7 @@ export class ZoomBar extends Component { }); xScale - .range([0, width]) + .range([axisLeftWidth, width]) .domain(extent(stackDataArray, (d: any) => d.date)); yScale @@ -228,9 +145,6 @@ export class ZoomBar extends Component { container, "path.zoom-graph-line", ) - .attr("stroke", "#8e8e8e") - .attr("stroke-width", 3) - .attr("fill", "none") .datum(stackDataArray) .transition( this.services.transitions.getTransition( @@ -267,7 +181,6 @@ export class ZoomBar extends Component { container, "path.zoom-graph-area", ) - .attr("fill", "#e0e0e0") .datum(stackDataArray) .transition( this.services.transitions.getTransition( @@ -277,6 +190,64 @@ export class ZoomBar extends Component { ) .attr("d", areaGenerator); + const updateBrushHandle = (g, selection) => { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{ type: "w" }, { type: "e" }]) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32); + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{ type: "w" }, { type: "e" }]) + .join("rect") + .attr("class", function (d) { + return "handle-bar handle-bar--" + d.type; + }) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12); + }; + + const brushed = () => { + const selection = event.selection; + if (selection === null) { + // do nothing + console.log("selection === null"); + } else { + // TODO - pass selection to callback function or update scaleDomain + } + // update brush handle position + select(svg).call(updateBrushHandle, selection); + + const domain = [ + mainXScale.invert(selection[0]), + mainXScale.invert(selection[1]), + ]; + // TODO -- update zoomDomain in model + // this.model.set({zoomDomain: domain}, {animate: false}); + }; + + const brush = brushX() + .extent([ + [0 + axisLeftWidth, 0], + [700, this.height], + ]) + .on("start brush end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") + .call(brush) + .call(brush.move, xScale.range()); } } } @@ -285,5 +256,4 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } - } diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 02fcf7a55f..59019956a8 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -1,11 +1,25 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { - rect.zoom-handle.dragging, - rect.zoom-handle:hover { - fill: black; - cursor: col-resize; + rect.zoom-bg { + fill: $ui-background; + stroke: $ui-01; } - rect.zoom-handle-bar { - pointer-events: none; + path.zoom-graph-line { + stroke: $ui-04; + stroke-width: 3; + fill: none; + } + + path.zoom-graph-area { + fill: $ui-03; + stroke: $ui-04; + } + + g.brush rect.handle { + fill: $icon-02; + } + + g.brush rect.handle-bar { + fill: $ui-02; } } From 147b2591dabb0099420f6bb54fe741321b60ac7f Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 16:03:16 +0800 Subject: [PATCH 073/510] feat: set brush selection to zoomDomain in Model - use flag to avoid recursive update event --- packages/core/src/components/axes/zoom-bar.ts | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index b200d522b2..12b7b356be 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,7 +17,14 @@ export class ZoomBar extends Component { ogXScale: any; + // use this flag to avoid recursive update events + skipNextUpdate = false; + render(animate = true) { + if (this.skipNextUpdate === true) { + this.skipNextUpdate = false; + return; + } // TODO - get correct axis left width const axisLeftWidth = 23; const svg = this.getContainerSVG(); @@ -28,10 +35,10 @@ export class ZoomBar extends Component { const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition, + mainXAxisPosition ); const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition, + mainYAxisPosition ); const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") @@ -96,7 +103,7 @@ export class ZoomBar extends Component { const yScale = mainYScale.copy(); const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true, + useAttrs: true }); xScale @@ -117,8 +124,8 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i, - ), + i + ) ) .y( (d, i) => @@ -128,8 +135,8 @@ export class ZoomBar extends Component { mainYScaleType, mainYAxisPosition, d, - i, - ), + i + ) ) .curve(this.services.curves.getD3Curve()); // .defined((d: any, i) => { @@ -143,14 +150,14 @@ export class ZoomBar extends Component { // }); const lineGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-line", + "path.zoom-graph-line" ) .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-line-update", - animate, - ), + animate + ) ) .attr("d", lineGenerator); @@ -161,8 +168,8 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i, - ), + i + ) ) .y0(this.height) .y1( @@ -173,20 +180,20 @@ export class ZoomBar extends Component { mainYScaleType, mainYAxisPosition, d, - i, - ), + i + ) ); const areaGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-area", + "path.zoom-graph-area" ) .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-area-update", - animate, - ), + animate + ) ) .attr("d", areaGenerator); @@ -196,9 +203,17 @@ export class ZoomBar extends Component { svg.select("g.brush") .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 3; + } else if (d.type === "e") { + return selection[1] - 3; + } + }) .attr("y", 0) .attr("width", handleSize) - .attr("height", 32); + .attr("height", 32) + .style("display", null); // always display // handle-bar svg.select("g.brush") .selectAll("rect.handle-bar") @@ -220,10 +235,10 @@ export class ZoomBar extends Component { }; const brushed = () => { - const selection = event.selection; + let selection = event.selection; if (selection === null) { - // do nothing - console.log("selection === null"); + // set to default full range + selection = xScale.range(); } else { // TODO - pass selection to callback function or update scaleDomain } @@ -231,23 +246,29 @@ export class ZoomBar extends Component { select(svg).call(updateBrushHandle, selection); const domain = [ - mainXScale.invert(selection[0]), - mainXScale.invert(selection[1]), + xScale.invert(selection[0]), + xScale.invert(selection[1]) ]; - // TODO -- update zoomDomain in model - // this.model.set({zoomDomain: domain}, {animate: false}); + // only if the brush event comes from mouseup event + if (event.sourceEvent != null && event.type === "end") { + this.skipNextUpdate = true; // avoid recursive update + this.model.set( + { zoomDomain: domain }, + { animate: false } + ); + } }; const brush = brushX() .extent([ [0 + axisLeftWidth, 0], - [700, this.height], + [700, this.height] ]) .on("start brush end", brushed); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") .call(brush) - .call(brush.move, xScale.range()); + .call(brush.move, xScale.range()); // default to full range } } } From 58c0c2b7818992d79f321625fe9c7e59b5f15937 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:42:26 +0800 Subject: [PATCH 074/510] feat: create ZoomBarOptions and default configuration - default to disable zoombar --- packages/core/src/axis-chart.ts | 9 ++++----- packages/core/src/configuration.ts | 13 +++++++++++-- packages/core/src/interfaces/charts.ts | 7 ++++++- packages/core/src/interfaces/components.ts | 10 ++++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index e4d105905f..04f9bdeeb5 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -126,9 +126,7 @@ export class AxisChart extends Chart { const zoomBarComponent = { id: "zoom-bar", - components: [ - new ZoomBar(this.model, this.services) - ], + components: [new ZoomBar(this.model, this.services)], growth: { x: LayoutGrowth.PREFERRED, y: LayoutGrowth.FIXED @@ -151,8 +149,9 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } - - topLevelLayoutComponents.push(zoomBarComponent); + if (this.model.getOptions().zoomBar.enabled === true) { + topLevelLayoutComponents.push(zoomBarComponent); + } topLevelLayoutComponents.push(fullFrameComponent); return [ diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index a7bb324344..1ae340990e 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -22,7 +22,8 @@ import { TruncationTypes, StackedBarOptions, MeterChartOptions, - GaugeTypes + GaugeTypes, + ZoomBarOptions } from "./interfaces"; import enUSLocaleObject from "date-fns/locale/en-US/index"; @@ -125,6 +126,13 @@ export const timeScale: TimeScaleOptions = { } }; +/** + * ZoomBar options + */ +export const zoomBar: ZoomBarOptions = { + enabled: false +}; + /** * Base chart options common to any chart */ @@ -153,7 +161,8 @@ const chart: BaseChartOptions = { const axisChart: AxisChartOptions = Tools.merge({}, chart, { axes, timeScale, - grid + grid, + zoomBar } as AxisChartOptions); /** diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 69d901e0e8..5f4587b7d9 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -3,7 +3,8 @@ import { LegendOptions, TooltipOptions, GridOptions, - AxesOptions + AxesOptions, + ZoomBarOptions } from "./index"; import { BarOptions, StackedBarOptions } from "./components"; import { TimeScaleOptions } from "./axis-scales"; @@ -40,6 +41,10 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index b80332d5c4..d83a5e464c 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -111,3 +111,13 @@ export interface BarOptions { export interface StackedBarOptions extends BarOptions { dividerSize?: number; } + +/** + * customize the ZoomBar component + */ +export interface ZoomBarOptions { + /** + * is the zoom-bar visible or not + */ + enabled?: boolean; +} From 7e2a269acfa60a49ec562a26389724d863a7d78a Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:44:58 +0800 Subject: [PATCH 075/510] feat: create zoombar story in storybook --- packages/core/demo/data/time-series-axis.ts | 34 ++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index c1c7183571..e9519866dc 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -13,7 +13,7 @@ export const lineTimeSeriesData15seconds = { { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } ] } ] @@ -418,3 +418,35 @@ export const lineTimeSeriesAllLabelsInPrimaryFormatOptions = { addSpaceOnEdges: 0 } }; + +// ZoomBar +export const lineTimeSeriesDataZoomBar = { + labels: ["Qty"], + datasets: [ + { + label: "Dataset 1", + data: [ + { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, + { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, + { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, + { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, + { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, + { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } + ] + } + ] +}; + +export const lineTimeSeriesZoomBarOptions = { + title: "Line (time series) - zoom-bar enabled", + axes: { + left: {}, + bottom: { + scaleType: "time" + } + }, + zoomBar: { + enabled: true + } +}; From 9c09bae1825049580eb0668a5a4146004d1e4327 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 11:17:12 +0800 Subject: [PATCH 076/510] fix: set tslint trailing-comma to never --- packages/core/tslint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/tslint.json b/packages/core/tslint.json index 91271d283a..a5a0970b82 100644 --- a/packages/core/tslint.json +++ b/packages/core/tslint.json @@ -92,7 +92,7 @@ "trailing-comma": [ true, { - "multiline": "always", + "multiline": "never", "singleline": "never" } ] From f8fd345b9f7e4b88e40ef2e0242e3b4aff611d71 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 12:23:10 +0800 Subject: [PATCH 077/510] fix: allow zoomDomain to update for every brush event --- packages/core/src/components/axes/zoom-bar.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 12b7b356be..37c68ab870 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,14 +17,7 @@ export class ZoomBar extends Component { ogXScale: any; - // use this flag to avoid recursive update events - skipNextUpdate = false; - render(animate = true) { - if (this.skipNextUpdate === true) { - this.skipNextUpdate = false; - return; - } // TODO - get correct axis left width const axisLeftWidth = 23; const svg = this.getContainerSVG(); @@ -245,17 +238,24 @@ export class ZoomBar extends Component { // update brush handle position select(svg).call(updateBrushHandle, selection); - const domain = [ + const newDomain = [ xScale.invert(selection[0]), xScale.invert(selection[1]) ]; + // only if the brush event comes from mouseup event - if (event.sourceEvent != null && event.type === "end") { - this.skipNextUpdate = true; // avoid recursive update - this.model.set( - { zoomDomain: domain }, - { animate: false } - ); + if (event.sourceEvent != null) { + // only if zoomDomain is never set or needs update + if ( + zoomDomain === undefined || + zoomDomain[0] !== newDomain[0] || + zoomDomain[1] !== newDomain[1] + ) { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + } } }; @@ -266,9 +266,12 @@ export class ZoomBar extends Component { ]) .on("start brush end", brushed); - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") - .call(brush) - .call(brush.move, xScale.range()); // default to full range + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( + brush + ); + if (zoomDomain === undefined) { + brushArea.call(brush.move, xScale.range()); // default to full range + } } } } From d7d8736b6b629f80ccf5d7aa89c87d51fcb879ee Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 23:06:38 +0800 Subject: [PATCH 078/510] feat: provide ZoomBar options for brush event callback - selected x range and domain as callback parameters --- packages/core/demo/data/time-series-axis.ts | 20 +++++++++++++++- packages/core/src/components/axes/zoom-bar.ts | 23 +++++++++++++++++++ packages/core/src/configuration.ts | 5 +++- packages/core/src/interfaces/components.ts | 12 ++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e9519866dc..e79de136b6 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -437,6 +437,21 @@ export const lineTimeSeriesDataZoomBar = { } ] }; +const selectionStartFun = (selection, domain) => { + console.log("ZoomBar SelectionStart callback!"); + console.log(selection); + console.log(domain); +}; +const selectionInProgressFun = (selection, domain) => { + console.log("ZoomBar SelectionInProgress callback!"); + console.log(selection); + console.log(domain); +}; +const selectionEndFun = (selection, domain) => { + console.log("ZoomBar SelectionEnd callback!"); + console.log(selection); + console.log(domain); +}; export const lineTimeSeriesZoomBarOptions = { title: "Line (time series) - zoom-bar enabled", @@ -447,6 +462,9 @@ export const lineTimeSeriesZoomBarOptions = { } }, zoomBar: { - enabled: true + enabled: true, + selectionStart: selectionStartFun, + selectionInProgress: selectionInProgressFun, + selectionEnd: selectionEndFun } }; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 37c68ab870..4fdfa2fec5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -256,6 +256,29 @@ export class ZoomBar extends Component { { animate: false } ); } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress( + selection, + newDomain + ); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } } }; diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 1ae340990e..b7e8e301d8 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -130,7 +130,10 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false + enabled: false, + selectionStart: undefined, + selectionInProgress: undefined, + selectionEnd: undefined }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index d83a5e464c..077fb63bf7 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -120,4 +120,16 @@ export interface ZoomBarOptions { * is the zoom-bar visible or not */ enabled?: boolean; + /** + * a function to handle selection start event + */ + selectionStart?: Function; + /** + * a function to handle selection in progress event + */ + selectionInProgress?: Function; + /** + * a function to handle selection end event + */ + selectionEnd?: Function; } From 404b8f94911b36ff57edd0f1022b37cbd0e0a3ed Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Mon, 15 Jun 2020 18:46:54 +0800 Subject: [PATCH 079/510] feat: apply clipPath to line chart - v1 --- packages/core/src/charts/line.ts | 2 + packages/core/src/components/axes/cover.ts | 64 +++++++++++++++++++ packages/core/src/components/component.ts | 25 ++++++-- packages/core/src/components/graphs/line.ts | 3 +- packages/core/src/components/index.ts | 1 + .../core/src/services/essentials/dom-utils.ts | 30 +++++++++ 6 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/components/axes/cover.ts diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index 469bf7a28e..d766c7ad5a 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -11,6 +11,7 @@ import { Line, Ruler, Scatter, + Cover, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) Tooltip, @@ -40,6 +41,7 @@ export class LineChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts new file mode 100644 index 0000000000..20b4083e09 --- /dev/null +++ b/packages/core/src/components/axes/cover.ts @@ -0,0 +1,64 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { axisBottom, axisLeft } from "d3-axis"; +import { mouse, select } from "d3-selection"; +import { TooltipTypes, Events } from "../../interfaces"; + +export class Cover extends Component { + type = "cover"; + + coverClipPath: any; + + render(animate = true) { + // Create the cover + this.createCover(); + } + + + createCover() { + const svg = this.parent; + console.log("!!! cover svg: ", svg); + const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainYScale = this.services.cartesianScales.getMainYScale(); + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + // Get height + this.coverClipPath = DOMUtils.appendOrSelect(svg, `clipPath.${this.type}`); + this.coverClipPath + .attr("id", `${this.type}Clip`); + const coverRect = DOMUtils.appendOrSelect( + this.coverClipPath, + "rect.cover" + ); + coverRect + .attr("x", xScaleStart) + .attr("y", yScaleStart) + .attr("width", xScaleEnd - xScaleStart) + .attr("height", yScaleEnd - yScaleStart); + + this.coverClipPath + .merge(coverRect) + .lower(); + + const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); + coverG + .attr("clip-path", `url(#${this.type}Clip)`) + .attr("id", `g-${this.type}Clip`); + + } + + cleanCover(g) { + const options = this.model.getOptions(); + g.selectAll("line").attr("stroke", options.grid.strokeColor); + + // Remove extra elements + g.selectAll("text").remove(); + g.select(".domain").remove(); + } +} diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 7530628ac1..2b1d8d84d0 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -90,10 +90,27 @@ export class Component { "style", "prefix" ); - return DOMUtils.appendOrSelect( - this.parent, - `g.${settings.prefix}--${chartprefix}--${this.type}` - ); + if (this.type === "line" || this.type === "scatter") { + const { width, height } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + return DOMUtils.appendOrSelectForAxisChart( + this.parent, + `clipPath.cover`, + this.type, + 23, + 0, + (width - 23), + height, + ); + + } else { + return DOMUtils.appendOrSelect( + this.parent, + `g.${settings.prefix}--${chartprefix}--${this.type}` + ); + } + } return this.parent; diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 2d2c5c36c7..2be9c949a9 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -131,6 +131,7 @@ export class Line extends Component { this.parent .selectAll("path.line") + .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-hover-line") ) @@ -145,7 +146,7 @@ export class Line extends Component { handleLegendMouseOut = (event: CustomEvent) => { this.parent - .selectAll("path.line") + .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-mouseout-line") ) diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 60daf43009..8be94d9e0e 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -35,3 +35,4 @@ export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; +export * from "./axes/cover"; diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index 36a080d616..2d938bbeca 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -149,6 +149,36 @@ export class DOMUtils extends Service { return selection; } + static appendOrSelectForAxisChart(parent, query, type, x, y, width, height) { + + const querySections = query.split("."); + const elementToAppend = querySections[0]; + + const parentOfSelection = parent.select(`${elementToAppend}.${querySections.slice(1).join(" ")}`); + const selection = parent.select(`g#g-coverClip`); + if (parentOfSelection.empty() && parent) { + parent + .append(elementToAppend) + .attr("id", `coverClip`) + .append("svg:rect") + .attr("x", x) + .attr("y", y) + .attr("width", width) + .attr("height", height) + .attr("class", querySections.slice(1).join(" ")); + } + if (selection.empty() && parent) { + parent + .append("g") + .attr("clip-path", `url(#coverClip)`) + .attr("id", `g-coverClip`); + + return parent; + } + + return selection; + } + protected svg: Element; protected width: string; protected height: string; From 4a6a9b916d821c1eb1fbafecad3738c4a4994ec0 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 13:37:58 +0800 Subject: [PATCH 080/510] feat: apply cover to axis chart --- packages/core/src/charts/area-stacked.ts | 2 ++ packages/core/src/charts/area.ts | 2 ++ packages/core/src/charts/bar-grouped.ts | 2 ++ packages/core/src/charts/bar-simple.ts | 2 ++ packages/core/src/charts/bar-stacked.ts | 2 ++ packages/core/src/charts/bubble.ts | 4 ++- packages/core/src/charts/scatter.ts | 2 ++ packages/core/src/components/component.ts | 26 +++++++++---------- .../core/src/services/essentials/dom-utils.ts | 8 +++--- 9 files changed, 31 insertions(+), 19 deletions(-) diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index c89a4f132d..8f18b69cf5 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -7,6 +7,7 @@ import { Tools } from "../tools"; // Components import { Grid, + Cover, StackedArea, TwoDimensionalAxes, Line, @@ -35,6 +36,7 @@ export class StackedAreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new StackedArea(this.model, this.services), diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index aaf6cbad0a..a61e3f669c 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -7,6 +7,7 @@ import { Tools } from "../tools"; // Components import { Grid, + Cover, Area, Line, Ruler, @@ -39,6 +40,7 @@ export class AreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index bba066a001..6a2be76a82 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, GroupedBar, TwoDimensionalAxes, ZeroLine, @@ -39,6 +40,7 @@ export class GroupedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new GroupedBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 0115bd6959..bf2da6772b 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, SimpleBar, TwoDimensionalAxes, ZeroLine, @@ -39,6 +40,7 @@ export class SimpleBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new SimpleBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 5edcdfc9b3..443f483d33 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, StackedBar, StackedBarRuler, TwoDimensionalAxes, @@ -42,6 +43,7 @@ export class StackedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new StackedBarRuler(this.model, this.services), new StackedBar(this.model, this.services), diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 05f1f5e4f1..dcdb8fcced 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -15,7 +15,8 @@ import { Tooltip, Legend, LayoutComponent, - Skeleton + Skeleton, + Cover } from "../components/index"; export class BubbleChart extends AxisChart { @@ -42,6 +43,7 @@ export class BubbleChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Bubble(this.model, this.services), diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 5d2a20c6cb..f3cda814cb 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -9,6 +9,7 @@ import { Skeletons } from "../interfaces/enums"; import { Grid, Ruler, + Cover, Scatter, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) @@ -42,6 +43,7 @@ export class ScatterChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Scatter(this.model, this.services), diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 2b1d8d84d0..c49dbd79fc 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -90,19 +90,19 @@ export class Component { "style", "prefix" ); - if (this.type === "line" || this.type === "scatter") { - const { width, height } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); - return DOMUtils.appendOrSelectForAxisChart( - this.parent, - `clipPath.cover`, - this.type, - 23, - 0, - (width - 23), - height, - ); + + + if ( + this.type === "line" || + this.type === "scatter" || + this.type === "area" || + this.type === "bubble" || + this.type === "area-stacked" || + this.type === "grouped-bar" || + this.type === "simple-bar" || + this.type === "scatter-stacked" + ) { + return DOMUtils.appendOrSelectForAxisChart(this.parent,`clipPath.cover`); } else { return DOMUtils.appendOrSelect( diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index 2d938bbeca..47159ec49c 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -149,22 +149,19 @@ export class DOMUtils extends Service { return selection; } - static appendOrSelectForAxisChart(parent, query, type, x, y, width, height) { + static appendOrSelectForAxisChart(parent, query) { const querySections = query.split("."); const elementToAppend = querySections[0]; const parentOfSelection = parent.select(`${elementToAppend}.${querySections.slice(1).join(" ")}`); const selection = parent.select(`g#g-coverClip`); + /* if (parentOfSelection.empty() && parent) { parent .append(elementToAppend) .attr("id", `coverClip`) .append("svg:rect") - .attr("x", x) - .attr("y", y) - .attr("width", width) - .attr("height", height) .attr("class", querySections.slice(1).join(" ")); } if (selection.empty() && parent) { @@ -175,6 +172,7 @@ export class DOMUtils extends Service { return parent; } + */ return selection; } From a4c08953dbb70e0170398ecb7220dba344002a96 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 13:57:19 +0800 Subject: [PATCH 081/510] refactor: clean code --- packages/core/src/charts/area-stacked.ts | 2 +- packages/core/src/charts/area.ts | 2 +- packages/core/src/charts/bar-grouped.ts | 2 +- packages/core/src/charts/bar-simple.ts | 2 +- packages/core/src/charts/bar-stacked.ts | 2 +- packages/core/src/charts/bubble.ts | 4 ++-- packages/core/src/charts/line.ts | 2 +- packages/core/src/charts/scatter.ts | 2 +- packages/core/src/components/axes/cover.ts | 10 ---------- packages/core/src/components/index.ts | 3 ++- packages/core/src/services/essentials/dom-utils.ts | 4 ---- 11 files changed, 11 insertions(+), 24 deletions(-) diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 8f18b69cf5..71cc66c34d 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,8 +6,8 @@ import { Tools } from "../tools"; // Components import { - Grid, Cover, + Grid, StackedArea, TwoDimensionalAxes, Line, diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index a61e3f669c..6d808c893c 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,8 +6,8 @@ import { Tools } from "../tools"; // Components import { - Grid, Cover, + Grid, Area, Line, Ruler, diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 6a2be76a82..63f82294ca 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, GroupedBar, TwoDimensionalAxes, ZeroLine, diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index bf2da6772b..21a1ccecc0 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, SimpleBar, TwoDimensionalAxes, ZeroLine, diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 443f483d33..5816fd62e8 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, StackedBar, StackedBarRuler, TwoDimensionalAxes, diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index dcdb8fcced..11469b0355 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Ruler, Bubble, @@ -15,8 +16,7 @@ import { Tooltip, Legend, LayoutComponent, - Skeleton, - Cover + Skeleton } from "../components/index"; export class BubbleChart extends AxisChart { diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index d766c7ad5a..943686c6c2 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,11 +7,11 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Line, Ruler, Scatter, - Cover, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) Tooltip, diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index f3cda814cb..e38fc66d51 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,9 +7,9 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Ruler, - Cover, Scatter, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts index 20b4083e09..16179b45c5 100644 --- a/packages/core/src/components/axes/cover.ts +++ b/packages/core/src/components/axes/cover.ts @@ -21,7 +21,6 @@ export class Cover extends Component { createCover() { const svg = this.parent; - console.log("!!! cover svg: ", svg); const mainXScale = this.services.cartesianScales.getMainXScale(); const mainYScale = this.services.cartesianScales.getMainYScale(); @@ -52,13 +51,4 @@ export class Cover extends Component { .attr("id", `g-${this.type}Clip`); } - - cleanCover(g) { - const options = this.model.getOptions(); - g.selectAll("line").attr("stroke", options.grid.strokeColor); - - // Remove extra elements - g.selectAll("text").remove(); - g.select(".domain").remove(); - } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 8be94d9e0e..a48f26437e 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,9 +30,10 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; +export * from "./axes/cover"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; -export * from "./axes/cover"; + diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index 47159ec49c..5974f62da0 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -156,7 +156,6 @@ export class DOMUtils extends Service { const parentOfSelection = parent.select(`${elementToAppend}.${querySections.slice(1).join(" ")}`); const selection = parent.select(`g#g-coverClip`); - /* if (parentOfSelection.empty() && parent) { parent .append(elementToAppend) @@ -169,11 +168,8 @@ export class DOMUtils extends Service { .append("g") .attr("clip-path", `url(#coverClip)`) .attr("id", `g-coverClip`); - return parent; } - */ - return selection; } From ff30a6b458d78a46758667074c4be1abde895a67 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 16 Jun 2020 14:57:41 +0800 Subject: [PATCH 082/510] feat: set axes margins as zoom bar left margin --- .../components/axes/two-dimensional-axes.ts | 3 +++ packages/core/src/components/axes/zoom-bar.ts | 20 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/axes/two-dimensional-axes.ts b/packages/core/src/components/axes/two-dimensional-axes.ts index ec843b20ab..d95e70e557 100644 --- a/packages/core/src/components/axes/two-dimensional-axes.ts +++ b/packages/core/src/components/axes/two-dimensional-axes.ts @@ -114,6 +114,9 @@ export class TwoDimensionalAxes extends Component { if (isNotEqual) { this.margins = Object.assign(this.margins, margins); + // also set new margins to model to allow external components to access + this.model.set({ axesMargins: this.margins }, { animate: false }); + Object.keys(this.children).forEach((childKey) => { const child = this.children[childKey]; child.margins = this.margins; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 4fdfa2fec5..0bf64460d9 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -18,10 +18,7 @@ export class ZoomBar extends Component { ogXScale: any; render(animate = true) { - // TODO - get correct axis left width - const axisLeftWidth = 23; const svg = this.getContainerSVG(); - const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); @@ -34,6 +31,13 @@ export class ZoomBar extends Component { mainYAxisPosition ); + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") .attr("width", "100%") .attr("height", this.height) @@ -48,7 +52,7 @@ export class ZoomBar extends Component { .attr("fill", "none"); const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") - .attr("x", axisLeftWidth) + .attr("x", axesLeftMargin) .attr("y", 0) .attr("width", "100%") .attr("height", "100%") @@ -100,7 +104,7 @@ export class ZoomBar extends Component { }); xScale - .range([axisLeftWidth, width]) + .range([axesLeftMargin, width]) .domain(extent(stackDataArray, (d: any) => d.date)); yScale @@ -232,8 +236,6 @@ export class ZoomBar extends Component { if (selection === null) { // set to default full range selection = xScale.range(); - } else { - // TODO - pass selection to callback function or update scaleDomain } // update brush handle position select(svg).call(updateBrushHandle, selection); @@ -284,8 +286,8 @@ export class ZoomBar extends Component { const brush = brushX() .extent([ - [0 + axisLeftWidth, 0], - [700, this.height] + [axesLeftMargin, 0], + [width, this.height] ]) .on("start brush end", brushed); From 89ff2dfefa46026b8f883718e84e03b74685e2b6 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 16:04:29 +0800 Subject: [PATCH 083/510] refactor: add todo --- packages/core/src/components/component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index c49dbd79fc..5664f29ec8 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -91,18 +91,18 @@ export class Component { "prefix" ); - + // @todo Chart type equals to axis-chart if ( - this.type === "line" || - this.type === "scatter" || - this.type === "area" || + this.type === "line" || + this.type === "scatter" || + this.type === "area" || this.type === "bubble" || this.type === "area-stacked" || this.type === "grouped-bar" || this.type === "simple-bar" || this.type === "scatter-stacked" ) { - return DOMUtils.appendOrSelectForAxisChart(this.parent,`clipPath.cover`); + return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); } else { return DOMUtils.appendOrSelect( From a96a2911feae5302bb490dd7ac87a22a8be45e8c Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 16 Jun 2020 16:54:51 +0800 Subject: [PATCH 084/510] fix: double the minTickSize for datetime ticks --- packages/core/src/components/axes/axis.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 04433e6399..484106f8fa 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -109,10 +109,9 @@ export class Axis extends Component { // if zoomDomain is available, update scale domain to Date array. const zoomDomain = this.model.get("zoomDomain"); if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain.map(d => new Date(d))); + scale.domain(zoomDomain.map((d) => new Date(d))); } - // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -412,11 +411,11 @@ export class Axis extends Component { const ticksNumber = isTimeScaleType ? axis.tickValues().length : scale.ticks().length; - const estimatedTickSize = width / ticksNumber / 1.6; - - rotateTicks = estimatedTickSize < minTickSize; + const estimatedTickSize = width / ticksNumber / 2; + rotateTicks = isTimeScaleType + ? estimatedTickSize < minTickSize * 2 // datetime tick could be very long + : estimatedTickSize < minTickSize; } - if (rotateTicks) { if (!isNumberOfTicksProvided) { axis.ticks( From dfd7fd85e276245a44022b41e035811b16e8d3fd Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 18 Jun 2020 13:42:33 +0800 Subject: [PATCH 085/510] feat: add chart brush --- packages/core/src/charts/area-stacked.ts | 5 +- packages/core/src/charts/area.ts | 5 +- packages/core/src/charts/bar-grouped.ts | 5 +- packages/core/src/charts/bar-simple.ts | 5 +- packages/core/src/charts/bar-stacked.ts | 5 +- packages/core/src/charts/bubble.ts | 5 +- packages/core/src/charts/donut.ts | 5 +- packages/core/src/charts/line.ts | 5 +- packages/core/src/charts/pie.ts | 5 +- packages/core/src/charts/radar.ts | 4 +- packages/core/src/charts/scatter.ts | 5 +- packages/core/src/components/axes/brush.ts | 142 +++++++++++++++++++++ packages/core/src/components/index.ts | 1 + 13 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/axes/brush.ts diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 71cc66c34d..b87365a97b 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, Cover, Grid, StackedArea, @@ -34,7 +35,7 @@ export class StackedAreaChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -48,6 +49,8 @@ export class StackedAreaChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 6d808c893c..8d5b94aa49 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, Cover, Grid, Area, @@ -38,7 +39,7 @@ export class AreaChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -51,6 +52,8 @@ export class AreaChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 63f82294ca..5668d302eb 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, GroupedBar, @@ -38,7 +39,7 @@ export class GroupedBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -49,6 +50,8 @@ export class GroupedBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 21a1ccecc0..951cd4509f 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, SimpleBar, @@ -38,7 +39,7 @@ export class SimpleBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -49,6 +50,8 @@ export class SimpleBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 5816fd62e8..e5684fb737 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, StackedBar, @@ -41,7 +42,7 @@ export class StackedBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class StackedBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 11469b0355..fd1c03a354 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Ruler, @@ -41,7 +42,7 @@ export class BubbleChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class BubbleChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/donut.ts b/packages/core/src/charts/donut.ts index 100faa5fac..18e00d2c36 100644 --- a/packages/core/src/charts/donut.ts +++ b/packages/core/src/charts/donut.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Donut, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -33,13 +34,15 @@ export class DonutChart extends PieChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new Donut(this.model, this.services), new Skeleton(this.model, this.services, { skeleton: Skeletons.DONUT }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getChartComponents(graphFrameComponents); return components; } diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index 943686c6c2..e950b1c4af 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Line, @@ -39,7 +40,7 @@ export class LineChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -51,6 +52,8 @@ export class LineChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 0fc4429807..9631b459a8 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Pie, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -46,13 +47,15 @@ export class PieChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new Pie(this.model, this.services), new Skeleton(this.model, this.services, { skeleton: Skeletons.PIE }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 8758bf1591..6a79b1a13b 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, // the imports below are needed because of typescript bug (error TS4029) Legend, LayoutComponent @@ -41,7 +42,8 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [new Radar(this.model, this.services)]; + const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index e38fc66d51..4c5b92d89b 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Ruler, @@ -41,7 +42,7 @@ export class ScatterChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class ScatterChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts new file mode 100644 index 0000000000..60dc0a8945 --- /dev/null +++ b/packages/core/src/components/axes/brush.ts @@ -0,0 +1,142 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { extent } from "d3-array"; +import { brushX } from "d3-brush"; +import { event } from "d3-selection"; +import { scaleTime } from "d3-scale"; + +export class Brush extends Component { + type = "brush"; + + render(animate = true) { + const svg = this.parent; + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const mainXScaleType = cartesianScales.getScaleTypeByPosition( + mainXAxisPosition + ); + + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + + const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainYScale = this.services.cartesianScales.getMainYScale(); + + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + const container = DOMUtils.appendOrSelect(svg, "svg.brush-container") + .attr("width", "100%") + .attr("height", "100%") + .attr("opacity", 1); + + if (mainXScale) { + const displayData = this.model.getDisplayData(); + + if (mainXScaleType === ScaleTypes.TIME) { + // Get all date values provided in data + // TODO - Could be re-used through the model + let allDates = []; + displayData.forEach((data) => { + allDates = allDates.concat(Number(data.date)); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + + // Go through all date values + // And get corresponding data from each dataset + const stackDataArray = allDates.map((date) => { + let count = 0; + let correspondingSum = 0; + const correspondingData = {}; + + displayData.forEach((data) => { + if (Number(data.date) === Number(date)) { + ++count; + correspondingSum += data.value; + } + }); + correspondingData["date"] = date; + correspondingData["value"] = correspondingSum; + + return correspondingData; + }); + + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + + let zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain === undefined) { + zoomDomain = extent(stackDataArray, (d: any) => d.date); // default to full range + this.model.set( + { zoomDomain: zoomDomain }, + { animate: false } + ); + } + + const brushed = () => { + let selection = event.selection; + if (selection !== null) { + // update zoombar handle position + // select(svg).call(updateBrushHandle, selection); + + // get current zoomDomain + zoomDomain = this.model.get("zoomDomain"); + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([axesLeftMargin, width]) + .domain(zoomDomain); + + const newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + // only if the brush event comes from mouseup event + if (event.sourceEvent != null) { + // only if zoomDomain needs update + if (zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1]) { + this.model.set( + { zoomDomain: newDomain, zoomDomainForZoomBar: newDomain }, + { animate: false } + ); + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + } + }; + + const brush = brushX() + .extent([ + [xScaleStart, 0], + [width, yScaleEnd] + ]) + .on("end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.chart-brush").call( + brush + ); + // no need for having default brush selection + // @todo try to hide brush after selection + setTimeout(()=> {brushArea.call(brush.move);}, 0); + } + } + } +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index a48f26437e..4d9959743a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,6 +30,7 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; +export * from "./axes/brush"; export * from "./axes/cover"; export * from "./axes/grid"; export * from "./axes/ruler"; From c359888676388415e5f7f2f66eeb42cc7d88bbf5 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 18 Jun 2020 14:19:35 +0800 Subject: [PATCH 086/510] refactor: add brush in axis chart component --- packages/core/src/axis-chart.ts | 3 +++ packages/core/src/charts/area-stacked.ts | 3 --- packages/core/src/charts/area.ts | 3 --- packages/core/src/charts/bar-grouped.ts | 3 --- packages/core/src/charts/bar-simple.ts | 3 --- packages/core/src/charts/bar-stacked.ts | 3 --- packages/core/src/charts/bubble.ts | 3 --- packages/core/src/charts/donut.ts | 3 --- packages/core/src/charts/line.ts | 3 --- packages/core/src/charts/pie.ts | 5 +---- packages/core/src/charts/radar.ts | 4 +--- packages/core/src/charts/scatter.ts | 3 --- 12 files changed, 5 insertions(+), 34 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 04f9bdeeb5..543d0ac6d4 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -8,6 +8,7 @@ import { AxisChartOptions } from "./interfaces/index"; import { + Brush, LayoutComponent, Legend, Title, @@ -48,6 +49,8 @@ export class AxisChart extends Chart { } }; + !!this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ? + graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index b87365a97b..42895bdc6b 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, Cover, Grid, StackedArea, @@ -49,8 +48,6 @@ export class StackedAreaChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 8d5b94aa49..2b313f6cc7 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, Cover, Grid, Area, @@ -52,8 +51,6 @@ export class AreaChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 5668d302eb..59354739e6 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, GroupedBar, @@ -50,8 +49,6 @@ export class GroupedBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 951cd4509f..5709ea0e9b 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, SimpleBar, @@ -50,8 +49,6 @@ export class SimpleBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index e5684fb737..c2510fa7da 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, StackedBar, @@ -53,8 +52,6 @@ export class StackedBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index fd1c03a354..7238de0aa3 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Ruler, @@ -53,8 +52,6 @@ export class BubbleChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/donut.ts b/packages/core/src/charts/donut.ts index 18e00d2c36..a3c9a33a61 100644 --- a/packages/core/src/charts/donut.ts +++ b/packages/core/src/charts/donut.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Donut, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -41,8 +40,6 @@ export class DonutChart extends PieChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getChartComponents(graphFrameComponents); return components; } diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index e950b1c4af..ee4e07f163 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Line, @@ -52,8 +51,6 @@ export class LineChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 9631b459a8..4642ea8f2d 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -8,7 +8,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Pie, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -53,9 +52,7 @@ export class PieChart extends Chart { skeleton: Skeletons.PIE }) ]; - - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 6a79b1a13b..80b8e7888c 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, // the imports below are needed because of typescript bug (error TS4029) Legend, LayoutComponent @@ -43,8 +42,7 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 4c5b92d89b..2acacdf9df 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Ruler, @@ -53,8 +52,6 @@ export class ScatterChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); From 3fb8b3a7634c6ef8cc8aaf86dfd1029bbfe5336d Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 18 Jun 2020 22:32:35 +0800 Subject: [PATCH 087/510] refactor: move brush functions to ZoomBar class function --- packages/core/src/components/axes/zoom-bar.ts | 180 +++++++++--------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0bf64460d9..8f5d2587e7 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -194,94 +194,8 @@ export class ZoomBar extends Component { ) .attr("d", areaGenerator); - const updateBrushHandle = (g, selection) => { - const handleSize = 5; - // handle - svg.select("g.brush") - .selectAll("rect.handle") - .data([{ type: "w" }, { type: "e" }]) - .attr("x", function (d) { - if (d.type === "w") { - return selection[0] - 3; - } else if (d.type === "e") { - return selection[1] - 3; - } - }) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) - .style("display", null); // always display - // handle-bar - svg.select("g.brush") - .selectAll("rect.handle-bar") - .data([{ type: "w" }, { type: "e" }]) - .join("rect") - .attr("class", function (d) { - return "handle-bar handle-bar--" + d.type; - }) - .attr("x", function (d) { - if (d.type === "w") { - return selection[0] - 1; - } else if (d.type === "e") { - return selection[1] - 1; - } - }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12); - }; - - const brushed = () => { - let selection = event.selection; - if (selection === null) { - // set to default full range - selection = xScale.range(); - } - // update brush handle position - select(svg).call(updateBrushHandle, selection); - - const newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { - // only if zoomDomain is never set or needs update - if ( - zoomDomain === undefined || - zoomDomain[0] !== newDomain[0] || - zoomDomain[1] !== newDomain[1] - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress( - selection, - newDomain - ); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); - } - } + const brushEventListener = () => { + this.brushed(zoomDomain, xScale, event.selection); }; const brush = brushX() @@ -289,13 +203,18 @@ export class ZoomBar extends Component { [axesLeftMargin, 0], [width, this.height] ]) - .on("start brush end", brushed); + .on("start brush end", brushEventListener); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( brush ); if (zoomDomain === undefined) { brushArea.call(brush.move, xScale.range()); // default to full range + } else { + // brushArea.call( + // brush.move, + // zoomDomain.map((domain) => xScale(domain)) //set brush to correct position + // ); } } } @@ -305,4 +224,87 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } + + // brush event listener + brushed(zoomDomain, scale, selection) { + if (selection === null) { + // set to default full range + selection = scale.range(); + } + // update brush handle position + this.updateBrushHandle(this.getContainerSVG(), selection); + + const newDomain = [ + scale.invert(selection[0]), + scale.invert(selection[1]) + ]; + // only if the brush event comes from mouseup event + if (event.sourceEvent != null) { + // only if zoomDomain is never set or needs update + if ( + zoomDomain === undefined || + zoomDomain[0] !== newDomain[0] || + zoomDomain[1] !== newDomain[1] + ) { + this.model.set({ zoomDomain: newDomain }, { animate: false }); + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + } + + updateBrushHandle(svg, selection) { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{ type: "w" }, { type: "e" }]) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 3; + } else if (d.type === "e") { + return selection[1] - 3; + } + }) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32) + .style("display", null); // always display + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{ type: "w" }, { type: "e" }]) + .join("rect") + .attr("class", function (d) { + return "handle-bar handle-bar--" + d.type; + }) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12); + } } From 90783b9995461f8e11990374092ef54f5d9e02dd Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 19 Jun 2020 11:57:00 +0800 Subject: [PATCH 088/510] fix: handel situation of zoom bar selection[0] equals selection[1] --- packages/core/src/components/axes/zoom-bar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 8f5d2587e7..d7aff2470a 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -227,7 +227,9 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - if (selection === null) { + // follow d3 behavior: when selection[0] === selection[1], reset default full range + // @todo find a better way to handel the situation when selection[0] === selection[1] + if (selection === null || selection[0] === selection[1]) { // set to default full range selection = scale.range(); } From 7125fa0cb713d9a99ac46f39d295fbbea0145dd1 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 12:37:08 +0800 Subject: [PATCH 089/510] feat: ZoomBar could update brush based on zoomDomain - fix some brush handle UI bugs --- packages/core/src/components/axes/zoom-bar.ts | 104 +++++++++++------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d7aff2470a..3c26a897bd 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,6 +17,8 @@ export class ZoomBar extends Component { ogXScale: any; + brush = brushX(); + render(animate = true) { const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -198,23 +200,30 @@ export class ZoomBar extends Component { this.brushed(zoomDomain, xScale, event.selection); }; - const brush = brushX() + this.brush .extent([ [axesLeftMargin, 0], [width, this.height] ]) + .on("start brush end", null) // remove old listener first .on("start brush end", brushEventListener); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( - brush + this.brush ); - if (zoomDomain === undefined) { - brushArea.call(brush.move, xScale.range()); // default to full range + if ( + zoomDomain === undefined || + zoomDomain[0].valueOf() === zoomDomain[1].valueOf() + ) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range() + ); } else { - // brushArea.call( - // brush.move, - // zoomDomain.map((domain) => xScale(domain)) //set brush to correct position - // ); + const selected = zoomDomain.map((domain) => xScale(domain)); + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle(this.getContainerSVG(), selected); } } } @@ -240,8 +249,13 @@ export class ZoomBar extends Component { scale.invert(selection[0]), scale.invert(selection[1]) ]; - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { + + // be aware that the value of d3.event changes during an event! + // update zoomDomain only if the event comes from mousemove event + if ( + event.sourceEvent != null && + event.sourceEvent.type === "mousemove" + ) { // only if zoomDomain is never set or needs update if ( zoomDomain === undefined || @@ -250,45 +264,53 @@ export class ZoomBar extends Component { ) { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); - } + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if (zoomBarOptions.selectionEnd !== undefined && event.type === "end") { + zoomBarOptions.selectionEnd(selection, newDomain); } } updateBrushHandle(svg, selection) { - const handleSize = 5; + // @todo the handle size, height are calculated by d3 library + // need to figure out how to override the value + const handleWidth = 6; + const handleHeight = 38; + const handleXDiff = -handleWidth / 2; + const handleYDiff = -(handleHeight - this.height) / 2; + + const handleBarWidth = 2; + const handleBarHeight = 12; + const handleBarXDiff = -handleBarWidth / 2; + const handleYBarDiff = + (handleHeight - handleBarHeight) / 2 + handleYDiff; // handle svg.select("g.brush") .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { if (d.type === "w") { - return selection[0] - 3; + return selection[0] + handleXDiff; } else if (d.type === "e") { - return selection[1] - 3; + return selection[1] + handleXDiff; } }) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) + .attr("y", handleYDiff) + .attr("width", handleWidth) + .attr("height", handleHeight) .style("display", null); // always display // handle-bar svg.select("g.brush") @@ -300,13 +322,13 @@ export class ZoomBar extends Component { }) .attr("x", function (d) { if (d.type === "w") { - return selection[0] - 1; + return selection[0] + handleBarXDiff; } else if (d.type === "e") { - return selection[1] - 1; + return selection[1] + handleBarXDiff; } }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12); + .attr("y", handleYBarDiff) + .attr("width", handleBarWidth) + .attr("height", handleBarHeight); } } From 3262bd17d385988ef828e43b5375a68ac94b8e69 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 12:51:20 +0800 Subject: [PATCH 090/510] fix: handle empty selection --- packages/core/src/components/axes/zoom-bar.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 3c26a897bd..04507c90a3 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -197,7 +197,21 @@ export class ZoomBar extends Component { .attr("d", areaGenerator); const brushEventListener = () => { - this.brushed(zoomDomain, xScale, event.selection); + const selection = event.selection; + // follow d3 behavior: when selection is null, reset default full range + // @todo find a better way to handel the situation when selection is null + // select behavior is completed, but nothing selected + if (selection === null) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range() + ); + } else if (selection[0] === selection[1]) { + // select behavior is not completed yet, do nothing + } else { + this.brushed(zoomDomain, xScale, selection); + } }; this.brush @@ -236,12 +250,6 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - // follow d3 behavior: when selection[0] === selection[1], reset default full range - // @todo find a better way to handel the situation when selection[0] === selection[1] - if (selection === null || selection[0] === selection[1]) { - // set to default full range - selection = scale.range(); - } // update brush handle position this.updateBrushHandle(this.getContainerSVG(), selection); From 7820ada1448e04e202c4f1d25a7aa70d7597fb55 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 19 Jun 2020 14:13:17 +0800 Subject: [PATCH 091/510] fix: set to default full range when same selection of brush --- packages/core/src/components/axes/brush.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 60dc0a8945..0a22fe0d79 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -45,7 +45,7 @@ export class Brush extends Component { if (mainXScaleType === ScaleTypes.TIME) { // Get all date values provided in data - // TODO - Could be re-used through the model + // @todo - Could be re-used through the model let allDates = []; displayData.forEach((data) => { allDates = allDates.concat(Number(data.date)); @@ -86,10 +86,8 @@ export class Brush extends Component { const brushed = () => { let selection = event.selection; - if (selection !== null) { - // update zoombar handle position - // select(svg).call(updateBrushHandle, selection); + if (selection !== null) { // get current zoomDomain zoomDomain = this.model.get("zoomDomain"); // create xScale based on current zoomDomain @@ -97,17 +95,26 @@ export class Brush extends Component { .range([axesLeftMargin, width]) .domain(zoomDomain); - const newDomain = [ + let newDomain = [ xScale.invert(selection[0]), xScale.invert(selection[1]) ]; + + // check if slected start time and end time are the same + if(newDomain[0].valueOf() === newDomain[1].valueOf()) { + // same as d3 behavior and zoombar behavior: set to default full range + newDomain = extent(stackDataArray, (d: any) => d.date); + } // only if the brush event comes from mouseup event if (event.sourceEvent != null) { // only if zoomDomain needs update - if (zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1]) { + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { this.model.set( - { zoomDomain: newDomain, zoomDomainForZoomBar: newDomain }, + { zoomDomain: newDomain }, { animate: false } ); } From f35c7421d13a9fb6c0b239bb928d6d08f11feb4d Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 13:59:36 +0800 Subject: [PATCH 092/510] fix: fix bug when selection === null --- packages/core/src/components/axes/zoom-bar.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 04507c90a3..f7894985c5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -202,11 +202,7 @@ export class ZoomBar extends Component { // @todo find a better way to handel the situation when selection is null // select behavior is completed, but nothing selected if (selection === null) { - brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle( - this.getContainerSVG(), - xScale.range() - ); + this.brushed(zoomDomain, xScale, xScale.range()); } else if (selection[0] === selection[1]) { // select behavior is not completed yet, do nothing } else { @@ -259,10 +255,12 @@ export class ZoomBar extends Component { ]; // be aware that the value of d3.event changes during an event! - // update zoomDomain only if the event comes from mousemove event + // update zoomDomain only if the event comes from mouse event if ( event.sourceEvent != null && - event.sourceEvent.type === "mousemove" + (event.sourceEvent.type === "mousemove" || + event.sourceEvent.type === "mouseup" || + event.sourceEvent.type === "mousedown") ) { // only if zoomDomain is never set or needs update if ( From c365abd7b037c5f57075c386cdfe55485a7d3940 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 14:41:24 +0800 Subject: [PATCH 093/510] feat: add ZoomBarOptions.initZoomDomain - update storybook - add init() and destroy() in ZoomBar --- packages/core/demo/data/time-series-axis.ts | 5 ++++ packages/core/src/components/axes/zoom-bar.ts | 25 ++++++++++++++++--- packages/core/src/configuration.ts | 1 + packages/core/src/interfaces/components.ts | 6 +++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e79de136b6..845e1bd1a7 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -453,6 +453,10 @@ const selectionEndFun = (selection, domain) => { console.log(domain); }; +const initZoomDomain = [ + new Date(2020, 11, 10, 23, 59, 25), + new Date(2020, 11, 11, 0, 0, 25) +]; export const lineTimeSeriesZoomBarOptions = { title: "Line (time series) - zoom-bar enabled", axes: { @@ -463,6 +467,7 @@ export const lineTimeSeriesZoomBarOptions = { }, zoomBar: { enabled: true, + initZoomDomain: initZoomDomain, selectionStart: selectionStartFun, selectionInProgress: selectionInProgressFun, selectionEnd: selectionEndFun diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index f7894985c5..0137960f8d 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -19,6 +19,17 @@ export class ZoomBar extends Component { brush = brushX(); + init() { + // get initZoomDomain + const zoomBarOptions = this.model.getOptions().zoomBar; + if (zoomBarOptions.initZoomDomain !== undefined) { + this.model.set( + { zoomDomain: zoomBarOptions.initZoomDomain }, + { skipUpdate: true } + ); + } + } + render(animate = true) { const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -221,6 +232,7 @@ export class ZoomBar extends Component { const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( this.brush ); + if ( zoomDomain === undefined || zoomDomain[0].valueOf() === zoomDomain[1].valueOf() @@ -239,10 +251,11 @@ export class ZoomBar extends Component { } } - zoomIn() { - const mainXScale = this.services.cartesianScales.getMainXScale(); - console.log("zoom in", mainXScale.domain()); - } + // could be used by Toolbar + // zoomIn() { + // const mainXScale = this.services.cartesianScales.getMainXScale(); + // console.log("zoom in", mainXScale.domain()); + // } // brush event listener brushed(zoomDomain, scale, selection) { @@ -337,4 +350,8 @@ export class ZoomBar extends Component { .attr("width", handleBarWidth) .attr("height", handleBarHeight); } + + destroy() { + this.brush.on("start brush end", null); // remove event listener + } } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index b7e8e301d8..70e7095f54 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -131,6 +131,7 @@ export const timeScale: TimeScaleOptions = { */ export const zoomBar: ZoomBarOptions = { enabled: false, + initZoomDomain: undefined, selectionStart: undefined, selectionInProgress: undefined, selectionEnd: undefined diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 077fb63bf7..6fd842a302 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -120,6 +120,12 @@ export interface ZoomBarOptions { * is the zoom-bar visible or not */ enabled?: boolean; + + /** + * an two element array which represents the initial zoom domain + */ + initZoomDomain?: Object[]; + /** * a function to handle selection start event */ From 8358944e1395c45afd8fbfd7307b53e4ffe0d4e5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Sat, 20 Jun 2020 18:03:35 +0800 Subject: [PATCH 094/510] feat: create Zoombar storybook demo group --- packages/core/demo/data/index.ts | 54 +++++++++++++ packages/core/demo/data/time-series-axis.ts | 55 ------------- packages/core/demo/data/zoom-bar.ts | 88 +++++++++++++++++++++ 3 files changed, 142 insertions(+), 55 deletions(-) create mode 100644 packages/core/demo/data/zoom-bar.ts diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 541cf0a005..b30c40f9fb 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -10,6 +10,7 @@ import * as stepDemos from "./step"; import * as meterDemos from "./meter"; import * as timeSeriesAxisDemos from "./time-series-axis"; import * as radarDemos from "./radar"; +import * as zoomBarDemos from "./zoom-bar"; export * from "./area"; export * from "./bar"; @@ -707,6 +708,59 @@ let allDemoGroups = [ chartType: chartTypes.RadarChart } ] + }, + { + title: "Zoom bar", + demos: [ + { + options: zoomBarDemos.zoomBarSimpleBarTimeSeriesOptions, + data: zoomBarDemos.zoomBarSimpleBarTimeSeriesData, + chartType: chartTypes.SimpleBarChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarStackedBarTimeSeriesOptions, + data: zoomBarDemos.zoomBarStackedBarTimeSeriesData, + chartType: chartTypes.StackedBarChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarBubbleTimeSeriesOptions, + data: zoomBarDemos.zoomBarBubbleTimeSeriesData, + chartType: chartTypes.BubbleChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeriesOptions, + data: zoomBarDemos.zoomBarLineTimeSeriesData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarScatterTimeSeriesOptions, + data: zoomBarDemos.zoomBarScatterTimeSeriesData, + chartType: chartTypes.ScatterChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarStepTimeSeriesOptions, + data: zoomBarDemos.zoomBarStepTimeSeriesData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeries15secondsOptions, + data: zoomBarDemos.zoomBarLineTimeSeries15secondsData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeriesInitDomainOptions, + data: zoomBarDemos.zoomBarLineTimeSeriesInitDomainData, + chartType: chartTypes.LineChart, + isDemoExample: false + } + ] } ] as any; diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index 845e1bd1a7..4ac497de89 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -418,58 +418,3 @@ export const lineTimeSeriesAllLabelsInPrimaryFormatOptions = { addSpaceOnEdges: 0 } }; - -// ZoomBar -export const lineTimeSeriesDataZoomBar = { - labels: ["Qty"], - datasets: [ - { - label: "Dataset 1", - data: [ - { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, - { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, - { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, - { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, - { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } - ] - } - ] -}; -const selectionStartFun = (selection, domain) => { - console.log("ZoomBar SelectionStart callback!"); - console.log(selection); - console.log(domain); -}; -const selectionInProgressFun = (selection, domain) => { - console.log("ZoomBar SelectionInProgress callback!"); - console.log(selection); - console.log(domain); -}; -const selectionEndFun = (selection, domain) => { - console.log("ZoomBar SelectionEnd callback!"); - console.log(selection); - console.log(domain); -}; - -const initZoomDomain = [ - new Date(2020, 11, 10, 23, 59, 25), - new Date(2020, 11, 11, 0, 0, 25) -]; -export const lineTimeSeriesZoomBarOptions = { - title: "Line (time series) - zoom-bar enabled", - axes: { - left: {}, - bottom: { - scaleType: "time" - } - }, - zoomBar: { - enabled: true, - initZoomDomain: initZoomDomain, - selectionStart: selectionStartFun, - selectionInProgress: selectionInProgressFun, - selectionEnd: selectionEndFun - } -}; diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts new file mode 100644 index 0000000000..5c97e33973 --- /dev/null +++ b/packages/core/demo/data/zoom-bar.ts @@ -0,0 +1,88 @@ +import * as timeSeriesAxisChart from "./time-series-axis"; +import * as barChart from "./bar"; +import * as bubbleChart from "./bubble"; +import * as lineChart from "./line"; +import * as scatterChart from "./scatter"; +import * as stepChart from "./step"; + +// default function for selection callback +const selectionStartFun = (selection, domain) => { + console.log("ZoomBar SelectionStart callback!"); + console.log(selection); + console.log(domain); +}; +const selectionInProgressFun = (selection, domain) => { + console.log("ZoomBar SelectionInProgress callback!"); + console.log(selection); + console.log(domain); +}; +const selectionEndFun = (selection, domain) => { + console.log("ZoomBar SelectionEnd callback!"); + console.log(selection); + console.log(domain); +}; + +const initZoomDomain = [ + new Date(2020, 11, 10, 23, 59, 25), + new Date(2020, 11, 11, 0, 0, 25) +]; + +const defaultZoomBarOptions = { + enabled: true, + initZoomDomain: undefined, + selectionStart: selectionStartFun, + selectionInProgress: selectionInProgressFun, + selectionEnd: selectionEndFun +}; + +// utility function to update title and enable zoomBar option +const updateOptions = (options) => { + options["title"] = options["title"] + " - Zoom bar enabled"; + options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); + return options; +}; + +export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; +export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( + Object.assign({}, barChart.simpleBarTimeSeriesOptions) +); + +export const zoomBarStackedBarTimeSeriesData = + barChart.stackedBarTimeSeriesData; +export const zoomBarStackedBarTimeSeriesOptions = updateOptions( + Object.assign({}, barChart.stackedBarTimeSeriesOptions) +); + +export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; +export const zoomBarBubbleTimeSeriesOptions = updateOptions( + Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) +); + +export const zoomBarLineTimeSeriesData = lineChart.lineTimeSeriesData; +export const zoomBarLineTimeSeriesOptions = updateOptions( + Object.assign({}, lineChart.lineTimeSeriesOptions) +); + +export const zoomBarScatterTimeSeriesData = scatterChart.scatterTimeSeriesData; +export const zoomBarScatterTimeSeriesOptions = updateOptions( + Object.assign({}, scatterChart.scatterTimeSeriesOptions) +); + +export const zoomBarStepTimeSeriesData = stepChart.stepTimeSeriesData; +export const zoomBarStepTimeSeriesOptions = updateOptions( + Object.assign({}, stepChart.stepTimeSeriesOptions) +); + +export const zoomBarLineTimeSeries15secondsData = + timeSeriesAxisChart.lineTimeSeriesData15seconds; +export const zoomBarLineTimeSeries15secondsOptions = updateOptions( + Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) +); + +export const zoomBarLineTimeSeriesInitDomainData = + timeSeriesAxisChart.lineTimeSeriesData15seconds; +export const zoomBarLineTimeSeriesInitDomainOptions = updateOptions( + Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) +); +zoomBarLineTimeSeriesInitDomainOptions["title"] += " with initial zoom domain"; +zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initZoomDomain = initZoomDomain; From 7fae730def6b88343b9dcc80bdb276a89244a70e Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 23 Jun 2020 10:08:11 +0800 Subject: [PATCH 095/510] fix: apply cover to stacked bar --- packages/core/src/components/component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 5664f29ec8..6aa5e17ee0 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -100,6 +100,7 @@ export class Component { this.type === "area-stacked" || this.type === "grouped-bar" || this.type === "simple-bar" || + this.type === "stacked-bar" || this.type === "scatter-stacked" ) { return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); From 54320c88480669c18aec79ea8e7dff7f8b979798 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 24 Jun 2020 12:23:11 +0800 Subject: [PATCH 096/510] feat: create ZoomBar event and handler - use Events.ZoomBar.UPDATE to handle axesMargins update --- .../core/src/components/axes/two-dimensional-axes.ts | 4 +++- packages/core/src/components/axes/zoom-bar.ts | 9 ++++++++- packages/core/src/interfaces/events.ts | 7 +++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/axes/two-dimensional-axes.ts b/packages/core/src/components/axes/two-dimensional-axes.ts index d95e70e557..aa8b1cccb3 100644 --- a/packages/core/src/components/axes/two-dimensional-axes.ts +++ b/packages/core/src/components/axes/two-dimensional-axes.ts @@ -5,6 +5,7 @@ import { Axis } from "./axis"; import { Tools } from "../../tools"; import { DOMUtils } from "../../services"; import { Threshold } from "../essentials/threshold"; +import { Events } from "./../../interfaces"; export class TwoDimensionalAxes extends Component { type = "2D-axes"; @@ -115,7 +116,8 @@ export class TwoDimensionalAxes extends Component { this.margins = Object.assign(this.margins, margins); // also set new margins to model to allow external components to access - this.model.set({ axesMargins: this.margins }, { animate: false }); + this.model.set({ axesMargins: this.margins }, { skipUpdate: true }); + this.services.events.dispatchEvent(Events.ZoomBar.UPDATE); Object.keys(this.children).forEach((childKey) => { const child = this.children[childKey]; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0137960f8d..a6d85ed844 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -1,7 +1,7 @@ // Internal Imports import { Component } from "../component"; import { Tools } from "../../tools"; -import { ScaleTypes } from "../../interfaces"; +import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports @@ -20,6 +20,10 @@ export class ZoomBar extends Component { brush = brushX(); init() { + this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); + // get initZoomDomain const zoomBarOptions = this.model.getOptions().zoomBar; if (zoomBarOptions.initZoomDomain !== undefined) { @@ -353,5 +357,8 @@ export class ZoomBar extends Component { destroy() { this.brush.on("start brush end", null); // remove event listener + this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); } } diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 2850204dc5..2a25fd5992 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -17,6 +17,13 @@ export enum Model { UPDATE = "model-update" } +/** + * enum of all events related to the zoom-bar + */ +export enum ZoomBar { + UPDATE = "zoom-bar-update" +} + /** * enum of all axis-related events */ From bf56f46ddd450d142d2c77dd42261fe38cc65472 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 24 Jun 2020 12:48:50 +0800 Subject: [PATCH 097/510] fix: fix merge error - move ZoomBarOptions from BaseChartOptions to AxisChartOptions --- packages/core/src/interfaces/charts.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 5f4587b7d9..6c2567e1b6 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -41,10 +41,6 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; - /** - * zoombar configuration - */ - zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ @@ -117,6 +113,10 @@ export interface AxisChartOptions extends BaseChartOptions { axes?: AxesOptions; grid?: GridOptions; timeScale?: TimeScaleOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; } /** @@ -213,7 +213,7 @@ export interface StackedAreaChartOptions extends ScatterChartOptions { | string | { name: string; - }; + }; } /** From abeecce56ab21d7b6f02537098fe8a9e89dd0f2a Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 14:26:51 +0800 Subject: [PATCH 098/510] feat: update color for non-highlighted areas - remove zoombar graph line - add zoombar unselected graph area with clip - add zoombar baseline - clear d3.brush selection style --- packages/core/src/components/axes/zoom-bar.ts | 142 +++++++++++------- .../core/src/styles/components/_zoom-bar.scss | 16 +- 2 files changed, 97 insertions(+), 61 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index a6d85ed844..d04c7a4eeb 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,6 +13,8 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + clipId = "zoomBarClip"; + height = 32; ogXScale: any; @@ -72,9 +74,7 @@ export class ZoomBar extends Component { .attr("x", axesLeftMargin) .attr("y", 0) .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "white") - .attr("stroke", "#e8e8e8"); + .attr("height", "100%"); if (mainXScale) { const displayData = this.model.getDisplayData(); @@ -153,63 +153,44 @@ export class ZoomBar extends Component { ) ) .curve(this.services.curves.getD3Curve()); - // .defined((d: any, i) => { - // if (zoomDomain) { - // const dTimestamp = +d.label; - - // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; - // } - - // return true; - // }); - const lineGraph = DOMUtils.appendOrSelect( - container, - "path.zoom-graph-line" - ) - .datum(stackDataArray) - .transition( - this.services.transitions.getTransition( - "zoom-pan-line-update", - animate - ) - ) - .attr("d", lineGenerator); - - const areaGenerator = area() - .x((d, i) => - cartesianScales.getValueFromScale( - xScale, - mainXScaleType, - mainXAxisPosition, + const accessorFunc = (scale, scaleType, axisPosition) => { + return (d, i) => { + return cartesianScales.getValueFromScale( + scale, + scaleType, + axisPosition, d, i - ) - ) - .y0(this.height) - .y1( - (d, i) => - this.height - - cartesianScales.getValueFromScale( - yScale, - mainYScaleType, - mainYAxisPosition, - d, - i - ) - ); - - const areaGraph = DOMUtils.appendOrSelect( + ); + }; + }; + this.renderZoomBarArea( container, - "path.zoom-graph-area" - ) - .datum(stackDataArray) - .transition( - this.services.transitions.getTransition( - "zoom-pan-area-update", - animate - ) - ) - .attr("d", areaGenerator); + "path.zoom-graph-area-unselected", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + stackDataArray, + animate, + undefined + ); + this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); + this.renderZoomBarArea( + container, + "path.zoom-graph-area", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + stackDataArray, + animate, + "zoomBarClip" + ); + const baselineGenerator = line()([ + [axesLeftMargin, this.height], + [width, this.height] + ]); + const zoomBaseline = DOMUtils.appendOrSelect( + container, + "path.zoom-bg-baseline" + ).attr("d", baselineGenerator); const brushEventListener = () => { const selection = event.selection; @@ -352,7 +333,52 @@ export class ZoomBar extends Component { }) .attr("y", handleYBarDiff) .attr("width", handleBarWidth) - .attr("height", handleBarHeight); + .attr("height", handleBarHeight) + .attr("cursor", "ew-resize"); + + this.updateClipPath( + svg, + this.clipId, + selection[0], + 0, + selection[1] - selection[0], + this.height + ); + } + + renderZoomBarArea( + container, + querySelector, + xFunc, + y1Func, + datum, + animate, + clipId + ) { + const areaGenerator = area() + .x((d, i) => xFunc(d, i)) + .y0(this.height) + .y1((d, i) => this.height - y1Func(d, i)); + + const areaGraph = DOMUtils.appendOrSelect(container, querySelector) + .datum(datum) + .attr("d", areaGenerator); + + if (clipId) { + areaGraph.attr("clip-path", `url(#${clipId})`); + } + } + + updateClipPath(svg, clipId, x, y, width, height) { + const zoomBarClipPath = DOMUtils.appendOrSelect(svg, `clipPath`).attr( + "id", + clipId + ); + DOMUtils.appendOrSelect(zoomBarClipPath, "rect") + .attr("x", x) + .attr("y", y) + .attr("width", width) + .attr("height", height); } destroy() { diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 59019956a8..32dfc32842 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -4,15 +4,19 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: $ui-01; } - path.zoom-graph-line { + path.zoom-bg-baseline { stroke: $ui-04; - stroke-width: 3; - fill: none; + stroke-width: 2; } path.zoom-graph-area { fill: $ui-03; stroke: $ui-04; + stroke-width: 1; + } + path.zoom-graph-area-unselected { + fill: $ui-01; + stroke: none; } g.brush rect.handle { @@ -22,4 +26,10 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { g.brush rect.handle-bar { fill: $ui-02; } + + // clear d3.brush selection style + g.brush rect.selection { + fill: none; + stroke: none; + } } From 668754b8b503a2117bb4112beacffdb49baa87b5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 15:01:58 +0800 Subject: [PATCH 099/510] fix: avoid extra brush handle update --- packages/core/src/components/axes/zoom-bar.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d04c7a4eeb..cc5fd6bfe3 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -218,8 +218,10 @@ export class ZoomBar extends Component { this.brush ); - if ( - zoomDomain === undefined || + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + } else if ( zoomDomain[0].valueOf() === zoomDomain[1].valueOf() ) { brushArea.call(this.brush.move, xScale.range()); // default to full range @@ -229,8 +231,16 @@ export class ZoomBar extends Component { ); } else { const selected = zoomDomain.map((domain) => xScale(domain)); - brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle(this.getContainerSVG(), selected); + if (selected[1] - selected[0] < 1) { + // initialization not completed yet + // don't update brushHandle to avoid flash + } else { + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle( + this.getContainerSVG(), + selected + ); + } } } } From 8aa16b15747766e34e1f1cbf15868c8a4151d24b Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 15:34:34 +0800 Subject: [PATCH 100/510] fix: fix the brush handle style --- packages/core/src/components/axes/zoom-bar.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index cc5fd6bfe3..840d15198f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -299,18 +299,14 @@ export class ZoomBar extends Component { } updateBrushHandle(svg, selection) { - // @todo the handle size, height are calculated by d3 library - // need to figure out how to override the value - const handleWidth = 6; - const handleHeight = 38; + const handleWidth = 5; + const handleHeight = this.height; const handleXDiff = -handleWidth / 2; - const handleYDiff = -(handleHeight - this.height) / 2; - const handleBarWidth = 2; + const handleBarWidth = 1; const handleBarHeight = 12; const handleBarXDiff = -handleBarWidth / 2; - const handleYBarDiff = - (handleHeight - handleBarHeight) / 2 + handleYDiff; + const handleYBarDiff = (handleHeight - handleBarHeight) / 2; // handle svg.select("g.brush") .selectAll("rect.handle") @@ -322,7 +318,7 @@ export class ZoomBar extends Component { return selection[1] + handleXDiff; } }) - .attr("y", handleYDiff) + .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) .style("display", null); // always display From 0cbcd6f4c42c3c9a63b0fccefd47609b391c946f Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 14:00:53 +0800 Subject: [PATCH 101/510] refactor: remove unnecessary svg.brush-container --- packages/core/src/components/axes/brush.ts | 42 ++++++++++++---------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 0a22fe0d79..517a73eb80 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -30,16 +30,10 @@ export class Brush extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); const mainYScale = this.services.cartesianScales.getMainYScale(); - const [xScaleStart, xScaleEnd] = mainXScale.range(); const [yScaleEnd, yScaleStart] = mainYScale.range(); - const container = DOMUtils.appendOrSelect(svg, "svg.brush-container") - .attr("width", "100%") - .attr("height", "100%") - .attr("opacity", 1); - if (mainXScale) { const displayData = this.model.getDisplayData(); @@ -85,7 +79,7 @@ export class Brush extends Component { } const brushed = () => { - let selection = event.selection; + const selection = event.selection; if (selection !== null) { // get current zoomDomain @@ -99,19 +93,24 @@ export class Brush extends Component { xScale.invert(selection[0]), xScale.invert(selection[1]) ]; - + // check if slected start time and end time are the same - if(newDomain[0].valueOf() === newDomain[1].valueOf()) { + if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoombar behavior: set to default full range - newDomain = extent(stackDataArray, (d: any) => d.date); + newDomain = extent( + stackDataArray, + (d: any) => d.date + ); } // only if the brush event comes from mouseup event if (event.sourceEvent != null) { // only if zoomDomain needs update if ( - zoomDomain[0].valueOf() !== newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== newDomain[1].valueOf() + zoomDomain[0].valueOf() !== + newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== + newDomain[1].valueOf() ) { this.model.set( { zoomDomain: newDomain }, @@ -119,12 +118,16 @@ export class Brush extends Component { ); } // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; + const zoomBarOptions = this.model.getOptions() + .zoomBar; if ( zoomBarOptions.selectionEnd !== undefined && event.type === "end" ) { - zoomBarOptions.selectionEnd(selection, newDomain); + zoomBarOptions.selectionEnd( + selection, + newDomain + ); } } } @@ -137,12 +140,15 @@ export class Brush extends Component { ]) .on("end", brushed); - const brushArea = DOMUtils.appendOrSelect(svg, "g.chart-brush").call( - brush - ); + const brushArea = DOMUtils.appendOrSelect( + svg, + "g.chart-brush" + ).call(brush); // no need for having default brush selection // @todo try to hide brush after selection - setTimeout(()=> {brushArea.call(brush.move);}, 0); + setTimeout(() => { + brushArea.call(brush.move); + }, 0); } } } From 21b1f6dd5aab6244a6c64251380598fce1010ee5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 14:17:17 +0800 Subject: [PATCH 102/510] fix: move brush layer to back to allow tooltips in graph --- packages/core/src/axis-chart.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 543d0ac6d4..3690c7b924 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -6,7 +6,7 @@ import { LegendPositions, ChartConfig, AxisChartOptions -} from "./interfaces/index"; +} from "./interfaces"; import { Brush, LayoutComponent, @@ -15,10 +15,10 @@ import { AxisChartsTooltip, Spacer, ZoomBar -} from "./components/index"; +} from "./components"; import { Tools } from "./tools"; -import { CartesianScales, Curves } from "./services/index"; +import { CartesianScales, Curves } from "./services"; export class AxisChart extends Chart { services: any = Object.assign(this.services, { @@ -49,8 +49,16 @@ export class AxisChart extends Chart { } }; - !!this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ? - graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + if ( + this.model.getOptions().zoomBar && + this.model.getOptions().zoomBar.enabled + ) { + graphFrameComponents.splice( + 1, + 0, + new Brush(this.model, this.services) + ); + } const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, From 2df119e85aa1d7d20a354a0ad84cbf1796244ae1 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 16:18:27 +0800 Subject: [PATCH 103/510] feat: add stacked-area chart with zoom bar in storybook --- packages/core/demo/data/index.ts | 6 ++++++ packages/core/demo/data/zoom-bar.ts | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index b30c40f9fb..9c38d98318 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -712,6 +712,12 @@ let allDemoGroups = [ { title: "Zoom bar", demos: [ + { + options: zoomBarDemos.zoomBarStackedAreaTimeSeriesOptions, + data: zoomBarDemos.zoomBarStackedAreaTimeSeriesData, + chartType: chartTypes.StackedAreaChart, + isDemoExample: false + }, { options: zoomBarDemos.zoomBarSimpleBarTimeSeriesOptions, data: zoomBarDemos.zoomBarSimpleBarTimeSeriesData, diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 5c97e33973..0016bbcad7 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -1,9 +1,10 @@ -import * as timeSeriesAxisChart from "./time-series-axis"; +import * as areaChart from "./area"; import * as barChart from "./bar"; import * as bubbleChart from "./bubble"; import * as lineChart from "./line"; import * as scatterChart from "./scatter"; import * as stepChart from "./step"; +import * as timeSeriesAxisChart from "./time-series-axis"; // default function for selection callback const selectionStartFun = (selection, domain) => { @@ -42,6 +43,12 @@ const updateOptions = (options) => { return options; }; +export const zoomBarStackedAreaTimeSeriesData = + areaChart.stackedAreaTimeSeriesData; +export const zoomBarStackedAreaTimeSeriesOptions = updateOptions( + Object.assign({}, areaChart.stackedAreaTimeSeriesOptions) +); + export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( Object.assign({}, barChart.simpleBarTimeSeriesOptions) From 636d2b5b4b131ad34a35cded6c4c9fa873ce598a Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 14:28:08 +0800 Subject: [PATCH 104/510] refactor: delete unused axis-zoom service --- packages/core/src/services/axis-zoom.ts | 60 ------------------------- packages/core/src/services/index.ts | 1 - 2 files changed, 61 deletions(-) delete mode 100644 packages/core/src/services/axis-zoom.ts diff --git a/packages/core/src/services/axis-zoom.ts b/packages/core/src/services/axis-zoom.ts deleted file mode 100644 index 8dbb2b3d80..0000000000 --- a/packages/core/src/services/axis-zoom.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Internal Imports -import { Service } from "./service"; -import { Tools } from "../tools"; - -// D3 Imports -import { brushX } from "d3-brush"; -import { event } from "d3-selection"; - -export class AxisZoom extends Service { - brush: any; - - brushed(e) { - if (event) { - const selectedRange = event.selection; - - const mainXAxis = this.services.axes.getMainXAxis(); - const mainXAxisRangeStart = mainXAxis.scale.range()[0]; - - const newDomain = selectedRange.map(d => d + mainXAxisRangeStart) - .map(mainXAxis.scale.invert, mainXAxis.scale); - - this.model.set({ - zoomDomain: newDomain.map(d => new Date(+d)) - }); - } - } - - // We need a custom debounce function here - // because of the async nature of d3.event - debounceWithD3Event(func, wait) { - let timeout; - return function() { - const e = Object.assign({}, event); - const context = this; - const later = function() { - timeout = null; - func.apply(context, [e]); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - } - - getZoomInstance() { - const mainXAxis = this.services.axes.getMainXAxis(); - const mainYAxis = this.services.axes.getMainYAxis(); - const xMaxRange = mainXAxis.scale.range()[1]; - const yMaxRange = mainYAxis.scale.range()[0]; - - this.brush = brushX() - .extent([ - [0, 0], - [xMaxRange, yMaxRange] - ]) - .on("end", this.brushed.bind(this)); - - - return this.brush; - } -} diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index 4e9fcc0e17..d999a07207 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -4,6 +4,5 @@ export * from "./essentials/events"; export * from "./essentials/transitions"; // MISC -export * from "./axis-zoom"; export * from "./scales-cartesian"; export * from "./curves"; From f170785a39efae21cd1530557e35162ad107f962 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 14:41:59 +0800 Subject: [PATCH 105/510] feat: allow addSpaceOnEdges to work with zoom bar - extends domain in zoom bar and brush --- packages/core/src/components/axes/brush.ts | 6 ++- packages/core/src/components/axes/zoom-bar.ts | 13 +++++-- .../core/src/services/scales-cartesian.ts | 39 +++++++++++-------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 517a73eb80..efc35a735d 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -71,7 +71,11 @@ export class Brush extends Component { let zoomDomain = this.model.get("zoomDomain"); if (zoomDomain === undefined) { - zoomDomain = extent(stackDataArray, (d: any) => d.date); // default to full range + // default to full range with extended domain + zoomDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) + ); this.model.set( { zoomDomain: zoomDomain }, { animate: false } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 840d15198f..b3b4b2b571 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -120,9 +120,16 @@ export class ZoomBar extends Component { useAttrs: true }); - xScale - .range([axesLeftMargin, width]) - .domain(extent(stackDataArray, (d: any) => d.date)); + // @todo could be a better function to extend domain with default value + const xDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) + ); + // add value 0 to the extended domain for zoom bar area graph + stackDataArray.unshift({ date: xDomain[0], value: 0 }); + stackDataArray.push({ date: xDomain[1], value: 0 }); + + xScale.range([axesLeftMargin, width]).domain(xDomain); yScale .range([0, this.height - 6]) diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 162b0c833f..9614fea1c2 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -5,7 +5,6 @@ import { AxisPositions, CartesianOrientations, ScaleTypes, - AxesOptions, ThresholdOptions } from "../interfaces"; import { Tools } from "../tools"; @@ -181,7 +180,13 @@ export class CartesianScales extends Service { return this.scales[this.getMainYAxisPosition()]; } - getValueFromScale(scale: any, scaleType: ScaleTypes, axisPosition: AxisPositions, datum: any, index?: number) { + getValueFromScale( + scale: any, + scaleType: ScaleTypes, + axisPosition: AxisPositions, + datum: any, + index?: number + ) { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); const axisOptions = axesOptions[axisPosition]; @@ -201,14 +206,23 @@ export class CartesianScales extends Service { return scaledValue; } - getValueThroughAxisPosition(axisPosition: AxisPositions, datum: any, index?: number) { + getValueThroughAxisPosition( + axisPosition: AxisPositions, + datum: any, + index?: number + ) { const scaleType = this.scaleTypes[axisPosition]; const scale = this.scales[axisPosition]; - return this.getValueFromScale(scale, scaleType, axisPosition, datum, index); + return this.getValueFromScale( + scale, + scaleType, + axisPosition, + datum, + index + ); } - getDomainValue(d, i) { return this.getValueThroughAxisPosition(this.domainAxisPosition, d, i); } @@ -304,7 +318,7 @@ export class CartesianScales extends Service { const domainScale = this.getDomainScale(); // Find the highest threshold for the domain const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value, + (a, b) => b.value - a.value )[0]; const scaleType = this.getScaleTypeByPosition(domainAxisPosition); @@ -318,7 +332,7 @@ export class CartesianScales extends Service { return { threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value), + scaleValue: domainScale(highestThreshold.value) }; } @@ -338,12 +352,12 @@ export class CartesianScales extends Service { const rangeScale = this.getRangeScale(); // Find the highest threshold for the range const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value, + (a, b) => b.value - a.value )[0]; return { threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value), + scaleValue: rangeScale(highestThreshold.value) }; } @@ -458,12 +472,6 @@ export class CartesianScales extends Service { return map(displayData, (d) => d[mapsTo]).keys(); } - // If scale is a TIME scale and zoomDomain is available, return Date array as the domain - const zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain && axisOptions && scaleType === ScaleTypes.TIME) { - return zoomDomain.map(d => new Date(d)); - } - // Get the extent of the domain let domain; let allDataValues; @@ -483,7 +491,6 @@ export class CartesianScales extends Service { domain = extent(allDataValues); domain = this.extendsDomain(axisPosition, domain); - return domain; } From 6022f24f45884cd8ddcf8a420fcb331dba3b32e3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 16:12:13 +0800 Subject: [PATCH 106/510] refactor: code refactoring - reduce code differences from master branch --- .../core/src/services/scales-cartesian.ts | 119 +++++++++--------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 9614fea1c2..11be646307 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -302,65 +302,6 @@ export class CartesianScales extends Service { } } - getHighestDomainThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const domainAxisPosition = this.getDomainAxisPosition(); - - const { thresholds } = axesOptions[domainAxisPosition]; - - if (!thresholds) { - return null; - } - - const domainScale = this.getDomainScale(); - // Find the highest threshold for the domain - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - const scaleType = this.getScaleTypeByPosition(domainAxisPosition); - if ( - scaleType === ScaleTypes.TIME && - (typeof highestThreshold.value === "string" || - highestThreshold.value.getTime === undefined) - ) { - highestThreshold.value = new Date(highestThreshold.value); - } - - return { - threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value) - }; - } - - getHighestRangeThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const rangeAxisPosition = this.getRangeAxisPosition(); - - const { thresholds } = axesOptions[rangeAxisPosition]; - - if (!thresholds) { - return null; - } - - const rangeScale = this.getRangeScale(); - // Find the highest threshold for the range - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - return { - threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value) - }; - } - protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); @@ -491,6 +432,7 @@ export class CartesianScales extends Service { domain = extent(allDataValues); domain = this.extendsDomain(axisPosition, domain); + return domain; } @@ -521,6 +463,65 @@ export class CartesianScales extends Service { return scale; } + + protected getHighestDomainThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const domainAxisPosition = this.getDomainAxisPosition(); + + const { thresholds } = axesOptions[domainAxisPosition]; + + if (!thresholds) { + return null; + } + + const domainScale = this.getDomainScale(); + // Find the highest threshold for the domain + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value + )[0]; + + const scaleType = this.getScaleTypeByPosition(domainAxisPosition); + if ( + scaleType === ScaleTypes.TIME && + (typeof highestThreshold.value === "string" || + highestThreshold.value.getTime === undefined) + ) { + highestThreshold.value = new Date(highestThreshold.value); + } + + return { + threshold: highestThreshold, + scaleValue: domainScale(highestThreshold.value) + }; + } + + protected getHighestRangeThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const rangeAxisPosition = this.getRangeAxisPosition(); + + const { thresholds } = axesOptions[rangeAxisPosition]; + + if (!thresholds) { + return null; + } + + const rangeScale = this.getRangeScale(); + // Find the highest threshold for the range + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value + )[0]; + + return { + threshold: highestThreshold, + scaleValue: rangeScale(highestThreshold.value) + }; + } } function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { From 49205e19ccd1bac1180654c710e9514a53ee1ea1 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 23:25:56 +0800 Subject: [PATCH 107/510] fix: display multiline tooltip with zoom bar - use ruler backdrop as brush area - svg.chart-grid-backdrop --- packages/core/src/axis-chart.ts | 6 +----- packages/core/src/components/axes/brush.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 3690c7b924..f5559f6f56 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -53,11 +53,7 @@ export class AxisChart extends Chart { this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ) { - graphFrameComponents.splice( - 1, - 0, - new Brush(this.model, this.services) - ); + graphFrameComponents.push(new Brush(this.model, this.services)); } const graphFrameComponent = { id: "graph-frame", diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index efc35a735d..35f12159b9 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -146,7 +146,7 @@ export class Brush extends Component { const brushArea = DOMUtils.appendOrSelect( svg, - "g.chart-brush" + "svg.chart-grid-backdrop" ).call(brush); // no need for having default brush selection // @todo try to hide brush after selection From b604235dc97c28f3f0e3005f08449308e569eb94 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:42:26 +0800 Subject: [PATCH 108/510] feat: create ZoomBarOptions and default configuration - default to disable zoombar --- packages/core/src/interfaces/charts.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 6c2567e1b6..e2912c1496 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -41,6 +41,10 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ From 2c87006daa35236045ad6954527214516d934550 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 2 Jul 2020 14:21:12 +0800 Subject: [PATCH 109/510] fix: chart brush with correct range --- packages/core/src/components/axes/brush.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 35f12159b9..c06fdac4c0 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -90,7 +90,7 @@ export class Brush extends Component { zoomDomain = this.model.get("zoomDomain"); // create xScale based on current zoomDomain const xScale = scaleTime() - .range([axesLeftMargin, width]) + .range([0, width]) .domain(zoomDomain); let newDomain = [ @@ -101,9 +101,9 @@ export class Brush extends Component { // check if slected start time and end time are the same if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoombar behavior: set to default full range - newDomain = extent( - stackDataArray, - (d: any) => d.date + newDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) ); } @@ -139,15 +139,19 @@ export class Brush extends Component { const brush = brushX() .extent([ - [xScaleStart, 0], + [0, 0], [width, yScaleEnd] ]) .on("end", brushed); - - const brushArea = DOMUtils.appendOrSelect( + const backdrop = DOMUtils.appendOrSelect( svg, "svg.chart-grid-backdrop" + ); + const brushArea = DOMUtils.appendOrSelect( + backdrop, + "g.chart-brush" ).call(brush); + // no need for having default brush selection // @todo try to hide brush after selection setTimeout(() => { From d325a229e487888a4745f7e21038cc9b72b291a3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 2 Jul 2020 15:59:02 +0800 Subject: [PATCH 110/510] fix: remove graph out of zoom domain --- .../core/src/components/graphs/bar-grouped.ts | 4 ++++ .../core/src/components/graphs/bar-simple.ts | 5 +++++ .../core/src/components/graphs/bar-stacked.ts | 4 ++++ packages/core/src/components/graphs/bar.ts | 12 ++++++++++++ packages/core/src/components/graphs/scatter.ts | 16 ++++++++++++++++ 5 files changed, 41 insertions(+) diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 48377b5957..07f12e8c64 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -123,6 +123,10 @@ export class GroupedBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(0); const y1 = this.services.cartesianScales.getRangeValue(d.value); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } return Tools.generateSVGPathString( { x0, x1, y0, y1 }, this.services.cartesianScales.getOrientation() diff --git a/packages/core/src/components/graphs/bar-simple.ts b/packages/core/src/components/graphs/bar-simple.ts index 2cbf168934..aeb72950d1 100644 --- a/packages/core/src/components/graphs/bar-simple.ts +++ b/packages/core/src/components/graphs/bar-simple.ts @@ -70,6 +70,11 @@ export class SimpleBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(0); const y1 = this.services.cartesianScales.getRangeValue(d, i); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } + return Tools.generateSVGPathString( { x0, x1, y0, y1 }, this.services.cartesianScales.getOrientation() diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index 3c9bcbdb9c..55d376f835 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -97,6 +97,10 @@ export class StackedBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(d[0], i); let y1 = this.services.cartesianScales.getRangeValue(d[1], i); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } // Add the divider gap if ( Math.abs(y1 - y0) > 0 && diff --git a/packages/core/src/components/graphs/bar.ts b/packages/core/src/components/graphs/bar.ts index dff83021d5..59c18f5685 100644 --- a/packages/core/src/components/graphs/bar.ts +++ b/packages/core/src/components/graphs/bar.ts @@ -16,4 +16,16 @@ export class Bar extends Component { return Math.min(options.bars.maxWidth, mainXScale.step() / 2); } + + protected isOutOfZoomDomain(x0: number, x1: number) { + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain !== undefined) { + const domainScale = this.services.cartesianScales.getDomainScale(); + return ( + x0 < domainScale(zoomDomain[0]) || + x1 > domainScale(zoomDomain[1]) + ); + } + return false; + } } diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index 0ceab80518..2d8074dc49 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -37,6 +37,19 @@ export class Scatter extends Component { } } + filterOutOfDomain(data) { + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain !== undefined) { + return data.filter( + (d) => + d[domainIdentifier] > zoomDomain[0] && + d[domainIdentifier] < zoomDomain[1] + ); + } + return data; + } + render(animate: boolean) { // Grab container SVG const svg = this.getContainerSVG(); @@ -64,6 +77,9 @@ export class Scatter extends Component { ); } + // filter out of domain data + scatterData = this.filterOutOfDomain(scatterData); + // Update data on dot groups const circles = svg .selectAll("circle.dot") From 5e6a58e6155a1f605d22e41be7e73ed3e1af8815 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 12:44:35 +0800 Subject: [PATCH 111/510] refactor: code refactoring - create getZoomBarData(), getDefaultZoomBarDomain() in model - remove unused code --- packages/core/src/components/axes/brush.ts | 193 ++++------- packages/core/src/components/axes/cover.ts | 25 +- packages/core/src/components/axes/zoom-bar.ts | 312 ++++++++---------- packages/core/src/model.ts | 46 ++- 4 files changed, 256 insertions(+), 320 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index c06fdac4c0..16f21dce44 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -15,149 +15,80 @@ export class Brush extends Component { render(animate = true) { const svg = this.parent; + const backdrop = DOMUtils.appendOrSelect( + svg, + "svg.chart-grid-backdrop" + ); + const { width, height } = DOMUtils.getSVGElementSize(backdrop, { + useAttrs: true + }); + const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( mainXAxisPosition ); - // get axes margins - let axesLeftMargin = 0; - const axesMargins = this.model.get("axesMargins"); - if (axesMargins && axesMargins.left) { - axesLeftMargin = axesMargins.left; - } - const mainXScale = this.services.cartesianScales.getMainXScale(); - const mainYScale = this.services.cartesianScales.getMainYScale(); - - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const [yScaleEnd, yScaleStart] = mainYScale.range(); - - if (mainXScale) { - const displayData = this.model.getDisplayData(); - - if (mainXScaleType === ScaleTypes.TIME) { - // Get all date values provided in data - // @todo - Could be re-used through the model - let allDates = []; - displayData.forEach((data) => { - allDates = allDates.concat(Number(data.date)); - }); - allDates = Tools.removeArrayDuplicates(allDates).sort(); - - // Go through all date values - // And get corresponding data from each dataset - const stackDataArray = allDates.map((date) => { - let count = 0; - let correspondingSum = 0; - const correspondingData = {}; - - displayData.forEach((data) => { - if (Number(data.date) === Number(date)) { - ++count; - correspondingSum += data.value; - } - }); - correspondingData["date"] = date; - correspondingData["value"] = correspondingSum; - - return correspondingData; - }); - const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); - - let zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain === undefined) { - // default to full range with extended domain - zoomDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - this.model.set( - { zoomDomain: zoomDomain }, - { animate: false } - ); - } - - const brushed = () => { - const selection = event.selection; - - if (selection !== null) { - // get current zoomDomain - zoomDomain = this.model.get("zoomDomain"); - // create xScale based on current zoomDomain - const xScale = scaleTime() - .range([0, width]) - .domain(zoomDomain); - - let newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // check if slected start time and end time are the same - if (newDomain[0].valueOf() === newDomain[1].valueOf()) { - // same as d3 behavior and zoombar behavior: set to default full range - newDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - } + 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.model.getDefaultZoomBarDomain(); + this.model.set({ zoomDomain: zoomDomain }, { animate: false }); + } - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { - // only if zoomDomain needs update - if ( - zoomDomain[0].valueOf() !== - newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== - newDomain[1].valueOf() - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } - // call external callback - const zoomBarOptions = this.model.getOptions() - .zoomBar; - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd( - selection, - newDomain - ); - } - } + const brushed = () => { + const selection = event.selection; + + if (selection !== null) { + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([0, width]) + .domain(zoomDomain); + + let newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + // 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.model.getDefaultZoomBarDomain(); } - }; - const brush = brushX() - .extent([ - [0, 0], - [width, yScaleEnd] - ]) - .on("end", brushed); - const backdrop = DOMUtils.appendOrSelect( - svg, - "svg.chart-grid-backdrop" - ); - const brushArea = DOMUtils.appendOrSelect( - backdrop, - "g.chart-brush" - ).call(brush); + // only if zoomDomain needs update + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + } - // no need for having default brush selection - // @todo try to hide brush after selection - setTimeout(() => { - brushArea.call(brush.move); - }, 0); - } + // clear brush selection + brushArea.call(brush.move, null); + } + }; + + // leave some space to display selection strokes besides axis + const brush = brushX() + .extent([ + [2, 0], + [width - 1, height - 1] + ]) + .on("end", brushed); + + const brushArea = DOMUtils.appendOrSelect( + backdrop, + "g.chart-brush" + ).call(brush); } } } diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts index 16179b45c5..2536c9ef40 100644 --- a/packages/core/src/components/axes/cover.ts +++ b/packages/core/src/components/axes/cover.ts @@ -1,13 +1,7 @@ // Internal Imports import { Component } from "../component"; -import { Tools } from "../../tools"; import { DOMUtils } from "../../services"; -// D3 Imports -import { axisBottom, axisLeft } from "d3-axis"; -import { mouse, select } from "d3-selection"; -import { TooltipTypes, Events } from "../../interfaces"; - export class Cover extends Component { type = "cover"; @@ -18,19 +12,21 @@ export class Cover extends Component { this.createCover(); } - createCover() { const svg = this.parent; - const mainXScale = this.services.cartesianScales.getMainXScale(); - const mainYScale = this.services.cartesianScales.getMainYScale(); + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); const [xScaleStart, xScaleEnd] = mainXScale.range(); const [yScaleEnd, yScaleStart] = mainYScale.range(); // Get height - this.coverClipPath = DOMUtils.appendOrSelect(svg, `clipPath.${this.type}`); - this.coverClipPath - .attr("id", `${this.type}Clip`); + this.coverClipPath = DOMUtils.appendOrSelect( + svg, + `clipPath.${this.type}` + ); + this.coverClipPath.attr("id", `${this.type}Clip`); const coverRect = DOMUtils.appendOrSelect( this.coverClipPath, "rect.cover" @@ -41,14 +37,11 @@ export class Cover extends Component { .attr("width", xScaleEnd - xScaleStart) .attr("height", yScaleEnd - yScaleStart); - this.coverClipPath - .merge(coverRect) - .lower(); + this.coverClipPath.merge(coverRect).lower(); const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); coverG .attr("clip-path", `url(#${this.type}Clip)`) .attr("id", `g-${this.type}Clip`); - } } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index b3b4b2b571..6e3d8efe91 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,11 +13,13 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + brushSelector = "g.brush"; // needs to be this value for d3.brush API + clipId = "zoomBarClip"; height = 32; - ogXScale: any; + spacerHeight = 20; brush = brushX(); @@ -64,9 +66,9 @@ export class ZoomBar extends Component { const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) - .attr("y", 32) + .attr("y", this.height) .attr("width", "100%") - .attr("height", 20) + .attr("height", this.spacerHeight) .attr("opacity", 1) .attr("fill", "none"); @@ -76,189 +78,134 @@ export class ZoomBar extends Component { .attr("width", "100%") .attr("height", "100%"); - if (mainXScale) { - const displayData = this.model.getDisplayData(); - - if (mainXScaleType === ScaleTypes.TIME) { - // Get all date values provided in data - // TODO - Could be re-used through the model - let allDates = []; - displayData.forEach((data) => { - allDates = allDates.concat(Number(data.date)); - }); - allDates = Tools.removeArrayDuplicates(allDates).sort(); - - // Go through all date values - // And get corresponding data from each dataset - const stackDataArray = allDates.map((date) => { - let count = 0; - let correspondingSum = 0; - const correspondingData = {}; - - displayData.forEach((data) => { - if (Number(data.date) === Number(date)) { - ++count; - correspondingSum += data.value; - } - }); - correspondingData["date"] = date; - correspondingData["value"] = correspondingSum; - - return correspondingData; - }); - - if (!this.ogXScale) { - this.ogXScale = cartesianScales.getDomainScale(); - } - const xScale = mainXScale.copy(); - if (!this.ogXScale) { - this.ogXScale = xScale; - } - const yScale = mainYScale.copy(); + if (mainXScale && mainXScaleType === ScaleTypes.TIME) { + const zoomBarData = this.model.getZoomBarData(); + const xScale = mainXScale.copy(); + const yScale = mainYScale.copy(); - const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); - // @todo could be a better function to extend domain with default value - const xDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - // add value 0 to the extended domain for zoom bar area graph - stackDataArray.unshift({ date: xDomain[0], value: 0 }); - stackDataArray.push({ date: xDomain[1], value: 0 }); + const defaultDomain = this.model.getDefaultZoomBarDomain(); + // add value 0 to the extended domain for zoom bar area graph + this.compensateDataForDefaultDomain(zoomBarData, defaultDomain, 0); - xScale.range([axesLeftMargin, width]).domain(xDomain); + xScale.range([axesLeftMargin, width]).domain(defaultDomain); - yScale - .range([0, this.height - 6]) - .domain(extent(stackDataArray, (d: any) => d.value)); + yScale + .range([0, this.height - 6]) + .domain(extent(zoomBarData, (d: any) => d.value)); - const zoomDomain = this.model.get("zoomDomain"); + const zoomDomain = this.model.get("zoomDomain"); - // D3 line generator function - const lineGenerator = line() - .x((d, i) => + // D3 line generator function + const lineGenerator = line() + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) + .y( + (d, i) => + this.height - cartesianScales.getValueFromScale( - xScale, - mainXScaleType, - mainXAxisPosition, + yScale, + mainYScaleType, + mainYAxisPosition, d, i ) - ) - .y( - (d, i) => - this.height - - cartesianScales.getValueFromScale( - yScale, - mainYScaleType, - mainYAxisPosition, - d, - i - ) - ) - .curve(this.services.curves.getD3Curve()); - const accessorFunc = (scale, scaleType, axisPosition) => { - return (d, i) => { - return cartesianScales.getValueFromScale( - scale, - scaleType, - axisPosition, - d, - i - ); - }; - }; - this.renderZoomBarArea( - container, - "path.zoom-graph-area-unselected", - accessorFunc(xScale, mainXScaleType, mainXAxisPosition), - accessorFunc(yScale, mainYScaleType, mainYAxisPosition), - stackDataArray, - animate, - undefined - ); - this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); - this.renderZoomBarArea( - container, - "path.zoom-graph-area", - accessorFunc(xScale, mainXScaleType, mainXAxisPosition), - accessorFunc(yScale, mainYScaleType, mainYAxisPosition), - stackDataArray, - animate, - "zoomBarClip" - ); - const baselineGenerator = line()([ - [axesLeftMargin, this.height], - [width, this.height] - ]); - const zoomBaseline = DOMUtils.appendOrSelect( - container, - "path.zoom-bg-baseline" - ).attr("d", baselineGenerator); - - const brushEventListener = () => { - const selection = event.selection; - // follow d3 behavior: when selection is null, reset default full range - // @todo find a better way to handel the situation when selection is null - // select behavior is completed, but nothing selected - if (selection === null) { - this.brushed(zoomDomain, xScale, xScale.range()); - } else if (selection[0] === selection[1]) { - // select behavior is not completed yet, do nothing - } else { - this.brushed(zoomDomain, xScale, selection); - } + ) + .curve(this.services.curves.getD3Curve()); + const accessorFunc = (scale, scaleType, axisPosition) => { + return (d, i) => { + return cartesianScales.getValueFromScale( + scale, + scaleType, + axisPosition, + d, + i + ); }; + }; + this.renderZoomBarArea( + container, + "path.zoom-graph-area-unselected", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + zoomBarData, + animate, + undefined + ); + this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); + this.renderZoomBarArea( + container, + "path.zoom-graph-area", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + zoomBarData, + animate, + this.clipId + ); + const baselineGenerator = line()([ + [axesLeftMargin, this.height], + [width, this.height] + ]); + const zoomBaseline = DOMUtils.appendOrSelect( + container, + "path.zoom-bg-baseline" + ).attr("d", baselineGenerator); + + const brushEventListener = () => { + const selection = event.selection; + // follow d3 behavior: when selection is null, reset default full range + // select behavior is completed, but nothing selected + if (selection === null) { + this.brushed(zoomDomain, xScale, xScale.range()); + } else if (selection[0] === selection[1]) { + // select behavior is not completed yet, do nothing + } else { + this.brushed(zoomDomain, xScale, selection); + } + }; - this.brush - .extent([ - [axesLeftMargin, 0], - [width, this.height] - ]) - .on("start brush end", null) // remove old listener first - .on("start brush end", brushEventListener); - - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( - this.brush - ); - - if (zoomDomain === undefined) { - // do nothing, initialization not completed yet + this.brush + .extent([ + [axesLeftMargin, 0], + [width, this.height] + ]) + .on("start brush end", null) // remove old listener first + .on("start brush end", brushEventListener); + + const brushArea = DOMUtils.appendOrSelect( + svg, + this.brushSelector + ).call(this.brush); + + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + } else if (zoomDomain[0].valueOf() === zoomDomain[1].valueOf()) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle(this.getContainerSVG(), xScale.range()); + } else { + const selected = zoomDomain.map((domain) => xScale(domain)); + if (selected[1] - selected[0] < 1) { + // initialization not completed yet // don't update brushHandle to avoid flash - } else if ( - zoomDomain[0].valueOf() === zoomDomain[1].valueOf() - ) { - brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle( - this.getContainerSVG(), - xScale.range() - ); } else { - const selected = zoomDomain.map((domain) => xScale(domain)); - if (selected[1] - selected[0] < 1) { - // initialization not completed yet - // don't update brushHandle to avoid flash - } else { - brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle( - this.getContainerSVG(), - selected - ); - } + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle(this.getContainerSVG(), selected); } } } } - // could be used by Toolbar - // zoomIn() { - // const mainXScale = this.services.cartesianScales.getMainXScale(); - // console.log("zoom in", mainXScale.domain()); - // } - // brush event listener brushed(zoomDomain, scale, selection) { // update brush handle position @@ -315,7 +262,7 @@ export class ZoomBar extends Component { const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; // handle - svg.select("g.brush") + svg.select(this.brushSelector) .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { @@ -330,7 +277,7 @@ export class ZoomBar extends Component { .attr("height", handleHeight) .style("display", null); // always display // handle-bar - svg.select("g.brush") + svg.select(this.brushSelector) .selectAll("rect.handle-bar") .data([{ type: "w" }, { type: "e" }]) .join("rect") @@ -364,7 +311,7 @@ export class ZoomBar extends Component { querySelector, xFunc, y1Func, - datum, + data, animate, clipId ) { @@ -374,7 +321,7 @@ export class ZoomBar extends Component { .y1((d, i) => this.height - y1Func(d, i)); const areaGraph = DOMUtils.appendOrSelect(container, querySelector) - .datum(datum) + .datum(data) .attr("d", areaGenerator); if (clipId) { @@ -394,6 +341,29 @@ export class ZoomBar extends Component { .attr("height", height); } + // assume the domains in data are already sorted + compensateDataForDefaultDomain(data, defaultDomain, value) { + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); + // if min domain is extended + if (Number(defaultDomain[0]) < Number(data[0][domainIdentifier])) { + const newDatum = {}; + newDatum[domainIdentifier] = defaultDomain[0]; + newDatum[rangeIdentifier] = value; + data.unshift(newDatum); + } + // if max domain is extended + if ( + Number(defaultDomain[1]) > + Number(data[data.length - 1][domainIdentifier]) + ) { + const newDatum = {}; + newDatum[domainIdentifier] = defaultDomain[1]; + newDatum[rangeIdentifier] = value; + data.push(newDatum); + } + } + destroy() { this.brush.on("start brush end", null); // remove event listener this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 999d689680..a795d809e7 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -5,8 +5,9 @@ import * as colorPalettes from "./services/colorPalettes"; import { Events, ScaleTypes } from "./interfaces"; // D3 -import { scaleOrdinal } from "d3-scale"; +import { extent } from "d3-array"; import { map } from "d3-collection"; +import { scaleOrdinal } from "d3-scale"; import { stack } from "d3-shape"; /** The charting model layer which includes mainly the chart data and options, @@ -38,7 +39,48 @@ export class ChartModel { constructor(services: any) { this.services = services; } + // get display data for zoom bar + // basically it's sum of value grouped by time + getZoomBarData() { + const { cartesianScales } = this.services; + const domainIdentifier = cartesianScales.getDomainIdentifier(); + const rangeIdentifier = cartesianScales.getRangeIdentifier(); + + const displayData = this.getDisplayData(); + // get all dates (Number) in displayData + let allDates = []; + displayData.forEach((data) => { + allDates = allDates.concat(Number(data[domainIdentifier])); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + // Go through all date values + // And get corresponding data from each dataset + return allDates.map((date) => { + let sum = 0; + const datum = {}; + + displayData.forEach((data) => { + if (Number(data[domainIdentifier]) === date) { + sum += data[rangeIdentifier]; + } + }); + datum[domainIdentifier] = new Date(date); + datum[rangeIdentifier] = sum; + return datum; + }); + } + getDefaultZoomBarDomain() { + const zoomBarData = this.getZoomBarData(); + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const domainIdentifier = cartesianScales.getDomainIdentifier(); + // default to full range with extended domain + return cartesianScales.extendsDomain( + mainXAxisPosition, + extent(zoomBarData, (d: any) => d[domainIdentifier]) + ); + } getAllDataFromDomain() { if (!this.get("data")) { return null; @@ -95,7 +137,7 @@ export class ChartModel { return allDataFromDomain.filter((datum) => { const group = dataGroups.find( - (group) => group.name === datum[groupMapsTo] + (g) => g.name === datum[groupMapsTo] ); return group.status === ACTIVE; From 14b913929f586f89d9954fb36350572b58a3a12f Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 16:13:14 +0800 Subject: [PATCH 112/510] fix: set min selection difference threshold --- packages/core/src/components/axes/zoom-bar.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6e3d8efe91..6c1dd80fdb 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,6 +13,11 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + // The minimum selection x range to trigger handler update + // Smaller number may introduce a handler flash during initialization + // Bigger number may not trigger handler update while selection area on chart is very small + MIN_SELECTION_DIFF = 9e-10; + brushSelector = "g.brush"; // needs to be this value for d3.brush API clipId = "zoomBarClip"; @@ -195,7 +200,7 @@ export class ZoomBar extends Component { this.updateBrushHandle(this.getContainerSVG(), xScale.range()); } else { const selected = zoomDomain.map((domain) => xScale(domain)); - if (selected[1] - selected[0] < 1) { + if (selected[1] - selected[0] < this.MIN_SELECTION_DIFF) { // initialization not completed yet // don't update brushHandle to avoid flash } else { From 7ed16a35c9a1a9f4b148d3c51c079ff117a00aa2 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 16:46:44 +0800 Subject: [PATCH 113/510] refactor: code refactoring - zoom-bar.scss - remove unused scss settings --- packages/core/src/components/axes/axis.ts | 2 +- packages/core/src/components/axes/zoom-bar.ts | 2 +- .../core/src/styles/components/_zoom-bar.scss | 24 +++++++++---------- packages/core/src/styles/graphs/index.scss | 4 ---- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 484106f8fa..38f4c2b408 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -109,7 +109,7 @@ export class Axis extends Component { // if zoomDomain is available, update scale domain to Date array. const zoomDomain = this.model.get("zoomDomain"); if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain.map((d) => new Date(d))); + scale.domain(zoomDomain); } // Identify the corresponding d3 axis function diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6c1dd80fdb..edcb1f792b 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -18,7 +18,7 @@ export class ZoomBar extends Component { // Bigger number may not trigger handler update while selection area on chart is very small MIN_SELECTION_DIFF = 9e-10; - brushSelector = "g.brush"; // needs to be this value for d3.brush API + brushSelector = "g.zoom-bar-brush"; // needs to match the style in _zoom-bar.scss clipId = "zoomBarClip"; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 32dfc32842..13c20ecc30 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -19,17 +19,17 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: none; } - g.brush rect.handle { - fill: $icon-02; - } - - g.brush rect.handle-bar { - fill: $ui-02; - } - - // clear d3.brush selection style - g.brush rect.selection { - fill: none; - stroke: none; + g.zoom-bar-brush { + rect.handle { + fill: $icon-02; + } + rect.handle-bar { + fill: $ui-02; + } + // clear d3.brush selection style + rect.selection { + fill: none; + stroke: none; + } } } diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index bebafaffc3..5530eba1a0 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -6,7 +6,3 @@ @import "./scatter-stacked"; @import "./radar"; @import "./gauge"; - -svg.#{$prefix}--#{$charts-prefix}--chart-svg svg.graph-frame { - overflow-x: hidden; -} From b9cff61db47a4a0676d00a3d9cc5996d0c3fa8ba Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 6 Jul 2020 17:09:13 +0800 Subject: [PATCH 114/510] fix: avoid extra/duplicate external callback --- packages/core/src/components/axes/brush.ts | 44 ++++++++++++++++++- packages/core/src/components/axes/zoom-bar.ts | 38 +++++++++------- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 16f21dce44..8067e6aaa8 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -40,6 +40,47 @@ export class Brush extends Component { this.model.set({ zoomDomain: zoomDomain }, { animate: false }); } + const eventHandler = () => { + const selection = event.selection; + const xScale = scaleTime().range([0, width]).domain(zoomDomain); + + const newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + if ( + selection != null && + event.sourceEvent != null && + (event.sourceEvent.type === "mousemove" || + event.sourceEvent.type === "mouseup" || + event.sourceEvent.type === "mousedown") + ) { + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress( + selection, + newDomain + ); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + }; const brushed = () => { const selection = event.selection; @@ -83,7 +124,8 @@ export class Brush extends Component { [2, 0], [width - 1, height - 1] ]) - .on("end", brushed); + .on("start brush end", eventHandler) + .on("end.brushed", brushed); const brushArea = DOMUtils.appendOrSelect( backdrop, diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index edcb1f792b..05722a4116 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -237,23 +237,27 @@ export class ZoomBar extends Component { ) { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if (zoomBarOptions.selectionEnd !== undefined && event.type === "end") { - zoomBarOptions.selectionEnd(selection, newDomain); + + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } } } From 117508c9c977d9820d8eb62b6def050232e8a9e7 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 7 Jul 2020 12:33:39 +0800 Subject: [PATCH 115/510] fix: remove ZoomBarOptions in BaseChartOptions --- packages/core/src/interfaces/charts.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index e2912c1496..3e29930b2a 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -41,10 +41,6 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; - /** - * zoombar configuration - */ - zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ @@ -217,7 +213,7 @@ export interface StackedAreaChartOptions extends ScatterChartOptions { | string | { name: string; - }; + }; } /** From 87b750d9dc25db2c231fbe7c9391b1057c5e2f90 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 11:28:33 +0800 Subject: [PATCH 116/510] refactor: change initZoomDomain to initialZoomDomain - remove unnecessary undefined setting --- packages/core/demo/data/zoom-bar.ts | 27 +++++++++---------- packages/core/src/components/axes/zoom-bar.ts | 4 +-- packages/core/src/configuration.ts | 1 - packages/core/src/interfaces/components.ts | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 0016bbcad7..fbe74fba2a 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -23,21 +23,20 @@ const selectionEndFun = (selection, domain) => { console.log(domain); }; -const initZoomDomain = [ +const initialZoomDomain = [ new Date(2020, 11, 10, 23, 59, 25), new Date(2020, 11, 11, 0, 0, 25) ]; const defaultZoomBarOptions = { enabled: true, - initZoomDomain: undefined, selectionStart: selectionStartFun, selectionInProgress: selectionInProgressFun, selectionEnd: selectionEndFun }; // utility function to update title and enable zoomBar option -const updateOptions = (options) => { +const addZoomBarToOptions = (options) => { options["title"] = options["title"] + " - Zoom bar enabled"; options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); return options; @@ -45,51 +44,51 @@ const updateOptions = (options) => { export const zoomBarStackedAreaTimeSeriesData = areaChart.stackedAreaTimeSeriesData; -export const zoomBarStackedAreaTimeSeriesOptions = updateOptions( +export const zoomBarStackedAreaTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, areaChart.stackedAreaTimeSeriesOptions) ); export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; -export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( +export const zoomBarSimpleBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.simpleBarTimeSeriesOptions) ); export const zoomBarStackedBarTimeSeriesData = barChart.stackedBarTimeSeriesData; -export const zoomBarStackedBarTimeSeriesOptions = updateOptions( +export const zoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.stackedBarTimeSeriesOptions) ); export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; -export const zoomBarBubbleTimeSeriesOptions = updateOptions( +export const zoomBarBubbleTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) ); export const zoomBarLineTimeSeriesData = lineChart.lineTimeSeriesData; -export const zoomBarLineTimeSeriesOptions = updateOptions( +export const zoomBarLineTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, lineChart.lineTimeSeriesOptions) ); export const zoomBarScatterTimeSeriesData = scatterChart.scatterTimeSeriesData; -export const zoomBarScatterTimeSeriesOptions = updateOptions( +export const zoomBarScatterTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, scatterChart.scatterTimeSeriesOptions) ); export const zoomBarStepTimeSeriesData = stepChart.stepTimeSeriesData; -export const zoomBarStepTimeSeriesOptions = updateOptions( +export const zoomBarStepTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, stepChart.stepTimeSeriesOptions) ); export const zoomBarLineTimeSeries15secondsData = timeSeriesAxisChart.lineTimeSeriesData15seconds; -export const zoomBarLineTimeSeries15secondsOptions = updateOptions( +export const zoomBarLineTimeSeries15secondsOptions = addZoomBarToOptions( Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) ); export const zoomBarLineTimeSeriesInitDomainData = timeSeriesAxisChart.lineTimeSeriesData15seconds; -export const zoomBarLineTimeSeriesInitDomainOptions = updateOptions( +export const zoomBarLineTimeSeriesInitDomainOptions = addZoomBarToOptions( Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) ); -zoomBarLineTimeSeriesInitDomainOptions["title"] += " with initial zoom domain"; -zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initZoomDomain = initZoomDomain; +zoomBarLineTimeSeriesInitDomainOptions["title"] += " zoomed domain"; +zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initialZoomDomain = initialZoomDomain; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 05722a4116..2bbd6ad4ea 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -35,9 +35,9 @@ export class ZoomBar extends Component { // get initZoomDomain const zoomBarOptions = this.model.getOptions().zoomBar; - if (zoomBarOptions.initZoomDomain !== undefined) { + if (zoomBarOptions.initialZoomDomain !== undefined) { this.model.set( - { zoomDomain: zoomBarOptions.initZoomDomain }, + { zoomDomain: zoomBarOptions.initialZoomDomain }, { skipUpdate: true } ); } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 70e7095f54..b7e8e301d8 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -131,7 +131,6 @@ export const timeScale: TimeScaleOptions = { */ export const zoomBar: ZoomBarOptions = { enabled: false, - initZoomDomain: undefined, selectionStart: undefined, selectionInProgress: undefined, selectionEnd: undefined diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 6fd842a302..3c13b8beef 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -124,7 +124,7 @@ export interface ZoomBarOptions { /** * an two element array which represents the initial zoom domain */ - initZoomDomain?: Object[]; + initialZoomDomain?: Object[]; /** * a function to handle selection start event From c86b37d6748958e14a4bfa1b94245280f014b7d2 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 12:40:27 +0800 Subject: [PATCH 117/510] refactor: use Tools.getProperty to load zoomBarOptions --- packages/core/src/axis-chart.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index f5559f6f56..c7feb56bae 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -31,6 +31,11 @@ export class AxisChart extends Chart { } protected getAxisChartComponents(graphFrameComponents: any[]) { + const zoomBarEnabled = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "enabled" + ); const titleComponent = { id: "title", components: [new Title(this.model, this.services)], @@ -49,12 +54,10 @@ export class AxisChart extends Chart { } }; - if ( - this.model.getOptions().zoomBar && - this.model.getOptions().zoomBar.enabled - ) { + if (zoomBarEnabled) { graphFrameComponents.push(new Brush(this.model, this.services)); } + const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, @@ -156,7 +159,7 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } - if (this.model.getOptions().zoomBar.enabled === true) { + if (zoomBarEnabled) { topLevelLayoutComponents.push(zoomBarComponent); } topLevelLayoutComponents.push(fullFrameComponent); From 4f0797565f034a3cca911a317d2c0e1ebac36919 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 13:37:33 +0800 Subject: [PATCH 118/510] refactor: update code format --- packages/core/src/charts/pie.ts | 2 +- packages/core/src/charts/radar.ts | 6 ++++-- packages/core/src/components/component.ts | 7 ++++--- packages/core/src/components/index.ts | 1 - packages/core/src/model.ts | 5 ++++- packages/core/src/services/essentials/dom-utils.ts | 5 +++-- packages/core/src/styles/components/_zoom-bar.scss | 3 +++ 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 4642ea8f2d..87b74217aa 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -52,7 +52,7 @@ export class PieChart extends Chart { skeleton: Skeletons.PIE }) ]; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 80b8e7888c..c9b3d67e6e 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -41,8 +41,10 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; - + const graphFrameComponents: any[] = [ + new Radar(this.model, this.services) + ]; + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 6aa5e17ee0..b5996e519c 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -103,15 +103,16 @@ export class Component { this.type === "stacked-bar" || this.type === "scatter-stacked" ) { - return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); - + return DOMUtils.appendOrSelectForAxisChart( + this.parent, + `clipPath.cover` + ); } else { return DOMUtils.appendOrSelect( this.parent, `g.${settings.prefix}--${chartprefix}--${this.type}` ); } - } return this.parent; diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 4d9959743a..1534f8db5a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -37,4 +37,3 @@ export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; - diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index a795d809e7..be8b412eb0 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -70,17 +70,20 @@ export class ChartModel { return datum; }); } + getDefaultZoomBarDomain() { const zoomBarData = this.getZoomBarData(); const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const domainIdentifier = cartesianScales.getDomainIdentifier(); + // default to full range with extended domain return cartesianScales.extendsDomain( mainXAxisPosition, extent(zoomBarData, (d: any) => d[domainIdentifier]) ); } + getAllDataFromDomain() { if (!this.get("data")) { return null; @@ -137,7 +140,7 @@ export class ChartModel { return allDataFromDomain.filter((datum) => { const group = dataGroups.find( - (g) => g.name === datum[groupMapsTo] + (dataGroup) => dataGroup.name === datum[groupMapsTo] ); return group.status === ACTIVE; diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index 5974f62da0..45d2aee43d 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -150,11 +150,12 @@ export class DOMUtils extends Service { } static appendOrSelectForAxisChart(parent, query) { - const querySections = query.split("."); const elementToAppend = querySections[0]; - const parentOfSelection = parent.select(`${elementToAppend}.${querySections.slice(1).join(" ")}`); + const parentOfSelection = parent.select( + `${elementToAppend}.${querySections.slice(1).join(" ")}` + ); const selection = parent.select(`g#g-coverClip`); if (parentOfSelection.empty() && parent) { parent diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 13c20ecc30..d52d6c7978 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -14,6 +14,7 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: $ui-04; stroke-width: 1; } + path.zoom-graph-area-unselected { fill: $ui-01; stroke: none; @@ -23,9 +24,11 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { rect.handle { fill: $icon-02; } + rect.handle-bar { fill: $ui-02; } + // clear d3.brush selection style rect.selection { fill: none; From ee2efbdfd30d6244b5fad22ed77d5da01a044549 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 15:38:10 +0800 Subject: [PATCH 119/510] refactor: use Tools.getProperty to get initialZoomDomain --- packages/core/src/components/axes/zoom-bar.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 2bbd6ad4ea..32c4cbb61f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -34,10 +34,14 @@ export class ZoomBar extends Component { }); // get initZoomDomain - const zoomBarOptions = this.model.getOptions().zoomBar; - if (zoomBarOptions.initialZoomDomain !== undefined) { + const initialZoomDomain = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "initialZoomDomain" + ); + if (initialZoomDomain !== null) { this.model.set( - { zoomDomain: zoomBarOptions.initialZoomDomain }, + { zoomDomain: initialZoomDomain }, { skipUpdate: true } ); } From 37c9afef12bfa8a2483db34139c156f4ca1a3db8 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 12:13:31 +0800 Subject: [PATCH 120/510] refactor: Change Cover to ChartClip --- packages/core/src/charts/area-stacked.ts | 4 +- packages/core/src/charts/area.ts | 4 +- packages/core/src/charts/bar-grouped.ts | 4 +- packages/core/src/charts/bar-simple.ts | 4 +- packages/core/src/charts/bar-stacked.ts | 4 +- packages/core/src/charts/bubble.ts | 4 +- packages/core/src/charts/line.ts | 4 +- packages/core/src/charts/scatter.ts | 4 +- .../core/src/components/axes/chart-clip.ts | 50 +++++++++++++++++++ packages/core/src/components/axes/cover.ts | 47 ----------------- packages/core/src/components/component.ts | 2 +- packages/core/src/components/index.ts | 2 +- .../core/src/services/essentials/dom-utils.ts | 8 +-- 13 files changed, 72 insertions(+), 69 deletions(-) create mode 100644 packages/core/src/components/axes/chart-clip.ts delete mode 100644 packages/core/src/components/axes/cover.ts diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 42895bdc6b..8104c10ef0 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,7 +6,7 @@ import { Tools } from "../tools"; // Components import { - Cover, + ChartClip, Grid, StackedArea, TwoDimensionalAxes, @@ -36,7 +36,7 @@ export class StackedAreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new StackedArea(this.model, this.services), diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 2b313f6cc7..23959cce54 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,7 +6,7 @@ import { Tools } from "../tools"; // Components import { - Cover, + ChartClip, Grid, Area, Line, @@ -40,7 +40,7 @@ export class AreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 59354739e6..0c259fb01e 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, GroupedBar, TwoDimensionalAxes, @@ -40,7 +40,7 @@ export class GroupedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new GroupedBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 5709ea0e9b..4d6d17345a 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, SimpleBar, TwoDimensionalAxes, @@ -40,7 +40,7 @@ export class SimpleBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new SimpleBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index c2510fa7da..a680c9402b 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, StackedBar, StackedBarRuler, @@ -43,7 +43,7 @@ export class StackedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new StackedBarRuler(this.model, this.services), new StackedBar(this.model, this.services), diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 7238de0aa3..090a72ec29 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Ruler, Bubble, @@ -43,7 +43,7 @@ export class BubbleChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Bubble(this.model, this.services), diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index ee4e07f163..3e0c2959b2 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Line, Ruler, @@ -41,7 +41,7 @@ export class LineChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 2acacdf9df..6eebc8a9e0 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Ruler, Scatter, @@ -43,7 +43,7 @@ export class ScatterChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Scatter(this.model, this.services), diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts new file mode 100644 index 0000000000..f7bb54fc21 --- /dev/null +++ b/packages/core/src/components/axes/chart-clip.ts @@ -0,0 +1,50 @@ +// Internal Imports +import { Component } from "../component"; +import { DOMUtils } from "../../services"; + +// This class is used to create the clipPath to clip the chart graphs +// It's necessary for zoom in/out behavior +export class ChartClip extends Component { + type = "chart-clip"; + + chartClipPath: any; + + clipPathId = "id-" + this.type; + + render(animate = true) { + // Create the clipPath + this.createClipPath(); + } + + createClipPath() { + const svg = this.parent; + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + // Get height + this.chartClipPath = DOMUtils.appendOrSelect( + svg, + `clipPath.${this.type}` + ).attr("id", this.clipPathId); + const clipRect = DOMUtils.appendOrSelect( + this.chartClipPath, + `rect.${this.type}` + ); + clipRect + .attr("x", xScaleStart) + .attr("y", yScaleStart) + .attr("width", xScaleEnd - xScaleStart) + .attr("height", yScaleEnd - yScaleStart); + + this.chartClipPath.merge(clipRect).lower(); + + const clipG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); + clipG + .attr("clip-path", `url(#${this.clipPathId})`) + .attr("id", `g-${this.type}`); + } +} diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts deleted file mode 100644 index 2536c9ef40..0000000000 --- a/packages/core/src/components/axes/cover.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Internal Imports -import { Component } from "../component"; -import { DOMUtils } from "../../services"; - -export class Cover extends Component { - type = "cover"; - - coverClipPath: any; - - render(animate = true) { - // Create the cover - this.createCover(); - } - - createCover() { - const svg = this.parent; - const { cartesianScales } = this.services; - const mainXScale = cartesianScales.getMainXScale(); - const mainYScale = cartesianScales.getMainYScale(); - - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const [yScaleEnd, yScaleStart] = mainYScale.range(); - - // Get height - this.coverClipPath = DOMUtils.appendOrSelect( - svg, - `clipPath.${this.type}` - ); - this.coverClipPath.attr("id", `${this.type}Clip`); - const coverRect = DOMUtils.appendOrSelect( - this.coverClipPath, - "rect.cover" - ); - coverRect - .attr("x", xScaleStart) - .attr("y", yScaleStart) - .attr("width", xScaleEnd - xScaleStart) - .attr("height", yScaleEnd - yScaleStart); - - this.coverClipPath.merge(coverRect).lower(); - - const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); - coverG - .attr("clip-path", `url(#${this.type}Clip)`) - .attr("id", `g-${this.type}Clip`); - } -} diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index b5996e519c..f79a5e347f 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -105,7 +105,7 @@ export class Component { ) { return DOMUtils.appendOrSelectForAxisChart( this.parent, - `clipPath.cover` + `clipPath.chart-clip` ); } else { return DOMUtils.appendOrSelect( diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 1534f8db5a..506b51b1fa 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -31,7 +31,7 @@ export * from "./layout/layout"; export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; export * from "./axes/brush"; -export * from "./axes/cover"; +export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index 45d2aee43d..72661ac164 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -156,19 +156,19 @@ export class DOMUtils extends Service { const parentOfSelection = parent.select( `${elementToAppend}.${querySections.slice(1).join(" ")}` ); - const selection = parent.select(`g#g-coverClip`); + const selection = parent.select(`g#g-chart-clip`); if (parentOfSelection.empty() && parent) { parent .append(elementToAppend) - .attr("id", `coverClip`) + .attr("id", `chart-clip`) .append("svg:rect") .attr("class", querySections.slice(1).join(" ")); } if (selection.empty() && parent) { parent .append("g") - .attr("clip-path", `url(#coverClip)`) - .attr("id", `g-coverClip`); + .attr("clip-path", `url(#id-chart-clip)`) + .attr("id", `g-chart-clip`); return parent; } return selection; From 21c4866f713e82095661460c52a20bdd01ac203f Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 12:29:05 +0800 Subject: [PATCH 121/510] refactor: Change Brush to ChartBrush --- packages/core/src/axis-chart.ts | 6 ++++-- .../src/components/axes/{brush.ts => chart-brush.ts} | 9 ++++----- packages/core/src/components/index.ts | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) rename packages/core/src/components/axes/{brush.ts => chart-brush.ts} (95%) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index c7feb56bae..b79ca91dfa 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -8,7 +8,7 @@ import { AxisChartOptions } from "./interfaces"; import { - Brush, + ChartBrush, LayoutComponent, Legend, Title, @@ -55,7 +55,9 @@ export class AxisChart extends Chart { }; if (zoomBarEnabled) { - graphFrameComponents.push(new Brush(this.model, this.services)); + graphFrameComponents.push( + new ChartBrush(this.model, this.services) + ); } const graphFrameComponent = { diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/chart-brush.ts similarity index 95% rename from packages/core/src/components/axes/brush.ts rename to packages/core/src/components/axes/chart-brush.ts index 8067e6aaa8..7fa5a1caf3 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -1,17 +1,16 @@ // Internal Imports import { Component } from "../component"; -import { Tools } from "../../tools"; import { ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports -import { extent } from "d3-array"; import { brushX } from "d3-brush"; import { event } from "d3-selection"; import { scaleTime } from "d3-scale"; -export class Brush extends Component { - type = "brush"; +// This class is used for handle brush events in chart +export class ChartBrush extends Component { + type = "chart-brush"; render(animate = true) { const svg = this.parent; @@ -129,7 +128,7 @@ export class Brush extends Component { const brushArea = DOMUtils.appendOrSelect( backdrop, - "g.chart-brush" + `g.${this.type}` ).call(brush); } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 506b51b1fa..369cbbe15a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,7 +30,7 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; -export * from "./axes/brush"; +export * from "./axes/chart-brush"; export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; From f0b278643de556cc1d794bc7ee6df4aae7b167be Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 14:18:36 +0800 Subject: [PATCH 122/510] refactor: set model.set() function default config --- packages/core/src/model.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index be8b412eb0..63254bb3e7 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -314,9 +314,12 @@ export class ChartModel { set(newState: any, configs?: any) { this.state = Object.assign({}, this.state, newState); - - if (!configs || !configs.skipUpdate) { - this.update(configs ? configs.animate : true); + const newConfig = Object.assign( + { skipUpdate: false, animate: true }, // default configs + configs + ); + if (!newConfig.skipUpdate) { + this.update(newConfig.animate); } } From d5955a3dd204fedbc5dde71d6b148fe59aa5b479 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 14:30:32 +0800 Subject: [PATCH 123/510] fix: remove unnecessary selector --- packages/core/src/components/graphs/line.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 2be9c949a9..02caebb0be 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -131,7 +131,6 @@ export class Line extends Component { this.parent .selectAll("path.line") - .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-hover-line") ) @@ -142,16 +141,16 @@ export class Line extends Component { return Configuration.lines.opacity.selected; }); - }; + } handleLegendMouseOut = (event: CustomEvent) => { this.parent - .selectAll(`g#coverClip`) + .selectAll("path.line") .transition( this.services.transitions.getTransition("legend-mouseout-line") ) .attr("opacity", Configuration.lines.opacity.selected); - }; + } destroy() { // Remove event listeners From 03d3ba0984954ecc8790d3fa407a24e1d28c54d4 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 15:22:22 +0800 Subject: [PATCH 124/510] refactor: create reusable getMainXScaleType() --- packages/core/src/components/axes/chart-brush.ts | 8 ++------ packages/core/src/components/axes/zoom-bar.ts | 8 ++------ packages/core/src/services/scales-cartesian.ts | 10 ++++------ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 7fa5a1caf3..19b9ee14b9 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -23,12 +23,8 @@ export class ChartBrush extends Component { }); const { cartesianScales } = this.services; - const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition - ); - - const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainXScaleType = cartesianScales.getMainXScaleType(); + const mainXScale = cartesianScales.getMainXScale(); if (mainXScale && mainXScaleType === ScaleTypes.TIME) { // get current zoomDomain diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 32c4cbb61f..897a1b9417 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -54,12 +54,8 @@ export class ZoomBar extends Component { const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition - ); - const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition - ); + const mainXScaleType = cartesianScales.getMainXScaleType(); + const mainYScaleType = cartesianScales.getMainYScaleType(); // get axes margins let axesLeftMargin = 0; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 11be646307..4904006ba5 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -231,14 +231,12 @@ export class CartesianScales extends Service { return this.getValueThroughAxisPosition(this.rangeAxisPosition, d, i); } - getXValue(d, i) { - const mainXAxisPosition = this.getMainXAxisPosition(); - return this.getValueThroughAxisPosition(mainXAxisPosition, d, i); + getMainXScaleType() { + return this.getScaleTypeByPosition(this.getMainXAxisPosition()); } - getYValue(d, i) { - const mainYAxisPosition = this.getMainYAxisPosition(); - return this.getValueThroughAxisPosition(mainYAxisPosition, d, i); + getMainYScaleType() { + return this.getScaleTypeByPosition(this.getMainYAxisPosition()); } getDomainIdentifier() { From 56c3e73e81bb133007e170dbeedbaf73a46bd2e8 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 16:22:13 +0800 Subject: [PATCH 125/510] refactor: make sure zoom bar only shows with supported options - zoomBar is available when -- zoomBar is enabled -- main X axis position is bottom -- main X axis scale type is time --- packages/core/src/axis-chart.ts | 21 +++++++++++++++++++-- packages/core/src/components/axes/axis.ts | 13 +++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index b79ca91dfa..6d4d515cba 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -5,7 +5,9 @@ import { LegendOrientations, LegendPositions, ChartConfig, - AxisChartOptions + AxisChartOptions, + AxisPositions, + ScaleTypes } from "./interfaces"; import { ChartBrush, @@ -31,11 +33,26 @@ export class AxisChart extends Chart { } protected getAxisChartComponents(graphFrameComponents: any[]) { - const zoomBarEnabled = Tools.getProperty( + const isZoomBarEnabled = Tools.getProperty( this.model.getOptions(), "zoomBar", "enabled" ); + + this.services.cartesianScales.findDomainAndRangeAxes(); // need to do this before getMainXAxisPosition() + const mainXAxisPosition = this.services.cartesianScales.getMainXAxisPosition(); + const mainXScaleType = Tools.getProperty( + this.model.getOptions(), + "axes", + mainXAxisPosition, + "scaleType" + ); + // @todo - Zoom Bar only supports main axis at BOTTOM axis and time scale for now + const zoomBarEnabled = + isZoomBarEnabled && + mainXAxisPosition === AxisPositions.BOTTOM && + mainXScaleType === ScaleTypes.TIME; + const titleComponent = { id: "title", components: [new Title(this.model, this.services)], diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 38f4c2b408..bf0e0e92a1 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -106,12 +106,6 @@ export class Axis extends Component { scale.range([startPosition, endPosition]); } - // if zoomDomain is available, update scale domain to Date array. - const zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain); - } - // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -170,6 +164,13 @@ export class Axis extends Component { const scaleType = this.scaleType || axisOptions.scaleType || ScaleTypes.LINEAR; + // if zoomDomain is available, scale type is time, and axis position isBOTTOM or TOP + // update scale domain to zoomDomain. + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && isTimeScaleType && !isVerticalAxis) { + scale.domain(zoomDomain); + } + // Initialize axis object const axis = axisFunction(scale).tickSizeOuter(0); From 8983eff6745a878edb0fa2c16e7fcfd7db2ebbd5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 22:15:30 +0800 Subject: [PATCH 126/510] refactor: set clip-path url in containerSVG --- .../core/src/components/axes/chart-clip.ts | 11 ++---- packages/core/src/components/component.ts | 34 +++++++------------ .../src/components/graphs/area-stacked.ts | 2 +- packages/core/src/components/graphs/area.ts | 2 +- .../core/src/components/graphs/bar-grouped.ts | 2 +- .../core/src/components/graphs/bar-simple.ts | 2 +- .../core/src/components/graphs/bar-stacked.ts | 2 +- packages/core/src/components/graphs/line.ts | 6 ++-- .../src/components/graphs/scatter-stacked.ts | 2 +- .../core/src/components/graphs/scatter.ts | 2 +- .../core/src/services/essentials/dom-utils.ts | 25 -------------- 11 files changed, 24 insertions(+), 66 deletions(-) diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts index f7bb54fc21..321db115e5 100644 --- a/packages/core/src/components/axes/chart-clip.ts +++ b/packages/core/src/components/axes/chart-clip.ts @@ -2,15 +2,13 @@ import { Component } from "../component"; import { DOMUtils } from "../../services"; -// This class is used to create the clipPath to clip the chart graphs +// This class is used to create the clipPath to clip the chart components // It's necessary for zoom in/out behavior export class ChartClip extends Component { type = "chart-clip"; chartClipPath: any; - clipPathId = "id-" + this.type; - render(animate = true) { // Create the clipPath this.createClipPath(); @@ -29,7 +27,7 @@ export class ChartClip extends Component { this.chartClipPath = DOMUtils.appendOrSelect( svg, `clipPath.${this.type}` - ).attr("id", this.clipPathId); + ).attr("id", this.chartClipId); const clipRect = DOMUtils.appendOrSelect( this.chartClipPath, `rect.${this.type}` @@ -41,10 +39,5 @@ export class ChartClip extends Component { .attr("height", yScaleEnd - yScaleStart); this.chartClipPath.merge(clipRect).lower(); - - const clipG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); - clipG - .attr("clip-path", `url(#${this.clipPathId})`) - .attr("id", `g-${this.type}`); } } diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index f79a5e347f..91ccf82ef8 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -19,6 +19,8 @@ export class Component { protected model: ChartModel; protected services: any; + protected chartClipId = "chart-clip-id"; + constructor(model: ChartModel, services: any, configs?: any) { this.model = model; this.services = services; @@ -83,7 +85,7 @@ export class Component { return this.parent; } - getContainerSVG() { + getContainerSVG(withinChartClip = false) { if (this.type) { const chartprefix = Tools.getProperty( this.model.getOptions(), @@ -91,28 +93,16 @@ export class Component { "prefix" ); - // @todo Chart type equals to axis-chart - if ( - this.type === "line" || - this.type === "scatter" || - this.type === "area" || - this.type === "bubble" || - this.type === "area-stacked" || - this.type === "grouped-bar" || - this.type === "simple-bar" || - this.type === "stacked-bar" || - this.type === "scatter-stacked" - ) { - return DOMUtils.appendOrSelectForAxisChart( - this.parent, - `clipPath.chart-clip` - ); - } else { - return DOMUtils.appendOrSelect( - this.parent, - `g.${settings.prefix}--${chartprefix}--${this.type}` - ); + const svg = DOMUtils.appendOrSelect( + this.parent, + `g.${settings.prefix}--${chartprefix}--${this.type}` + ); + + if (withinChartClip) { + svg.attr("clip-path", `url(#${this.chartClipId})`); } + + return svg; } return this.parent; diff --git a/packages/core/src/components/graphs/area-stacked.ts b/packages/core/src/components/graphs/area-stacked.ts index 5f6609df63..2e086cd958 100644 --- a/packages/core/src/components/graphs/area-stacked.ts +++ b/packages/core/src/components/graphs/area-stacked.ts @@ -28,7 +28,7 @@ export class StackedArea extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const self = this; const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/components/graphs/area.ts b/packages/core/src/components/graphs/area.ts index cd94376aaf..127dab7ac0 100644 --- a/packages/core/src/components/graphs/area.ts +++ b/packages/core/src/components/graphs/area.ts @@ -26,7 +26,7 @@ export class Area extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const { cartesianScales } = this.services; const orientation = cartesianScales.getOrientation(); diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 07f12e8c64..85217ba207 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -44,7 +44,7 @@ export class GroupedBar extends Bar { this.setGroupScale(); // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const allDataLabels = map( displayData, diff --git a/packages/core/src/components/graphs/bar-simple.ts b/packages/core/src/components/graphs/bar-simple.ts index aeb72950d1..2f74a5c922 100644 --- a/packages/core/src/components/graphs/bar-simple.ts +++ b/packages/core/src/components/graphs/bar-simple.ts @@ -31,7 +31,7 @@ export class SimpleBar extends Bar { const { groupMapsTo } = options.data; // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); // Update data on all bars const bars = svg diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index 55d376f835..a4cce26d0f 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -28,7 +28,7 @@ export class StackedBar extends Bar { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); // Chart options mixed with the internal configurations const displayData = this.model.getDisplayData(); diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 02caebb0be..3668bc294c 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -25,7 +25,7 @@ export class Line extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const { cartesianScales, curves } = this.services; const getDomainValue = (d, i) => cartesianScales.getDomainValue(d, i); @@ -141,7 +141,7 @@ export class Line extends Component { return Configuration.lines.opacity.selected; }); - } + }; handleLegendMouseOut = (event: CustomEvent) => { this.parent @@ -150,7 +150,7 @@ export class Line extends Component { this.services.transitions.getTransition("legend-mouseout-line") ) .attr("opacity", Configuration.lines.opacity.selected); - } + }; destroy() { // Remove event listeners diff --git a/packages/core/src/components/graphs/scatter-stacked.ts b/packages/core/src/components/graphs/scatter-stacked.ts index e477182786..ca5ad2a4c1 100644 --- a/packages/core/src/components/graphs/scatter-stacked.ts +++ b/packages/core/src/components/graphs/scatter-stacked.ts @@ -7,7 +7,7 @@ export class StackedScatter extends Scatter { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index 2d8074dc49..539d61b7e3 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -52,7 +52,7 @@ export class Scatter extends Component { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/services/essentials/dom-utils.ts b/packages/core/src/services/essentials/dom-utils.ts index 72661ac164..36a080d616 100644 --- a/packages/core/src/services/essentials/dom-utils.ts +++ b/packages/core/src/services/essentials/dom-utils.ts @@ -149,31 +149,6 @@ export class DOMUtils extends Service { return selection; } - static appendOrSelectForAxisChart(parent, query) { - const querySections = query.split("."); - const elementToAppend = querySections[0]; - - const parentOfSelection = parent.select( - `${elementToAppend}.${querySections.slice(1).join(" ")}` - ); - const selection = parent.select(`g#g-chart-clip`); - if (parentOfSelection.empty() && parent) { - parent - .append(elementToAppend) - .attr("id", `chart-clip`) - .append("svg:rect") - .attr("class", querySections.slice(1).join(" ")); - } - if (selection.empty() && parent) { - parent - .append("g") - .attr("clip-path", `url(#id-chart-clip)`) - .attr("id", `g-chart-clip`); - return parent; - } - return selection; - } - protected svg: Element; protected width: string; protected height: string; From 7246a7564f1a81e2a782b2e1df37a16735ec2bc8 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 11:52:45 +0800 Subject: [PATCH 127/510] refactor: dispatch zoombar selection events instead of callback --- packages/core/demo/data/zoom-bar.ts | 26 +------------- .../core/src/components/axes/chart-brush.ts | 36 +++++++------------ packages/core/src/components/axes/zoom-bar.ts | 33 +++++++---------- packages/core/src/configuration.ts | 5 +-- packages/core/src/interfaces/components.ts | 13 ------- packages/core/src/interfaces/events.ts | 5 ++- 6 files changed, 32 insertions(+), 86 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index fbe74fba2a..2dc1b076e1 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -6,39 +6,15 @@ import * as scatterChart from "./scatter"; import * as stepChart from "./step"; import * as timeSeriesAxisChart from "./time-series-axis"; -// default function for selection callback -const selectionStartFun = (selection, domain) => { - console.log("ZoomBar SelectionStart callback!"); - console.log(selection); - console.log(domain); -}; -const selectionInProgressFun = (selection, domain) => { - console.log("ZoomBar SelectionInProgress callback!"); - console.log(selection); - console.log(domain); -}; -const selectionEndFun = (selection, domain) => { - console.log("ZoomBar SelectionEnd callback!"); - console.log(selection); - console.log(domain); -}; - const initialZoomDomain = [ new Date(2020, 11, 10, 23, 59, 25), new Date(2020, 11, 11, 0, 0, 25) ]; -const defaultZoomBarOptions = { - enabled: true, - selectionStart: selectionStartFun, - selectionInProgress: selectionInProgressFun, - selectionEnd: selectionEndFun -}; - // utility function to update title and enable zoomBar option const addZoomBarToOptions = (options) => { options["title"] = options["title"] + " - Zoom bar enabled"; - options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); + options["zoomBar"] = { enabled: true }; return options; }; diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 19b9ee14b9..e8ba9b3f7d 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -1,6 +1,6 @@ // Internal Imports import { Component } from "../component"; -import { ScaleTypes } from "../../interfaces"; +import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports @@ -51,29 +51,19 @@ export class ChartBrush extends Component { event.sourceEvent.type === "mouseup" || event.sourceEvent.type === "mousedown") ) { - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress( - selection, - newDomain - ); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); + // dispatch selection events + let zoomBarEventType; + if (event.type === "start") { + zoomBarEventType = Events.ZoomBar.SELECTION_START; + } else if (event.type === "brush") { + zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; + } else if (event.type === "end") { + zoomBarEventType = Events.ZoomBar.SELECTION_END; } + this.services.events.dispatchEvent(zoomBarEventType, { + selection, + newDomain + }); } }; const brushed = () => { diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 897a1b9417..258d60ea1a 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -8,7 +8,7 @@ import { DOMUtils } from "../../services"; import { extent } from "d3-array"; import { brushX } from "d3-brush"; import { area, line } from "d3-shape"; -import { event, select, selectAll } from "d3-selection"; +import { event } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; @@ -238,26 +238,19 @@ export class ZoomBar extends Component { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); + // dispatch selection events + let zoomBarEventType; + if (event.type === "start") { + zoomBarEventType = Events.ZoomBar.SELECTION_START; + } else if (event.type === "brush") { + zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; + } else if (event.type === "end") { + zoomBarEventType = Events.ZoomBar.SELECTION_END; } + this.services.events.dispatchEvent(zoomBarEventType, { + selection, + newDomain + }); } } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index b7e8e301d8..1ae340990e 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -130,10 +130,7 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false, - selectionStart: undefined, - selectionInProgress: undefined, - selectionEnd: undefined + enabled: false }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 3c13b8beef..37fd47ff37 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -125,17 +125,4 @@ export interface ZoomBarOptions { * an two element array which represents the initial zoom domain */ initialZoomDomain?: Object[]; - - /** - * a function to handle selection start event - */ - selectionStart?: Function; - /** - * a function to handle selection in progress event - */ - selectionInProgress?: Function; - /** - * a function to handle selection end event - */ - selectionEnd?: Function; } diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 2a25fd5992..1da2416ead 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -21,7 +21,10 @@ export enum Model { * enum of all events related to the zoom-bar */ export enum ZoomBar { - UPDATE = "zoom-bar-update" + UPDATE = "zoom-bar-update", + SELECTION_START = "zoom-bar-selection-start", + SELECTION_IN_PROGRESS = "zoom-bar-selection-in-progress", + SELECTION_END = "zoom-bar-selection-end" } /** From cbb0ec7f13f35ab974044316f7d946c78f45b021 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 15:48:41 +0800 Subject: [PATCH 128/510] feat: update chart brush selection storke to dash --- .../core/src/components/axes/chart-brush.ts | 36 +++++++++++++++++-- .../src/styles/components/_chart-brush.scss | 9 +++++ .../core/src/styles/components/index.scss | 1 + 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/styles/components/_chart-brush.scss diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e8ba9b3f7d..e267ae0bda 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -10,8 +10,12 @@ 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 = "chart-brush"; + selectionSelector = "rect.selection"; // needs to match the class name in d3.brush + render(animate = true) { const svg = this.parent; const backdrop = DOMUtils.appendOrSelect( @@ -35,8 +39,36 @@ export class ChartBrush extends Component { 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 + + brushArea + .select(this.selectionSelector) + .attr("stroke-dasharray", dashArray); + }; + const eventHandler = () => { const selection = event.selection; + + updateSelectionDash(selection); + const xScale = scaleTime().range([0, width]).domain(zoomDomain); const newDomain = [ @@ -106,8 +138,8 @@ export class ChartBrush extends Component { // leave some space to display selection strokes besides axis const brush = brushX() .extent([ - [2, 0], - [width - 1, height - 1] + [0, 0], + [width - 1, height] ]) .on("start brush end", eventHandler) .on("end.brushed", brushed); diff --git a/packages/core/src/styles/components/_chart-brush.scss b/packages/core/src/styles/components/_chart-brush.scss new file mode 100644 index 0000000000..f316c7d486 --- /dev/null +++ b/packages/core/src/styles/components/_chart-brush.scss @@ -0,0 +1,9 @@ +.#{$prefix}--#{$charts-prefix}--chart-brush { + g.chart-brush { + rect.selection { + fill: $ui-03; + fill-opacity: 0.3; + stroke: $interactive-03; + } + } +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index 0b96507fbe..e9632f12a1 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -1,5 +1,6 @@ @import "./axis"; @import "./callouts"; +@import "./chart-brush"; @import "./grid"; @import "./ruler"; @import "./skeleton"; From e91c62f3ce578f43ba007c8cf90ba76d63138648 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 17:33:44 +0800 Subject: [PATCH 129/510] feat: show tooltip when mouseover zoombar handle --- packages/core/src/components/axes/zoom-bar.ts | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 258d60ea1a..4498bb214f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -197,7 +197,11 @@ export class ZoomBar extends Component { // don't update brushHandle to avoid flash } else if (zoomDomain[0].valueOf() === zoomDomain[1].valueOf()) { brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle(this.getContainerSVG(), xScale.range()); + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range(), + xScale.domain() + ); } else { const selected = zoomDomain.map((domain) => xScale(domain)); if (selected[1] - selected[0] < this.MIN_SELECTION_DIFF) { @@ -205,7 +209,11 @@ export class ZoomBar extends Component { // don't update brushHandle to avoid flash } else { brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle(this.getContainerSVG(), selected); + this.updateBrushHandle( + this.getContainerSVG(), + selected, + zoomDomain + ); } } } @@ -213,14 +221,14 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - // update brush handle position - this.updateBrushHandle(this.getContainerSVG(), selection); - const newDomain = [ scale.invert(selection[0]), scale.invert(selection[1]) ]; + // update brush handle position + this.updateBrushHandle(this.getContainerSVG(), selection, newDomain); + // be aware that the value of d3.event changes during an event! // update zoomDomain only if the event comes from mouse event if ( @@ -254,7 +262,20 @@ export class ZoomBar extends Component { } } - updateBrushHandle(svg, selection) { + updateBrushHandleTooltip(svg, domain) { + // remove old handle tooltip + svg.select("title").remove(); + // add new handle tooltip + svg.append("title").text((d) => { + if (d.type === "w") { + return domain[0]; + } else if (d.type === "e") { + return domain[1]; + } + }); + } + + updateBrushHandle(svg, selection, domain) { const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -263,6 +284,7 @@ export class ZoomBar extends Component { const handleBarHeight = 12; const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; + // handle svg.select(this.brushSelector) .selectAll("rect.handle") @@ -277,7 +299,10 @@ export class ZoomBar extends Component { .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) - .style("display", null); // always display + .attr("cursor", "pointer") + .style("display", null) // always display + .call(this.updateBrushHandleTooltip, domain); + // handle-bar svg.select(this.brushSelector) .selectAll("rect.handle-bar") @@ -296,7 +321,8 @@ export class ZoomBar extends Component { .attr("y", handleYBarDiff) .attr("width", handleBarWidth) .attr("height", handleBarHeight) - .attr("cursor", "ew-resize"); + .attr("cursor", "pointer") + .call(this.updateBrushHandleTooltip, domain); this.updateClipPath( svg, From 04695f7012e7541b10324683cf305cb579c89ce8 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 11:54:28 +0800 Subject: [PATCH 130/510] fix: avoid tick rotation flip during zoom domain changing - always rotate ticks during zoom domain changing --- packages/core/src/components/axes/axis.ts | 33 ++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index bf0e0e92a1..9650d9217f 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -29,6 +29,8 @@ export class Axis extends Component { scale: any; scaleType: ScaleTypes; + zoomDomainChanging = false; + constructor(model: ChartModel, services: any, configs?: any) { super(model, services, configs); @@ -37,6 +39,25 @@ export class Axis extends Component { } this.margins = this.configs.margins; + this.init(); + } + + init() { + this.services.events.addEventListener( + Events.ZoomBar.SELECTION_START, + () => { + this.zoomDomainChanging = true; + } + ); + this.services.events.addEventListener( + Events.ZoomBar.SELECTION_END, + () => { + this.zoomDomainChanging = false; + // need another update after zoom bar selection is completed + // to make sure the tick rotation is calculated correctly + this.services.events.dispatchEvent(Events.Model.UPDATE, {}); + } + ); } render(animate = true) { @@ -417,7 +438,9 @@ export class Axis extends Component { ? estimatedTickSize < minTickSize * 2 // datetime tick could be very long : estimatedTickSize < minTickSize; } - if (rotateTicks) { + + // always rotate ticks if zoomDomain is changing to avoid rotation flips during zoomDomain changing + if (rotateTicks || this.zoomDomainChanging) { if (!isNumberOfTicksProvided) { axis.ticks( this.getNumberOfFittingTicks( @@ -635,5 +658,13 @@ export class Axis extends Component { .on("mouseover", null) .on("mousemove", null) .on("mouseout", null); + this.services.events.removeEventListener( + Events.ZoomBar.SELECTION_START, + {} + ); + this.services.events.removeEventListener( + Events.ZoomBar.SELECTION_END, + {} + ); } } From 4e783e4341c850bbfe9e1d36ee2330fdaed4f1bd Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 18:19:27 +0800 Subject: [PATCH 131/510] fix: move chart brush selection above all graphs --- .../core/src/components/axes/chart-brush.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e267ae0bda..944e2245e6 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -16,6 +16,8 @@ export class ChartBrush extends Component { selectionSelector = "rect.selection"; // needs to match the class name in d3.brush + selectionElementId = "ChartBrushSelectionId"; + render(animate = true) { const svg = this.parent; const backdrop = DOMUtils.appendOrSelect( @@ -66,6 +68,9 @@ export class ChartBrush extends Component { const eventHandler = () => { const selection = event.selection; + if (selection === null) { + return; + } updateSelectionDash(selection); @@ -148,6 +153,26 @@ export class ChartBrush extends Component { backdrop, `g.${this.type}` ).call(brush); + + // set an id for rect.selection to be referred + brushArea + .select(this.selectionSelector) + .attr("id", this.selectionElementId); + + // create the chart brush group + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const selectionArea = this.getContainerSVG().attr( + "transform", + `translate(${xScaleStart},0)` + ); + // clear old svg + selectionArea.selectAll("svg").remove(); + // create a svg referring to d3 brush rect.selection + // this is to draw the selection above all graphs + selectionArea + .append("svg") + .append("use") + .attr("xlink:href", `#${this.selectionElementId}`); } } } From d5548129e9c41366831ec23e54fba3e8c9fd37e7 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 20:47:11 +0800 Subject: [PATCH 132/510] fix: use another svg to display front selection --- .../core/src/components/axes/chart-brush.ts | 55 +++++++++---------- .../src/styles/components/_chart-brush.scss | 15 ++++- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 944e2245e6..d07edd0c4e 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -16,14 +16,22 @@ export class ChartBrush extends Component { selectionSelector = "rect.selection"; // needs to match the class name in d3.brush - selectionElementId = "ChartBrushSelectionId"; + frontSelectionSelector = "rect.frontSelection"; // needs to match the class name in _chart-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 = brushArea.select(this.selectionSelector); + const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true }); @@ -31,6 +39,12 @@ export class ChartBrush extends Component { 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 @@ -61,9 +75,7 @@ export class ChartBrush extends Component { dashArray += "," + selectionWidth.toString(); // bottom (invisible) dashArray += "," + height.toString(); // left - brushArea - .select(this.selectionSelector) - .attr("stroke-dasharray", dashArray); + frontSelection.attr("stroke-dasharray", dashArray); }; const eventHandler = () => { @@ -72,6 +84,14 @@ export class ChartBrush extends Component { 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("display", null); + updateSelectionDash(selection); const xScale = scaleTime().range([0, width]).domain(zoomDomain); @@ -137,6 +157,8 @@ export class ChartBrush extends Component { // clear brush selection brushArea.call(brush.move, null); + // hide frontSelection + frontSelection.style("display", "none"); } }; @@ -149,30 +171,7 @@ export class ChartBrush extends Component { .on("start brush end", eventHandler) .on("end.brushed", brushed); - const brushArea = DOMUtils.appendOrSelect( - backdrop, - `g.${this.type}` - ).call(brush); - - // set an id for rect.selection to be referred - brushArea - .select(this.selectionSelector) - .attr("id", this.selectionElementId); - - // create the chart brush group - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const selectionArea = this.getContainerSVG().attr( - "transform", - `translate(${xScaleStart},0)` - ); - // clear old svg - selectionArea.selectAll("svg").remove(); - // create a svg referring to d3 brush rect.selection - // this is to draw the selection above all graphs - selectionArea - .append("svg") - .append("use") - .attr("xlink:href", `#${this.selectionElementId}`); + brushArea.call(brush); } } } diff --git a/packages/core/src/styles/components/_chart-brush.scss b/packages/core/src/styles/components/_chart-brush.scss index f316c7d486..d458f791d4 100644 --- a/packages/core/src/styles/components/_chart-brush.scss +++ b/packages/core/src/styles/components/_chart-brush.scss @@ -1,9 +1,18 @@ .#{$prefix}--#{$charts-prefix}--chart-brush { + // disable default d3 brush selection g.chart-brush { rect.selection { - fill: $ui-03; - fill-opacity: 0.3; - stroke: $interactive-03; + fill: none; + fill-opacity: 0; + stroke: none; } } } + +g.#{$prefix}--#{$charts-prefix}--chart-brush { + rect.frontSelection { + fill: $ui-03; + fill-opacity: 0.3; + stroke: $interactive-03; + } +} From dbc0e4bd8c957215288e2747b4aa894b4f1cacc6 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 20:59:47 +0800 Subject: [PATCH 133/510] fix: set cursor for front selection --- packages/core/src/components/axes/chart-brush.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index d07edd0c4e..85e48b3d08 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -90,6 +90,7 @@ export class ChartBrush extends Component { .attr("y", d3Selection.attr("y")) .attr("width", d3Selection.attr("width")) .attr("height", d3Selection.attr("height")) + .style("cursor", "pointer") .style("display", null); updateSelectionDash(selection); From 1e68f23b95591aadf14d7c284ad27b42e97fe09d Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 16 Jul 2020 15:07:40 +0800 Subject: [PATCH 134/510] fix: use unique chartClipId for each chart --- packages/core/src/components/axes/chart-clip.ts | 15 +++++++++++++++ packages/core/src/components/component.ts | 8 +++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts index 321db115e5..4a8a206c2f 100644 --- a/packages/core/src/components/axes/chart-clip.ts +++ b/packages/core/src/components/axes/chart-clip.ts @@ -1,14 +1,29 @@ // Internal Imports import { Component } from "../component"; import { DOMUtils } from "../../services"; +import { ChartModel } from "../../model"; // This class is used to create the clipPath to clip the chart components // It's necessary for zoom in/out behavior export class ChartClip extends Component { type = "chart-clip"; + // Give every chart-clip a distinct ID + // so they don't interfere each other in a page with multiple charts + chartClipId = "chart-clip-id-" + Math.floor(Math.random() * 99999999999); + chartClipPath: any; + constructor(model: ChartModel, services: any, configs?: any) { + super(model, services, configs); + this.init(); + } + + init() { + // set unique chartClipId in this chart to model + this.model.set({ chartClipId: this.chartClipId }, { skipUpdate: true }); + } + render(animate = true) { // Create the clipPath this.createClipPath(); diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 91ccf82ef8..87d55fba62 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -19,8 +19,6 @@ export class Component { protected model: ChartModel; protected services: any; - protected chartClipId = "chart-clip-id"; - constructor(model: ChartModel, services: any, configs?: any) { this.model = model; this.services = services; @@ -99,7 +97,11 @@ export class Component { ); if (withinChartClip) { - svg.attr("clip-path", `url(#${this.chartClipId})`); + // get unique chartClipId int this chart from model + const chartClipId = this.model.get("chartClipId"); + if (chartClipId) { + svg.attr("clip-path", `url(#${chartClipId})`); + } } return svg; From ad317297db388f4d82547b5b0aef11499c31f3db Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 16 Jul 2020 16:17:58 +0800 Subject: [PATCH 135/510] fix: keep zoom bar handle inside zoom bar range --- packages/core/src/components/axes/zoom-bar.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 4498bb214f..adc38f9bc9 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -20,7 +20,9 @@ export class ZoomBar extends Component { brushSelector = "g.zoom-bar-brush"; // needs to match the style in _zoom-bar.scss - clipId = "zoomBarClip"; + // Give every zoomBarClip a distinct ID + // so they don't interfere the other zoom bars in a page + clipId = "zoomBarClip-" + Math.floor(Math.random() * 99999999999); height = 32; @@ -28,6 +30,9 @@ export class ZoomBar extends Component { brush = brushX(); + // The max allowed selection ragne, will be updated soon in render() + maxSelectionRange: [0, 0]; + init() { this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { this.render(); @@ -97,6 +102,8 @@ export class ZoomBar extends Component { this.compensateDataForDefaultDomain(zoomBarData, defaultDomain, 0); xScale.range([axesLeftMargin, width]).domain(defaultDomain); + // keep max selection range + this.maxSelectionRange = xScale.range(); yScale .range([0, this.height - 6]) @@ -276,6 +283,7 @@ export class ZoomBar extends Component { } updateBrushHandle(svg, selection, domain) { + const self = this; const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -291,9 +299,17 @@ export class ZoomBar extends Component { .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { if (d.type === "w") { - return selection[0] + handleXDiff; + // handle should not exceed zoom bar range + return Math.max( + selection[0] + handleXDiff, + self.maxSelectionRange[0] + ); } else if (d.type === "e") { - return selection[1] + handleXDiff; + // handle should not exceed zoom bar range + return Math.min( + selection[1] + handleXDiff, + self.maxSelectionRange[1] - handleWidth + ); } }) .attr("y", 0) @@ -313,9 +329,15 @@ export class ZoomBar extends Component { }) .attr("x", function (d) { if (d.type === "w") { - return selection[0] + handleBarXDiff; + return Math.max( + selection[0] + handleBarXDiff, + self.maxSelectionRange[0] - handleXDiff + handleBarXDiff + ); } else if (d.type === "e") { - return selection[1] + handleBarXDiff; + return Math.min( + selection[1] + handleBarXDiff, + self.maxSelectionRange[1] + handleXDiff + handleBarXDiff + ); } }) .attr("y", handleYBarDiff) From abfd3e16b36bdbc5e5244a4a47ce097101374991 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 20 Jul 2020 21:42:26 +0800 Subject: [PATCH 136/510] fix: use DOMUtils.appendOrSelect in case the element does not exist yet --- packages/core/src/components/axes/chart-brush.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 85e48b3d08..e872ec1cf5 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -30,7 +30,10 @@ export class ChartBrush extends Component { const brushArea = DOMUtils.appendOrSelect(backdrop, `g.${this.type}`); // set an id for rect.selection to be referred - const d3Selection = brushArea.select(this.selectionSelector); + const d3Selection = DOMUtils.appendOrSelect( + brushArea, + this.selectionSelector + ); const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true From e1d463671a2240617a0982ea28d46177ca184311 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 20 Jul 2020 22:25:10 +0800 Subject: [PATCH 137/510] feat: allow user to set zoom bar data --- packages/core/demo/data/index.ts | 6 +++++ packages/core/demo/data/zoom-bar.ts | 31 +++++++++++++++++++--- packages/core/src/interfaces/components.ts | 4 +++ packages/core/src/model.ts | 19 ++++++++++--- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 9c38d98318..f067ad309f 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -730,6 +730,12 @@ let allDemoGroups = [ chartType: chartTypes.StackedBarChart, isDemoExample: false }, + { + options: zoomBarDemos.definedZoomBarStackedBarTimeSeriesOptions, + data: zoomBarDemos.definedZoomBarStackedBarTimeSeriesData, + chartType: chartTypes.StackedBarChart, + isDemoExample: false + }, { options: zoomBarDemos.zoomBarBubbleTimeSeriesOptions, data: zoomBarDemos.zoomBarBubbleTimeSeriesData, diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 2dc1b076e1..98249d52da 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -11,10 +11,28 @@ const initialZoomDomain = [ new Date(2020, 11, 11, 0, 0, 25) ]; +const definedZoomBarData = [ + { date: new Date(2019, 0, 1), value: 10000 }, + { date: new Date(2019, 0, 2), value: 10 }, + { date: new Date(2019, 0, 3), value: 75000 }, + { date: new Date(2019, 0, 5), value: 65000 }, + { date: new Date(2019, 0, 6), value: 57312 }, + { date: new Date(2019, 0, 8), value: 10000 }, + { date: new Date(2019, 0, 13), value: 49213 }, + { date: new Date(2019, 0, 15), value: 70323 }, + { date: new Date(2019, 0, 17), value: 51213 }, + { date: new Date(2019, 0, 19), value: 21300 } +]; + // utility function to update title and enable zoomBar option -const addZoomBarToOptions = (options) => { - options["title"] = options["title"] + " - Zoom bar enabled"; - options["zoomBar"] = { enabled: true }; +const addZoomBarToOptions = (options, includeDefinedZoomBarData = false) => { + if (includeDefinedZoomBarData) { + options["title"] = options["title"] + " - Defined zoom bar enabled"; + options["zoomBar"] = { enabled: true, data: definedZoomBarData }; + } else { + options["title"] = options["title"] + " - Zoom bar enabled"; + options["zoomBar"] = { enabled: true }; + } return options; }; @@ -35,6 +53,13 @@ export const zoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.stackedBarTimeSeriesOptions) ); +export const definedZoomBarStackedBarTimeSeriesData = + barChart.stackedBarTimeSeriesData; +export const definedZoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( + Object.assign({}, barChart.stackedBarTimeSeriesOptions), + true +); + export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; export const zoomBarBubbleTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 37fd47ff37..4dfe3ebb5a 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -125,4 +125,8 @@ export interface ZoomBarOptions { * an two element array which represents the initial zoom domain */ initialZoomDomain?: Object[]; + /** + * options related to zoom bar data + */ + data?: Object[]; } diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 63254bb3e7..843b353b2a 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -46,10 +46,23 @@ export class ChartModel { const domainIdentifier = cartesianScales.getDomainIdentifier(); const rangeIdentifier = cartesianScales.getRangeIdentifier(); - const displayData = this.getDisplayData(); + let zoomBarData; + // check if pre-defined zoom bar data exists + const definedZoomBarData = Tools.getProperty( + this.getOptions(), + "zoomBar", + "data" + ); + // if user already defines zoom bar data, use it + if (definedZoomBarData) { + zoomBarData = definedZoomBarData; + } else { + // use displayData if not defined + zoomBarData = this.getDisplayData(); + } // get all dates (Number) in displayData let allDates = []; - displayData.forEach((data) => { + zoomBarData.forEach((data) => { allDates = allDates.concat(Number(data[domainIdentifier])); }); allDates = Tools.removeArrayDuplicates(allDates).sort(); @@ -59,7 +72,7 @@ export class ChartModel { let sum = 0; const datum = {}; - displayData.forEach((data) => { + zoomBarData.forEach((data) => { if (Number(data[domainIdentifier]) === date) { sum += data[rangeIdentifier]; } From f101cb9bb6befd957dfc5badca8a1f26feb413a5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 11:38:02 +0800 Subject: [PATCH 138/510] fix: definedZoomBarData needs at least two elements --- packages/core/src/components/axes/zoom-bar.ts | 3 +++ packages/core/src/model.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index adc38f9bc9..6d134f0534 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -393,6 +393,9 @@ export class ZoomBar extends Component { // assume the domains in data are already sorted compensateDataForDefaultDomain(data, defaultDomain, value) { + if (!data || data.length < 2) { + return; + } const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); // if min domain is extended diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 843b353b2a..b6fa3f9ba4 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -54,7 +54,7 @@ export class ChartModel { "data" ); // if user already defines zoom bar data, use it - if (definedZoomBarData) { + if (definedZoomBarData && definedZoomBarData.length > 1) { zoomBarData = definedZoomBarData; } else { // use displayData if not defined From cd191bdb0f2b416c146bdbec1d2cad9a02024880 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 13:43:41 +0800 Subject: [PATCH 139/510] fix: axis needs to set opacity if not loading --- packages/core/src/components/axes/axis.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 9650d9217f..df49acc264 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -473,15 +473,13 @@ export class Axis extends Component { // because the Skeleton component draws them if (isDataLoading) { container.attr("opacity", 0); + } else { + container.attr("opacity", 1); } - axisRef - .selectAll("g.tick") - .attr("aria-label", d => d); + axisRef.selectAll("g.tick").attr("aria-label", (d) => d); - invisibleAxisRef - .selectAll("g.tick") - .attr("aria-label", d => d); + invisibleAxisRef.selectAll("g.tick").attr("aria-label", (d) => d); // truncate the label if it's too long // only applies to discrete type From d8a8c0c90a1a570ee684700896db39e555c67ec1 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 15:15:46 +0800 Subject: [PATCH 140/510] fix: format zoom bar handle tooltip --- packages/core/src/components/axes/zoom-bar.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6d134f0534..0c742f6585 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -3,6 +3,10 @@ import { Component } from "../component"; import { Tools } from "../../tools"; import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; +import { + computeTimeIntervalName, + formatTick +} from "../../services/time-series"; // D3 Imports import { extent } from "d3-array"; @@ -269,21 +273,27 @@ export class ZoomBar extends Component { } } - updateBrushHandleTooltip(svg, domain) { + updateBrushHandleTooltip(svg, domain, timeScaleOptions) { + const timeInterval = computeTimeIntervalName(domain); + // remove old handle tooltip svg.select("title").remove(); // add new handle tooltip svg.append("title").text((d) => { if (d.type === "w") { - return domain[0]; + return formatTick(domain[0], 0, timeInterval, timeScaleOptions); } else if (d.type === "e") { - return domain[1]; + return formatTick(domain[1], 0, timeInterval, timeScaleOptions); } }); } updateBrushHandle(svg, selection, domain) { const self = this; + const timeScaleOptions = Tools.getProperty( + this.model.getOptions(), + "timeScale" + ); const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -317,7 +327,7 @@ export class ZoomBar extends Component { .attr("height", handleHeight) .attr("cursor", "pointer") .style("display", null) // always display - .call(this.updateBrushHandleTooltip, domain); + .call(this.updateBrushHandleTooltip, domain, timeScaleOptions); // handle-bar svg.select(this.brushSelector) @@ -344,7 +354,7 @@ export class ZoomBar extends Component { .attr("width", handleBarWidth) .attr("height", handleBarHeight) .attr("cursor", "pointer") - .call(this.updateBrushHandleTooltip, domain); + .call(this.updateBrushHandleTooltip, domain, timeScaleOptions); this.updateClipPath( svg, From da666c53d7dae2f0063d1612b0dd30a21b3f2796 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 27 May 2020 15:44:15 +0800 Subject: [PATCH 141/510] feat: initial zoom bar implementation from PR 511 --- packages/core/src/axis-chart.ts | 16 +- packages/core/src/chart.ts | 9 +- packages/core/src/components/axes/axis.ts | 9 +- packages/core/src/components/axes/zoom-bar.ts | 216 ++++++++++++++++++ packages/core/src/components/index.ts | 1 + packages/core/src/model.ts | 10 +- packages/core/src/services/axis-zoom.ts | 60 +++++ packages/core/src/services/index.ts | 1 + .../core/src/services/scales-cartesian.ts | 6 + .../core/src/styles/components/_zoom-bar.scss | 11 + .../core/src/styles/components/index.scss | 1 + packages/core/src/styles/graphs/index.scss | 4 + 12 files changed, 333 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/axes/zoom-bar.ts create mode 100644 packages/core/src/services/axis-zoom.ts create mode 100644 packages/core/src/styles/components/_zoom-bar.scss diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index b9751b3a31..e4d105905f 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -12,7 +12,8 @@ import { Legend, Title, AxisChartsTooltip, - Spacer + Spacer, + ZoomBar } from "./components/index"; import { Tools } from "./tools"; @@ -123,6 +124,17 @@ export class AxisChart extends Chart { } }; + const zoomBarComponent = { + id: "zoom-bar", + components: [ + new ZoomBar(this.model, this.services) + ], + growth: { + x: LayoutGrowth.PREFERRED, + y: LayoutGrowth.FIXED + } + }; + // Add chart title if it exists const topLevelLayoutComponents = []; if (this.model.getOptions().title) { @@ -139,6 +151,8 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } + + topLevelLayoutComponents.push(zoomBarComponent); topLevelLayoutComponents.push(fullFrameComponent); return [ diff --git a/packages/core/src/chart.ts b/packages/core/src/chart.ts index ea65e9f3c3..4e86305488 100644 --- a/packages/core/src/chart.ts +++ b/packages/core/src/chart.ts @@ -37,7 +37,7 @@ export class Chart { // Contains the code that uses properties that are overridable by the super-class init(holder: Element, chartConfigs: ChartConfig) { // Store the holder in the model - this.model.set({ holder }, true); + this.model.set({ holder }, { skipUpdate: true }); // Initialize all services Object.keys(this.services).forEach((serviceName) => { @@ -49,8 +49,9 @@ export class Chart { }); // Call update() when model has been updated - this.services.events.addEventListener(ChartEvents.Model.UPDATE, () => { - this.update(true); + this.services.events.addEventListener(ChartEvents.Model.UPDATE, (e) => { + const animate = !!Tools.getProperty(e, "detail", "animate"); + this.update(animate); }); // Set model data & options @@ -110,7 +111,7 @@ export class Chart { // Remove the chart holder this.services.domUtils.getHolder().remove(); - this.model.set({ destroyed: true }, true); + this.model.set({ destroyed: true }, { skipUpdate: true }); } protected getChartComponents(graphFrameComponents: any[]) { diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index f88909e0c6..d421ab09af 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -114,6 +114,13 @@ export class Axis extends Component { scale.range([startPosition, endPosition]); } + // if zoomDomain is available, update scale domain to Date array. + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { + scale.domain(zoomDomain.map(d => new Date(d))); + } + + // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -419,7 +426,7 @@ export class Axis extends Component { const ticksNumber = isTimeScaleType ? axis.tickValues().length : scale.ticks().length; - const estimatedTickSize = width / ticksNumber / 2; + const estimatedTickSize = width / ticksNumber / 1.6; rotateTicks = estimatedTickSize < minTickSize; } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts new file mode 100644 index 0000000000..dee5a24f55 --- /dev/null +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -0,0 +1,216 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { area, line } from "d3-shape"; +import { extent } from "d3-array"; +import { drag } from "d3-drag"; +import { event, select, selectAll } from "d3-selection"; + +export class ZoomBar extends Component { + type = "zoom-bar"; + + ogXScale: any; + + dragged = Tools.debounce((element, d, e) => { + element = select(element); + const startingHandle = element.attr("class").indexOf("start") !== -1; + + let domain; + if (startingHandle) { + domain = [ + this.ogXScale.invert(e.x), + this.ogXScale.domain()[1] + // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) + ]; + } else { + domain = [ + this.ogXScale.domain()[0], + this.ogXScale.invert(e.x) + // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) + ]; + } + this.model.set({ zoomDomain: domain }, { animate: false }); + }, 2.5); + + render(animate = true) { + const svg = this.getContainerSVG(); + + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); + const mainXScaleType = cartesianScales.getScaleTypeByPosition(mainXAxisPosition); + const mainYScaleType = cartesianScales.getScaleTypeByPosition(mainYAxisPosition); + + const height = 32; + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") + // .attr("transform", "translateX(10)") + .attr("width", "100%") + .attr("height", height) + .attr("opacity", 1); + + const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") + .attr("x", 0) + .attr("y", 32) + .attr("width", "100%") + .attr("height", 20) + .attr("opacity", 1) + .attr("fill", "none"); + + const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") + .attr("x", 0) + .attr("y", 0) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "white") + .attr("stroke", "#e8e8e8"); + + if (mainXScale) { + const displayData = this.model.getDisplayData(); + + if (mainXScaleType === ScaleTypes.TIME) { + // Get all date values provided in data + // TODO - Could be re-used through the model + let allDates = []; + displayData.datasets.forEach(dataset => { + allDates = allDates.concat(dataset.data.map(datum => Number(datum.date))); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + + // Go through all date values + // And get corresponding data from each dataset + const stackDataArray = allDates.map((date) => { + let count = 0; + let correspondingSum = 0; + const correspondingData = {}; + + displayData.datasets.forEach(dataset => { + const correspondingDatum = dataset.data.find(datum => Number(datum.date) === Number(date)); + if (correspondingDatum) { + ++count; + correspondingSum += correspondingDatum.value; + } + }); + correspondingData["label"] = date; + correspondingData["value"] = correspondingSum; + + return correspondingData; + }); + + const xScale = mainXScale.copy(); + if (!this.ogXScale) { + this.ogXScale = xScale; + } + + const yScale = mainYScale.copy(); + + const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); + + xScale.range([0, +width]) + .domain(extent(stackDataArray, (d: any) => d.label)); + + yScale.range([0, height - 6]) + .domain(extent(stackDataArray, (d: any) => d.value)); + + const zoomDomain = this.model.get("zoomDomain"); + + // D3 line generator function + const lineGenerator = line() + .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .y((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)) + .curve(this.services.curves.getD3Curve()); + // .defined((d: any, i) => { + // if (zoomDomain) { + // const dTimestamp = +d.label; + + // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; + // } + + // return true; + // }); + + const lineGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-line") + .attr("stroke", "#8e8e8e") + .attr("stroke-width", 3) + .attr("fill", "none") + .datum(stackDataArray) + .transition(this.services.transitions.getTransition("zoom-pan-line-update", animate)) + .attr("d", lineGenerator); + + const areaGenerator = area() + .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .y0(height) + .y1((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)); + + const areaGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-area") + .attr("fill", "#e0e0e0") + .datum(stackDataArray) + .transition(this.services.transitions.getTransition("zoom-pan-area-update", animate)) + .attr("d", areaGenerator); + + const startHandlePosition = zoomDomain ? xScale(+zoomDomain[0]) : 0; + // Handle #1 + const startHandle = DOMUtils.appendOrSelect(container, "rect.zoom-handle.start") + .attr("x", startHandlePosition) + .attr("width", 5) + .attr("height", "100%") + .attr("fill", "#525252"); + + DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.start") + .attr("x", startHandlePosition + 2) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + const endHandlePosition = zoomDomain ? xScale(+zoomDomain[1]) : xScale.range()[1]; + // console.log("endHandlePosition", endHandlePosition) + + // Handle #2 + const handle2 = DOMUtils.appendOrSelect(container, "rect.zoom-handle.end") + .attr("x", endHandlePosition - 5) + .attr("width", 5) + .attr("height", "100%") + .attr("fill", "#525252"); + + DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.end") + .attr("x", endHandlePosition - 5 + 2) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + + const outboundRangeRight = DOMUtils.appendOrSelect(container, "rect.outbound-range.right") + .attr("x", endHandlePosition) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "#fff") + .attr("fill-opacity", 0.85); + + const self = this; + // handle2.on("click", this.zoomIn.bind(this)); + selectAll("rect.zoom-handle").call( + drag() + .on("start", function() { + select(this).classed("dragging", true); + }) + .on("drag", function(d) { + self.dragged(this, d, event); + }) + .on("end", function() { + select(this).classed("dragging", false); + }) + ); + } + } + } + + zoomIn() { + const mainXScale = this.services.cartesianScales.getMainXScale(); + console.log("zoom in", mainXScale.domain()); + } +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 4142972992..60daf43009 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -34,3 +34,4 @@ export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; +export * from "./axes/zoom-bar"; diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 67f15f296a..999d689680 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -267,11 +267,11 @@ export class ChartModel { return this.state.options; } - set(newState: any, skipUpdate = false) { + set(newState: any, configs?: any) { this.state = Object.assign({}, this.state, newState); - if (!skipUpdate) { - this.update(); + if (!configs || !configs.skipUpdate) { + this.update(configs ? configs.animate : true); } } @@ -298,7 +298,7 @@ export class ChartModel { * Updates miscellanous information within the model * such as the color scales, or the legend data labels */ - update() { + update(animate = true) { if (!this.getDisplayData()) { return; } @@ -306,7 +306,7 @@ export class ChartModel { this.updateAllDataGroups(); this.setColorScale(); - this.services.events.dispatchEvent(Events.Model.UPDATE); + this.services.events.dispatchEvent(Events.Model.UPDATE, { animate }); } setUpdateCallback(cb: Function) { diff --git a/packages/core/src/services/axis-zoom.ts b/packages/core/src/services/axis-zoom.ts new file mode 100644 index 0000000000..8dbb2b3d80 --- /dev/null +++ b/packages/core/src/services/axis-zoom.ts @@ -0,0 +1,60 @@ +// Internal Imports +import { Service } from "./service"; +import { Tools } from "../tools"; + +// D3 Imports +import { brushX } from "d3-brush"; +import { event } from "d3-selection"; + +export class AxisZoom extends Service { + brush: any; + + brushed(e) { + if (event) { + const selectedRange = event.selection; + + const mainXAxis = this.services.axes.getMainXAxis(); + const mainXAxisRangeStart = mainXAxis.scale.range()[0]; + + const newDomain = selectedRange.map(d => d + mainXAxisRangeStart) + .map(mainXAxis.scale.invert, mainXAxis.scale); + + this.model.set({ + zoomDomain: newDomain.map(d => new Date(+d)) + }); + } + } + + // We need a custom debounce function here + // because of the async nature of d3.event + debounceWithD3Event(func, wait) { + let timeout; + return function() { + const e = Object.assign({}, event); + const context = this; + const later = function() { + timeout = null; + func.apply(context, [e]); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } + + getZoomInstance() { + const mainXAxis = this.services.axes.getMainXAxis(); + const mainYAxis = this.services.axes.getMainYAxis(); + const xMaxRange = mainXAxis.scale.range()[1]; + const yMaxRange = mainYAxis.scale.range()[0]; + + this.brush = brushX() + .extent([ + [0, 0], + [xMaxRange, yMaxRange] + ]) + .on("end", this.brushed.bind(this)); + + + return this.brush; + } +} diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index d999a07207..4e9fcc0e17 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -4,5 +4,6 @@ export * from "./essentials/events"; export * from "./essentials/transitions"; // MISC +export * from "./axis-zoom"; export * from "./scales-cartesian"; export * from "./curves"; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 45a2bd142b..e75d6f14e7 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -382,6 +382,12 @@ export class CartesianScales extends Service { return map(displayData, (d) => d[mapsTo]).keys(); } + // If scale is a TIME scale and zoomDomain is available, return Date array as the domain + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && axisOptions && scaleType === ScaleTypes.TIME) { + return zoomDomain.map(d => new Date(d)); + } + // Get the extent of the domain let domain; let allDataValues; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss new file mode 100644 index 0000000000..02fcf7a55f --- /dev/null +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -0,0 +1,11 @@ +g.#{$prefix}--#{$charts-prefix}--zoom-bar { + rect.zoom-handle.dragging, + rect.zoom-handle:hover { + fill: black; + cursor: col-resize; + } + + rect.zoom-handle-bar { + pointer-events: none; + } +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index 3fff851a8b..0b96507fbe 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -10,3 +10,4 @@ @import "./meter-title"; @import "./tooltip"; @import "./threshold"; +@import "./zoom-bar"; diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index 5530eba1a0..bebafaffc3 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -6,3 +6,7 @@ @import "./scatter-stacked"; @import "./radar"; @import "./gauge"; + +svg.#{$prefix}--#{$charts-prefix}--chart-svg svg.graph-frame { + overflow-x: hidden; +} From f83d9c32d19f782e803fb7b8c61f213446ff8342 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 2 Jun 2020 16:33:50 +0800 Subject: [PATCH 142/510] fix: update ZoomBar to match new data format - fix trailing-comma - update scales-cartesian.getValueFromScale() - update line time-series 15 seconds demo data --- packages/core/demo/data/time-series-axis.ts | 12 +- packages/core/src/components/axes/zoom-bar.ts | 165 +++++++++++++----- .../core/src/services/scales-cartesian.ts | 165 ++++++++++-------- packages/core/tslint.json | 2 +- 4 files changed, 216 insertions(+), 128 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e3a3502c92..c1c7183571 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -8,12 +8,12 @@ export const lineTimeSeriesData15seconds = { label: "Dataset 1", data: [ { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 30), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 45), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 0), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 15), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 30), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 10 } + { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, + { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, + { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, + { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, + { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 }, ] } ] diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index dee5a24f55..f6ac815a7b 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -23,13 +23,13 @@ export class ZoomBar extends Component { if (startingHandle) { domain = [ this.ogXScale.invert(e.x), - this.ogXScale.domain()[1] + this.ogXScale.domain()[1], // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } else { domain = [ this.ogXScale.domain()[0], - this.ogXScale.invert(e.x) + this.ogXScale.invert(e.x), // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } @@ -44,8 +44,12 @@ export class ZoomBar extends Component { const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition(mainXAxisPosition); - const mainYScaleType = cartesianScales.getScaleTypeByPosition(mainYAxisPosition); + const mainXScaleType = cartesianScales.getScaleTypeByPosition( + mainXAxisPosition + ); + const mainYScaleType = cartesianScales.getScaleTypeByPosition( + mainYAxisPosition + ); const height = 32; const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") @@ -77,8 +81,8 @@ export class ZoomBar extends Component { // Get all date values provided in data // TODO - Could be re-used through the model let allDates = []; - displayData.datasets.forEach(dataset => { - allDates = allDates.concat(dataset.data.map(datum => Number(datum.date))); + displayData.forEach((data) => { + allDates = allDates.concat(Number(data.date)); }); allDates = Tools.removeArrayDuplicates(allDates).sort(); @@ -89,14 +93,13 @@ export class ZoomBar extends Component { let correspondingSum = 0; const correspondingData = {}; - displayData.datasets.forEach(dataset => { - const correspondingDatum = dataset.data.find(datum => Number(datum.date) === Number(date)); - if (correspondingDatum) { + displayData.forEach((data) => { + if (Number(data.date) === Number(date)) { ++count; - correspondingSum += correspondingDatum.value; + correspondingSum += data.value; } }); - correspondingData["label"] = date; + correspondingData["date"] = date; correspondingData["value"] = correspondingSum; return correspondingData; @@ -109,53 +112,113 @@ export class ZoomBar extends Component { const yScale = mainYScale.copy(); - const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true, + }); - xScale.range([0, +width]) - .domain(extent(stackDataArray, (d: any) => d.label)); + xScale + .range([0, width]) + .domain(extent(stackDataArray, (d: any) => d.date)); - yScale.range([0, height - 6]) + yScale + .range([0, height - 6]) .domain(extent(stackDataArray, (d: any) => d.value)); const zoomDomain = this.model.get("zoomDomain"); // D3 line generator function const lineGenerator = line() - .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) - .y((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)) + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) + .y( + (d, i) => + height - + cartesianScales.getValueFromScale( + yScale, + mainYScaleType, + mainYAxisPosition, + d, + i + ) + ) .curve(this.services.curves.getD3Curve()); - // .defined((d: any, i) => { - // if (zoomDomain) { - // const dTimestamp = +d.label; - - // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; - // } - - // return true; - // }); - - const lineGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-line") + // .defined((d: any, i) => { + // if (zoomDomain) { + // const dTimestamp = +d.label; + + // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; + // } + + // return true; + // }); + const lineGraph = DOMUtils.appendOrSelect( + container, + "path.zoom-graph-line" + ) .attr("stroke", "#8e8e8e") .attr("stroke-width", 3) .attr("fill", "none") .datum(stackDataArray) - .transition(this.services.transitions.getTransition("zoom-pan-line-update", animate)) + .transition( + this.services.transitions.getTransition( + "zoom-pan-line-update", + animate + ) + ) .attr("d", lineGenerator); const areaGenerator = area() - .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) .y0(height) - .y1((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)); - - const areaGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-area") + .y1( + (d, i) => + height - + cartesianScales.getValueFromScale( + yScale, + mainYScaleType, + mainYAxisPosition, + d, + i + ) + ); + + const areaGraph = DOMUtils.appendOrSelect( + container, + "path.zoom-graph-area" + ) .attr("fill", "#e0e0e0") .datum(stackDataArray) - .transition(this.services.transitions.getTransition("zoom-pan-area-update", animate)) + .transition( + this.services.transitions.getTransition( + "zoom-pan-area-update", + animate + ) + ) .attr("d", areaGenerator); - const startHandlePosition = zoomDomain ? xScale(+zoomDomain[0]) : 0; + const startHandlePosition = zoomDomain + ? xScale(+zoomDomain[0]) + : 0; // Handle #1 - const startHandle = DOMUtils.appendOrSelect(container, "rect.zoom-handle.start") + const startHandle = DOMUtils.appendOrSelect( + container, + "rect.zoom-handle.start" + ) .attr("x", startHandlePosition) .attr("width", 5) .attr("height", "100%") @@ -167,11 +230,16 @@ export class ZoomBar extends Component { .attr("width", 1) .attr("height", 12) .attr("fill", "#fff"); - const endHandlePosition = zoomDomain ? xScale(+zoomDomain[1]) : xScale.range()[1]; + const endHandlePosition = zoomDomain + ? xScale(+zoomDomain[1]) + : xScale.range()[1]; // console.log("endHandlePosition", endHandlePosition) // Handle #2 - const handle2 = DOMUtils.appendOrSelect(container, "rect.zoom-handle.end") + const endHandle = DOMUtils.appendOrSelect( + container, + "rect.zoom-handle.end" + ) .attr("x", endHandlePosition - 5) .attr("width", 5) .attr("height", "100%") @@ -184,24 +252,27 @@ export class ZoomBar extends Component { .attr("height", 12) .attr("fill", "#fff"); - const outboundRangeRight = DOMUtils.appendOrSelect(container, "rect.outbound-range.right") - .attr("x", endHandlePosition) - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "#fff") - .attr("fill-opacity", 0.85); + const outboundRangeRight = DOMUtils.appendOrSelect( + container, + "rect.outbound-range.right" + ) + .attr("x", endHandlePosition) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "#fff") + .attr("fill-opacity", 0.85); const self = this; // handle2.on("click", this.zoomIn.bind(this)); selectAll("rect.zoom-handle").call( drag() - .on("start", function() { + .on("start", function () { select(this).classed("dragging", true); }) - .on("drag", function(d) { + .on("drag", function (d) { self.dragged(this, d, event); }) - .on("end", function() { + .on("end", function () { select(this).classed("dragging", false); }) ); diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index e75d6f14e7..162b0c833f 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -181,33 +181,50 @@ export class CartesianScales extends Service { return this.scales[this.getMainYAxisPosition()]; } - getValueFromScale(axisPosition: AxisPositions, datum: any, index?: number) { + getValueFromScale(scale: any, scaleType: ScaleTypes, axisPosition: AxisPositions, datum: any, index?: number) { const options = this.model.getOptions(); - const axisOptions = Tools.getProperty(options, "axes", axisPosition); - - const scaleType = this.scaleTypes[axisPosition]; - const scale = this.scales[axisPosition]; - + const axesOptions = Tools.getProperty(options, "axes"); + const axisOptions = axesOptions[axisPosition]; const { mapsTo } = axisOptions; const value = datum[mapsTo] !== undefined ? datum[mapsTo] : datum; - - if (scaleType === ScaleTypes.LABELS) { - return scale(value) + scale.step() / 2; + let scaledValue; + switch (scaleType) { + case ScaleTypes.LABELS: + scaledValue = scale(value) + scale.step() / 2; + break; + case ScaleTypes.TIME: + scaledValue = scale(new Date(value)); + break; + default: + scaledValue = scale(value); } + return scaledValue; + } - if (scaleType === ScaleTypes.TIME) { - return scale(new Date(value)); - } + getValueThroughAxisPosition(axisPosition: AxisPositions, datum: any, index?: number) { + const scaleType = this.scaleTypes[axisPosition]; + const scale = this.scales[axisPosition]; - return scale(value); + return this.getValueFromScale(scale, scaleType, axisPosition, datum, index); } + getDomainValue(d, i) { - return this.getValueFromScale(this.domainAxisPosition, d, i); + return this.getValueThroughAxisPosition(this.domainAxisPosition, d, i); } getRangeValue(d, i) { - return this.getValueFromScale(this.rangeAxisPosition, d, i); + return this.getValueThroughAxisPosition(this.rangeAxisPosition, d, i); + } + + getXValue(d, i) { + const mainXAxisPosition = this.getMainXAxisPosition(); + return this.getValueThroughAxisPosition(mainXAxisPosition, d, i); + } + + getYValue(d, i) { + const mainYAxisPosition = this.getMainYAxisPosition(); + return this.getValueThroughAxisPosition(mainYAxisPosition, d, i); } getDomainIdentifier() { @@ -271,6 +288,65 @@ export class CartesianScales extends Service { } } + getHighestDomainThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const domainAxisPosition = this.getDomainAxisPosition(); + + const { thresholds } = axesOptions[domainAxisPosition]; + + if (!thresholds) { + return null; + } + + const domainScale = this.getDomainScale(); + // Find the highest threshold for the domain + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value, + )[0]; + + const scaleType = this.getScaleTypeByPosition(domainAxisPosition); + if ( + scaleType === ScaleTypes.TIME && + (typeof highestThreshold.value === "string" || + highestThreshold.value.getTime === undefined) + ) { + highestThreshold.value = new Date(highestThreshold.value); + } + + return { + threshold: highestThreshold, + scaleValue: domainScale(highestThreshold.value), + }; + } + + getHighestRangeThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const rangeAxisPosition = this.getRangeAxisPosition(); + + const { thresholds } = axesOptions[rangeAxisPosition]; + + if (!thresholds) { + return null; + } + + const rangeScale = this.getRangeScale(); + // Find the highest threshold for the range + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value, + )[0]; + + return { + threshold: highestThreshold, + scaleValue: rangeScale(highestThreshold.value), + }; + } + protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); @@ -438,65 +514,6 @@ export class CartesianScales extends Service { return scale; } - - getHighestDomainThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const domainAxisPosition = this.getDomainAxisPosition(); - - const { thresholds } = axesOptions[domainAxisPosition]; - - if (!thresholds) { - return null; - } - - const domainScale = this.getDomainScale(); - // Find the highest threshold for the domain - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - const scaleType = this.getScaleTypeByPosition(domainAxisPosition); - if ( - scaleType === ScaleTypes.TIME && - (typeof highestThreshold.value === "string" || - highestThreshold.value.getTime === undefined) - ) { - highestThreshold.value = new Date(highestThreshold.value); - } - - return { - threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value) - }; - } - - getHighestRangeThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const rangeAxisPosition = this.getRangeAxisPosition(); - - const { thresholds } = axesOptions[rangeAxisPosition]; - - if (!thresholds) { - return null; - } - - const rangeScale = this.getRangeScale(); - // Find the highest threshold for the range - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - return { - threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value) - }; - } } function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { diff --git a/packages/core/tslint.json b/packages/core/tslint.json index a5a0970b82..91271d283a 100644 --- a/packages/core/tslint.json +++ b/packages/core/tslint.json @@ -92,7 +92,7 @@ "trailing-comma": [ true, { - "multiline": "never", + "multiline": "always", "singleline": "never" } ] From c3f350c0f9da07e9ce4bcd580bf1d5b994bafa18 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 3 Jun 2020 21:56:58 +0800 Subject: [PATCH 143/510] feat: use d3-brush to implement selection --- packages/core/src/components/axes/zoom-bar.ts | 184 +++++++++--------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index f6ac815a7b..8066d0aa08 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -5,20 +5,30 @@ import { ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports -import { area, line } from "d3-shape"; import { extent } from "d3-array"; +import { brushX } from "d3-brush"; import { drag } from "d3-drag"; -import { event, select, selectAll } from "d3-selection"; +import { area, line } from "d3-shape"; +import { event, select, selectAll, BaseType } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + height = 32; + ogXScale: any; dragged = Tools.debounce((element, d, e) => { + console.log("dragged"); + console.log(this.ogXScale.invert(100)); + console.log(d); + console.log(e); element = select(element); const startingHandle = element.attr("class").indexOf("start") !== -1; - + console.log("startingHandle?:" + startingHandle); + const oldDomain = this.model.get("zoomDomain"); + console.log("old domain"); + console.log(oldDomain); let domain; if (startingHandle) { domain = [ @@ -33,9 +43,13 @@ export class ZoomBar extends Component { // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } + console.log("new domain"); + console.log(domain); this.model.set({ zoomDomain: domain }, { animate: false }); }, 2.5); + + render(animate = true) { const svg = this.getContainerSVG(); @@ -45,19 +59,69 @@ export class ZoomBar extends Component { const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition + mainXAxisPosition, ); const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition + mainYAxisPosition, ); - const height = 32; + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") // .attr("transform", "translateX(10)") .attr("width", "100%") - .attr("height", height) + .attr("height", this.height) .attr("opacity", 1); + const brushHandle = (g, selection) => { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{type: "w"}, {type: "e"}]) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32) + .attr("fill", "#525252"); + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{type: "w"}, {type: "e"}]) + .join("rect") + .attr("class", function(d) { return "handle-bar handle-bar--" + d.type; }) + .attr("x", function(d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + + }; + + const brushed = () => { + const selection = event.selection; + if (selection === null) { + // do nothing + console.log("selection === null"); + } else { + // TODO - pass selection to callback function or update scaleDomain + } + // update brush handle position + select(svg).call(brushHandle, selection); + }; + + const brush = brushX() + .extent([[0, 0], [700, this.height]]) + .on("start brush end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") + .call(brush) + .call(brush.move, mainXScale.range()); // TODO -- mainXScale.range() incorrect + const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) .attr("y", 32) @@ -105,11 +169,13 @@ export class ZoomBar extends Component { return correspondingData; }); + // if (!this.ogXScale) { + // this.ogXScale = cartesianScales.getDomainScale(); + // } const xScale = mainXScale.copy(); if (!this.ogXScale) { this.ogXScale = xScale; } - const yScale = mainYScale.copy(); const { width } = DOMUtils.getSVGElementSize(this.parent, { @@ -121,7 +187,7 @@ export class ZoomBar extends Component { .domain(extent(stackDataArray, (d: any) => d.date)); yScale - .range([0, height - 6]) + .range([0, this.height - 6]) .domain(extent(stackDataArray, (d: any) => d.value)); const zoomDomain = this.model.get("zoomDomain"); @@ -134,19 +200,19 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i - ) + i, + ), ) .y( (d, i) => - height - + this.height - cartesianScales.getValueFromScale( yScale, mainYScaleType, mainYAxisPosition, d, - i - ) + i, + ), ) .curve(this.services.curves.getD3Curve()); // .defined((d: any, i) => { @@ -160,7 +226,7 @@ export class ZoomBar extends Component { // }); const lineGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-line" + "path.zoom-graph-line", ) .attr("stroke", "#8e8e8e") .attr("stroke-width", 3) @@ -169,8 +235,8 @@ export class ZoomBar extends Component { .transition( this.services.transitions.getTransition( "zoom-pan-line-update", - animate - ) + animate, + ), ) .attr("d", lineGenerator); @@ -181,101 +247,36 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i - ) + i, + ), ) - .y0(height) + .y0(this.height) .y1( (d, i) => - height - + this.height - cartesianScales.getValueFromScale( yScale, mainYScaleType, mainYAxisPosition, d, - i - ) + i, + ), ); const areaGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-area" + "path.zoom-graph-area", ) .attr("fill", "#e0e0e0") .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-area-update", - animate - ) + animate, + ), ) .attr("d", areaGenerator); - const startHandlePosition = zoomDomain - ? xScale(+zoomDomain[0]) - : 0; - // Handle #1 - const startHandle = DOMUtils.appendOrSelect( - container, - "rect.zoom-handle.start" - ) - .attr("x", startHandlePosition) - .attr("width", 5) - .attr("height", "100%") - .attr("fill", "#525252"); - - DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.start") - .attr("x", startHandlePosition + 2) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - const endHandlePosition = zoomDomain - ? xScale(+zoomDomain[1]) - : xScale.range()[1]; - // console.log("endHandlePosition", endHandlePosition) - - // Handle #2 - const endHandle = DOMUtils.appendOrSelect( - container, - "rect.zoom-handle.end" - ) - .attr("x", endHandlePosition - 5) - .attr("width", 5) - .attr("height", "100%") - .attr("fill", "#525252"); - - DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.end") - .attr("x", endHandlePosition - 5 + 2) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - - const outboundRangeRight = DOMUtils.appendOrSelect( - container, - "rect.outbound-range.right" - ) - .attr("x", endHandlePosition) - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "#fff") - .attr("fill-opacity", 0.85); - - const self = this; - // handle2.on("click", this.zoomIn.bind(this)); - selectAll("rect.zoom-handle").call( - drag() - .on("start", function () { - select(this).classed("dragging", true); - }) - .on("drag", function (d) { - self.dragged(this, d, event); - }) - .on("end", function () { - select(this).classed("dragging", false); - }) - ); } } } @@ -284,4 +285,5 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } + } From 0014e1b1ac274f0185c838c02f63eacef6fff199 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 8 Jun 2020 16:04:58 +0800 Subject: [PATCH 144/510] fix: add left margin to align to chart - using .scss to set fill and stroke color --- packages/core/src/components/axes/zoom-bar.ts | 162 +++++++----------- .../core/src/styles/components/_zoom-bar.scss | 26 ++- 2 files changed, 86 insertions(+), 102 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 8066d0aa08..b200d522b2 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -7,9 +7,8 @@ import { DOMUtils } from "../../services"; // D3 Imports import { extent } from "d3-array"; import { brushX } from "d3-brush"; -import { drag } from "d3-drag"; import { area, line } from "d3-shape"; -import { event, select, selectAll, BaseType } from "d3-selection"; +import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; @@ -18,39 +17,9 @@ export class ZoomBar extends Component { ogXScale: any; - dragged = Tools.debounce((element, d, e) => { - console.log("dragged"); - console.log(this.ogXScale.invert(100)); - console.log(d); - console.log(e); - element = select(element); - const startingHandle = element.attr("class").indexOf("start") !== -1; - console.log("startingHandle?:" + startingHandle); - const oldDomain = this.model.get("zoomDomain"); - console.log("old domain"); - console.log(oldDomain); - let domain; - if (startingHandle) { - domain = [ - this.ogXScale.invert(e.x), - this.ogXScale.domain()[1], - // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) - ]; - } else { - domain = [ - this.ogXScale.domain()[0], - this.ogXScale.invert(e.x), - // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) - ]; - } - console.log("new domain"); - console.log(domain); - this.model.set({ zoomDomain: domain }, { animate: false }); - }, 2.5); - - - render(animate = true) { + // TODO - get correct axis left width + const axisLeftWidth = 23; const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -65,63 +34,11 @@ export class ZoomBar extends Component { mainYAxisPosition, ); - const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") - // .attr("transform", "translateX(10)") .attr("width", "100%") .attr("height", this.height) .attr("opacity", 1); - const brushHandle = (g, selection) => { - const handleSize = 5; - // handle - svg.select("g.brush") - .selectAll("rect.handle") - .data([{type: "w"}, {type: "e"}]) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) - .attr("fill", "#525252"); - // handle-bar - svg.select("g.brush") - .selectAll("rect.handle-bar") - .data([{type: "w"}, {type: "e"}]) - .join("rect") - .attr("class", function(d) { return "handle-bar handle-bar--" + d.type; }) - .attr("x", function(d) { - if (d.type === "w") { - return selection[0] - 1; - } else if (d.type === "e") { - return selection[1] - 1; - } - }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - - }; - - const brushed = () => { - const selection = event.selection; - if (selection === null) { - // do nothing - console.log("selection === null"); - } else { - // TODO - pass selection to callback function or update scaleDomain - } - // update brush handle position - select(svg).call(brushHandle, selection); - }; - - const brush = brushX() - .extent([[0, 0], [700, this.height]]) - .on("start brush end", brushed); - - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") - .call(brush) - .call(brush.move, mainXScale.range()); // TODO -- mainXScale.range() incorrect - const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) .attr("y", 32) @@ -131,7 +48,7 @@ export class ZoomBar extends Component { .attr("fill", "none"); const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") - .attr("x", 0) + .attr("x", axisLeftWidth) .attr("y", 0) .attr("width", "100%") .attr("height", "100%") @@ -169,9 +86,9 @@ export class ZoomBar extends Component { return correspondingData; }); - // if (!this.ogXScale) { - // this.ogXScale = cartesianScales.getDomainScale(); - // } + if (!this.ogXScale) { + this.ogXScale = cartesianScales.getDomainScale(); + } const xScale = mainXScale.copy(); if (!this.ogXScale) { this.ogXScale = xScale; @@ -183,7 +100,7 @@ export class ZoomBar extends Component { }); xScale - .range([0, width]) + .range([axisLeftWidth, width]) .domain(extent(stackDataArray, (d: any) => d.date)); yScale @@ -228,9 +145,6 @@ export class ZoomBar extends Component { container, "path.zoom-graph-line", ) - .attr("stroke", "#8e8e8e") - .attr("stroke-width", 3) - .attr("fill", "none") .datum(stackDataArray) .transition( this.services.transitions.getTransition( @@ -267,7 +181,6 @@ export class ZoomBar extends Component { container, "path.zoom-graph-area", ) - .attr("fill", "#e0e0e0") .datum(stackDataArray) .transition( this.services.transitions.getTransition( @@ -277,6 +190,64 @@ export class ZoomBar extends Component { ) .attr("d", areaGenerator); + const updateBrushHandle = (g, selection) => { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{ type: "w" }, { type: "e" }]) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32); + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{ type: "w" }, { type: "e" }]) + .join("rect") + .attr("class", function (d) { + return "handle-bar handle-bar--" + d.type; + }) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12); + }; + + const brushed = () => { + const selection = event.selection; + if (selection === null) { + // do nothing + console.log("selection === null"); + } else { + // TODO - pass selection to callback function or update scaleDomain + } + // update brush handle position + select(svg).call(updateBrushHandle, selection); + + const domain = [ + mainXScale.invert(selection[0]), + mainXScale.invert(selection[1]), + ]; + // TODO -- update zoomDomain in model + // this.model.set({zoomDomain: domain}, {animate: false}); + }; + + const brush = brushX() + .extent([ + [0 + axisLeftWidth, 0], + [700, this.height], + ]) + .on("start brush end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") + .call(brush) + .call(brush.move, xScale.range()); } } } @@ -285,5 +256,4 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } - } diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 02fcf7a55f..59019956a8 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -1,11 +1,25 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { - rect.zoom-handle.dragging, - rect.zoom-handle:hover { - fill: black; - cursor: col-resize; + rect.zoom-bg { + fill: $ui-background; + stroke: $ui-01; } - rect.zoom-handle-bar { - pointer-events: none; + path.zoom-graph-line { + stroke: $ui-04; + stroke-width: 3; + fill: none; + } + + path.zoom-graph-area { + fill: $ui-03; + stroke: $ui-04; + } + + g.brush rect.handle { + fill: $icon-02; + } + + g.brush rect.handle-bar { + fill: $ui-02; } } From 8fd4294de856c5dc63127ec10a4e70d989257b22 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 16:03:16 +0800 Subject: [PATCH 145/510] feat: set brush selection to zoomDomain in Model - use flag to avoid recursive update event --- packages/core/src/components/axes/zoom-bar.ts | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index b200d522b2..12b7b356be 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,7 +17,14 @@ export class ZoomBar extends Component { ogXScale: any; + // use this flag to avoid recursive update events + skipNextUpdate = false; + render(animate = true) { + if (this.skipNextUpdate === true) { + this.skipNextUpdate = false; + return; + } // TODO - get correct axis left width const axisLeftWidth = 23; const svg = this.getContainerSVG(); @@ -28,10 +35,10 @@ export class ZoomBar extends Component { const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition, + mainXAxisPosition ); const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition, + mainYAxisPosition ); const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") @@ -96,7 +103,7 @@ export class ZoomBar extends Component { const yScale = mainYScale.copy(); const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true, + useAttrs: true }); xScale @@ -117,8 +124,8 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i, - ), + i + ) ) .y( (d, i) => @@ -128,8 +135,8 @@ export class ZoomBar extends Component { mainYScaleType, mainYAxisPosition, d, - i, - ), + i + ) ) .curve(this.services.curves.getD3Curve()); // .defined((d: any, i) => { @@ -143,14 +150,14 @@ export class ZoomBar extends Component { // }); const lineGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-line", + "path.zoom-graph-line" ) .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-line-update", - animate, - ), + animate + ) ) .attr("d", lineGenerator); @@ -161,8 +168,8 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i, - ), + i + ) ) .y0(this.height) .y1( @@ -173,20 +180,20 @@ export class ZoomBar extends Component { mainYScaleType, mainYAxisPosition, d, - i, - ), + i + ) ); const areaGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-area", + "path.zoom-graph-area" ) .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-area-update", - animate, - ), + animate + ) ) .attr("d", areaGenerator); @@ -196,9 +203,17 @@ export class ZoomBar extends Component { svg.select("g.brush") .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 3; + } else if (d.type === "e") { + return selection[1] - 3; + } + }) .attr("y", 0) .attr("width", handleSize) - .attr("height", 32); + .attr("height", 32) + .style("display", null); // always display // handle-bar svg.select("g.brush") .selectAll("rect.handle-bar") @@ -220,10 +235,10 @@ export class ZoomBar extends Component { }; const brushed = () => { - const selection = event.selection; + let selection = event.selection; if (selection === null) { - // do nothing - console.log("selection === null"); + // set to default full range + selection = xScale.range(); } else { // TODO - pass selection to callback function or update scaleDomain } @@ -231,23 +246,29 @@ export class ZoomBar extends Component { select(svg).call(updateBrushHandle, selection); const domain = [ - mainXScale.invert(selection[0]), - mainXScale.invert(selection[1]), + xScale.invert(selection[0]), + xScale.invert(selection[1]) ]; - // TODO -- update zoomDomain in model - // this.model.set({zoomDomain: domain}, {animate: false}); + // only if the brush event comes from mouseup event + if (event.sourceEvent != null && event.type === "end") { + this.skipNextUpdate = true; // avoid recursive update + this.model.set( + { zoomDomain: domain }, + { animate: false } + ); + } }; const brush = brushX() .extent([ [0 + axisLeftWidth, 0], - [700, this.height], + [700, this.height] ]) .on("start brush end", brushed); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") .call(brush) - .call(brush.move, xScale.range()); + .call(brush.move, xScale.range()); // default to full range } } } From 7a644af1488e0b1d58a0945504c9e4ad3d1ad99e Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:42:26 +0800 Subject: [PATCH 146/510] feat: create ZoomBarOptions and default configuration - default to disable zoombar --- packages/core/src/axis-chart.ts | 9 ++++----- packages/core/src/configuration.ts | 13 +++++++++++-- packages/core/src/interfaces/charts.ts | 7 ++++++- packages/core/src/interfaces/components.ts | 10 ++++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index e4d105905f..04f9bdeeb5 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -126,9 +126,7 @@ export class AxisChart extends Chart { const zoomBarComponent = { id: "zoom-bar", - components: [ - new ZoomBar(this.model, this.services) - ], + components: [new ZoomBar(this.model, this.services)], growth: { x: LayoutGrowth.PREFERRED, y: LayoutGrowth.FIXED @@ -151,8 +149,9 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } - - topLevelLayoutComponents.push(zoomBarComponent); + if (this.model.getOptions().zoomBar.enabled === true) { + topLevelLayoutComponents.push(zoomBarComponent); + } topLevelLayoutComponents.push(fullFrameComponent); return [ diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index f93d747285..d09e139d44 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -23,7 +23,8 @@ import { StackedBarOptions, MeterChartOptions, GaugeTypes, - Alignments + Alignments, + ZoomBarOptions } from "./interfaces"; import enUSLocaleObject from "date-fns/locale/en-US/index"; @@ -127,6 +128,13 @@ export const timeScale: TimeScaleOptions = { } }; +/** + * ZoomBar options + */ +export const zoomBar: ZoomBarOptions = { + enabled: false +}; + /** * Base chart options common to any chart */ @@ -155,7 +163,8 @@ const chart: BaseChartOptions = { const axisChart: AxisChartOptions = Tools.merge({}, chart, { axes, timeScale, - grid + grid, + zoomBar } as AxisChartOptions); /** diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index eceba01b06..5ff08b4f9d 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -8,7 +8,8 @@ import { LegendOptions, TooltipOptions, GridOptions, - AxesOptions + AxesOptions, + ZoomBarOptions } from "./index"; import { BarOptions, StackedBarOptions } from "./components"; import { TimeScaleOptions } from "./axis-scales"; @@ -45,6 +46,10 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 4b56616f59..57199a3dad 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -116,3 +116,13 @@ export interface BarOptions { export interface StackedBarOptions extends BarOptions { dividerSize?: number; } + +/** + * customize the ZoomBar component + */ +export interface ZoomBarOptions { + /** + * is the zoom-bar visible or not + */ + enabled?: boolean; +} From d52b43ba025ef937596a3b08c8995aceb17b24dd Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:44:58 +0800 Subject: [PATCH 147/510] feat: create zoombar story in storybook --- packages/core/demo/data/time-series-axis.ts | 34 ++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index c1c7183571..e9519866dc 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -13,7 +13,7 @@ export const lineTimeSeriesData15seconds = { { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } ] } ] @@ -418,3 +418,35 @@ export const lineTimeSeriesAllLabelsInPrimaryFormatOptions = { addSpaceOnEdges: 0 } }; + +// ZoomBar +export const lineTimeSeriesDataZoomBar = { + labels: ["Qty"], + datasets: [ + { + label: "Dataset 1", + data: [ + { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, + { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, + { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, + { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, + { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, + { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } + ] + } + ] +}; + +export const lineTimeSeriesZoomBarOptions = { + title: "Line (time series) - zoom-bar enabled", + axes: { + left: {}, + bottom: { + scaleType: "time" + } + }, + zoomBar: { + enabled: true + } +}; From 1e2b28c26c94aef6d1f06397c1e30f09e1383b01 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 11:17:12 +0800 Subject: [PATCH 148/510] fix: set tslint trailing-comma to never --- packages/core/tslint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/tslint.json b/packages/core/tslint.json index 91271d283a..a5a0970b82 100644 --- a/packages/core/tslint.json +++ b/packages/core/tslint.json @@ -92,7 +92,7 @@ "trailing-comma": [ true, { - "multiline": "always", + "multiline": "never", "singleline": "never" } ] From f23f4997231a95018419a330025f8856ddc32af3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 12:23:10 +0800 Subject: [PATCH 149/510] fix: allow zoomDomain to update for every brush event --- packages/core/src/components/axes/zoom-bar.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 12b7b356be..37c68ab870 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,14 +17,7 @@ export class ZoomBar extends Component { ogXScale: any; - // use this flag to avoid recursive update events - skipNextUpdate = false; - render(animate = true) { - if (this.skipNextUpdate === true) { - this.skipNextUpdate = false; - return; - } // TODO - get correct axis left width const axisLeftWidth = 23; const svg = this.getContainerSVG(); @@ -245,17 +238,24 @@ export class ZoomBar extends Component { // update brush handle position select(svg).call(updateBrushHandle, selection); - const domain = [ + const newDomain = [ xScale.invert(selection[0]), xScale.invert(selection[1]) ]; + // only if the brush event comes from mouseup event - if (event.sourceEvent != null && event.type === "end") { - this.skipNextUpdate = true; // avoid recursive update - this.model.set( - { zoomDomain: domain }, - { animate: false } - ); + if (event.sourceEvent != null) { + // only if zoomDomain is never set or needs update + if ( + zoomDomain === undefined || + zoomDomain[0] !== newDomain[0] || + zoomDomain[1] !== newDomain[1] + ) { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + } } }; @@ -266,9 +266,12 @@ export class ZoomBar extends Component { ]) .on("start brush end", brushed); - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") - .call(brush) - .call(brush.move, xScale.range()); // default to full range + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( + brush + ); + if (zoomDomain === undefined) { + brushArea.call(brush.move, xScale.range()); // default to full range + } } } } From 51e541354431e9e62f597ce8957bb665694c817c Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 23:06:38 +0800 Subject: [PATCH 150/510] feat: provide ZoomBar options for brush event callback - selected x range and domain as callback parameters --- packages/core/demo/data/time-series-axis.ts | 20 +++++++++++++++- packages/core/src/components/axes/zoom-bar.ts | 23 +++++++++++++++++++ packages/core/src/configuration.ts | 5 +++- packages/core/src/interfaces/components.ts | 12 ++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e9519866dc..e79de136b6 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -437,6 +437,21 @@ export const lineTimeSeriesDataZoomBar = { } ] }; +const selectionStartFun = (selection, domain) => { + console.log("ZoomBar SelectionStart callback!"); + console.log(selection); + console.log(domain); +}; +const selectionInProgressFun = (selection, domain) => { + console.log("ZoomBar SelectionInProgress callback!"); + console.log(selection); + console.log(domain); +}; +const selectionEndFun = (selection, domain) => { + console.log("ZoomBar SelectionEnd callback!"); + console.log(selection); + console.log(domain); +}; export const lineTimeSeriesZoomBarOptions = { title: "Line (time series) - zoom-bar enabled", @@ -447,6 +462,9 @@ export const lineTimeSeriesZoomBarOptions = { } }, zoomBar: { - enabled: true + enabled: true, + selectionStart: selectionStartFun, + selectionInProgress: selectionInProgressFun, + selectionEnd: selectionEndFun } }; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 37c68ab870..4fdfa2fec5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -256,6 +256,29 @@ export class ZoomBar extends Component { { animate: false } ); } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress( + selection, + newDomain + ); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } } }; diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index d09e139d44..5109d282e1 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -132,7 +132,10 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false + enabled: false, + selectionStart: undefined, + selectionInProgress: undefined, + selectionEnd: undefined }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 57199a3dad..33d100ff45 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -125,4 +125,16 @@ export interface ZoomBarOptions { * is the zoom-bar visible or not */ enabled?: boolean; + /** + * a function to handle selection start event + */ + selectionStart?: Function; + /** + * a function to handle selection in progress event + */ + selectionInProgress?: Function; + /** + * a function to handle selection end event + */ + selectionEnd?: Function; } From 119c254015ca180bb127833676e391bc249d11d3 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Mon, 15 Jun 2020 18:46:54 +0800 Subject: [PATCH 151/510] feat: apply clipPath to line chart - v1 --- packages/core/src/charts/line.ts | 2 + packages/core/src/components/axes/cover.ts | 64 +++++++++++++++++++++ packages/core/src/components/component.ts | 25 ++++++-- packages/core/src/components/graphs/line.ts | 3 +- packages/core/src/components/index.ts | 1 + 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/components/axes/cover.ts diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index 469bf7a28e..d766c7ad5a 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -11,6 +11,7 @@ import { Line, Ruler, Scatter, + Cover, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) Tooltip, @@ -40,6 +41,7 @@ export class LineChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts new file mode 100644 index 0000000000..20b4083e09 --- /dev/null +++ b/packages/core/src/components/axes/cover.ts @@ -0,0 +1,64 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { axisBottom, axisLeft } from "d3-axis"; +import { mouse, select } from "d3-selection"; +import { TooltipTypes, Events } from "../../interfaces"; + +export class Cover extends Component { + type = "cover"; + + coverClipPath: any; + + render(animate = true) { + // Create the cover + this.createCover(); + } + + + createCover() { + const svg = this.parent; + console.log("!!! cover svg: ", svg); + const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainYScale = this.services.cartesianScales.getMainYScale(); + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + // Get height + this.coverClipPath = DOMUtils.appendOrSelect(svg, `clipPath.${this.type}`); + this.coverClipPath + .attr("id", `${this.type}Clip`); + const coverRect = DOMUtils.appendOrSelect( + this.coverClipPath, + "rect.cover" + ); + coverRect + .attr("x", xScaleStart) + .attr("y", yScaleStart) + .attr("width", xScaleEnd - xScaleStart) + .attr("height", yScaleEnd - yScaleStart); + + this.coverClipPath + .merge(coverRect) + .lower(); + + const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); + coverG + .attr("clip-path", `url(#${this.type}Clip)`) + .attr("id", `g-${this.type}Clip`); + + } + + cleanCover(g) { + const options = this.model.getOptions(); + g.selectAll("line").attr("stroke", options.grid.strokeColor); + + // Remove extra elements + g.selectAll("text").remove(); + g.select(".domain").remove(); + } +} diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 7530628ac1..2b1d8d84d0 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -90,10 +90,27 @@ export class Component { "style", "prefix" ); - return DOMUtils.appendOrSelect( - this.parent, - `g.${settings.prefix}--${chartprefix}--${this.type}` - ); + if (this.type === "line" || this.type === "scatter") { + const { width, height } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + return DOMUtils.appendOrSelectForAxisChart( + this.parent, + `clipPath.cover`, + this.type, + 23, + 0, + (width - 23), + height, + ); + + } else { + return DOMUtils.appendOrSelect( + this.parent, + `g.${settings.prefix}--${chartprefix}--${this.type}` + ); + } + } return this.parent; diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 2d2c5c36c7..2be9c949a9 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -131,6 +131,7 @@ export class Line extends Component { this.parent .selectAll("path.line") + .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-hover-line") ) @@ -145,7 +146,7 @@ export class Line extends Component { handleLegendMouseOut = (event: CustomEvent) => { this.parent - .selectAll("path.line") + .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-mouseout-line") ) diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 60daf43009..8be94d9e0e 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -35,3 +35,4 @@ export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; +export * from "./axes/cover"; From 9b002cc3ad4b2078674b1af5911c982d72a021f3 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 13:37:58 +0800 Subject: [PATCH 152/510] feat: apply cover to axis chart --- packages/core/src/charts/area-stacked.ts | 2 ++ packages/core/src/charts/area.ts | 2 ++ packages/core/src/charts/bar-grouped.ts | 2 ++ packages/core/src/charts/bar-simple.ts | 2 ++ packages/core/src/charts/bar-stacked.ts | 2 ++ packages/core/src/charts/bubble.ts | 4 +++- packages/core/src/charts/scatter.ts | 2 ++ packages/core/src/components/component.ts | 26 +++++++++++------------ 8 files changed, 28 insertions(+), 14 deletions(-) diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index c89a4f132d..8f18b69cf5 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -7,6 +7,7 @@ import { Tools } from "../tools"; // Components import { Grid, + Cover, StackedArea, TwoDimensionalAxes, Line, @@ -35,6 +36,7 @@ export class StackedAreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new StackedArea(this.model, this.services), diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index aaf6cbad0a..a61e3f669c 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -7,6 +7,7 @@ import { Tools } from "../tools"; // Components import { Grid, + Cover, Area, Line, Ruler, @@ -39,6 +40,7 @@ export class AreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index bba066a001..6a2be76a82 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, GroupedBar, TwoDimensionalAxes, ZeroLine, @@ -39,6 +40,7 @@ export class GroupedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new GroupedBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 0115bd6959..bf2da6772b 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, SimpleBar, TwoDimensionalAxes, ZeroLine, @@ -39,6 +40,7 @@ export class SimpleBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new SimpleBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 5edcdfc9b3..443f483d33 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, StackedBar, StackedBarRuler, TwoDimensionalAxes, @@ -42,6 +43,7 @@ export class StackedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new StackedBarRuler(this.model, this.services), new StackedBar(this.model, this.services), diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 05f1f5e4f1..dcdb8fcced 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -15,7 +15,8 @@ import { Tooltip, Legend, LayoutComponent, - Skeleton + Skeleton, + Cover } from "../components/index"; export class BubbleChart extends AxisChart { @@ -42,6 +43,7 @@ export class BubbleChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Bubble(this.model, this.services), diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 5d2a20c6cb..f3cda814cb 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -9,6 +9,7 @@ import { Skeletons } from "../interfaces/enums"; import { Grid, Ruler, + Cover, Scatter, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) @@ -42,6 +43,7 @@ export class ScatterChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Scatter(this.model, this.services), diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 2b1d8d84d0..c49dbd79fc 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -90,19 +90,19 @@ export class Component { "style", "prefix" ); - if (this.type === "line" || this.type === "scatter") { - const { width, height } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); - return DOMUtils.appendOrSelectForAxisChart( - this.parent, - `clipPath.cover`, - this.type, - 23, - 0, - (width - 23), - height, - ); + + + if ( + this.type === "line" || + this.type === "scatter" || + this.type === "area" || + this.type === "bubble" || + this.type === "area-stacked" || + this.type === "grouped-bar" || + this.type === "simple-bar" || + this.type === "scatter-stacked" + ) { + return DOMUtils.appendOrSelectForAxisChart(this.parent,`clipPath.cover`); } else { return DOMUtils.appendOrSelect( From 6d16f424e88574e2607ed4035522011d2bce9d82 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 13:57:19 +0800 Subject: [PATCH 153/510] refactor: clean code --- packages/core/src/charts/area-stacked.ts | 2 +- packages/core/src/charts/area.ts | 2 +- packages/core/src/charts/bar-grouped.ts | 2 +- packages/core/src/charts/bar-simple.ts | 2 +- packages/core/src/charts/bar-stacked.ts | 2 +- packages/core/src/charts/bubble.ts | 4 ++-- packages/core/src/charts/line.ts | 2 +- packages/core/src/charts/scatter.ts | 2 +- packages/core/src/components/axes/cover.ts | 10 ---------- packages/core/src/components/index.ts | 3 ++- 10 files changed, 11 insertions(+), 20 deletions(-) diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 8f18b69cf5..71cc66c34d 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,8 +6,8 @@ import { Tools } from "../tools"; // Components import { - Grid, Cover, + Grid, StackedArea, TwoDimensionalAxes, Line, diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index a61e3f669c..6d808c893c 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,8 +6,8 @@ import { Tools } from "../tools"; // Components import { - Grid, Cover, + Grid, Area, Line, Ruler, diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 6a2be76a82..63f82294ca 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, GroupedBar, TwoDimensionalAxes, ZeroLine, diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index bf2da6772b..21a1ccecc0 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, SimpleBar, TwoDimensionalAxes, ZeroLine, diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 443f483d33..5816fd62e8 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, StackedBar, StackedBarRuler, TwoDimensionalAxes, diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index dcdb8fcced..11469b0355 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Ruler, Bubble, @@ -15,8 +16,7 @@ import { Tooltip, Legend, LayoutComponent, - Skeleton, - Cover + Skeleton } from "../components/index"; export class BubbleChart extends AxisChart { diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index d766c7ad5a..943686c6c2 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,11 +7,11 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Line, Ruler, Scatter, - Cover, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) Tooltip, diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index f3cda814cb..e38fc66d51 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,9 +7,9 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Ruler, - Cover, Scatter, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts index 20b4083e09..16179b45c5 100644 --- a/packages/core/src/components/axes/cover.ts +++ b/packages/core/src/components/axes/cover.ts @@ -21,7 +21,6 @@ export class Cover extends Component { createCover() { const svg = this.parent; - console.log("!!! cover svg: ", svg); const mainXScale = this.services.cartesianScales.getMainXScale(); const mainYScale = this.services.cartesianScales.getMainYScale(); @@ -52,13 +51,4 @@ export class Cover extends Component { .attr("id", `g-${this.type}Clip`); } - - cleanCover(g) { - const options = this.model.getOptions(); - g.selectAll("line").attr("stroke", options.grid.strokeColor); - - // Remove extra elements - g.selectAll("text").remove(); - g.select(".domain").remove(); - } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 8be94d9e0e..a48f26437e 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,9 +30,10 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; +export * from "./axes/cover"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; -export * from "./axes/cover"; + From 54c7e0474d15189d9a5d599851f88e5e3ac77660 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 16 Jun 2020 14:57:41 +0800 Subject: [PATCH 154/510] feat: set axes margins as zoom bar left margin --- .../components/axes/two-dimensional-axes.ts | 3 +++ packages/core/src/components/axes/zoom-bar.ts | 20 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/axes/two-dimensional-axes.ts b/packages/core/src/components/axes/two-dimensional-axes.ts index ec843b20ab..d95e70e557 100644 --- a/packages/core/src/components/axes/two-dimensional-axes.ts +++ b/packages/core/src/components/axes/two-dimensional-axes.ts @@ -114,6 +114,9 @@ export class TwoDimensionalAxes extends Component { if (isNotEqual) { this.margins = Object.assign(this.margins, margins); + // also set new margins to model to allow external components to access + this.model.set({ axesMargins: this.margins }, { animate: false }); + Object.keys(this.children).forEach((childKey) => { const child = this.children[childKey]; child.margins = this.margins; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 4fdfa2fec5..0bf64460d9 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -18,10 +18,7 @@ export class ZoomBar extends Component { ogXScale: any; render(animate = true) { - // TODO - get correct axis left width - const axisLeftWidth = 23; const svg = this.getContainerSVG(); - const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); @@ -34,6 +31,13 @@ export class ZoomBar extends Component { mainYAxisPosition ); + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") .attr("width", "100%") .attr("height", this.height) @@ -48,7 +52,7 @@ export class ZoomBar extends Component { .attr("fill", "none"); const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") - .attr("x", axisLeftWidth) + .attr("x", axesLeftMargin) .attr("y", 0) .attr("width", "100%") .attr("height", "100%") @@ -100,7 +104,7 @@ export class ZoomBar extends Component { }); xScale - .range([axisLeftWidth, width]) + .range([axesLeftMargin, width]) .domain(extent(stackDataArray, (d: any) => d.date)); yScale @@ -232,8 +236,6 @@ export class ZoomBar extends Component { if (selection === null) { // set to default full range selection = xScale.range(); - } else { - // TODO - pass selection to callback function or update scaleDomain } // update brush handle position select(svg).call(updateBrushHandle, selection); @@ -284,8 +286,8 @@ export class ZoomBar extends Component { const brush = brushX() .extent([ - [0 + axisLeftWidth, 0], - [700, this.height] + [axesLeftMargin, 0], + [width, this.height] ]) .on("start brush end", brushed); From fb25ad3d0d5f6a0084b292249e7991cf459adc0f Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 16:04:29 +0800 Subject: [PATCH 155/510] refactor: add todo --- packages/core/src/components/component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index c49dbd79fc..5664f29ec8 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -91,18 +91,18 @@ export class Component { "prefix" ); - + // @todo Chart type equals to axis-chart if ( - this.type === "line" || - this.type === "scatter" || - this.type === "area" || + this.type === "line" || + this.type === "scatter" || + this.type === "area" || this.type === "bubble" || this.type === "area-stacked" || this.type === "grouped-bar" || this.type === "simple-bar" || this.type === "scatter-stacked" ) { - return DOMUtils.appendOrSelectForAxisChart(this.parent,`clipPath.cover`); + return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); } else { return DOMUtils.appendOrSelect( From 9fa80a71141e0ea811bf51499b14c9f40d2c6a4b Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 16 Jun 2020 16:54:51 +0800 Subject: [PATCH 156/510] fix: double the minTickSize for datetime ticks --- packages/core/src/components/axes/axis.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index d421ab09af..a4e66c1ab6 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -117,10 +117,9 @@ export class Axis extends Component { // if zoomDomain is available, update scale domain to Date array. const zoomDomain = this.model.get("zoomDomain"); if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain.map(d => new Date(d))); + scale.domain(zoomDomain.map((d) => new Date(d))); } - // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -426,11 +425,11 @@ export class Axis extends Component { const ticksNumber = isTimeScaleType ? axis.tickValues().length : scale.ticks().length; - const estimatedTickSize = width / ticksNumber / 1.6; - - rotateTicks = estimatedTickSize < minTickSize; + const estimatedTickSize = width / ticksNumber / 2; + rotateTicks = isTimeScaleType + ? estimatedTickSize < minTickSize * 2 // datetime tick could be very long + : estimatedTickSize < minTickSize; } - if (rotateTicks) { if (!isNumberOfTicksProvided) { axis.ticks( From 6e5e67c52b26d4f180e6af0045daffccbe72a57c Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 18 Jun 2020 13:42:33 +0800 Subject: [PATCH 157/510] feat: add chart brush --- packages/core/src/charts/area-stacked.ts | 5 +- packages/core/src/charts/area.ts | 5 +- packages/core/src/charts/bar-grouped.ts | 5 +- packages/core/src/charts/bar-simple.ts | 5 +- packages/core/src/charts/bar-stacked.ts | 5 +- packages/core/src/charts/bubble.ts | 5 +- packages/core/src/charts/donut.ts | 5 +- packages/core/src/charts/line.ts | 5 +- packages/core/src/charts/pie.ts | 5 +- packages/core/src/charts/radar.ts | 4 +- packages/core/src/charts/scatter.ts | 5 +- packages/core/src/components/axes/brush.ts | 142 +++++++++++++++++++++ packages/core/src/components/index.ts | 1 + 13 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/axes/brush.ts diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 71cc66c34d..b87365a97b 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, Cover, Grid, StackedArea, @@ -34,7 +35,7 @@ export class StackedAreaChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -48,6 +49,8 @@ export class StackedAreaChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 6d808c893c..8d5b94aa49 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, Cover, Grid, Area, @@ -38,7 +39,7 @@ export class AreaChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -51,6 +52,8 @@ export class AreaChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 63f82294ca..5668d302eb 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, GroupedBar, @@ -38,7 +39,7 @@ export class GroupedBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -49,6 +50,8 @@ export class GroupedBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 21a1ccecc0..951cd4509f 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, SimpleBar, @@ -38,7 +39,7 @@ export class SimpleBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -49,6 +50,8 @@ export class SimpleBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 5816fd62e8..e5684fb737 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, StackedBar, @@ -41,7 +42,7 @@ export class StackedBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class StackedBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 11469b0355..fd1c03a354 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Ruler, @@ -41,7 +42,7 @@ export class BubbleChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class BubbleChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/donut.ts b/packages/core/src/charts/donut.ts index 100faa5fac..18e00d2c36 100644 --- a/packages/core/src/charts/donut.ts +++ b/packages/core/src/charts/donut.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Donut, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -33,13 +34,15 @@ export class DonutChart extends PieChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new Donut(this.model, this.services), new Skeleton(this.model, this.services, { skeleton: Skeletons.DONUT }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getChartComponents(graphFrameComponents); return components; } diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index 943686c6c2..e950b1c4af 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Line, @@ -39,7 +40,7 @@ export class LineChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -51,6 +52,8 @@ export class LineChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 0fc4429807..9631b459a8 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Pie, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -46,13 +47,15 @@ export class PieChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new Pie(this.model, this.services), new Skeleton(this.model, this.services, { skeleton: Skeletons.PIE }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 8758bf1591..6a79b1a13b 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, // the imports below are needed because of typescript bug (error TS4029) Legend, LayoutComponent @@ -41,7 +42,8 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [new Radar(this.model, this.services)]; + const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index e38fc66d51..4c5b92d89b 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Ruler, @@ -41,7 +42,7 @@ export class ScatterChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class ScatterChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts new file mode 100644 index 0000000000..60dc0a8945 --- /dev/null +++ b/packages/core/src/components/axes/brush.ts @@ -0,0 +1,142 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { extent } from "d3-array"; +import { brushX } from "d3-brush"; +import { event } from "d3-selection"; +import { scaleTime } from "d3-scale"; + +export class Brush extends Component { + type = "brush"; + + render(animate = true) { + const svg = this.parent; + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const mainXScaleType = cartesianScales.getScaleTypeByPosition( + mainXAxisPosition + ); + + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + + const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainYScale = this.services.cartesianScales.getMainYScale(); + + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + const container = DOMUtils.appendOrSelect(svg, "svg.brush-container") + .attr("width", "100%") + .attr("height", "100%") + .attr("opacity", 1); + + if (mainXScale) { + const displayData = this.model.getDisplayData(); + + if (mainXScaleType === ScaleTypes.TIME) { + // Get all date values provided in data + // TODO - Could be re-used through the model + let allDates = []; + displayData.forEach((data) => { + allDates = allDates.concat(Number(data.date)); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + + // Go through all date values + // And get corresponding data from each dataset + const stackDataArray = allDates.map((date) => { + let count = 0; + let correspondingSum = 0; + const correspondingData = {}; + + displayData.forEach((data) => { + if (Number(data.date) === Number(date)) { + ++count; + correspondingSum += data.value; + } + }); + correspondingData["date"] = date; + correspondingData["value"] = correspondingSum; + + return correspondingData; + }); + + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + + let zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain === undefined) { + zoomDomain = extent(stackDataArray, (d: any) => d.date); // default to full range + this.model.set( + { zoomDomain: zoomDomain }, + { animate: false } + ); + } + + const brushed = () => { + let selection = event.selection; + if (selection !== null) { + // update zoombar handle position + // select(svg).call(updateBrushHandle, selection); + + // get current zoomDomain + zoomDomain = this.model.get("zoomDomain"); + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([axesLeftMargin, width]) + .domain(zoomDomain); + + const newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + // only if the brush event comes from mouseup event + if (event.sourceEvent != null) { + // only if zoomDomain needs update + if (zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1]) { + this.model.set( + { zoomDomain: newDomain, zoomDomainForZoomBar: newDomain }, + { animate: false } + ); + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + } + }; + + const brush = brushX() + .extent([ + [xScaleStart, 0], + [width, yScaleEnd] + ]) + .on("end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.chart-brush").call( + brush + ); + // no need for having default brush selection + // @todo try to hide brush after selection + setTimeout(()=> {brushArea.call(brush.move);}, 0); + } + } + } +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index a48f26437e..4d9959743a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,6 +30,7 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; +export * from "./axes/brush"; export * from "./axes/cover"; export * from "./axes/grid"; export * from "./axes/ruler"; From 8fa249b4c873434bd5360ea70d8d08352d1a9d55 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 18 Jun 2020 14:19:35 +0800 Subject: [PATCH 158/510] refactor: add brush in axis chart component --- packages/core/src/axis-chart.ts | 3 +++ packages/core/src/charts/area-stacked.ts | 3 --- packages/core/src/charts/area.ts | 3 --- packages/core/src/charts/bar-grouped.ts | 3 --- packages/core/src/charts/bar-simple.ts | 3 --- packages/core/src/charts/bar-stacked.ts | 3 --- packages/core/src/charts/bubble.ts | 3 --- packages/core/src/charts/donut.ts | 3 --- packages/core/src/charts/line.ts | 3 --- packages/core/src/charts/pie.ts | 5 +---- packages/core/src/charts/radar.ts | 4 +--- packages/core/src/charts/scatter.ts | 3 --- 12 files changed, 5 insertions(+), 34 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 04f9bdeeb5..543d0ac6d4 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -8,6 +8,7 @@ import { AxisChartOptions } from "./interfaces/index"; import { + Brush, LayoutComponent, Legend, Title, @@ -48,6 +49,8 @@ export class AxisChart extends Chart { } }; + !!this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ? + graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index b87365a97b..42895bdc6b 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, Cover, Grid, StackedArea, @@ -49,8 +48,6 @@ export class StackedAreaChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 8d5b94aa49..2b313f6cc7 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, Cover, Grid, Area, @@ -52,8 +51,6 @@ export class AreaChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 5668d302eb..59354739e6 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, GroupedBar, @@ -50,8 +49,6 @@ export class GroupedBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 951cd4509f..5709ea0e9b 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, SimpleBar, @@ -50,8 +49,6 @@ export class SimpleBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index e5684fb737..c2510fa7da 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, StackedBar, @@ -53,8 +52,6 @@ export class StackedBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index fd1c03a354..7238de0aa3 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Ruler, @@ -53,8 +52,6 @@ export class BubbleChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/donut.ts b/packages/core/src/charts/donut.ts index 18e00d2c36..a3c9a33a61 100644 --- a/packages/core/src/charts/donut.ts +++ b/packages/core/src/charts/donut.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Donut, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -41,8 +40,6 @@ export class DonutChart extends PieChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getChartComponents(graphFrameComponents); return components; } diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index e950b1c4af..ee4e07f163 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Line, @@ -52,8 +51,6 @@ export class LineChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 9631b459a8..4642ea8f2d 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -8,7 +8,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Pie, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -53,9 +52,7 @@ export class PieChart extends Chart { skeleton: Skeletons.PIE }) ]; - - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 6a79b1a13b..80b8e7888c 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, // the imports below are needed because of typescript bug (error TS4029) Legend, LayoutComponent @@ -43,8 +42,7 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 4c5b92d89b..2acacdf9df 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Ruler, @@ -53,8 +52,6 @@ export class ScatterChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); From 752f5cb3f9bbfa048997c926fda16f9c9c1b98b6 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 18 Jun 2020 22:32:35 +0800 Subject: [PATCH 159/510] refactor: move brush functions to ZoomBar class function --- packages/core/src/components/axes/zoom-bar.ts | 180 +++++++++--------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0bf64460d9..8f5d2587e7 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -194,94 +194,8 @@ export class ZoomBar extends Component { ) .attr("d", areaGenerator); - const updateBrushHandle = (g, selection) => { - const handleSize = 5; - // handle - svg.select("g.brush") - .selectAll("rect.handle") - .data([{ type: "w" }, { type: "e" }]) - .attr("x", function (d) { - if (d.type === "w") { - return selection[0] - 3; - } else if (d.type === "e") { - return selection[1] - 3; - } - }) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) - .style("display", null); // always display - // handle-bar - svg.select("g.brush") - .selectAll("rect.handle-bar") - .data([{ type: "w" }, { type: "e" }]) - .join("rect") - .attr("class", function (d) { - return "handle-bar handle-bar--" + d.type; - }) - .attr("x", function (d) { - if (d.type === "w") { - return selection[0] - 1; - } else if (d.type === "e") { - return selection[1] - 1; - } - }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12); - }; - - const brushed = () => { - let selection = event.selection; - if (selection === null) { - // set to default full range - selection = xScale.range(); - } - // update brush handle position - select(svg).call(updateBrushHandle, selection); - - const newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { - // only if zoomDomain is never set or needs update - if ( - zoomDomain === undefined || - zoomDomain[0] !== newDomain[0] || - zoomDomain[1] !== newDomain[1] - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress( - selection, - newDomain - ); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); - } - } + const brushEventListener = () => { + this.brushed(zoomDomain, xScale, event.selection); }; const brush = brushX() @@ -289,13 +203,18 @@ export class ZoomBar extends Component { [axesLeftMargin, 0], [width, this.height] ]) - .on("start brush end", brushed); + .on("start brush end", brushEventListener); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( brush ); if (zoomDomain === undefined) { brushArea.call(brush.move, xScale.range()); // default to full range + } else { + // brushArea.call( + // brush.move, + // zoomDomain.map((domain) => xScale(domain)) //set brush to correct position + // ); } } } @@ -305,4 +224,87 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } + + // brush event listener + brushed(zoomDomain, scale, selection) { + if (selection === null) { + // set to default full range + selection = scale.range(); + } + // update brush handle position + this.updateBrushHandle(this.getContainerSVG(), selection); + + const newDomain = [ + scale.invert(selection[0]), + scale.invert(selection[1]) + ]; + // only if the brush event comes from mouseup event + if (event.sourceEvent != null) { + // only if zoomDomain is never set or needs update + if ( + zoomDomain === undefined || + zoomDomain[0] !== newDomain[0] || + zoomDomain[1] !== newDomain[1] + ) { + this.model.set({ zoomDomain: newDomain }, { animate: false }); + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + } + + updateBrushHandle(svg, selection) { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{ type: "w" }, { type: "e" }]) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 3; + } else if (d.type === "e") { + return selection[1] - 3; + } + }) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32) + .style("display", null); // always display + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{ type: "w" }, { type: "e" }]) + .join("rect") + .attr("class", function (d) { + return "handle-bar handle-bar--" + d.type; + }) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12); + } } From 2e69e2334e9d1ab6f433f1580ce1873c711f0a85 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 19 Jun 2020 11:57:00 +0800 Subject: [PATCH 160/510] fix: handel situation of zoom bar selection[0] equals selection[1] --- packages/core/src/components/axes/zoom-bar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 8f5d2587e7..d7aff2470a 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -227,7 +227,9 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - if (selection === null) { + // follow d3 behavior: when selection[0] === selection[1], reset default full range + // @todo find a better way to handel the situation when selection[0] === selection[1] + if (selection === null || selection[0] === selection[1]) { // set to default full range selection = scale.range(); } From 79b58a2ee9639fdd53e8fc71e5b48bc7d7bc6ec8 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 12:37:08 +0800 Subject: [PATCH 161/510] feat: ZoomBar could update brush based on zoomDomain - fix some brush handle UI bugs --- packages/core/src/components/axes/zoom-bar.ts | 104 +++++++++++------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d7aff2470a..3c26a897bd 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,6 +17,8 @@ export class ZoomBar extends Component { ogXScale: any; + brush = brushX(); + render(animate = true) { const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -198,23 +200,30 @@ export class ZoomBar extends Component { this.brushed(zoomDomain, xScale, event.selection); }; - const brush = brushX() + this.brush .extent([ [axesLeftMargin, 0], [width, this.height] ]) + .on("start brush end", null) // remove old listener first .on("start brush end", brushEventListener); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( - brush + this.brush ); - if (zoomDomain === undefined) { - brushArea.call(brush.move, xScale.range()); // default to full range + if ( + zoomDomain === undefined || + zoomDomain[0].valueOf() === zoomDomain[1].valueOf() + ) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range() + ); } else { - // brushArea.call( - // brush.move, - // zoomDomain.map((domain) => xScale(domain)) //set brush to correct position - // ); + const selected = zoomDomain.map((domain) => xScale(domain)); + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle(this.getContainerSVG(), selected); } } } @@ -240,8 +249,13 @@ export class ZoomBar extends Component { scale.invert(selection[0]), scale.invert(selection[1]) ]; - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { + + // be aware that the value of d3.event changes during an event! + // update zoomDomain only if the event comes from mousemove event + if ( + event.sourceEvent != null && + event.sourceEvent.type === "mousemove" + ) { // only if zoomDomain is never set or needs update if ( zoomDomain === undefined || @@ -250,45 +264,53 @@ export class ZoomBar extends Component { ) { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); - } + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if (zoomBarOptions.selectionEnd !== undefined && event.type === "end") { + zoomBarOptions.selectionEnd(selection, newDomain); } } updateBrushHandle(svg, selection) { - const handleSize = 5; + // @todo the handle size, height are calculated by d3 library + // need to figure out how to override the value + const handleWidth = 6; + const handleHeight = 38; + const handleXDiff = -handleWidth / 2; + const handleYDiff = -(handleHeight - this.height) / 2; + + const handleBarWidth = 2; + const handleBarHeight = 12; + const handleBarXDiff = -handleBarWidth / 2; + const handleYBarDiff = + (handleHeight - handleBarHeight) / 2 + handleYDiff; // handle svg.select("g.brush") .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { if (d.type === "w") { - return selection[0] - 3; + return selection[0] + handleXDiff; } else if (d.type === "e") { - return selection[1] - 3; + return selection[1] + handleXDiff; } }) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) + .attr("y", handleYDiff) + .attr("width", handleWidth) + .attr("height", handleHeight) .style("display", null); // always display // handle-bar svg.select("g.brush") @@ -300,13 +322,13 @@ export class ZoomBar extends Component { }) .attr("x", function (d) { if (d.type === "w") { - return selection[0] - 1; + return selection[0] + handleBarXDiff; } else if (d.type === "e") { - return selection[1] - 1; + return selection[1] + handleBarXDiff; } }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12); + .attr("y", handleYBarDiff) + .attr("width", handleBarWidth) + .attr("height", handleBarHeight); } } From c111f8cab726e01e0732c19ae01465e93b6a2752 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 12:51:20 +0800 Subject: [PATCH 162/510] fix: handle empty selection --- packages/core/src/components/axes/zoom-bar.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 3c26a897bd..04507c90a3 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -197,7 +197,21 @@ export class ZoomBar extends Component { .attr("d", areaGenerator); const brushEventListener = () => { - this.brushed(zoomDomain, xScale, event.selection); + const selection = event.selection; + // follow d3 behavior: when selection is null, reset default full range + // @todo find a better way to handel the situation when selection is null + // select behavior is completed, but nothing selected + if (selection === null) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range() + ); + } else if (selection[0] === selection[1]) { + // select behavior is not completed yet, do nothing + } else { + this.brushed(zoomDomain, xScale, selection); + } }; this.brush @@ -236,12 +250,6 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - // follow d3 behavior: when selection[0] === selection[1], reset default full range - // @todo find a better way to handel the situation when selection[0] === selection[1] - if (selection === null || selection[0] === selection[1]) { - // set to default full range - selection = scale.range(); - } // update brush handle position this.updateBrushHandle(this.getContainerSVG(), selection); From 482ca6a7cafed1b03ca593b3fdf37fc6cb5a954f Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 19 Jun 2020 14:13:17 +0800 Subject: [PATCH 163/510] fix: set to default full range when same selection of brush --- packages/core/src/components/axes/brush.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 60dc0a8945..0a22fe0d79 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -45,7 +45,7 @@ export class Brush extends Component { if (mainXScaleType === ScaleTypes.TIME) { // Get all date values provided in data - // TODO - Could be re-used through the model + // @todo - Could be re-used through the model let allDates = []; displayData.forEach((data) => { allDates = allDates.concat(Number(data.date)); @@ -86,10 +86,8 @@ export class Brush extends Component { const brushed = () => { let selection = event.selection; - if (selection !== null) { - // update zoombar handle position - // select(svg).call(updateBrushHandle, selection); + if (selection !== null) { // get current zoomDomain zoomDomain = this.model.get("zoomDomain"); // create xScale based on current zoomDomain @@ -97,17 +95,26 @@ export class Brush extends Component { .range([axesLeftMargin, width]) .domain(zoomDomain); - const newDomain = [ + let newDomain = [ xScale.invert(selection[0]), xScale.invert(selection[1]) ]; + + // check if slected start time and end time are the same + if(newDomain[0].valueOf() === newDomain[1].valueOf()) { + // same as d3 behavior and zoombar behavior: set to default full range + newDomain = extent(stackDataArray, (d: any) => d.date); + } // only if the brush event comes from mouseup event if (event.sourceEvent != null) { // only if zoomDomain needs update - if (zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1]) { + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { this.model.set( - { zoomDomain: newDomain, zoomDomainForZoomBar: newDomain }, + { zoomDomain: newDomain }, { animate: false } ); } From 3a9fab628bc2939e49c6da53420134ede1bcf348 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 13:59:36 +0800 Subject: [PATCH 164/510] fix: fix bug when selection === null --- packages/core/src/components/axes/zoom-bar.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 04507c90a3..f7894985c5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -202,11 +202,7 @@ export class ZoomBar extends Component { // @todo find a better way to handel the situation when selection is null // select behavior is completed, but nothing selected if (selection === null) { - brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle( - this.getContainerSVG(), - xScale.range() - ); + this.brushed(zoomDomain, xScale, xScale.range()); } else if (selection[0] === selection[1]) { // select behavior is not completed yet, do nothing } else { @@ -259,10 +255,12 @@ export class ZoomBar extends Component { ]; // be aware that the value of d3.event changes during an event! - // update zoomDomain only if the event comes from mousemove event + // update zoomDomain only if the event comes from mouse event if ( event.sourceEvent != null && - event.sourceEvent.type === "mousemove" + (event.sourceEvent.type === "mousemove" || + event.sourceEvent.type === "mouseup" || + event.sourceEvent.type === "mousedown") ) { // only if zoomDomain is never set or needs update if ( From b59a15e270b56e294eb197a69005434c92441e3e Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 14:41:24 +0800 Subject: [PATCH 165/510] feat: add ZoomBarOptions.initZoomDomain - update storybook - add init() and destroy() in ZoomBar --- packages/core/demo/data/time-series-axis.ts | 5 ++++ packages/core/src/components/axes/zoom-bar.ts | 25 ++++++++++++++++--- packages/core/src/configuration.ts | 1 + packages/core/src/interfaces/components.ts | 6 +++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e79de136b6..845e1bd1a7 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -453,6 +453,10 @@ const selectionEndFun = (selection, domain) => { console.log(domain); }; +const initZoomDomain = [ + new Date(2020, 11, 10, 23, 59, 25), + new Date(2020, 11, 11, 0, 0, 25) +]; export const lineTimeSeriesZoomBarOptions = { title: "Line (time series) - zoom-bar enabled", axes: { @@ -463,6 +467,7 @@ export const lineTimeSeriesZoomBarOptions = { }, zoomBar: { enabled: true, + initZoomDomain: initZoomDomain, selectionStart: selectionStartFun, selectionInProgress: selectionInProgressFun, selectionEnd: selectionEndFun diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index f7894985c5..0137960f8d 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -19,6 +19,17 @@ export class ZoomBar extends Component { brush = brushX(); + init() { + // get initZoomDomain + const zoomBarOptions = this.model.getOptions().zoomBar; + if (zoomBarOptions.initZoomDomain !== undefined) { + this.model.set( + { zoomDomain: zoomBarOptions.initZoomDomain }, + { skipUpdate: true } + ); + } + } + render(animate = true) { const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -221,6 +232,7 @@ export class ZoomBar extends Component { const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( this.brush ); + if ( zoomDomain === undefined || zoomDomain[0].valueOf() === zoomDomain[1].valueOf() @@ -239,10 +251,11 @@ export class ZoomBar extends Component { } } - zoomIn() { - const mainXScale = this.services.cartesianScales.getMainXScale(); - console.log("zoom in", mainXScale.domain()); - } + // could be used by Toolbar + // zoomIn() { + // const mainXScale = this.services.cartesianScales.getMainXScale(); + // console.log("zoom in", mainXScale.domain()); + // } // brush event listener brushed(zoomDomain, scale, selection) { @@ -337,4 +350,8 @@ export class ZoomBar extends Component { .attr("width", handleBarWidth) .attr("height", handleBarHeight); } + + destroy() { + this.brush.on("start brush end", null); // remove event listener + } } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 5109d282e1..ad37847348 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -133,6 +133,7 @@ export const timeScale: TimeScaleOptions = { */ export const zoomBar: ZoomBarOptions = { enabled: false, + initZoomDomain: undefined, selectionStart: undefined, selectionInProgress: undefined, selectionEnd: undefined diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 33d100ff45..d81ddce313 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -125,6 +125,12 @@ export interface ZoomBarOptions { * is the zoom-bar visible or not */ enabled?: boolean; + + /** + * an two element array which represents the initial zoom domain + */ + initZoomDomain?: Object[]; + /** * a function to handle selection start event */ From bbf53a56586cd11989b6af8c2aa4fb81bc7ed8d4 Mon Sep 17 00:00:00 2001 From: EricYang Date: Sat, 20 Jun 2020 18:03:35 +0800 Subject: [PATCH 166/510] feat: create Zoombar storybook demo group --- packages/core/demo/data/index.ts | 54 +++++++++++++ packages/core/demo/data/time-series-axis.ts | 55 ------------- packages/core/demo/data/zoom-bar.ts | 88 +++++++++++++++++++++ 3 files changed, 142 insertions(+), 55 deletions(-) create mode 100644 packages/core/demo/data/zoom-bar.ts diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 46b9693c9b..99a4cbad47 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -10,6 +10,7 @@ import * as stepDemos from "./step"; import * as meterDemos from "./meter"; import * as timeSeriesAxisDemos from "./time-series-axis"; import * as radarDemos from "./radar"; +import * as zoomBarDemos from "./zoom-bar"; export * from "./area"; export * from "./bar"; @@ -749,6 +750,59 @@ let allDemoGroups = [ chartType: chartTypes.RadarChart } ] + }, + { + title: "Zoom bar", + demos: [ + { + options: zoomBarDemos.zoomBarSimpleBarTimeSeriesOptions, + data: zoomBarDemos.zoomBarSimpleBarTimeSeriesData, + chartType: chartTypes.SimpleBarChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarStackedBarTimeSeriesOptions, + data: zoomBarDemos.zoomBarStackedBarTimeSeriesData, + chartType: chartTypes.StackedBarChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarBubbleTimeSeriesOptions, + data: zoomBarDemos.zoomBarBubbleTimeSeriesData, + chartType: chartTypes.BubbleChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeriesOptions, + data: zoomBarDemos.zoomBarLineTimeSeriesData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarScatterTimeSeriesOptions, + data: zoomBarDemos.zoomBarScatterTimeSeriesData, + chartType: chartTypes.ScatterChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarStepTimeSeriesOptions, + data: zoomBarDemos.zoomBarStepTimeSeriesData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeries15secondsOptions, + data: zoomBarDemos.zoomBarLineTimeSeries15secondsData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeriesInitDomainOptions, + data: zoomBarDemos.zoomBarLineTimeSeriesInitDomainData, + chartType: chartTypes.LineChart, + isDemoExample: false + } + ] } ] as any; diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index 845e1bd1a7..4ac497de89 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -418,58 +418,3 @@ export const lineTimeSeriesAllLabelsInPrimaryFormatOptions = { addSpaceOnEdges: 0 } }; - -// ZoomBar -export const lineTimeSeriesDataZoomBar = { - labels: ["Qty"], - datasets: [ - { - label: "Dataset 1", - data: [ - { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, - { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, - { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, - { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, - { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } - ] - } - ] -}; -const selectionStartFun = (selection, domain) => { - console.log("ZoomBar SelectionStart callback!"); - console.log(selection); - console.log(domain); -}; -const selectionInProgressFun = (selection, domain) => { - console.log("ZoomBar SelectionInProgress callback!"); - console.log(selection); - console.log(domain); -}; -const selectionEndFun = (selection, domain) => { - console.log("ZoomBar SelectionEnd callback!"); - console.log(selection); - console.log(domain); -}; - -const initZoomDomain = [ - new Date(2020, 11, 10, 23, 59, 25), - new Date(2020, 11, 11, 0, 0, 25) -]; -export const lineTimeSeriesZoomBarOptions = { - title: "Line (time series) - zoom-bar enabled", - axes: { - left: {}, - bottom: { - scaleType: "time" - } - }, - zoomBar: { - enabled: true, - initZoomDomain: initZoomDomain, - selectionStart: selectionStartFun, - selectionInProgress: selectionInProgressFun, - selectionEnd: selectionEndFun - } -}; diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts new file mode 100644 index 0000000000..5c97e33973 --- /dev/null +++ b/packages/core/demo/data/zoom-bar.ts @@ -0,0 +1,88 @@ +import * as timeSeriesAxisChart from "./time-series-axis"; +import * as barChart from "./bar"; +import * as bubbleChart from "./bubble"; +import * as lineChart from "./line"; +import * as scatterChart from "./scatter"; +import * as stepChart from "./step"; + +// default function for selection callback +const selectionStartFun = (selection, domain) => { + console.log("ZoomBar SelectionStart callback!"); + console.log(selection); + console.log(domain); +}; +const selectionInProgressFun = (selection, domain) => { + console.log("ZoomBar SelectionInProgress callback!"); + console.log(selection); + console.log(domain); +}; +const selectionEndFun = (selection, domain) => { + console.log("ZoomBar SelectionEnd callback!"); + console.log(selection); + console.log(domain); +}; + +const initZoomDomain = [ + new Date(2020, 11, 10, 23, 59, 25), + new Date(2020, 11, 11, 0, 0, 25) +]; + +const defaultZoomBarOptions = { + enabled: true, + initZoomDomain: undefined, + selectionStart: selectionStartFun, + selectionInProgress: selectionInProgressFun, + selectionEnd: selectionEndFun +}; + +// utility function to update title and enable zoomBar option +const updateOptions = (options) => { + options["title"] = options["title"] + " - Zoom bar enabled"; + options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); + return options; +}; + +export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; +export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( + Object.assign({}, barChart.simpleBarTimeSeriesOptions) +); + +export const zoomBarStackedBarTimeSeriesData = + barChart.stackedBarTimeSeriesData; +export const zoomBarStackedBarTimeSeriesOptions = updateOptions( + Object.assign({}, barChart.stackedBarTimeSeriesOptions) +); + +export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; +export const zoomBarBubbleTimeSeriesOptions = updateOptions( + Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) +); + +export const zoomBarLineTimeSeriesData = lineChart.lineTimeSeriesData; +export const zoomBarLineTimeSeriesOptions = updateOptions( + Object.assign({}, lineChart.lineTimeSeriesOptions) +); + +export const zoomBarScatterTimeSeriesData = scatterChart.scatterTimeSeriesData; +export const zoomBarScatterTimeSeriesOptions = updateOptions( + Object.assign({}, scatterChart.scatterTimeSeriesOptions) +); + +export const zoomBarStepTimeSeriesData = stepChart.stepTimeSeriesData; +export const zoomBarStepTimeSeriesOptions = updateOptions( + Object.assign({}, stepChart.stepTimeSeriesOptions) +); + +export const zoomBarLineTimeSeries15secondsData = + timeSeriesAxisChart.lineTimeSeriesData15seconds; +export const zoomBarLineTimeSeries15secondsOptions = updateOptions( + Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) +); + +export const zoomBarLineTimeSeriesInitDomainData = + timeSeriesAxisChart.lineTimeSeriesData15seconds; +export const zoomBarLineTimeSeriesInitDomainOptions = updateOptions( + Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) +); +zoomBarLineTimeSeriesInitDomainOptions["title"] += " with initial zoom domain"; +zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initZoomDomain = initZoomDomain; From 50791981ae8ff157728c8c9c983facaf57eb4bbb Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 23 Jun 2020 10:08:11 +0800 Subject: [PATCH 167/510] fix: apply cover to stacked bar --- packages/core/src/components/component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 5664f29ec8..6aa5e17ee0 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -100,6 +100,7 @@ export class Component { this.type === "area-stacked" || this.type === "grouped-bar" || this.type === "simple-bar" || + this.type === "stacked-bar" || this.type === "scatter-stacked" ) { return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); From 88cad1cc103b513a11226269df4b31bf9d2cbb09 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 24 Jun 2020 12:23:11 +0800 Subject: [PATCH 168/510] feat: create ZoomBar event and handler - use Events.ZoomBar.UPDATE to handle axesMargins update --- .../core/src/components/axes/two-dimensional-axes.ts | 4 +++- packages/core/src/components/axes/zoom-bar.ts | 9 ++++++++- packages/core/src/interfaces/events.ts | 7 +++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/axes/two-dimensional-axes.ts b/packages/core/src/components/axes/two-dimensional-axes.ts index d95e70e557..aa8b1cccb3 100644 --- a/packages/core/src/components/axes/two-dimensional-axes.ts +++ b/packages/core/src/components/axes/two-dimensional-axes.ts @@ -5,6 +5,7 @@ import { Axis } from "./axis"; import { Tools } from "../../tools"; import { DOMUtils } from "../../services"; import { Threshold } from "../essentials/threshold"; +import { Events } from "./../../interfaces"; export class TwoDimensionalAxes extends Component { type = "2D-axes"; @@ -115,7 +116,8 @@ export class TwoDimensionalAxes extends Component { this.margins = Object.assign(this.margins, margins); // also set new margins to model to allow external components to access - this.model.set({ axesMargins: this.margins }, { animate: false }); + this.model.set({ axesMargins: this.margins }, { skipUpdate: true }); + this.services.events.dispatchEvent(Events.ZoomBar.UPDATE); Object.keys(this.children).forEach((childKey) => { const child = this.children[childKey]; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0137960f8d..a6d85ed844 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -1,7 +1,7 @@ // Internal Imports import { Component } from "../component"; import { Tools } from "../../tools"; -import { ScaleTypes } from "../../interfaces"; +import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports @@ -20,6 +20,10 @@ export class ZoomBar extends Component { brush = brushX(); init() { + this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); + // get initZoomDomain const zoomBarOptions = this.model.getOptions().zoomBar; if (zoomBarOptions.initZoomDomain !== undefined) { @@ -353,5 +357,8 @@ export class ZoomBar extends Component { destroy() { this.brush.on("start brush end", null); // remove event listener + this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); } } diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 2850204dc5..2a25fd5992 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -17,6 +17,13 @@ export enum Model { UPDATE = "model-update" } +/** + * enum of all events related to the zoom-bar + */ +export enum ZoomBar { + UPDATE = "zoom-bar-update" +} + /** * enum of all axis-related events */ From 1d964fe89374d72b64c807e881a404f10ef67a4f Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 24 Jun 2020 12:48:50 +0800 Subject: [PATCH 169/510] fix: fix merge error - move ZoomBarOptions from BaseChartOptions to AxisChartOptions --- packages/core/src/interfaces/charts.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 5ff08b4f9d..372cd3a9ce 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -46,10 +46,6 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; - /** - * zoombar configuration - */ - zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ @@ -122,6 +118,10 @@ export interface AxisChartOptions extends BaseChartOptions { axes?: AxesOptions; grid?: GridOptions; timeScale?: TimeScaleOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; } /** @@ -218,7 +218,7 @@ export interface StackedAreaChartOptions extends ScatterChartOptions { | string | { name: string; - }; + }; } /** From 258bf4583068b0511312567ab50a9853913bc6e3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 14:26:51 +0800 Subject: [PATCH 170/510] feat: update color for non-highlighted areas - remove zoombar graph line - add zoombar unselected graph area with clip - add zoombar baseline - clear d3.brush selection style --- packages/core/src/components/axes/zoom-bar.ts | 142 +++++++++++------- .../core/src/styles/components/_zoom-bar.scss | 16 +- 2 files changed, 97 insertions(+), 61 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index a6d85ed844..d04c7a4eeb 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,6 +13,8 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + clipId = "zoomBarClip"; + height = 32; ogXScale: any; @@ -72,9 +74,7 @@ export class ZoomBar extends Component { .attr("x", axesLeftMargin) .attr("y", 0) .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "white") - .attr("stroke", "#e8e8e8"); + .attr("height", "100%"); if (mainXScale) { const displayData = this.model.getDisplayData(); @@ -153,63 +153,44 @@ export class ZoomBar extends Component { ) ) .curve(this.services.curves.getD3Curve()); - // .defined((d: any, i) => { - // if (zoomDomain) { - // const dTimestamp = +d.label; - - // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; - // } - - // return true; - // }); - const lineGraph = DOMUtils.appendOrSelect( - container, - "path.zoom-graph-line" - ) - .datum(stackDataArray) - .transition( - this.services.transitions.getTransition( - "zoom-pan-line-update", - animate - ) - ) - .attr("d", lineGenerator); - - const areaGenerator = area() - .x((d, i) => - cartesianScales.getValueFromScale( - xScale, - mainXScaleType, - mainXAxisPosition, + const accessorFunc = (scale, scaleType, axisPosition) => { + return (d, i) => { + return cartesianScales.getValueFromScale( + scale, + scaleType, + axisPosition, d, i - ) - ) - .y0(this.height) - .y1( - (d, i) => - this.height - - cartesianScales.getValueFromScale( - yScale, - mainYScaleType, - mainYAxisPosition, - d, - i - ) - ); - - const areaGraph = DOMUtils.appendOrSelect( + ); + }; + }; + this.renderZoomBarArea( container, - "path.zoom-graph-area" - ) - .datum(stackDataArray) - .transition( - this.services.transitions.getTransition( - "zoom-pan-area-update", - animate - ) - ) - .attr("d", areaGenerator); + "path.zoom-graph-area-unselected", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + stackDataArray, + animate, + undefined + ); + this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); + this.renderZoomBarArea( + container, + "path.zoom-graph-area", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + stackDataArray, + animate, + "zoomBarClip" + ); + const baselineGenerator = line()([ + [axesLeftMargin, this.height], + [width, this.height] + ]); + const zoomBaseline = DOMUtils.appendOrSelect( + container, + "path.zoom-bg-baseline" + ).attr("d", baselineGenerator); const brushEventListener = () => { const selection = event.selection; @@ -352,7 +333,52 @@ export class ZoomBar extends Component { }) .attr("y", handleYBarDiff) .attr("width", handleBarWidth) - .attr("height", handleBarHeight); + .attr("height", handleBarHeight) + .attr("cursor", "ew-resize"); + + this.updateClipPath( + svg, + this.clipId, + selection[0], + 0, + selection[1] - selection[0], + this.height + ); + } + + renderZoomBarArea( + container, + querySelector, + xFunc, + y1Func, + datum, + animate, + clipId + ) { + const areaGenerator = area() + .x((d, i) => xFunc(d, i)) + .y0(this.height) + .y1((d, i) => this.height - y1Func(d, i)); + + const areaGraph = DOMUtils.appendOrSelect(container, querySelector) + .datum(datum) + .attr("d", areaGenerator); + + if (clipId) { + areaGraph.attr("clip-path", `url(#${clipId})`); + } + } + + updateClipPath(svg, clipId, x, y, width, height) { + const zoomBarClipPath = DOMUtils.appendOrSelect(svg, `clipPath`).attr( + "id", + clipId + ); + DOMUtils.appendOrSelect(zoomBarClipPath, "rect") + .attr("x", x) + .attr("y", y) + .attr("width", width) + .attr("height", height); } destroy() { diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 59019956a8..32dfc32842 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -4,15 +4,19 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: $ui-01; } - path.zoom-graph-line { + path.zoom-bg-baseline { stroke: $ui-04; - stroke-width: 3; - fill: none; + stroke-width: 2; } path.zoom-graph-area { fill: $ui-03; stroke: $ui-04; + stroke-width: 1; + } + path.zoom-graph-area-unselected { + fill: $ui-01; + stroke: none; } g.brush rect.handle { @@ -22,4 +26,10 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { g.brush rect.handle-bar { fill: $ui-02; } + + // clear d3.brush selection style + g.brush rect.selection { + fill: none; + stroke: none; + } } From eb291272947dc26a8ce5fc249d3e007e17a97eff Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 15:01:58 +0800 Subject: [PATCH 171/510] fix: avoid extra brush handle update --- packages/core/src/components/axes/zoom-bar.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d04c7a4eeb..cc5fd6bfe3 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -218,8 +218,10 @@ export class ZoomBar extends Component { this.brush ); - if ( - zoomDomain === undefined || + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + } else if ( zoomDomain[0].valueOf() === zoomDomain[1].valueOf() ) { brushArea.call(this.brush.move, xScale.range()); // default to full range @@ -229,8 +231,16 @@ export class ZoomBar extends Component { ); } else { const selected = zoomDomain.map((domain) => xScale(domain)); - brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle(this.getContainerSVG(), selected); + if (selected[1] - selected[0] < 1) { + // initialization not completed yet + // don't update brushHandle to avoid flash + } else { + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle( + this.getContainerSVG(), + selected + ); + } } } } From 6b4b47ed2759ae25ba8edea8e252e2f66891253f Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 15:34:34 +0800 Subject: [PATCH 172/510] fix: fix the brush handle style --- packages/core/src/components/axes/zoom-bar.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index cc5fd6bfe3..840d15198f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -299,18 +299,14 @@ export class ZoomBar extends Component { } updateBrushHandle(svg, selection) { - // @todo the handle size, height are calculated by d3 library - // need to figure out how to override the value - const handleWidth = 6; - const handleHeight = 38; + const handleWidth = 5; + const handleHeight = this.height; const handleXDiff = -handleWidth / 2; - const handleYDiff = -(handleHeight - this.height) / 2; - const handleBarWidth = 2; + const handleBarWidth = 1; const handleBarHeight = 12; const handleBarXDiff = -handleBarWidth / 2; - const handleYBarDiff = - (handleHeight - handleBarHeight) / 2 + handleYDiff; + const handleYBarDiff = (handleHeight - handleBarHeight) / 2; // handle svg.select("g.brush") .selectAll("rect.handle") @@ -322,7 +318,7 @@ export class ZoomBar extends Component { return selection[1] + handleXDiff; } }) - .attr("y", handleYDiff) + .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) .style("display", null); // always display From 409c584c1d8d4fa8e5d867640494cf8483d22a47 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 14:00:53 +0800 Subject: [PATCH 173/510] refactor: remove unnecessary svg.brush-container --- packages/core/src/components/axes/brush.ts | 42 ++++++++++++---------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 0a22fe0d79..517a73eb80 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -30,16 +30,10 @@ export class Brush extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); const mainYScale = this.services.cartesianScales.getMainYScale(); - const [xScaleStart, xScaleEnd] = mainXScale.range(); const [yScaleEnd, yScaleStart] = mainYScale.range(); - const container = DOMUtils.appendOrSelect(svg, "svg.brush-container") - .attr("width", "100%") - .attr("height", "100%") - .attr("opacity", 1); - if (mainXScale) { const displayData = this.model.getDisplayData(); @@ -85,7 +79,7 @@ export class Brush extends Component { } const brushed = () => { - let selection = event.selection; + const selection = event.selection; if (selection !== null) { // get current zoomDomain @@ -99,19 +93,24 @@ export class Brush extends Component { xScale.invert(selection[0]), xScale.invert(selection[1]) ]; - + // check if slected start time and end time are the same - if(newDomain[0].valueOf() === newDomain[1].valueOf()) { + if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoombar behavior: set to default full range - newDomain = extent(stackDataArray, (d: any) => d.date); + newDomain = extent( + stackDataArray, + (d: any) => d.date + ); } // only if the brush event comes from mouseup event if (event.sourceEvent != null) { // only if zoomDomain needs update if ( - zoomDomain[0].valueOf() !== newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== newDomain[1].valueOf() + zoomDomain[0].valueOf() !== + newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== + newDomain[1].valueOf() ) { this.model.set( { zoomDomain: newDomain }, @@ -119,12 +118,16 @@ export class Brush extends Component { ); } // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; + const zoomBarOptions = this.model.getOptions() + .zoomBar; if ( zoomBarOptions.selectionEnd !== undefined && event.type === "end" ) { - zoomBarOptions.selectionEnd(selection, newDomain); + zoomBarOptions.selectionEnd( + selection, + newDomain + ); } } } @@ -137,12 +140,15 @@ export class Brush extends Component { ]) .on("end", brushed); - const brushArea = DOMUtils.appendOrSelect(svg, "g.chart-brush").call( - brush - ); + const brushArea = DOMUtils.appendOrSelect( + svg, + "g.chart-brush" + ).call(brush); // no need for having default brush selection // @todo try to hide brush after selection - setTimeout(()=> {brushArea.call(brush.move);}, 0); + setTimeout(() => { + brushArea.call(brush.move); + }, 0); } } } From 74527ed437da1f4d7a9b67371595deb231cddd36 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 14:17:17 +0800 Subject: [PATCH 174/510] fix: move brush layer to back to allow tooltips in graph --- packages/core/src/axis-chart.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 543d0ac6d4..3690c7b924 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -6,7 +6,7 @@ import { LegendPositions, ChartConfig, AxisChartOptions -} from "./interfaces/index"; +} from "./interfaces"; import { Brush, LayoutComponent, @@ -15,10 +15,10 @@ import { AxisChartsTooltip, Spacer, ZoomBar -} from "./components/index"; +} from "./components"; import { Tools } from "./tools"; -import { CartesianScales, Curves } from "./services/index"; +import { CartesianScales, Curves } from "./services"; export class AxisChart extends Chart { services: any = Object.assign(this.services, { @@ -49,8 +49,16 @@ export class AxisChart extends Chart { } }; - !!this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ? - graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + if ( + this.model.getOptions().zoomBar && + this.model.getOptions().zoomBar.enabled + ) { + graphFrameComponents.splice( + 1, + 0, + new Brush(this.model, this.services) + ); + } const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, From 405f3dadb52513a2a9e7e157214f2d9825a337a7 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 16:18:27 +0800 Subject: [PATCH 175/510] feat: add stacked-area chart with zoom bar in storybook --- packages/core/demo/data/index.ts | 6 ++++++ packages/core/demo/data/zoom-bar.ts | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 99a4cbad47..b826eceb22 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -754,6 +754,12 @@ let allDemoGroups = [ { title: "Zoom bar", demos: [ + { + options: zoomBarDemos.zoomBarStackedAreaTimeSeriesOptions, + data: zoomBarDemos.zoomBarStackedAreaTimeSeriesData, + chartType: chartTypes.StackedAreaChart, + isDemoExample: false + }, { options: zoomBarDemos.zoomBarSimpleBarTimeSeriesOptions, data: zoomBarDemos.zoomBarSimpleBarTimeSeriesData, diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 5c97e33973..0016bbcad7 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -1,9 +1,10 @@ -import * as timeSeriesAxisChart from "./time-series-axis"; +import * as areaChart from "./area"; import * as barChart from "./bar"; import * as bubbleChart from "./bubble"; import * as lineChart from "./line"; import * as scatterChart from "./scatter"; import * as stepChart from "./step"; +import * as timeSeriesAxisChart from "./time-series-axis"; // default function for selection callback const selectionStartFun = (selection, domain) => { @@ -42,6 +43,12 @@ const updateOptions = (options) => { return options; }; +export const zoomBarStackedAreaTimeSeriesData = + areaChart.stackedAreaTimeSeriesData; +export const zoomBarStackedAreaTimeSeriesOptions = updateOptions( + Object.assign({}, areaChart.stackedAreaTimeSeriesOptions) +); + export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( Object.assign({}, barChart.simpleBarTimeSeriesOptions) From 1a2750aa891bc3ae91b4bbfc4cda8ee9091213fb Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 14:28:08 +0800 Subject: [PATCH 176/510] refactor: delete unused axis-zoom service --- packages/core/src/services/axis-zoom.ts | 60 ------------------------- packages/core/src/services/index.ts | 1 - 2 files changed, 61 deletions(-) delete mode 100644 packages/core/src/services/axis-zoom.ts diff --git a/packages/core/src/services/axis-zoom.ts b/packages/core/src/services/axis-zoom.ts deleted file mode 100644 index 8dbb2b3d80..0000000000 --- a/packages/core/src/services/axis-zoom.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Internal Imports -import { Service } from "./service"; -import { Tools } from "../tools"; - -// D3 Imports -import { brushX } from "d3-brush"; -import { event } from "d3-selection"; - -export class AxisZoom extends Service { - brush: any; - - brushed(e) { - if (event) { - const selectedRange = event.selection; - - const mainXAxis = this.services.axes.getMainXAxis(); - const mainXAxisRangeStart = mainXAxis.scale.range()[0]; - - const newDomain = selectedRange.map(d => d + mainXAxisRangeStart) - .map(mainXAxis.scale.invert, mainXAxis.scale); - - this.model.set({ - zoomDomain: newDomain.map(d => new Date(+d)) - }); - } - } - - // We need a custom debounce function here - // because of the async nature of d3.event - debounceWithD3Event(func, wait) { - let timeout; - return function() { - const e = Object.assign({}, event); - const context = this; - const later = function() { - timeout = null; - func.apply(context, [e]); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - } - - getZoomInstance() { - const mainXAxis = this.services.axes.getMainXAxis(); - const mainYAxis = this.services.axes.getMainYAxis(); - const xMaxRange = mainXAxis.scale.range()[1]; - const yMaxRange = mainYAxis.scale.range()[0]; - - this.brush = brushX() - .extent([ - [0, 0], - [xMaxRange, yMaxRange] - ]) - .on("end", this.brushed.bind(this)); - - - return this.brush; - } -} diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index 4e9fcc0e17..d999a07207 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -4,6 +4,5 @@ export * from "./essentials/events"; export * from "./essentials/transitions"; // MISC -export * from "./axis-zoom"; export * from "./scales-cartesian"; export * from "./curves"; From cde69a21cd41940dbc41ea14c71846cfc6bc8fa0 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 14:41:59 +0800 Subject: [PATCH 177/510] feat: allow addSpaceOnEdges to work with zoom bar - extends domain in zoom bar and brush --- packages/core/src/components/axes/brush.ts | 6 ++- packages/core/src/components/axes/zoom-bar.ts | 13 +++++-- .../core/src/services/scales-cartesian.ts | 39 +++++++++++-------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 517a73eb80..efc35a735d 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -71,7 +71,11 @@ export class Brush extends Component { let zoomDomain = this.model.get("zoomDomain"); if (zoomDomain === undefined) { - zoomDomain = extent(stackDataArray, (d: any) => d.date); // default to full range + // default to full range with extended domain + zoomDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) + ); this.model.set( { zoomDomain: zoomDomain }, { animate: false } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 840d15198f..b3b4b2b571 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -120,9 +120,16 @@ export class ZoomBar extends Component { useAttrs: true }); - xScale - .range([axesLeftMargin, width]) - .domain(extent(stackDataArray, (d: any) => d.date)); + // @todo could be a better function to extend domain with default value + const xDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) + ); + // add value 0 to the extended domain for zoom bar area graph + stackDataArray.unshift({ date: xDomain[0], value: 0 }); + stackDataArray.push({ date: xDomain[1], value: 0 }); + + xScale.range([axesLeftMargin, width]).domain(xDomain); yScale .range([0, this.height - 6]) diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 162b0c833f..9614fea1c2 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -5,7 +5,6 @@ import { AxisPositions, CartesianOrientations, ScaleTypes, - AxesOptions, ThresholdOptions } from "../interfaces"; import { Tools } from "../tools"; @@ -181,7 +180,13 @@ export class CartesianScales extends Service { return this.scales[this.getMainYAxisPosition()]; } - getValueFromScale(scale: any, scaleType: ScaleTypes, axisPosition: AxisPositions, datum: any, index?: number) { + getValueFromScale( + scale: any, + scaleType: ScaleTypes, + axisPosition: AxisPositions, + datum: any, + index?: number + ) { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); const axisOptions = axesOptions[axisPosition]; @@ -201,14 +206,23 @@ export class CartesianScales extends Service { return scaledValue; } - getValueThroughAxisPosition(axisPosition: AxisPositions, datum: any, index?: number) { + getValueThroughAxisPosition( + axisPosition: AxisPositions, + datum: any, + index?: number + ) { const scaleType = this.scaleTypes[axisPosition]; const scale = this.scales[axisPosition]; - return this.getValueFromScale(scale, scaleType, axisPosition, datum, index); + return this.getValueFromScale( + scale, + scaleType, + axisPosition, + datum, + index + ); } - getDomainValue(d, i) { return this.getValueThroughAxisPosition(this.domainAxisPosition, d, i); } @@ -304,7 +318,7 @@ export class CartesianScales extends Service { const domainScale = this.getDomainScale(); // Find the highest threshold for the domain const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value, + (a, b) => b.value - a.value )[0]; const scaleType = this.getScaleTypeByPosition(domainAxisPosition); @@ -318,7 +332,7 @@ export class CartesianScales extends Service { return { threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value), + scaleValue: domainScale(highestThreshold.value) }; } @@ -338,12 +352,12 @@ export class CartesianScales extends Service { const rangeScale = this.getRangeScale(); // Find the highest threshold for the range const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value, + (a, b) => b.value - a.value )[0]; return { threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value), + scaleValue: rangeScale(highestThreshold.value) }; } @@ -458,12 +472,6 @@ export class CartesianScales extends Service { return map(displayData, (d) => d[mapsTo]).keys(); } - // If scale is a TIME scale and zoomDomain is available, return Date array as the domain - const zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain && axisOptions && scaleType === ScaleTypes.TIME) { - return zoomDomain.map(d => new Date(d)); - } - // Get the extent of the domain let domain; let allDataValues; @@ -483,7 +491,6 @@ export class CartesianScales extends Service { domain = extent(allDataValues); domain = this.extendsDomain(axisPosition, domain); - return domain; } From 68c0ccee455fe9354183da20d14c3ff09475b6f8 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 16:12:13 +0800 Subject: [PATCH 178/510] refactor: code refactoring - reduce code differences from master branch --- .../core/src/services/scales-cartesian.ts | 119 +++++++++--------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 9614fea1c2..11be646307 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -302,65 +302,6 @@ export class CartesianScales extends Service { } } - getHighestDomainThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const domainAxisPosition = this.getDomainAxisPosition(); - - const { thresholds } = axesOptions[domainAxisPosition]; - - if (!thresholds) { - return null; - } - - const domainScale = this.getDomainScale(); - // Find the highest threshold for the domain - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - const scaleType = this.getScaleTypeByPosition(domainAxisPosition); - if ( - scaleType === ScaleTypes.TIME && - (typeof highestThreshold.value === "string" || - highestThreshold.value.getTime === undefined) - ) { - highestThreshold.value = new Date(highestThreshold.value); - } - - return { - threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value) - }; - } - - getHighestRangeThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const rangeAxisPosition = this.getRangeAxisPosition(); - - const { thresholds } = axesOptions[rangeAxisPosition]; - - if (!thresholds) { - return null; - } - - const rangeScale = this.getRangeScale(); - // Find the highest threshold for the range - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - return { - threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value) - }; - } - protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); @@ -491,6 +432,7 @@ export class CartesianScales extends Service { domain = extent(allDataValues); domain = this.extendsDomain(axisPosition, domain); + return domain; } @@ -521,6 +463,65 @@ export class CartesianScales extends Service { return scale; } + + protected getHighestDomainThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const domainAxisPosition = this.getDomainAxisPosition(); + + const { thresholds } = axesOptions[domainAxisPosition]; + + if (!thresholds) { + return null; + } + + const domainScale = this.getDomainScale(); + // Find the highest threshold for the domain + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value + )[0]; + + const scaleType = this.getScaleTypeByPosition(domainAxisPosition); + if ( + scaleType === ScaleTypes.TIME && + (typeof highestThreshold.value === "string" || + highestThreshold.value.getTime === undefined) + ) { + highestThreshold.value = new Date(highestThreshold.value); + } + + return { + threshold: highestThreshold, + scaleValue: domainScale(highestThreshold.value) + }; + } + + protected getHighestRangeThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const rangeAxisPosition = this.getRangeAxisPosition(); + + const { thresholds } = axesOptions[rangeAxisPosition]; + + if (!thresholds) { + return null; + } + + const rangeScale = this.getRangeScale(); + // Find the highest threshold for the range + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value + )[0]; + + return { + threshold: highestThreshold, + scaleValue: rangeScale(highestThreshold.value) + }; + } } function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { From 4a489d598b34d6f8b577543ae197aef8947bbb85 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 23:25:56 +0800 Subject: [PATCH 179/510] fix: display multiline tooltip with zoom bar - use ruler backdrop as brush area - svg.chart-grid-backdrop --- packages/core/src/axis-chart.ts | 6 +----- packages/core/src/components/axes/brush.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 3690c7b924..f5559f6f56 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -53,11 +53,7 @@ export class AxisChart extends Chart { this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ) { - graphFrameComponents.splice( - 1, - 0, - new Brush(this.model, this.services) - ); + graphFrameComponents.push(new Brush(this.model, this.services)); } const graphFrameComponent = { id: "graph-frame", diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index efc35a735d..35f12159b9 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -146,7 +146,7 @@ export class Brush extends Component { const brushArea = DOMUtils.appendOrSelect( svg, - "g.chart-brush" + "svg.chart-grid-backdrop" ).call(brush); // no need for having default brush selection // @todo try to hide brush after selection From 2143a841e0dc55aaeaac3b3395754146410b25b7 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:42:26 +0800 Subject: [PATCH 180/510] feat: create ZoomBarOptions and default configuration - default to disable zoombar --- packages/core/src/interfaces/charts.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 372cd3a9ce..4956c8895b 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -46,6 +46,10 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ From 529253420dae28796baa8a359d58ffc84ae0b458 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 2 Jul 2020 14:21:12 +0800 Subject: [PATCH 181/510] fix: chart brush with correct range --- packages/core/src/components/axes/brush.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 35f12159b9..c06fdac4c0 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -90,7 +90,7 @@ export class Brush extends Component { zoomDomain = this.model.get("zoomDomain"); // create xScale based on current zoomDomain const xScale = scaleTime() - .range([axesLeftMargin, width]) + .range([0, width]) .domain(zoomDomain); let newDomain = [ @@ -101,9 +101,9 @@ export class Brush extends Component { // check if slected start time and end time are the same if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoombar behavior: set to default full range - newDomain = extent( - stackDataArray, - (d: any) => d.date + newDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) ); } @@ -139,15 +139,19 @@ export class Brush extends Component { const brush = brushX() .extent([ - [xScaleStart, 0], + [0, 0], [width, yScaleEnd] ]) .on("end", brushed); - - const brushArea = DOMUtils.appendOrSelect( + const backdrop = DOMUtils.appendOrSelect( svg, "svg.chart-grid-backdrop" + ); + const brushArea = DOMUtils.appendOrSelect( + backdrop, + "g.chart-brush" ).call(brush); + // no need for having default brush selection // @todo try to hide brush after selection setTimeout(() => { From ca95abdb18e5f8689abbfd652ae8fb37e2f07644 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 2 Jul 2020 15:59:02 +0800 Subject: [PATCH 182/510] fix: remove graph out of zoom domain --- .../core/src/components/graphs/bar-grouped.ts | 4 ++++ .../core/src/components/graphs/bar-simple.ts | 5 +++++ .../core/src/components/graphs/bar-stacked.ts | 4 ++++ packages/core/src/components/graphs/bar.ts | 12 ++++++++++++ packages/core/src/components/graphs/scatter.ts | 16 ++++++++++++++++ 5 files changed, 41 insertions(+) diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 48377b5957..07f12e8c64 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -123,6 +123,10 @@ export class GroupedBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(0); const y1 = this.services.cartesianScales.getRangeValue(d.value); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } return Tools.generateSVGPathString( { x0, x1, y0, y1 }, this.services.cartesianScales.getOrientation() diff --git a/packages/core/src/components/graphs/bar-simple.ts b/packages/core/src/components/graphs/bar-simple.ts index 2cbf168934..aeb72950d1 100644 --- a/packages/core/src/components/graphs/bar-simple.ts +++ b/packages/core/src/components/graphs/bar-simple.ts @@ -70,6 +70,11 @@ export class SimpleBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(0); const y1 = this.services.cartesianScales.getRangeValue(d, i); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } + return Tools.generateSVGPathString( { x0, x1, y0, y1 }, this.services.cartesianScales.getOrientation() diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index 3c9bcbdb9c..55d376f835 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -97,6 +97,10 @@ export class StackedBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(d[0], i); let y1 = this.services.cartesianScales.getRangeValue(d[1], i); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } // Add the divider gap if ( Math.abs(y1 - y0) > 0 && diff --git a/packages/core/src/components/graphs/bar.ts b/packages/core/src/components/graphs/bar.ts index dff83021d5..59c18f5685 100644 --- a/packages/core/src/components/graphs/bar.ts +++ b/packages/core/src/components/graphs/bar.ts @@ -16,4 +16,16 @@ export class Bar extends Component { return Math.min(options.bars.maxWidth, mainXScale.step() / 2); } + + protected isOutOfZoomDomain(x0: number, x1: number) { + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain !== undefined) { + const domainScale = this.services.cartesianScales.getDomainScale(); + return ( + x0 < domainScale(zoomDomain[0]) || + x1 > domainScale(zoomDomain[1]) + ); + } + return false; + } } diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index 0ceab80518..2d8074dc49 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -37,6 +37,19 @@ export class Scatter extends Component { } } + filterOutOfDomain(data) { + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain !== undefined) { + return data.filter( + (d) => + d[domainIdentifier] > zoomDomain[0] && + d[domainIdentifier] < zoomDomain[1] + ); + } + return data; + } + render(animate: boolean) { // Grab container SVG const svg = this.getContainerSVG(); @@ -64,6 +77,9 @@ export class Scatter extends Component { ); } + // filter out of domain data + scatterData = this.filterOutOfDomain(scatterData); + // Update data on dot groups const circles = svg .selectAll("circle.dot") From db4731c43e29738d5f5478b2d2e5168f2cf419d4 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 12:44:35 +0800 Subject: [PATCH 183/510] refactor: code refactoring - create getZoomBarData(), getDefaultZoomBarDomain() in model - remove unused code --- packages/core/src/components/axes/brush.ts | 193 ++++------- packages/core/src/components/axes/cover.ts | 25 +- packages/core/src/components/axes/zoom-bar.ts | 312 ++++++++---------- packages/core/src/model.ts | 46 ++- 4 files changed, 256 insertions(+), 320 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index c06fdac4c0..16f21dce44 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -15,149 +15,80 @@ export class Brush extends Component { render(animate = true) { const svg = this.parent; + const backdrop = DOMUtils.appendOrSelect( + svg, + "svg.chart-grid-backdrop" + ); + const { width, height } = DOMUtils.getSVGElementSize(backdrop, { + useAttrs: true + }); + const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( mainXAxisPosition ); - // get axes margins - let axesLeftMargin = 0; - const axesMargins = this.model.get("axesMargins"); - if (axesMargins && axesMargins.left) { - axesLeftMargin = axesMargins.left; - } - const mainXScale = this.services.cartesianScales.getMainXScale(); - const mainYScale = this.services.cartesianScales.getMainYScale(); - - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const [yScaleEnd, yScaleStart] = mainYScale.range(); - - if (mainXScale) { - const displayData = this.model.getDisplayData(); - - if (mainXScaleType === ScaleTypes.TIME) { - // Get all date values provided in data - // @todo - Could be re-used through the model - let allDates = []; - displayData.forEach((data) => { - allDates = allDates.concat(Number(data.date)); - }); - allDates = Tools.removeArrayDuplicates(allDates).sort(); - - // Go through all date values - // And get corresponding data from each dataset - const stackDataArray = allDates.map((date) => { - let count = 0; - let correspondingSum = 0; - const correspondingData = {}; - - displayData.forEach((data) => { - if (Number(data.date) === Number(date)) { - ++count; - correspondingSum += data.value; - } - }); - correspondingData["date"] = date; - correspondingData["value"] = correspondingSum; - - return correspondingData; - }); - const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); - - let zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain === undefined) { - // default to full range with extended domain - zoomDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - this.model.set( - { zoomDomain: zoomDomain }, - { animate: false } - ); - } - - const brushed = () => { - const selection = event.selection; - - if (selection !== null) { - // get current zoomDomain - zoomDomain = this.model.get("zoomDomain"); - // create xScale based on current zoomDomain - const xScale = scaleTime() - .range([0, width]) - .domain(zoomDomain); - - let newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // check if slected start time and end time are the same - if (newDomain[0].valueOf() === newDomain[1].valueOf()) { - // same as d3 behavior and zoombar behavior: set to default full range - newDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - } + 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.model.getDefaultZoomBarDomain(); + this.model.set({ zoomDomain: zoomDomain }, { animate: false }); + } - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { - // only if zoomDomain needs update - if ( - zoomDomain[0].valueOf() !== - newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== - newDomain[1].valueOf() - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } - // call external callback - const zoomBarOptions = this.model.getOptions() - .zoomBar; - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd( - selection, - newDomain - ); - } - } + const brushed = () => { + const selection = event.selection; + + if (selection !== null) { + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([0, width]) + .domain(zoomDomain); + + let newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + // 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.model.getDefaultZoomBarDomain(); } - }; - const brush = brushX() - .extent([ - [0, 0], - [width, yScaleEnd] - ]) - .on("end", brushed); - const backdrop = DOMUtils.appendOrSelect( - svg, - "svg.chart-grid-backdrop" - ); - const brushArea = DOMUtils.appendOrSelect( - backdrop, - "g.chart-brush" - ).call(brush); + // only if zoomDomain needs update + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + } - // no need for having default brush selection - // @todo try to hide brush after selection - setTimeout(() => { - brushArea.call(brush.move); - }, 0); - } + // clear brush selection + brushArea.call(brush.move, null); + } + }; + + // leave some space to display selection strokes besides axis + const brush = brushX() + .extent([ + [2, 0], + [width - 1, height - 1] + ]) + .on("end", brushed); + + const brushArea = DOMUtils.appendOrSelect( + backdrop, + "g.chart-brush" + ).call(brush); } } } diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts index 16179b45c5..2536c9ef40 100644 --- a/packages/core/src/components/axes/cover.ts +++ b/packages/core/src/components/axes/cover.ts @@ -1,13 +1,7 @@ // Internal Imports import { Component } from "../component"; -import { Tools } from "../../tools"; import { DOMUtils } from "../../services"; -// D3 Imports -import { axisBottom, axisLeft } from "d3-axis"; -import { mouse, select } from "d3-selection"; -import { TooltipTypes, Events } from "../../interfaces"; - export class Cover extends Component { type = "cover"; @@ -18,19 +12,21 @@ export class Cover extends Component { this.createCover(); } - createCover() { const svg = this.parent; - const mainXScale = this.services.cartesianScales.getMainXScale(); - const mainYScale = this.services.cartesianScales.getMainYScale(); + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); const [xScaleStart, xScaleEnd] = mainXScale.range(); const [yScaleEnd, yScaleStart] = mainYScale.range(); // Get height - this.coverClipPath = DOMUtils.appendOrSelect(svg, `clipPath.${this.type}`); - this.coverClipPath - .attr("id", `${this.type}Clip`); + this.coverClipPath = DOMUtils.appendOrSelect( + svg, + `clipPath.${this.type}` + ); + this.coverClipPath.attr("id", `${this.type}Clip`); const coverRect = DOMUtils.appendOrSelect( this.coverClipPath, "rect.cover" @@ -41,14 +37,11 @@ export class Cover extends Component { .attr("width", xScaleEnd - xScaleStart) .attr("height", yScaleEnd - yScaleStart); - this.coverClipPath - .merge(coverRect) - .lower(); + this.coverClipPath.merge(coverRect).lower(); const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); coverG .attr("clip-path", `url(#${this.type}Clip)`) .attr("id", `g-${this.type}Clip`); - } } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index b3b4b2b571..6e3d8efe91 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,11 +13,13 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + brushSelector = "g.brush"; // needs to be this value for d3.brush API + clipId = "zoomBarClip"; height = 32; - ogXScale: any; + spacerHeight = 20; brush = brushX(); @@ -64,9 +66,9 @@ export class ZoomBar extends Component { const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) - .attr("y", 32) + .attr("y", this.height) .attr("width", "100%") - .attr("height", 20) + .attr("height", this.spacerHeight) .attr("opacity", 1) .attr("fill", "none"); @@ -76,189 +78,134 @@ export class ZoomBar extends Component { .attr("width", "100%") .attr("height", "100%"); - if (mainXScale) { - const displayData = this.model.getDisplayData(); - - if (mainXScaleType === ScaleTypes.TIME) { - // Get all date values provided in data - // TODO - Could be re-used through the model - let allDates = []; - displayData.forEach((data) => { - allDates = allDates.concat(Number(data.date)); - }); - allDates = Tools.removeArrayDuplicates(allDates).sort(); - - // Go through all date values - // And get corresponding data from each dataset - const stackDataArray = allDates.map((date) => { - let count = 0; - let correspondingSum = 0; - const correspondingData = {}; - - displayData.forEach((data) => { - if (Number(data.date) === Number(date)) { - ++count; - correspondingSum += data.value; - } - }); - correspondingData["date"] = date; - correspondingData["value"] = correspondingSum; - - return correspondingData; - }); - - if (!this.ogXScale) { - this.ogXScale = cartesianScales.getDomainScale(); - } - const xScale = mainXScale.copy(); - if (!this.ogXScale) { - this.ogXScale = xScale; - } - const yScale = mainYScale.copy(); + if (mainXScale && mainXScaleType === ScaleTypes.TIME) { + const zoomBarData = this.model.getZoomBarData(); + const xScale = mainXScale.copy(); + const yScale = mainYScale.copy(); - const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); - // @todo could be a better function to extend domain with default value - const xDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - // add value 0 to the extended domain for zoom bar area graph - stackDataArray.unshift({ date: xDomain[0], value: 0 }); - stackDataArray.push({ date: xDomain[1], value: 0 }); + const defaultDomain = this.model.getDefaultZoomBarDomain(); + // add value 0 to the extended domain for zoom bar area graph + this.compensateDataForDefaultDomain(zoomBarData, defaultDomain, 0); - xScale.range([axesLeftMargin, width]).domain(xDomain); + xScale.range([axesLeftMargin, width]).domain(defaultDomain); - yScale - .range([0, this.height - 6]) - .domain(extent(stackDataArray, (d: any) => d.value)); + yScale + .range([0, this.height - 6]) + .domain(extent(zoomBarData, (d: any) => d.value)); - const zoomDomain = this.model.get("zoomDomain"); + const zoomDomain = this.model.get("zoomDomain"); - // D3 line generator function - const lineGenerator = line() - .x((d, i) => + // D3 line generator function + const lineGenerator = line() + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) + .y( + (d, i) => + this.height - cartesianScales.getValueFromScale( - xScale, - mainXScaleType, - mainXAxisPosition, + yScale, + mainYScaleType, + mainYAxisPosition, d, i ) - ) - .y( - (d, i) => - this.height - - cartesianScales.getValueFromScale( - yScale, - mainYScaleType, - mainYAxisPosition, - d, - i - ) - ) - .curve(this.services.curves.getD3Curve()); - const accessorFunc = (scale, scaleType, axisPosition) => { - return (d, i) => { - return cartesianScales.getValueFromScale( - scale, - scaleType, - axisPosition, - d, - i - ); - }; - }; - this.renderZoomBarArea( - container, - "path.zoom-graph-area-unselected", - accessorFunc(xScale, mainXScaleType, mainXAxisPosition), - accessorFunc(yScale, mainYScaleType, mainYAxisPosition), - stackDataArray, - animate, - undefined - ); - this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); - this.renderZoomBarArea( - container, - "path.zoom-graph-area", - accessorFunc(xScale, mainXScaleType, mainXAxisPosition), - accessorFunc(yScale, mainYScaleType, mainYAxisPosition), - stackDataArray, - animate, - "zoomBarClip" - ); - const baselineGenerator = line()([ - [axesLeftMargin, this.height], - [width, this.height] - ]); - const zoomBaseline = DOMUtils.appendOrSelect( - container, - "path.zoom-bg-baseline" - ).attr("d", baselineGenerator); - - const brushEventListener = () => { - const selection = event.selection; - // follow d3 behavior: when selection is null, reset default full range - // @todo find a better way to handel the situation when selection is null - // select behavior is completed, but nothing selected - if (selection === null) { - this.brushed(zoomDomain, xScale, xScale.range()); - } else if (selection[0] === selection[1]) { - // select behavior is not completed yet, do nothing - } else { - this.brushed(zoomDomain, xScale, selection); - } + ) + .curve(this.services.curves.getD3Curve()); + const accessorFunc = (scale, scaleType, axisPosition) => { + return (d, i) => { + return cartesianScales.getValueFromScale( + scale, + scaleType, + axisPosition, + d, + i + ); }; + }; + this.renderZoomBarArea( + container, + "path.zoom-graph-area-unselected", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + zoomBarData, + animate, + undefined + ); + this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); + this.renderZoomBarArea( + container, + "path.zoom-graph-area", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + zoomBarData, + animate, + this.clipId + ); + const baselineGenerator = line()([ + [axesLeftMargin, this.height], + [width, this.height] + ]); + const zoomBaseline = DOMUtils.appendOrSelect( + container, + "path.zoom-bg-baseline" + ).attr("d", baselineGenerator); + + const brushEventListener = () => { + const selection = event.selection; + // follow d3 behavior: when selection is null, reset default full range + // select behavior is completed, but nothing selected + if (selection === null) { + this.brushed(zoomDomain, xScale, xScale.range()); + } else if (selection[0] === selection[1]) { + // select behavior is not completed yet, do nothing + } else { + this.brushed(zoomDomain, xScale, selection); + } + }; - this.brush - .extent([ - [axesLeftMargin, 0], - [width, this.height] - ]) - .on("start brush end", null) // remove old listener first - .on("start brush end", brushEventListener); - - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( - this.brush - ); - - if (zoomDomain === undefined) { - // do nothing, initialization not completed yet + this.brush + .extent([ + [axesLeftMargin, 0], + [width, this.height] + ]) + .on("start brush end", null) // remove old listener first + .on("start brush end", brushEventListener); + + const brushArea = DOMUtils.appendOrSelect( + svg, + this.brushSelector + ).call(this.brush); + + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + } else if (zoomDomain[0].valueOf() === zoomDomain[1].valueOf()) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle(this.getContainerSVG(), xScale.range()); + } else { + const selected = zoomDomain.map((domain) => xScale(domain)); + if (selected[1] - selected[0] < 1) { + // initialization not completed yet // don't update brushHandle to avoid flash - } else if ( - zoomDomain[0].valueOf() === zoomDomain[1].valueOf() - ) { - brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle( - this.getContainerSVG(), - xScale.range() - ); } else { - const selected = zoomDomain.map((domain) => xScale(domain)); - if (selected[1] - selected[0] < 1) { - // initialization not completed yet - // don't update brushHandle to avoid flash - } else { - brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle( - this.getContainerSVG(), - selected - ); - } + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle(this.getContainerSVG(), selected); } } } } - // could be used by Toolbar - // zoomIn() { - // const mainXScale = this.services.cartesianScales.getMainXScale(); - // console.log("zoom in", mainXScale.domain()); - // } - // brush event listener brushed(zoomDomain, scale, selection) { // update brush handle position @@ -315,7 +262,7 @@ export class ZoomBar extends Component { const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; // handle - svg.select("g.brush") + svg.select(this.brushSelector) .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { @@ -330,7 +277,7 @@ export class ZoomBar extends Component { .attr("height", handleHeight) .style("display", null); // always display // handle-bar - svg.select("g.brush") + svg.select(this.brushSelector) .selectAll("rect.handle-bar") .data([{ type: "w" }, { type: "e" }]) .join("rect") @@ -364,7 +311,7 @@ export class ZoomBar extends Component { querySelector, xFunc, y1Func, - datum, + data, animate, clipId ) { @@ -374,7 +321,7 @@ export class ZoomBar extends Component { .y1((d, i) => this.height - y1Func(d, i)); const areaGraph = DOMUtils.appendOrSelect(container, querySelector) - .datum(datum) + .datum(data) .attr("d", areaGenerator); if (clipId) { @@ -394,6 +341,29 @@ export class ZoomBar extends Component { .attr("height", height); } + // assume the domains in data are already sorted + compensateDataForDefaultDomain(data, defaultDomain, value) { + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); + // if min domain is extended + if (Number(defaultDomain[0]) < Number(data[0][domainIdentifier])) { + const newDatum = {}; + newDatum[domainIdentifier] = defaultDomain[0]; + newDatum[rangeIdentifier] = value; + data.unshift(newDatum); + } + // if max domain is extended + if ( + Number(defaultDomain[1]) > + Number(data[data.length - 1][domainIdentifier]) + ) { + const newDatum = {}; + newDatum[domainIdentifier] = defaultDomain[1]; + newDatum[rangeIdentifier] = value; + data.push(newDatum); + } + } + destroy() { this.brush.on("start brush end", null); // remove event listener this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 999d689680..a795d809e7 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -5,8 +5,9 @@ import * as colorPalettes from "./services/colorPalettes"; import { Events, ScaleTypes } from "./interfaces"; // D3 -import { scaleOrdinal } from "d3-scale"; +import { extent } from "d3-array"; import { map } from "d3-collection"; +import { scaleOrdinal } from "d3-scale"; import { stack } from "d3-shape"; /** The charting model layer which includes mainly the chart data and options, @@ -38,7 +39,48 @@ export class ChartModel { constructor(services: any) { this.services = services; } + // get display data for zoom bar + // basically it's sum of value grouped by time + getZoomBarData() { + const { cartesianScales } = this.services; + const domainIdentifier = cartesianScales.getDomainIdentifier(); + const rangeIdentifier = cartesianScales.getRangeIdentifier(); + + const displayData = this.getDisplayData(); + // get all dates (Number) in displayData + let allDates = []; + displayData.forEach((data) => { + allDates = allDates.concat(Number(data[domainIdentifier])); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + // Go through all date values + // And get corresponding data from each dataset + return allDates.map((date) => { + let sum = 0; + const datum = {}; + + displayData.forEach((data) => { + if (Number(data[domainIdentifier]) === date) { + sum += data[rangeIdentifier]; + } + }); + datum[domainIdentifier] = new Date(date); + datum[rangeIdentifier] = sum; + return datum; + }); + } + getDefaultZoomBarDomain() { + const zoomBarData = this.getZoomBarData(); + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const domainIdentifier = cartesianScales.getDomainIdentifier(); + // default to full range with extended domain + return cartesianScales.extendsDomain( + mainXAxisPosition, + extent(zoomBarData, (d: any) => d[domainIdentifier]) + ); + } getAllDataFromDomain() { if (!this.get("data")) { return null; @@ -95,7 +137,7 @@ export class ChartModel { return allDataFromDomain.filter((datum) => { const group = dataGroups.find( - (group) => group.name === datum[groupMapsTo] + (g) => g.name === datum[groupMapsTo] ); return group.status === ACTIVE; From a0132eeb339e862f601c0f933bc2a63b53f1c88f Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 16:13:14 +0800 Subject: [PATCH 184/510] fix: set min selection difference threshold --- packages/core/src/components/axes/zoom-bar.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6e3d8efe91..6c1dd80fdb 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,6 +13,11 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + // The minimum selection x range to trigger handler update + // Smaller number may introduce a handler flash during initialization + // Bigger number may not trigger handler update while selection area on chart is very small + MIN_SELECTION_DIFF = 9e-10; + brushSelector = "g.brush"; // needs to be this value for d3.brush API clipId = "zoomBarClip"; @@ -195,7 +200,7 @@ export class ZoomBar extends Component { this.updateBrushHandle(this.getContainerSVG(), xScale.range()); } else { const selected = zoomDomain.map((domain) => xScale(domain)); - if (selected[1] - selected[0] < 1) { + if (selected[1] - selected[0] < this.MIN_SELECTION_DIFF) { // initialization not completed yet // don't update brushHandle to avoid flash } else { From bbf2852336ca5c06c9c840d31b076ca49eb7161f Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 16:46:44 +0800 Subject: [PATCH 185/510] refactor: code refactoring - zoom-bar.scss - remove unused scss settings --- packages/core/src/components/axes/axis.ts | 2 +- packages/core/src/components/axes/zoom-bar.ts | 2 +- .../core/src/styles/components/_zoom-bar.scss | 24 +++++++++---------- packages/core/src/styles/graphs/index.scss | 4 ---- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index a4e66c1ab6..17a4b59a8a 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -117,7 +117,7 @@ export class Axis extends Component { // if zoomDomain is available, update scale domain to Date array. const zoomDomain = this.model.get("zoomDomain"); if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain.map((d) => new Date(d))); + scale.domain(zoomDomain); } // Identify the corresponding d3 axis function diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6c1dd80fdb..edcb1f792b 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -18,7 +18,7 @@ export class ZoomBar extends Component { // Bigger number may not trigger handler update while selection area on chart is very small MIN_SELECTION_DIFF = 9e-10; - brushSelector = "g.brush"; // needs to be this value for d3.brush API + brushSelector = "g.zoom-bar-brush"; // needs to match the style in _zoom-bar.scss clipId = "zoomBarClip"; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 32dfc32842..13c20ecc30 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -19,17 +19,17 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: none; } - g.brush rect.handle { - fill: $icon-02; - } - - g.brush rect.handle-bar { - fill: $ui-02; - } - - // clear d3.brush selection style - g.brush rect.selection { - fill: none; - stroke: none; + g.zoom-bar-brush { + rect.handle { + fill: $icon-02; + } + rect.handle-bar { + fill: $ui-02; + } + // clear d3.brush selection style + rect.selection { + fill: none; + stroke: none; + } } } diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index bebafaffc3..5530eba1a0 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -6,7 +6,3 @@ @import "./scatter-stacked"; @import "./radar"; @import "./gauge"; - -svg.#{$prefix}--#{$charts-prefix}--chart-svg svg.graph-frame { - overflow-x: hidden; -} From 6f8d06af80d43fc59305bb529165797f2e375bba Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 6 Jul 2020 17:09:13 +0800 Subject: [PATCH 186/510] fix: avoid extra/duplicate external callback --- packages/core/src/components/axes/brush.ts | 44 ++++++++++++++++++- packages/core/src/components/axes/zoom-bar.ts | 38 +++++++++------- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 16f21dce44..8067e6aaa8 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -40,6 +40,47 @@ export class Brush extends Component { this.model.set({ zoomDomain: zoomDomain }, { animate: false }); } + const eventHandler = () => { + const selection = event.selection; + const xScale = scaleTime().range([0, width]).domain(zoomDomain); + + const newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + if ( + selection != null && + event.sourceEvent != null && + (event.sourceEvent.type === "mousemove" || + event.sourceEvent.type === "mouseup" || + event.sourceEvent.type === "mousedown") + ) { + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress( + selection, + newDomain + ); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + }; const brushed = () => { const selection = event.selection; @@ -83,7 +124,8 @@ export class Brush extends Component { [2, 0], [width - 1, height - 1] ]) - .on("end", brushed); + .on("start brush end", eventHandler) + .on("end.brushed", brushed); const brushArea = DOMUtils.appendOrSelect( backdrop, diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index edcb1f792b..05722a4116 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -237,23 +237,27 @@ export class ZoomBar extends Component { ) { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if (zoomBarOptions.selectionEnd !== undefined && event.type === "end") { - zoomBarOptions.selectionEnd(selection, newDomain); + + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } } } From 35b81e1871925f39d843fc631a2e4446ee8d27de Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 7 Jul 2020 12:33:39 +0800 Subject: [PATCH 187/510] fix: remove ZoomBarOptions in BaseChartOptions --- packages/core/src/interfaces/charts.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 4956c8895b..15f49a187f 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -46,10 +46,6 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; - /** - * zoombar configuration - */ - zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ @@ -222,7 +218,7 @@ export interface StackedAreaChartOptions extends ScatterChartOptions { | string | { name: string; - }; + }; } /** From 298715ee079206117e47cefbc17eeb39c8f09416 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 11:28:33 +0800 Subject: [PATCH 188/510] refactor: change initZoomDomain to initialZoomDomain - remove unnecessary undefined setting --- packages/core/demo/data/zoom-bar.ts | 27 +++++++++---------- packages/core/src/components/axes/zoom-bar.ts | 4 +-- packages/core/src/configuration.ts | 1 - packages/core/src/interfaces/components.ts | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 0016bbcad7..fbe74fba2a 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -23,21 +23,20 @@ const selectionEndFun = (selection, domain) => { console.log(domain); }; -const initZoomDomain = [ +const initialZoomDomain = [ new Date(2020, 11, 10, 23, 59, 25), new Date(2020, 11, 11, 0, 0, 25) ]; const defaultZoomBarOptions = { enabled: true, - initZoomDomain: undefined, selectionStart: selectionStartFun, selectionInProgress: selectionInProgressFun, selectionEnd: selectionEndFun }; // utility function to update title and enable zoomBar option -const updateOptions = (options) => { +const addZoomBarToOptions = (options) => { options["title"] = options["title"] + " - Zoom bar enabled"; options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); return options; @@ -45,51 +44,51 @@ const updateOptions = (options) => { export const zoomBarStackedAreaTimeSeriesData = areaChart.stackedAreaTimeSeriesData; -export const zoomBarStackedAreaTimeSeriesOptions = updateOptions( +export const zoomBarStackedAreaTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, areaChart.stackedAreaTimeSeriesOptions) ); export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; -export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( +export const zoomBarSimpleBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.simpleBarTimeSeriesOptions) ); export const zoomBarStackedBarTimeSeriesData = barChart.stackedBarTimeSeriesData; -export const zoomBarStackedBarTimeSeriesOptions = updateOptions( +export const zoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.stackedBarTimeSeriesOptions) ); export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; -export const zoomBarBubbleTimeSeriesOptions = updateOptions( +export const zoomBarBubbleTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) ); export const zoomBarLineTimeSeriesData = lineChart.lineTimeSeriesData; -export const zoomBarLineTimeSeriesOptions = updateOptions( +export const zoomBarLineTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, lineChart.lineTimeSeriesOptions) ); export const zoomBarScatterTimeSeriesData = scatterChart.scatterTimeSeriesData; -export const zoomBarScatterTimeSeriesOptions = updateOptions( +export const zoomBarScatterTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, scatterChart.scatterTimeSeriesOptions) ); export const zoomBarStepTimeSeriesData = stepChart.stepTimeSeriesData; -export const zoomBarStepTimeSeriesOptions = updateOptions( +export const zoomBarStepTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, stepChart.stepTimeSeriesOptions) ); export const zoomBarLineTimeSeries15secondsData = timeSeriesAxisChart.lineTimeSeriesData15seconds; -export const zoomBarLineTimeSeries15secondsOptions = updateOptions( +export const zoomBarLineTimeSeries15secondsOptions = addZoomBarToOptions( Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) ); export const zoomBarLineTimeSeriesInitDomainData = timeSeriesAxisChart.lineTimeSeriesData15seconds; -export const zoomBarLineTimeSeriesInitDomainOptions = updateOptions( +export const zoomBarLineTimeSeriesInitDomainOptions = addZoomBarToOptions( Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) ); -zoomBarLineTimeSeriesInitDomainOptions["title"] += " with initial zoom domain"; -zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initZoomDomain = initZoomDomain; +zoomBarLineTimeSeriesInitDomainOptions["title"] += " zoomed domain"; +zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initialZoomDomain = initialZoomDomain; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 05722a4116..2bbd6ad4ea 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -35,9 +35,9 @@ export class ZoomBar extends Component { // get initZoomDomain const zoomBarOptions = this.model.getOptions().zoomBar; - if (zoomBarOptions.initZoomDomain !== undefined) { + if (zoomBarOptions.initialZoomDomain !== undefined) { this.model.set( - { zoomDomain: zoomBarOptions.initZoomDomain }, + { zoomDomain: zoomBarOptions.initialZoomDomain }, { skipUpdate: true } ); } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index ad37847348..5109d282e1 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -133,7 +133,6 @@ export const timeScale: TimeScaleOptions = { */ export const zoomBar: ZoomBarOptions = { enabled: false, - initZoomDomain: undefined, selectionStart: undefined, selectionInProgress: undefined, selectionEnd: undefined diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index d81ddce313..e250197b85 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -129,7 +129,7 @@ export interface ZoomBarOptions { /** * an two element array which represents the initial zoom domain */ - initZoomDomain?: Object[]; + initialZoomDomain?: Object[]; /** * a function to handle selection start event From 1361009e8292bf470b80a294646796e08b87a6d3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 12:40:27 +0800 Subject: [PATCH 189/510] refactor: use Tools.getProperty to load zoomBarOptions --- packages/core/src/axis-chart.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index f5559f6f56..c7feb56bae 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -31,6 +31,11 @@ export class AxisChart extends Chart { } protected getAxisChartComponents(graphFrameComponents: any[]) { + const zoomBarEnabled = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "enabled" + ); const titleComponent = { id: "title", components: [new Title(this.model, this.services)], @@ -49,12 +54,10 @@ export class AxisChart extends Chart { } }; - if ( - this.model.getOptions().zoomBar && - this.model.getOptions().zoomBar.enabled - ) { + if (zoomBarEnabled) { graphFrameComponents.push(new Brush(this.model, this.services)); } + const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, @@ -156,7 +159,7 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } - if (this.model.getOptions().zoomBar.enabled === true) { + if (zoomBarEnabled) { topLevelLayoutComponents.push(zoomBarComponent); } topLevelLayoutComponents.push(fullFrameComponent); From 5a858e64d35db7e01a4b59ab64c36a6a63842b81 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 13:37:33 +0800 Subject: [PATCH 190/510] refactor: update code format --- packages/core/src/charts/pie.ts | 2 +- packages/core/src/charts/radar.ts | 6 ++++-- packages/core/src/components/component.ts | 7 ++++--- packages/core/src/components/index.ts | 1 - packages/core/src/model.ts | 5 ++++- packages/core/src/styles/components/_zoom-bar.scss | 3 +++ 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 4642ea8f2d..87b74217aa 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -52,7 +52,7 @@ export class PieChart extends Chart { skeleton: Skeletons.PIE }) ]; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 80b8e7888c..c9b3d67e6e 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -41,8 +41,10 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; - + const graphFrameComponents: any[] = [ + new Radar(this.model, this.services) + ]; + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 6aa5e17ee0..b5996e519c 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -103,15 +103,16 @@ export class Component { this.type === "stacked-bar" || this.type === "scatter-stacked" ) { - return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); - + return DOMUtils.appendOrSelectForAxisChart( + this.parent, + `clipPath.cover` + ); } else { return DOMUtils.appendOrSelect( this.parent, `g.${settings.prefix}--${chartprefix}--${this.type}` ); } - } return this.parent; diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 4d9959743a..1534f8db5a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -37,4 +37,3 @@ export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; - diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index a795d809e7..be8b412eb0 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -70,17 +70,20 @@ export class ChartModel { return datum; }); } + getDefaultZoomBarDomain() { const zoomBarData = this.getZoomBarData(); const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const domainIdentifier = cartesianScales.getDomainIdentifier(); + // default to full range with extended domain return cartesianScales.extendsDomain( mainXAxisPosition, extent(zoomBarData, (d: any) => d[domainIdentifier]) ); } + getAllDataFromDomain() { if (!this.get("data")) { return null; @@ -137,7 +140,7 @@ export class ChartModel { return allDataFromDomain.filter((datum) => { const group = dataGroups.find( - (g) => g.name === datum[groupMapsTo] + (dataGroup) => dataGroup.name === datum[groupMapsTo] ); return group.status === ACTIVE; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 13c20ecc30..d52d6c7978 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -14,6 +14,7 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: $ui-04; stroke-width: 1; } + path.zoom-graph-area-unselected { fill: $ui-01; stroke: none; @@ -23,9 +24,11 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { rect.handle { fill: $icon-02; } + rect.handle-bar { fill: $ui-02; } + // clear d3.brush selection style rect.selection { fill: none; From dd04746b471cdf16f0952d9a540e75154e8dc3ec Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 15:38:10 +0800 Subject: [PATCH 191/510] refactor: use Tools.getProperty to get initialZoomDomain --- packages/core/src/components/axes/zoom-bar.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 2bbd6ad4ea..32c4cbb61f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -34,10 +34,14 @@ export class ZoomBar extends Component { }); // get initZoomDomain - const zoomBarOptions = this.model.getOptions().zoomBar; - if (zoomBarOptions.initialZoomDomain !== undefined) { + const initialZoomDomain = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "initialZoomDomain" + ); + if (initialZoomDomain !== null) { this.model.set( - { zoomDomain: zoomBarOptions.initialZoomDomain }, + { zoomDomain: initialZoomDomain }, { skipUpdate: true } ); } From 1efed36babea8e241f35cc03bff476ce7a09d297 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 12:13:31 +0800 Subject: [PATCH 192/510] refactor: Change Cover to ChartClip --- packages/core/src/charts/area-stacked.ts | 4 +- packages/core/src/charts/area.ts | 4 +- packages/core/src/charts/bar-grouped.ts | 4 +- packages/core/src/charts/bar-simple.ts | 4 +- packages/core/src/charts/bar-stacked.ts | 4 +- packages/core/src/charts/bubble.ts | 4 +- packages/core/src/charts/line.ts | 4 +- packages/core/src/charts/scatter.ts | 4 +- .../core/src/components/axes/chart-clip.ts | 50 +++++++++++++++++++ packages/core/src/components/axes/cover.ts | 47 ----------------- packages/core/src/components/component.ts | 2 +- packages/core/src/components/index.ts | 2 +- 12 files changed, 68 insertions(+), 65 deletions(-) create mode 100644 packages/core/src/components/axes/chart-clip.ts delete mode 100644 packages/core/src/components/axes/cover.ts diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 42895bdc6b..8104c10ef0 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,7 +6,7 @@ import { Tools } from "../tools"; // Components import { - Cover, + ChartClip, Grid, StackedArea, TwoDimensionalAxes, @@ -36,7 +36,7 @@ export class StackedAreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new StackedArea(this.model, this.services), diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 2b313f6cc7..23959cce54 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,7 +6,7 @@ import { Tools } from "../tools"; // Components import { - Cover, + ChartClip, Grid, Area, Line, @@ -40,7 +40,7 @@ export class AreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 59354739e6..0c259fb01e 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, GroupedBar, TwoDimensionalAxes, @@ -40,7 +40,7 @@ export class GroupedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new GroupedBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 5709ea0e9b..4d6d17345a 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, SimpleBar, TwoDimensionalAxes, @@ -40,7 +40,7 @@ export class SimpleBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new SimpleBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index c2510fa7da..a680c9402b 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, StackedBar, StackedBarRuler, @@ -43,7 +43,7 @@ export class StackedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new StackedBarRuler(this.model, this.services), new StackedBar(this.model, this.services), diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 7238de0aa3..090a72ec29 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Ruler, Bubble, @@ -43,7 +43,7 @@ export class BubbleChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Bubble(this.model, this.services), diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index ee4e07f163..3e0c2959b2 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Line, Ruler, @@ -41,7 +41,7 @@ export class LineChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 2acacdf9df..6eebc8a9e0 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Ruler, Scatter, @@ -43,7 +43,7 @@ export class ScatterChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Scatter(this.model, this.services), diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts new file mode 100644 index 0000000000..f7bb54fc21 --- /dev/null +++ b/packages/core/src/components/axes/chart-clip.ts @@ -0,0 +1,50 @@ +// Internal Imports +import { Component } from "../component"; +import { DOMUtils } from "../../services"; + +// This class is used to create the clipPath to clip the chart graphs +// It's necessary for zoom in/out behavior +export class ChartClip extends Component { + type = "chart-clip"; + + chartClipPath: any; + + clipPathId = "id-" + this.type; + + render(animate = true) { + // Create the clipPath + this.createClipPath(); + } + + createClipPath() { + const svg = this.parent; + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + // Get height + this.chartClipPath = DOMUtils.appendOrSelect( + svg, + `clipPath.${this.type}` + ).attr("id", this.clipPathId); + const clipRect = DOMUtils.appendOrSelect( + this.chartClipPath, + `rect.${this.type}` + ); + clipRect + .attr("x", xScaleStart) + .attr("y", yScaleStart) + .attr("width", xScaleEnd - xScaleStart) + .attr("height", yScaleEnd - yScaleStart); + + this.chartClipPath.merge(clipRect).lower(); + + const clipG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); + clipG + .attr("clip-path", `url(#${this.clipPathId})`) + .attr("id", `g-${this.type}`); + } +} diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts deleted file mode 100644 index 2536c9ef40..0000000000 --- a/packages/core/src/components/axes/cover.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Internal Imports -import { Component } from "../component"; -import { DOMUtils } from "../../services"; - -export class Cover extends Component { - type = "cover"; - - coverClipPath: any; - - render(animate = true) { - // Create the cover - this.createCover(); - } - - createCover() { - const svg = this.parent; - const { cartesianScales } = this.services; - const mainXScale = cartesianScales.getMainXScale(); - const mainYScale = cartesianScales.getMainYScale(); - - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const [yScaleEnd, yScaleStart] = mainYScale.range(); - - // Get height - this.coverClipPath = DOMUtils.appendOrSelect( - svg, - `clipPath.${this.type}` - ); - this.coverClipPath.attr("id", `${this.type}Clip`); - const coverRect = DOMUtils.appendOrSelect( - this.coverClipPath, - "rect.cover" - ); - coverRect - .attr("x", xScaleStart) - .attr("y", yScaleStart) - .attr("width", xScaleEnd - xScaleStart) - .attr("height", yScaleEnd - yScaleStart); - - this.coverClipPath.merge(coverRect).lower(); - - const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); - coverG - .attr("clip-path", `url(#${this.type}Clip)`) - .attr("id", `g-${this.type}Clip`); - } -} diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index b5996e519c..f79a5e347f 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -105,7 +105,7 @@ export class Component { ) { return DOMUtils.appendOrSelectForAxisChart( this.parent, - `clipPath.cover` + `clipPath.chart-clip` ); } else { return DOMUtils.appendOrSelect( diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 1534f8db5a..506b51b1fa 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -31,7 +31,7 @@ export * from "./layout/layout"; export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; export * from "./axes/brush"; -export * from "./axes/cover"; +export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; From bf2ec7d3af701a93eeea772ec4fbca2338afeb7a Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 12:29:05 +0800 Subject: [PATCH 193/510] refactor: Change Brush to ChartBrush --- packages/core/src/axis-chart.ts | 6 ++++-- .../src/components/axes/{brush.ts => chart-brush.ts} | 9 ++++----- packages/core/src/components/index.ts | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) rename packages/core/src/components/axes/{brush.ts => chart-brush.ts} (95%) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index c7feb56bae..b79ca91dfa 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -8,7 +8,7 @@ import { AxisChartOptions } from "./interfaces"; import { - Brush, + ChartBrush, LayoutComponent, Legend, Title, @@ -55,7 +55,9 @@ export class AxisChart extends Chart { }; if (zoomBarEnabled) { - graphFrameComponents.push(new Brush(this.model, this.services)); + graphFrameComponents.push( + new ChartBrush(this.model, this.services) + ); } const graphFrameComponent = { diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/chart-brush.ts similarity index 95% rename from packages/core/src/components/axes/brush.ts rename to packages/core/src/components/axes/chart-brush.ts index 8067e6aaa8..7fa5a1caf3 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -1,17 +1,16 @@ // Internal Imports import { Component } from "../component"; -import { Tools } from "../../tools"; import { ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports -import { extent } from "d3-array"; import { brushX } from "d3-brush"; import { event } from "d3-selection"; import { scaleTime } from "d3-scale"; -export class Brush extends Component { - type = "brush"; +// This class is used for handle brush events in chart +export class ChartBrush extends Component { + type = "chart-brush"; render(animate = true) { const svg = this.parent; @@ -129,7 +128,7 @@ export class Brush extends Component { const brushArea = DOMUtils.appendOrSelect( backdrop, - "g.chart-brush" + `g.${this.type}` ).call(brush); } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 506b51b1fa..369cbbe15a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,7 +30,7 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; -export * from "./axes/brush"; +export * from "./axes/chart-brush"; export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; From 9b87641d4cb07e591cfa2f6d12fb53a867a8231a Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 14:18:36 +0800 Subject: [PATCH 194/510] refactor: set model.set() function default config --- packages/core/src/model.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index be8b412eb0..63254bb3e7 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -314,9 +314,12 @@ export class ChartModel { set(newState: any, configs?: any) { this.state = Object.assign({}, this.state, newState); - - if (!configs || !configs.skipUpdate) { - this.update(configs ? configs.animate : true); + const newConfig = Object.assign( + { skipUpdate: false, animate: true }, // default configs + configs + ); + if (!newConfig.skipUpdate) { + this.update(newConfig.animate); } } From e5d3ec61a18fcb9b701d51edde7d9e26aef2ec89 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 14:30:32 +0800 Subject: [PATCH 195/510] fix: remove unnecessary selector --- packages/core/src/components/graphs/line.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 2be9c949a9..02caebb0be 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -131,7 +131,6 @@ export class Line extends Component { this.parent .selectAll("path.line") - .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-hover-line") ) @@ -142,16 +141,16 @@ export class Line extends Component { return Configuration.lines.opacity.selected; }); - }; + } handleLegendMouseOut = (event: CustomEvent) => { this.parent - .selectAll(`g#coverClip`) + .selectAll("path.line") .transition( this.services.transitions.getTransition("legend-mouseout-line") ) .attr("opacity", Configuration.lines.opacity.selected); - }; + } destroy() { // Remove event listeners From c6f6880c3fb22e84b776239a3fab4ecc4a0bb9ae Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 15:22:22 +0800 Subject: [PATCH 196/510] refactor: create reusable getMainXScaleType() --- packages/core/src/components/axes/chart-brush.ts | 8 ++------ packages/core/src/components/axes/zoom-bar.ts | 8 ++------ packages/core/src/services/scales-cartesian.ts | 10 ++++------ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 7fa5a1caf3..19b9ee14b9 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -23,12 +23,8 @@ export class ChartBrush extends Component { }); const { cartesianScales } = this.services; - const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition - ); - - const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainXScaleType = cartesianScales.getMainXScaleType(); + const mainXScale = cartesianScales.getMainXScale(); if (mainXScale && mainXScaleType === ScaleTypes.TIME) { // get current zoomDomain diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 32c4cbb61f..897a1b9417 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -54,12 +54,8 @@ export class ZoomBar extends Component { const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition - ); - const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition - ); + const mainXScaleType = cartesianScales.getMainXScaleType(); + const mainYScaleType = cartesianScales.getMainYScaleType(); // get axes margins let axesLeftMargin = 0; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 11be646307..4904006ba5 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -231,14 +231,12 @@ export class CartesianScales extends Service { return this.getValueThroughAxisPosition(this.rangeAxisPosition, d, i); } - getXValue(d, i) { - const mainXAxisPosition = this.getMainXAxisPosition(); - return this.getValueThroughAxisPosition(mainXAxisPosition, d, i); + getMainXScaleType() { + return this.getScaleTypeByPosition(this.getMainXAxisPosition()); } - getYValue(d, i) { - const mainYAxisPosition = this.getMainYAxisPosition(); - return this.getValueThroughAxisPosition(mainYAxisPosition, d, i); + getMainYScaleType() { + return this.getScaleTypeByPosition(this.getMainYAxisPosition()); } getDomainIdentifier() { From 2deeeb19ddebb122de021e19a556d7cd94bdac69 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 16:22:13 +0800 Subject: [PATCH 197/510] refactor: make sure zoom bar only shows with supported options - zoomBar is available when -- zoomBar is enabled -- main X axis position is bottom -- main X axis scale type is time --- packages/core/src/axis-chart.ts | 21 +++++++++++++++++++-- packages/core/src/components/axes/axis.ts | 13 +++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index b79ca91dfa..6d4d515cba 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -5,7 +5,9 @@ import { LegendOrientations, LegendPositions, ChartConfig, - AxisChartOptions + AxisChartOptions, + AxisPositions, + ScaleTypes } from "./interfaces"; import { ChartBrush, @@ -31,11 +33,26 @@ export class AxisChart extends Chart { } protected getAxisChartComponents(graphFrameComponents: any[]) { - const zoomBarEnabled = Tools.getProperty( + const isZoomBarEnabled = Tools.getProperty( this.model.getOptions(), "zoomBar", "enabled" ); + + this.services.cartesianScales.findDomainAndRangeAxes(); // need to do this before getMainXAxisPosition() + const mainXAxisPosition = this.services.cartesianScales.getMainXAxisPosition(); + const mainXScaleType = Tools.getProperty( + this.model.getOptions(), + "axes", + mainXAxisPosition, + "scaleType" + ); + // @todo - Zoom Bar only supports main axis at BOTTOM axis and time scale for now + const zoomBarEnabled = + isZoomBarEnabled && + mainXAxisPosition === AxisPositions.BOTTOM && + mainXScaleType === ScaleTypes.TIME; + const titleComponent = { id: "title", components: [new Title(this.model, this.services)], diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 17a4b59a8a..30df6c2f4b 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -114,12 +114,6 @@ export class Axis extends Component { scale.range([startPosition, endPosition]); } - // if zoomDomain is available, update scale domain to Date array. - const zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain); - } - // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -178,6 +172,13 @@ export class Axis extends Component { const scaleType = this.scaleType || axisOptions.scaleType || ScaleTypes.LINEAR; + // if zoomDomain is available, scale type is time, and axis position isBOTTOM or TOP + // update scale domain to zoomDomain. + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && isTimeScaleType && !isVerticalAxis) { + scale.domain(zoomDomain); + } + // Initialize axis object const axis = axisFunction(scale).tickSizeOuter(0); From 211f4f30a22aca7d6b95963421fee7e37c341415 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 22:15:30 +0800 Subject: [PATCH 198/510] refactor: set clip-path url in containerSVG --- .../core/src/components/axes/chart-clip.ts | 11 ++---- packages/core/src/components/component.ts | 34 +++++++------------ .../src/components/graphs/area-stacked.ts | 2 +- packages/core/src/components/graphs/area.ts | 2 +- .../core/src/components/graphs/bar-grouped.ts | 2 +- .../core/src/components/graphs/bar-simple.ts | 2 +- .../core/src/components/graphs/bar-stacked.ts | 2 +- packages/core/src/components/graphs/line.ts | 6 ++-- .../src/components/graphs/scatter-stacked.ts | 2 +- .../core/src/components/graphs/scatter.ts | 2 +- 10 files changed, 24 insertions(+), 41 deletions(-) diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts index f7bb54fc21..321db115e5 100644 --- a/packages/core/src/components/axes/chart-clip.ts +++ b/packages/core/src/components/axes/chart-clip.ts @@ -2,15 +2,13 @@ import { Component } from "../component"; import { DOMUtils } from "../../services"; -// This class is used to create the clipPath to clip the chart graphs +// This class is used to create the clipPath to clip the chart components // It's necessary for zoom in/out behavior export class ChartClip extends Component { type = "chart-clip"; chartClipPath: any; - clipPathId = "id-" + this.type; - render(animate = true) { // Create the clipPath this.createClipPath(); @@ -29,7 +27,7 @@ export class ChartClip extends Component { this.chartClipPath = DOMUtils.appendOrSelect( svg, `clipPath.${this.type}` - ).attr("id", this.clipPathId); + ).attr("id", this.chartClipId); const clipRect = DOMUtils.appendOrSelect( this.chartClipPath, `rect.${this.type}` @@ -41,10 +39,5 @@ export class ChartClip extends Component { .attr("height", yScaleEnd - yScaleStart); this.chartClipPath.merge(clipRect).lower(); - - const clipG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); - clipG - .attr("clip-path", `url(#${this.clipPathId})`) - .attr("id", `g-${this.type}`); } } diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index f79a5e347f..91ccf82ef8 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -19,6 +19,8 @@ export class Component { protected model: ChartModel; protected services: any; + protected chartClipId = "chart-clip-id"; + constructor(model: ChartModel, services: any, configs?: any) { this.model = model; this.services = services; @@ -83,7 +85,7 @@ export class Component { return this.parent; } - getContainerSVG() { + getContainerSVG(withinChartClip = false) { if (this.type) { const chartprefix = Tools.getProperty( this.model.getOptions(), @@ -91,28 +93,16 @@ export class Component { "prefix" ); - // @todo Chart type equals to axis-chart - if ( - this.type === "line" || - this.type === "scatter" || - this.type === "area" || - this.type === "bubble" || - this.type === "area-stacked" || - this.type === "grouped-bar" || - this.type === "simple-bar" || - this.type === "stacked-bar" || - this.type === "scatter-stacked" - ) { - return DOMUtils.appendOrSelectForAxisChart( - this.parent, - `clipPath.chart-clip` - ); - } else { - return DOMUtils.appendOrSelect( - this.parent, - `g.${settings.prefix}--${chartprefix}--${this.type}` - ); + const svg = DOMUtils.appendOrSelect( + this.parent, + `g.${settings.prefix}--${chartprefix}--${this.type}` + ); + + if (withinChartClip) { + svg.attr("clip-path", `url(#${this.chartClipId})`); } + + return svg; } return this.parent; diff --git a/packages/core/src/components/graphs/area-stacked.ts b/packages/core/src/components/graphs/area-stacked.ts index 5f6609df63..2e086cd958 100644 --- a/packages/core/src/components/graphs/area-stacked.ts +++ b/packages/core/src/components/graphs/area-stacked.ts @@ -28,7 +28,7 @@ export class StackedArea extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const self = this; const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/components/graphs/area.ts b/packages/core/src/components/graphs/area.ts index cd94376aaf..127dab7ac0 100644 --- a/packages/core/src/components/graphs/area.ts +++ b/packages/core/src/components/graphs/area.ts @@ -26,7 +26,7 @@ export class Area extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const { cartesianScales } = this.services; const orientation = cartesianScales.getOrientation(); diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 07f12e8c64..85217ba207 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -44,7 +44,7 @@ export class GroupedBar extends Bar { this.setGroupScale(); // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const allDataLabels = map( displayData, diff --git a/packages/core/src/components/graphs/bar-simple.ts b/packages/core/src/components/graphs/bar-simple.ts index aeb72950d1..2f74a5c922 100644 --- a/packages/core/src/components/graphs/bar-simple.ts +++ b/packages/core/src/components/graphs/bar-simple.ts @@ -31,7 +31,7 @@ export class SimpleBar extends Bar { const { groupMapsTo } = options.data; // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); // Update data on all bars const bars = svg diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index 55d376f835..a4cce26d0f 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -28,7 +28,7 @@ export class StackedBar extends Bar { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); // Chart options mixed with the internal configurations const displayData = this.model.getDisplayData(); diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 02caebb0be..3668bc294c 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -25,7 +25,7 @@ export class Line extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const { cartesianScales, curves } = this.services; const getDomainValue = (d, i) => cartesianScales.getDomainValue(d, i); @@ -141,7 +141,7 @@ export class Line extends Component { return Configuration.lines.opacity.selected; }); - } + }; handleLegendMouseOut = (event: CustomEvent) => { this.parent @@ -150,7 +150,7 @@ export class Line extends Component { this.services.transitions.getTransition("legend-mouseout-line") ) .attr("opacity", Configuration.lines.opacity.selected); - } + }; destroy() { // Remove event listeners diff --git a/packages/core/src/components/graphs/scatter-stacked.ts b/packages/core/src/components/graphs/scatter-stacked.ts index e477182786..ca5ad2a4c1 100644 --- a/packages/core/src/components/graphs/scatter-stacked.ts +++ b/packages/core/src/components/graphs/scatter-stacked.ts @@ -7,7 +7,7 @@ export class StackedScatter extends Scatter { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index 2d8074dc49..539d61b7e3 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -52,7 +52,7 @@ export class Scatter extends Component { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const options = this.model.getOptions(); const { groupMapsTo } = options.data; From 47b09eb15ccd96a97723125bc1730902d46b73ae Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 11:52:45 +0800 Subject: [PATCH 199/510] refactor: dispatch zoombar selection events instead of callback --- packages/core/demo/data/zoom-bar.ts | 26 +------------- .../core/src/components/axes/chart-brush.ts | 36 +++++++------------ packages/core/src/components/axes/zoom-bar.ts | 33 +++++++---------- packages/core/src/configuration.ts | 5 +-- packages/core/src/interfaces/components.ts | 13 ------- packages/core/src/interfaces/events.ts | 5 ++- 6 files changed, 32 insertions(+), 86 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index fbe74fba2a..2dc1b076e1 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -6,39 +6,15 @@ import * as scatterChart from "./scatter"; import * as stepChart from "./step"; import * as timeSeriesAxisChart from "./time-series-axis"; -// default function for selection callback -const selectionStartFun = (selection, domain) => { - console.log("ZoomBar SelectionStart callback!"); - console.log(selection); - console.log(domain); -}; -const selectionInProgressFun = (selection, domain) => { - console.log("ZoomBar SelectionInProgress callback!"); - console.log(selection); - console.log(domain); -}; -const selectionEndFun = (selection, domain) => { - console.log("ZoomBar SelectionEnd callback!"); - console.log(selection); - console.log(domain); -}; - const initialZoomDomain = [ new Date(2020, 11, 10, 23, 59, 25), new Date(2020, 11, 11, 0, 0, 25) ]; -const defaultZoomBarOptions = { - enabled: true, - selectionStart: selectionStartFun, - selectionInProgress: selectionInProgressFun, - selectionEnd: selectionEndFun -}; - // utility function to update title and enable zoomBar option const addZoomBarToOptions = (options) => { options["title"] = options["title"] + " - Zoom bar enabled"; - options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); + options["zoomBar"] = { enabled: true }; return options; }; diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 19b9ee14b9..e8ba9b3f7d 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -1,6 +1,6 @@ // Internal Imports import { Component } from "../component"; -import { ScaleTypes } from "../../interfaces"; +import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports @@ -51,29 +51,19 @@ export class ChartBrush extends Component { event.sourceEvent.type === "mouseup" || event.sourceEvent.type === "mousedown") ) { - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress( - selection, - newDomain - ); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); + // dispatch selection events + let zoomBarEventType; + if (event.type === "start") { + zoomBarEventType = Events.ZoomBar.SELECTION_START; + } else if (event.type === "brush") { + zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; + } else if (event.type === "end") { + zoomBarEventType = Events.ZoomBar.SELECTION_END; } + this.services.events.dispatchEvent(zoomBarEventType, { + selection, + newDomain + }); } }; const brushed = () => { diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 897a1b9417..258d60ea1a 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -8,7 +8,7 @@ import { DOMUtils } from "../../services"; import { extent } from "d3-array"; import { brushX } from "d3-brush"; import { area, line } from "d3-shape"; -import { event, select, selectAll } from "d3-selection"; +import { event } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; @@ -238,26 +238,19 @@ export class ZoomBar extends Component { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); + // dispatch selection events + let zoomBarEventType; + if (event.type === "start") { + zoomBarEventType = Events.ZoomBar.SELECTION_START; + } else if (event.type === "brush") { + zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; + } else if (event.type === "end") { + zoomBarEventType = Events.ZoomBar.SELECTION_END; } + this.services.events.dispatchEvent(zoomBarEventType, { + selection, + newDomain + }); } } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 5109d282e1..d09e139d44 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -132,10 +132,7 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false, - selectionStart: undefined, - selectionInProgress: undefined, - selectionEnd: undefined + enabled: false }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index e250197b85..5bdcf1f302 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -130,17 +130,4 @@ export interface ZoomBarOptions { * an two element array which represents the initial zoom domain */ initialZoomDomain?: Object[]; - - /** - * a function to handle selection start event - */ - selectionStart?: Function; - /** - * a function to handle selection in progress event - */ - selectionInProgress?: Function; - /** - * a function to handle selection end event - */ - selectionEnd?: Function; } diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 2a25fd5992..1da2416ead 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -21,7 +21,10 @@ export enum Model { * enum of all events related to the zoom-bar */ export enum ZoomBar { - UPDATE = "zoom-bar-update" + UPDATE = "zoom-bar-update", + SELECTION_START = "zoom-bar-selection-start", + SELECTION_IN_PROGRESS = "zoom-bar-selection-in-progress", + SELECTION_END = "zoom-bar-selection-end" } /** From c43abbffb67bc1d652e7d7d52dffc33271f5d2ac Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 15:48:41 +0800 Subject: [PATCH 200/510] feat: update chart brush selection storke to dash --- .../core/src/components/axes/chart-brush.ts | 36 +++++++++++++++++-- .../src/styles/components/_chart-brush.scss | 9 +++++ .../core/src/styles/components/index.scss | 1 + 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/styles/components/_chart-brush.scss diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e8ba9b3f7d..e267ae0bda 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -10,8 +10,12 @@ 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 = "chart-brush"; + selectionSelector = "rect.selection"; // needs to match the class name in d3.brush + render(animate = true) { const svg = this.parent; const backdrop = DOMUtils.appendOrSelect( @@ -35,8 +39,36 @@ export class ChartBrush extends Component { 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 + + brushArea + .select(this.selectionSelector) + .attr("stroke-dasharray", dashArray); + }; + const eventHandler = () => { const selection = event.selection; + + updateSelectionDash(selection); + const xScale = scaleTime().range([0, width]).domain(zoomDomain); const newDomain = [ @@ -106,8 +138,8 @@ export class ChartBrush extends Component { // leave some space to display selection strokes besides axis const brush = brushX() .extent([ - [2, 0], - [width - 1, height - 1] + [0, 0], + [width - 1, height] ]) .on("start brush end", eventHandler) .on("end.brushed", brushed); diff --git a/packages/core/src/styles/components/_chart-brush.scss b/packages/core/src/styles/components/_chart-brush.scss new file mode 100644 index 0000000000..f316c7d486 --- /dev/null +++ b/packages/core/src/styles/components/_chart-brush.scss @@ -0,0 +1,9 @@ +.#{$prefix}--#{$charts-prefix}--chart-brush { + g.chart-brush { + rect.selection { + fill: $ui-03; + fill-opacity: 0.3; + stroke: $interactive-03; + } + } +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index 0b96507fbe..e9632f12a1 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -1,5 +1,6 @@ @import "./axis"; @import "./callouts"; +@import "./chart-brush"; @import "./grid"; @import "./ruler"; @import "./skeleton"; From 234c451bfaa6c064efe9def438cd4381235aab7a Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 17:33:44 +0800 Subject: [PATCH 201/510] feat: show tooltip when mouseover zoombar handle --- packages/core/src/components/axes/zoom-bar.ts | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 258d60ea1a..4498bb214f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -197,7 +197,11 @@ export class ZoomBar extends Component { // don't update brushHandle to avoid flash } else if (zoomDomain[0].valueOf() === zoomDomain[1].valueOf()) { brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle(this.getContainerSVG(), xScale.range()); + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range(), + xScale.domain() + ); } else { const selected = zoomDomain.map((domain) => xScale(domain)); if (selected[1] - selected[0] < this.MIN_SELECTION_DIFF) { @@ -205,7 +209,11 @@ export class ZoomBar extends Component { // don't update brushHandle to avoid flash } else { brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle(this.getContainerSVG(), selected); + this.updateBrushHandle( + this.getContainerSVG(), + selected, + zoomDomain + ); } } } @@ -213,14 +221,14 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - // update brush handle position - this.updateBrushHandle(this.getContainerSVG(), selection); - const newDomain = [ scale.invert(selection[0]), scale.invert(selection[1]) ]; + // update brush handle position + this.updateBrushHandle(this.getContainerSVG(), selection, newDomain); + // be aware that the value of d3.event changes during an event! // update zoomDomain only if the event comes from mouse event if ( @@ -254,7 +262,20 @@ export class ZoomBar extends Component { } } - updateBrushHandle(svg, selection) { + updateBrushHandleTooltip(svg, domain) { + // remove old handle tooltip + svg.select("title").remove(); + // add new handle tooltip + svg.append("title").text((d) => { + if (d.type === "w") { + return domain[0]; + } else if (d.type === "e") { + return domain[1]; + } + }); + } + + updateBrushHandle(svg, selection, domain) { const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -263,6 +284,7 @@ export class ZoomBar extends Component { const handleBarHeight = 12; const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; + // handle svg.select(this.brushSelector) .selectAll("rect.handle") @@ -277,7 +299,10 @@ export class ZoomBar extends Component { .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) - .style("display", null); // always display + .attr("cursor", "pointer") + .style("display", null) // always display + .call(this.updateBrushHandleTooltip, domain); + // handle-bar svg.select(this.brushSelector) .selectAll("rect.handle-bar") @@ -296,7 +321,8 @@ export class ZoomBar extends Component { .attr("y", handleYBarDiff) .attr("width", handleBarWidth) .attr("height", handleBarHeight) - .attr("cursor", "ew-resize"); + .attr("cursor", "pointer") + .call(this.updateBrushHandleTooltip, domain); this.updateClipPath( svg, From 920a8137bfbd8ab84a4632d5609b066af77879d4 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 11:54:28 +0800 Subject: [PATCH 202/510] fix: avoid tick rotation flip during zoom domain changing - always rotate ticks during zoom domain changing --- packages/core/src/components/axes/axis.ts | 33 ++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 30df6c2f4b..99bea29d1b 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -29,6 +29,8 @@ export class Axis extends Component { scale: any; scaleType: ScaleTypes; + zoomDomainChanging = false; + constructor(model: ChartModel, services: any, configs?: any) { super(model, services, configs); @@ -37,6 +39,25 @@ export class Axis extends Component { } this.margins = this.configs.margins; + this.init(); + } + + init() { + this.services.events.addEventListener( + Events.ZoomBar.SELECTION_START, + () => { + this.zoomDomainChanging = true; + } + ); + this.services.events.addEventListener( + Events.ZoomBar.SELECTION_END, + () => { + this.zoomDomainChanging = false; + // need another update after zoom bar selection is completed + // to make sure the tick rotation is calculated correctly + this.services.events.dispatchEvent(Events.Model.UPDATE, {}); + } + ); } render(animate = true) { @@ -431,7 +452,9 @@ export class Axis extends Component { ? estimatedTickSize < minTickSize * 2 // datetime tick could be very long : estimatedTickSize < minTickSize; } - if (rotateTicks) { + + // always rotate ticks if zoomDomain is changing to avoid rotation flips during zoomDomain changing + if (rotateTicks || this.zoomDomainChanging) { if (!isNumberOfTicksProvided) { axis.ticks( this.getNumberOfFittingTicks( @@ -650,5 +673,13 @@ export class Axis extends Component { .on("mouseover", null) .on("mousemove", null) .on("mouseout", null); + this.services.events.removeEventListener( + Events.ZoomBar.SELECTION_START, + {} + ); + this.services.events.removeEventListener( + Events.ZoomBar.SELECTION_END, + {} + ); } } From dac36f7fae51f706f53ebc8d63beef3310b7dfec Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 18:19:27 +0800 Subject: [PATCH 203/510] fix: move chart brush selection above all graphs --- .../core/src/components/axes/chart-brush.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e267ae0bda..944e2245e6 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -16,6 +16,8 @@ export class ChartBrush extends Component { selectionSelector = "rect.selection"; // needs to match the class name in d3.brush + selectionElementId = "ChartBrushSelectionId"; + render(animate = true) { const svg = this.parent; const backdrop = DOMUtils.appendOrSelect( @@ -66,6 +68,9 @@ export class ChartBrush extends Component { const eventHandler = () => { const selection = event.selection; + if (selection === null) { + return; + } updateSelectionDash(selection); @@ -148,6 +153,26 @@ export class ChartBrush extends Component { backdrop, `g.${this.type}` ).call(brush); + + // set an id for rect.selection to be referred + brushArea + .select(this.selectionSelector) + .attr("id", this.selectionElementId); + + // create the chart brush group + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const selectionArea = this.getContainerSVG().attr( + "transform", + `translate(${xScaleStart},0)` + ); + // clear old svg + selectionArea.selectAll("svg").remove(); + // create a svg referring to d3 brush rect.selection + // this is to draw the selection above all graphs + selectionArea + .append("svg") + .append("use") + .attr("xlink:href", `#${this.selectionElementId}`); } } } From 47c193f18e460483906f12f162fe4f99b169b867 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 20:47:11 +0800 Subject: [PATCH 204/510] fix: use another svg to display front selection --- .../core/src/components/axes/chart-brush.ts | 55 +++++++++---------- .../src/styles/components/_chart-brush.scss | 15 ++++- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 944e2245e6..d07edd0c4e 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -16,14 +16,22 @@ export class ChartBrush extends Component { selectionSelector = "rect.selection"; // needs to match the class name in d3.brush - selectionElementId = "ChartBrushSelectionId"; + frontSelectionSelector = "rect.frontSelection"; // needs to match the class name in _chart-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 = brushArea.select(this.selectionSelector); + const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true }); @@ -31,6 +39,12 @@ export class ChartBrush extends Component { 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 @@ -61,9 +75,7 @@ export class ChartBrush extends Component { dashArray += "," + selectionWidth.toString(); // bottom (invisible) dashArray += "," + height.toString(); // left - brushArea - .select(this.selectionSelector) - .attr("stroke-dasharray", dashArray); + frontSelection.attr("stroke-dasharray", dashArray); }; const eventHandler = () => { @@ -72,6 +84,14 @@ export class ChartBrush extends Component { 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("display", null); + updateSelectionDash(selection); const xScale = scaleTime().range([0, width]).domain(zoomDomain); @@ -137,6 +157,8 @@ export class ChartBrush extends Component { // clear brush selection brushArea.call(brush.move, null); + // hide frontSelection + frontSelection.style("display", "none"); } }; @@ -149,30 +171,7 @@ export class ChartBrush extends Component { .on("start brush end", eventHandler) .on("end.brushed", brushed); - const brushArea = DOMUtils.appendOrSelect( - backdrop, - `g.${this.type}` - ).call(brush); - - // set an id for rect.selection to be referred - brushArea - .select(this.selectionSelector) - .attr("id", this.selectionElementId); - - // create the chart brush group - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const selectionArea = this.getContainerSVG().attr( - "transform", - `translate(${xScaleStart},0)` - ); - // clear old svg - selectionArea.selectAll("svg").remove(); - // create a svg referring to d3 brush rect.selection - // this is to draw the selection above all graphs - selectionArea - .append("svg") - .append("use") - .attr("xlink:href", `#${this.selectionElementId}`); + brushArea.call(brush); } } } diff --git a/packages/core/src/styles/components/_chart-brush.scss b/packages/core/src/styles/components/_chart-brush.scss index f316c7d486..d458f791d4 100644 --- a/packages/core/src/styles/components/_chart-brush.scss +++ b/packages/core/src/styles/components/_chart-brush.scss @@ -1,9 +1,18 @@ .#{$prefix}--#{$charts-prefix}--chart-brush { + // disable default d3 brush selection g.chart-brush { rect.selection { - fill: $ui-03; - fill-opacity: 0.3; - stroke: $interactive-03; + fill: none; + fill-opacity: 0; + stroke: none; } } } + +g.#{$prefix}--#{$charts-prefix}--chart-brush { + rect.frontSelection { + fill: $ui-03; + fill-opacity: 0.3; + stroke: $interactive-03; + } +} From 8a3969b41a0b7cfebf7403c0e6d0b3dbfd74730a Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 20:59:47 +0800 Subject: [PATCH 205/510] fix: set cursor for front selection --- packages/core/src/components/axes/chart-brush.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index d07edd0c4e..85e48b3d08 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -90,6 +90,7 @@ export class ChartBrush extends Component { .attr("y", d3Selection.attr("y")) .attr("width", d3Selection.attr("width")) .attr("height", d3Selection.attr("height")) + .style("cursor", "pointer") .style("display", null); updateSelectionDash(selection); From 4da5e87c17fc3cee1d89c31e5185dd056af23843 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 16 Jul 2020 15:07:40 +0800 Subject: [PATCH 206/510] fix: use unique chartClipId for each chart --- packages/core/src/components/axes/chart-clip.ts | 15 +++++++++++++++ packages/core/src/components/component.ts | 8 +++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts index 321db115e5..4a8a206c2f 100644 --- a/packages/core/src/components/axes/chart-clip.ts +++ b/packages/core/src/components/axes/chart-clip.ts @@ -1,14 +1,29 @@ // Internal Imports import { Component } from "../component"; import { DOMUtils } from "../../services"; +import { ChartModel } from "../../model"; // This class is used to create the clipPath to clip the chart components // It's necessary for zoom in/out behavior export class ChartClip extends Component { type = "chart-clip"; + // Give every chart-clip a distinct ID + // so they don't interfere each other in a page with multiple charts + chartClipId = "chart-clip-id-" + Math.floor(Math.random() * 99999999999); + chartClipPath: any; + constructor(model: ChartModel, services: any, configs?: any) { + super(model, services, configs); + this.init(); + } + + init() { + // set unique chartClipId in this chart to model + this.model.set({ chartClipId: this.chartClipId }, { skipUpdate: true }); + } + render(animate = true) { // Create the clipPath this.createClipPath(); diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 91ccf82ef8..87d55fba62 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -19,8 +19,6 @@ export class Component { protected model: ChartModel; protected services: any; - protected chartClipId = "chart-clip-id"; - constructor(model: ChartModel, services: any, configs?: any) { this.model = model; this.services = services; @@ -99,7 +97,11 @@ export class Component { ); if (withinChartClip) { - svg.attr("clip-path", `url(#${this.chartClipId})`); + // get unique chartClipId int this chart from model + const chartClipId = this.model.get("chartClipId"); + if (chartClipId) { + svg.attr("clip-path", `url(#${chartClipId})`); + } } return svg; From b3a1368652057fc223624e4a094cc3dd4464e61d Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 16 Jul 2020 16:17:58 +0800 Subject: [PATCH 207/510] fix: keep zoom bar handle inside zoom bar range --- packages/core/src/components/axes/zoom-bar.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 4498bb214f..adc38f9bc9 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -20,7 +20,9 @@ export class ZoomBar extends Component { brushSelector = "g.zoom-bar-brush"; // needs to match the style in _zoom-bar.scss - clipId = "zoomBarClip"; + // Give every zoomBarClip a distinct ID + // so they don't interfere the other zoom bars in a page + clipId = "zoomBarClip-" + Math.floor(Math.random() * 99999999999); height = 32; @@ -28,6 +30,9 @@ export class ZoomBar extends Component { brush = brushX(); + // The max allowed selection ragne, will be updated soon in render() + maxSelectionRange: [0, 0]; + init() { this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { this.render(); @@ -97,6 +102,8 @@ export class ZoomBar extends Component { this.compensateDataForDefaultDomain(zoomBarData, defaultDomain, 0); xScale.range([axesLeftMargin, width]).domain(defaultDomain); + // keep max selection range + this.maxSelectionRange = xScale.range(); yScale .range([0, this.height - 6]) @@ -276,6 +283,7 @@ export class ZoomBar extends Component { } updateBrushHandle(svg, selection, domain) { + const self = this; const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -291,9 +299,17 @@ export class ZoomBar extends Component { .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { if (d.type === "w") { - return selection[0] + handleXDiff; + // handle should not exceed zoom bar range + return Math.max( + selection[0] + handleXDiff, + self.maxSelectionRange[0] + ); } else if (d.type === "e") { - return selection[1] + handleXDiff; + // handle should not exceed zoom bar range + return Math.min( + selection[1] + handleXDiff, + self.maxSelectionRange[1] - handleWidth + ); } }) .attr("y", 0) @@ -313,9 +329,15 @@ export class ZoomBar extends Component { }) .attr("x", function (d) { if (d.type === "w") { - return selection[0] + handleBarXDiff; + return Math.max( + selection[0] + handleBarXDiff, + self.maxSelectionRange[0] - handleXDiff + handleBarXDiff + ); } else if (d.type === "e") { - return selection[1] + handleBarXDiff; + return Math.min( + selection[1] + handleBarXDiff, + self.maxSelectionRange[1] + handleXDiff + handleBarXDiff + ); } }) .attr("y", handleYBarDiff) From c6baa790df667885719b42ef36f6c005332f747f Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 20 Jul 2020 21:42:26 +0800 Subject: [PATCH 208/510] fix: use DOMUtils.appendOrSelect in case the element does not exist yet --- packages/core/src/components/axes/chart-brush.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 85e48b3d08..e872ec1cf5 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -30,7 +30,10 @@ export class ChartBrush extends Component { const brushArea = DOMUtils.appendOrSelect(backdrop, `g.${this.type}`); // set an id for rect.selection to be referred - const d3Selection = brushArea.select(this.selectionSelector); + const d3Selection = DOMUtils.appendOrSelect( + brushArea, + this.selectionSelector + ); const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true From ad51dc5d10df270ba040fd051ac63e053e8d3dc9 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 20 Jul 2020 22:25:10 +0800 Subject: [PATCH 209/510] feat: allow user to set zoom bar data --- packages/core/demo/data/index.ts | 6 +++++ packages/core/demo/data/zoom-bar.ts | 31 +++++++++++++++++++--- packages/core/src/interfaces/components.ts | 4 +++ packages/core/src/model.ts | 19 ++++++++++--- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index b826eceb22..7b0d92cf26 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -772,6 +772,12 @@ let allDemoGroups = [ chartType: chartTypes.StackedBarChart, isDemoExample: false }, + { + options: zoomBarDemos.definedZoomBarStackedBarTimeSeriesOptions, + data: zoomBarDemos.definedZoomBarStackedBarTimeSeriesData, + chartType: chartTypes.StackedBarChart, + isDemoExample: false + }, { options: zoomBarDemos.zoomBarBubbleTimeSeriesOptions, data: zoomBarDemos.zoomBarBubbleTimeSeriesData, diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 2dc1b076e1..98249d52da 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -11,10 +11,28 @@ const initialZoomDomain = [ new Date(2020, 11, 11, 0, 0, 25) ]; +const definedZoomBarData = [ + { date: new Date(2019, 0, 1), value: 10000 }, + { date: new Date(2019, 0, 2), value: 10 }, + { date: new Date(2019, 0, 3), value: 75000 }, + { date: new Date(2019, 0, 5), value: 65000 }, + { date: new Date(2019, 0, 6), value: 57312 }, + { date: new Date(2019, 0, 8), value: 10000 }, + { date: new Date(2019, 0, 13), value: 49213 }, + { date: new Date(2019, 0, 15), value: 70323 }, + { date: new Date(2019, 0, 17), value: 51213 }, + { date: new Date(2019, 0, 19), value: 21300 } +]; + // utility function to update title and enable zoomBar option -const addZoomBarToOptions = (options) => { - options["title"] = options["title"] + " - Zoom bar enabled"; - options["zoomBar"] = { enabled: true }; +const addZoomBarToOptions = (options, includeDefinedZoomBarData = false) => { + if (includeDefinedZoomBarData) { + options["title"] = options["title"] + " - Defined zoom bar enabled"; + options["zoomBar"] = { enabled: true, data: definedZoomBarData }; + } else { + options["title"] = options["title"] + " - Zoom bar enabled"; + options["zoomBar"] = { enabled: true }; + } return options; }; @@ -35,6 +53,13 @@ export const zoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.stackedBarTimeSeriesOptions) ); +export const definedZoomBarStackedBarTimeSeriesData = + barChart.stackedBarTimeSeriesData; +export const definedZoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( + Object.assign({}, barChart.stackedBarTimeSeriesOptions), + true +); + export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; export const zoomBarBubbleTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 5bdcf1f302..1a966db09f 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -130,4 +130,8 @@ export interface ZoomBarOptions { * an two element array which represents the initial zoom domain */ initialZoomDomain?: Object[]; + /** + * options related to zoom bar data + */ + data?: Object[]; } diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 63254bb3e7..843b353b2a 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -46,10 +46,23 @@ export class ChartModel { const domainIdentifier = cartesianScales.getDomainIdentifier(); const rangeIdentifier = cartesianScales.getRangeIdentifier(); - const displayData = this.getDisplayData(); + let zoomBarData; + // check if pre-defined zoom bar data exists + const definedZoomBarData = Tools.getProperty( + this.getOptions(), + "zoomBar", + "data" + ); + // if user already defines zoom bar data, use it + if (definedZoomBarData) { + zoomBarData = definedZoomBarData; + } else { + // use displayData if not defined + zoomBarData = this.getDisplayData(); + } // get all dates (Number) in displayData let allDates = []; - displayData.forEach((data) => { + zoomBarData.forEach((data) => { allDates = allDates.concat(Number(data[domainIdentifier])); }); allDates = Tools.removeArrayDuplicates(allDates).sort(); @@ -59,7 +72,7 @@ export class ChartModel { let sum = 0; const datum = {}; - displayData.forEach((data) => { + zoomBarData.forEach((data) => { if (Number(data[domainIdentifier]) === date) { sum += data[rangeIdentifier]; } From 1b95ed22ad7376fb2832d5fadce7590a8e7dfc7f Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 11:38:02 +0800 Subject: [PATCH 210/510] fix: definedZoomBarData needs at least two elements --- packages/core/src/components/axes/zoom-bar.ts | 3 +++ packages/core/src/model.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index adc38f9bc9..6d134f0534 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -393,6 +393,9 @@ export class ZoomBar extends Component { // assume the domains in data are already sorted compensateDataForDefaultDomain(data, defaultDomain, value) { + if (!data || data.length < 2) { + return; + } const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); // if min domain is extended diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 843b353b2a..b6fa3f9ba4 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -54,7 +54,7 @@ export class ChartModel { "data" ); // if user already defines zoom bar data, use it - if (definedZoomBarData) { + if (definedZoomBarData && definedZoomBarData.length > 1) { zoomBarData = definedZoomBarData; } else { // use displayData if not defined From d99bcaa5f433f5755bd63c0f3e187b388bdb66c9 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 13:43:41 +0800 Subject: [PATCH 211/510] fix: axis needs to set opacity if not loading --- packages/core/src/components/axes/axis.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 99bea29d1b..34edc044d2 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -487,15 +487,13 @@ export class Axis extends Component { // because the Skeleton component draws them if (isDataLoading) { container.attr("opacity", 0); + } else { + container.attr("opacity", 1); } - axisRef - .selectAll("g.tick") - .attr("aria-label", d => d); + axisRef.selectAll("g.tick").attr("aria-label", (d) => d); - invisibleAxisRef - .selectAll("g.tick") - .attr("aria-label", d => d); + invisibleAxisRef.selectAll("g.tick").attr("aria-label", (d) => d); // truncate the label if it's too long // only applies to discrete type From e37ebf709703c5f358f7c643e3262850113914fc Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 15:15:46 +0800 Subject: [PATCH 212/510] fix: format zoom bar handle tooltip --- packages/core/src/components/axes/zoom-bar.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6d134f0534..0c742f6585 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -3,6 +3,10 @@ import { Component } from "../component"; import { Tools } from "../../tools"; import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; +import { + computeTimeIntervalName, + formatTick +} from "../../services/time-series"; // D3 Imports import { extent } from "d3-array"; @@ -269,21 +273,27 @@ export class ZoomBar extends Component { } } - updateBrushHandleTooltip(svg, domain) { + updateBrushHandleTooltip(svg, domain, timeScaleOptions) { + const timeInterval = computeTimeIntervalName(domain); + // remove old handle tooltip svg.select("title").remove(); // add new handle tooltip svg.append("title").text((d) => { if (d.type === "w") { - return domain[0]; + return formatTick(domain[0], 0, timeInterval, timeScaleOptions); } else if (d.type === "e") { - return domain[1]; + return formatTick(domain[1], 0, timeInterval, timeScaleOptions); } }); } updateBrushHandle(svg, selection, domain) { const self = this; + const timeScaleOptions = Tools.getProperty( + this.model.getOptions(), + "timeScale" + ); const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -317,7 +327,7 @@ export class ZoomBar extends Component { .attr("height", handleHeight) .attr("cursor", "pointer") .style("display", null) // always display - .call(this.updateBrushHandleTooltip, domain); + .call(this.updateBrushHandleTooltip, domain, timeScaleOptions); // handle-bar svg.select(this.brushSelector) @@ -344,7 +354,7 @@ export class ZoomBar extends Component { .attr("width", handleBarWidth) .attr("height", handleBarHeight) .attr("cursor", "pointer") - .call(this.updateBrushHandleTooltip, domain); + .call(this.updateBrushHandleTooltip, domain, timeScaleOptions); this.updateClipPath( svg, From 38d53e908b65186ff93d64357e40b40298b75153 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Wed, 22 Jul 2020 14:01:10 +0800 Subject: [PATCH 213/510] feat: add toolbar in storybook --- packages/core/demo/data/zoom-bar.ts | 4 ++-- packages/core/src/axis-chart.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 98249d52da..c9e7c1e193 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -28,10 +28,10 @@ const definedZoomBarData = [ const addZoomBarToOptions = (options, includeDefinedZoomBarData = false) => { if (includeDefinedZoomBarData) { options["title"] = options["title"] + " - Defined zoom bar enabled"; - options["zoomBar"] = { enabled: true, data: definedZoomBarData }; + options["zoomBar"] = { enabled: true, data: definedZoomBarData, zoomRatio: 0.4, showToolBar: true }; } else { options["title"] = options["title"] + " - Zoom bar enabled"; - options["zoomBar"] = { enabled: true }; + options["zoomBar"] = { enabled: true, zoomRatio: 0.4, showToolBar: true }; } return options; }; diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 9ae572e767..7c27e2bc6a 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -189,7 +189,7 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } if (zoomBarEnabled) { - if (this.model.getOptions().zoomBar.showToolBar === true) { + if (this.model.getOptions().zoomBar.showToolBar) { topLevelLayoutComponents.push(toolBarComponent); } topLevelLayoutComponents.push(zoomBarComponent); From e6bd353631ee4ef80ee6339b027af44ae9b0ffce Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Wed, 22 Jul 2020 15:29:04 +0800 Subject: [PATCH 214/510] feat: add reset option --- packages/core/src/components/axes/tool-bar.ts | 67 +++-- .../core/src/styles/components/_tool-bar.scss | 245 +----------------- 2 files changed, 33 insertions(+), 279 deletions(-) diff --git a/packages/core/src/components/axes/tool-bar.ts b/packages/core/src/components/axes/tool-bar.ts index 743fbafa9c..a27a5835cc 100644 --- a/packages/core/src/components/axes/tool-bar.ts +++ b/packages/core/src/components/axes/tool-bar.ts @@ -77,7 +77,6 @@ export class ToolBar extends Component { // listen to show-tooltip Custom Events to render the tooltip this.services.events.addEventListener(Events.Toolbar.SHOW, () => { this.overflowMenuOptions.classed("hidden", false); - console.log("!!! , this.overflowMenuOptions: ", this.overflowMenuOptions.classed("hidden")); const defaultHTML = this.getOverflowMenuHTML(); this.overflowMenuOptions.html(defaultHTML); }); @@ -114,7 +113,7 @@ export class ToolBar extends Component { if (axesMargins && axesMargins.left) { axesLeftMargin = axesMargins.left; } - + this.overflowMenuStart = width - 20; this.zoomOutStart = this.overflowMenuStart - 30; this.zoomInStart = this.zoomOutStart - 30; @@ -213,15 +212,22 @@ export class ToolBar extends Component { const overflowMenu = overflowMenuGroup.select("svg#toolbar-overflow-menu-icon"); overflowMenu.on("click", function() { - if(self.overflowMenuOptions.classed("hidden")) { + if (self.overflowMenuOptions.classed("hidden")) { self.services.events.dispatchEvent(Events.Toolbar.SHOW); + document.getElementById("reset-Btn").addEventListener('click', function () { + const newDomain = self.model.getDefaultZoomBarDomain(); + self.model.set( + { zoomDomain: newDomain, selectionRange: [axesLeftMargin, width] }, + { animate: false } + ); + self.services.events.dispatchEvent(Events.Toolbar.HIDE); + }); } else { // Hide toolbar self.services.events.dispatchEvent(Events.Toolbar.HIDE); } - - }); + }); } getZoomInIcon() { @@ -231,7 +237,7 @@ export class ToolBar extends Component { Zoom in - + @@ -245,7 +251,7 @@ export class ToolBar extends Component { Zoom out - + @@ -272,48 +278,39 @@ export class ToolBar extends Component { ); let defaultHTML; - + const options = this.getMenuOptions(); - + defaultHTML = - `
    ` + + `
    + `; - - defaultHTML = ` - ` + ` + ) + .join("") + + `
`; return defaultHTML; } - + getMenuOptions() { - return ["Reset"]; + return ["Reset zoom"]; } - destroy() { this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { this.render(); diff --git a/packages/core/src/styles/components/_tool-bar.scss b/packages/core/src/styles/components/_tool-bar.scss index 4730fcc654..fa32ac8221 100644 --- a/packages/core/src/styles/components/_tool-bar.scss +++ b/packages/core/src/styles/components/_tool-bar.scss @@ -41,247 +41,4 @@ g.#{$prefix}--#{$charts-prefix}--tool-bar { } -@include overflow-menu; - -/* -.bx--overflow-menu__trigger { - display: inline-block; - background: none; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - border: 0; - padding: 0; - cursor: pointer; - width: 100%; - } - - .bx--overflow-menu__trigger::-moz-focus-inner { - border: 0; - } - - .bx--overflow-menu, - .bx--overflow-menu__trigger { - outline: 2px solid transparent; - outline-offset: -2px; - position: relative; - width: 2rem; - height: 2rem; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - cursor: pointer; - -webkit-transition: outline 110ms cubic-bezier(0, 0, 0.38, 0.9), background-color 110ms cubic-bezier(0, 0, 0.38, 0.9); - transition: outline 110ms cubic-bezier(0, 0, 0.38, 0.9), background-color 110ms cubic-bezier(0, 0, 0.38, 0.9); - } - - .bx--overflow-menu:focus, - .bx--overflow-menu__trigger:focus { - outline: 2px solid #0062ff; - outline-offset: -2px; - } - - .bx--overflow-menu:hover, - .bx--overflow-menu__trigger:hover { - background-color: #e5e5e5; - } - - .bx--overflow-menu__trigger.bx--tooltip--a11y.bx--tooltip__trigger:focus { - outline: 2px solid #0062ff; - outline-offset: -2px; - } - - .bx--overflow-menu__trigger.bx--tooltip--a11y.bx--tooltip__trigger:focus svg { - outline: none; - } - - .bx--overflow-menu.bx--overflow-menu--open, - .bx--overflow-menu.bx--overflow-menu--open - .bx--overflow-menu__trigger { - background-color: $interactive-02; - -webkit-transition: none; - transition: none; - -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.3); - box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.3); - } - - .bx--overflow-menu-options { - -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.3); - box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.3); - display: none; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-align: start; - -ms-flex-align: start; - align-items: flex-start; - position: absolute; - z-index: 10000; - background-color: $interactive-02; - width: 10rem; - list-style: none; - top: 32px; - left: 0; - } - - .bx--overflow-menu-options::after { - content: ''; - position: absolute; - display: block; - background-color: $interactive-02; - -webkit-transition: background-color 110ms cubic-bezier(0, 0, 0.38, 0.9); - transition: background-color 110ms cubic-bezier(0, 0, 0.38, 0.9); - } - - .bx--overflow-menu-options[data-floating-menu-direction='bottom']::after { - top: -0.1875rem; - left: 0; - width: 2rem; - height: 0.1875rem; - } - - .bx--overflow-menu-options[data-floating-menu-direction='left']::after { - right: -0.375rem; - top: 0; - height: 2rem; - width: 0.375rem; - } - - - .bx--overflow-menu--flip.bx--overflow-menu-options[data-floating-menu-direction='bottom']::after { - left: auto; - right: 0; - } - - .bx--overflow-menu-options--open { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - } - - .bx--overflow-menu-options__option { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - background-color: transparent; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - width: 100%; - height: 2.5rem; - padding: 0; - -webkit-transition: background-color 110ms cubic-bezier(0, 0, 0.38, 0.9); - transition: background-color 110ms cubic-bezier(0, 0, 0.38, 0.9); - } - - a.bx--overflow-menu-options__btn::before { - content: ''; - height: 100%; - vertical-align: middle; - display: inline-block; - } - - .bx--overflow-menu-options__btn { - font-size: 0.875rem; - font-weight: 400; - line-height: 1.125rem; - letter-spacing: 0.16px; - outline: 2px solid transparent; - outline-offset: -2px; - font-weight: 400; - width: 100%; - height: 100%; - border: none; - display: -webkit-inline-box; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background-color: transparent; - text-align: left; - padding: 0 1rem; - cursor: pointer; - color: $text-02; - max-width: 11.25rem; - -webkit-transition: outline 110ms cubic-bezier(0, 0, 0.38, 0.9), background-color 110ms cubic-bezier(0, 0, 0.38, 0.9), color 110ms cubic-bezier(0, 0, 0.38, 0.9); - transition: outline 110ms cubic-bezier(0, 0, 0.38, 0.9), background-color 110ms cubic-bezier(0, 0, 0.38, 0.9), color 110ms cubic-bezier(0, 0, 0.38, 0.9); - } - - .bx--overflow-menu-options__btn:hover { - color: $text-04; - } - - .bx--overflow-menu-options__btn:focus { - outline: 2px solid #0062ff; - outline-offset: -2px; - } - - .bx--overflow-menu-options__btn::-moz-focus-inner { - border: none; - } - - .bx--overflow-menu-options__btn svg { - fill: $text-02; - } - - .bx--overflow-menu-options__btn:hover svg { - fill: #171717; - } - - .bx--overflow-menu-options__option-content { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .bx--overflow-menu-options__option:hover { - background-color: #e5e5e5; - } - - .bx--overflow-menu-options__option--danger { - border-top: 1px solid #dcdcdc; - } - - .bx--overflow-menu-options__option--danger - .bx--overflow-menu-options__btn:hover, - .bx--overflow-menu-options__option--danger - .bx--overflow-menu-options__btn:focus { - color: #ffffff; - background-color: #da1e28; - } - - .bx--overflow-menu-options__option--danger - .bx--overflow-menu-options__btn:hover svg, - .bx--overflow-menu-options__option--danger - .bx--overflow-menu-options__btn:focus svg { - fill: #ffffff; - } - - .bx--overflow-menu-options__option--disabled:hover { - background-color: #f3f3f3; - cursor: not-allowed; - } - - .bx--overflow-menu-options__option--disabled - .bx--overflow-menu-options__btn { - color: $interactive-02; - pointer-events: none; - } - - .bx--overflow-menu-options__option--disabled - .bx--overflow-menu-options__btn:hover, .bx--overflow-menu-options__option--disabled - .bx--overflow-menu-options__btn:active, .bx--overflow-menu-options__option--disabled - .bx--overflow-menu-options__btn:focus { - outline: 2px solid transparent; - outline-offset: -2px; - background-color: #f3f3f3; - } -*/ \ No newline at end of file +@include overflow-menu; \ No newline at end of file From 729ae914f9470f8bc1ce83dbcef941288e1b8107 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Wed, 22 Jul 2020 17:44:33 +0800 Subject: [PATCH 215/510] fix: handle click overflow menu --- packages/core/src/components/axes/tool-bar.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/core/src/components/axes/tool-bar.ts b/packages/core/src/components/axes/tool-bar.ts index a27a5835cc..97ab6fc288 100644 --- a/packages/core/src/components/axes/tool-bar.ts +++ b/packages/core/src/components/axes/tool-bar.ts @@ -6,10 +6,7 @@ import { DOMUtils } from "../../services"; import { ChartModel } from "../../model"; // D3 Imports -import { extent } from "d3-array"; -import { line } from "d3-shape"; import { event, select, selectAll } from "d3-selection"; -import { scaleTime } from "d3-scale"; // Carbon position service import Position, { PLACEMENTS } from "@carbon/utils-position"; @@ -19,10 +16,6 @@ import settings from "carbon-components/es/globals/js/settings"; export class ToolBar extends Component { type = "tool-bar"; - // The minimum selection x range to trigger handler update - // Smaller number may introduce a handler flash during initialization - // Bigger number may not trigger handler update while selection area on chart is very small - MIN_SELECTION_DIFF = 9e-10; height = 20; @@ -34,8 +27,6 @@ export class ToolBar extends Component { overflowMenuStart = 0; - previousZoomDomain; - zoomRatio; overflowMenuOptions; @@ -76,13 +67,14 @@ export class ToolBar extends Component { // listen to show-tooltip Custom Events to render the tooltip this.services.events.addEventListener(Events.Toolbar.SHOW, () => { - this.overflowMenuOptions.classed("hidden", false); const defaultHTML = this.getOverflowMenuHTML(); + this.overflowMenuOptions.classed("hidden", false); this.overflowMenuOptions.html(defaultHTML); }); // listen to hide-tooltip Custom Events to hide the tooltip this.services.events.addEventListener(Events.Toolbar.HIDE, () => { + console.log("!!! catch hide event") this.overflowMenuOptions.classed("hidden", true); this.overflowMenuOptions.html(null); }); @@ -212,22 +204,30 @@ export class ToolBar extends Component { const overflowMenu = overflowMenuGroup.select("svg#toolbar-overflow-menu-icon"); overflowMenu.on("click", function() { - if (self.overflowMenuOptions.classed("hidden")) { + if (self.overflowMenuOptions.selectAll("ul.bx--overflow-menu-options--open").size() > 0) { + // Hide toolbar + self.services.events.dispatchEvent(Events.Toolbar.HIDE); + } else { self.services.events.dispatchEvent(Events.Toolbar.SHOW); - document.getElementById("reset-Btn").addEventListener('click', function () { + document.getElementById("reset-Btn").addEventListener("click", function () { const newDomain = self.model.getDefaultZoomBarDomain(); self.model.set( { zoomDomain: newDomain, selectionRange: [axesLeftMargin, width] }, { animate: false } ); self.services.events.dispatchEvent(Events.Toolbar.HIDE); - }); - } else { - // Hide toolbar - self.services.events.dispatchEvent(Events.Toolbar.HIDE); + }, true); } + event.stopImmediatePropagation(); + }); + document.body.addEventListener("click", function(e) { + if (self.overflowMenuOptions.selectAll("ul.bx--overflow-menu-options--open").size() > 0) { + console.log("!!! body click got triggered"); + self.services.events.dispatchEvent(Events.Toolbar.HIDE); + } }); + } getZoomInIcon() { From 3434c955ac9e38ce1440839fd99810da4fd8e93f Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Wed, 22 Jul 2020 18:04:35 +0800 Subject: [PATCH 216/510] feat: shift clicking on chart --- .../core/src/components/axes/chart-brush.ts | 100 +++++++++++++----- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e872ec1cf5..f116973030 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -5,7 +5,7 @@ import { DOMUtils } from "../../services"; // D3 Imports import { brushX } from "d3-brush"; -import { event } from "d3-selection"; +import { event, mouse } from "d3-selection"; import { scaleTime } from "d3-scale"; // This class is used for handle brush events in chart @@ -18,6 +18,13 @@ export class ChartBrush extends Component { frontSelectionSelector = "rect.frontSelection"; // needs to match the class name in _chart-brush.scss + zoomRatio; + + init() { + // get zoom in ratio + this.zoomRatio = this.model.getOptions().zoomBar.zoomRatio; + } + render(animate = true) { const svg = this.parent; // use this area to display selection above all graphs @@ -38,8 +45,14 @@ export class ChartBrush extends Component { const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true }); + const zoomRatio = this.zoomRatio; const { cartesianScales } = this.services; + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } const mainXScaleType = cartesianScales.getMainXScaleType(); const mainXScale = cartesianScales.getMainXScale(); const [xScaleStart, xScaleEnd] = mainXScale.range(); @@ -49,6 +62,17 @@ export class ChartBrush extends Component { this.frontSelectionSelector ); + const setDomain = (newDomain) => { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + }; + + const getDefaultZoomBarDomain = () => { + return this.model.getDefaultZoomBarDomain(); + }; + if (mainXScale && mainXScaleType === ScaleTypes.TIME) { // get current zoomDomain let zoomDomain = this.model.get("zoomDomain"); @@ -127,37 +151,39 @@ export class ChartBrush extends Component { }); } }; + + const handleZoomDomain = (startPoint, endPoint) => { + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([axesLeftMargin, 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 = getDefaultZoomBarDomain(); + } + + // only if zoomDomain needs update + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { + setDomain(newDomain); + } + }; + const brushed = () => { const selection = event.selection; if (selection !== null) { - // create xScale based on current zoomDomain - const xScale = scaleTime() - .range([0, width]) - .domain(zoomDomain); - - let newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // 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.model.getDefaultZoomBarDomain(); - } - - // only if zoomDomain needs update - if ( - zoomDomain[0].valueOf() !== newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== newDomain[1].valueOf() - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } + handleZoomDomain(selection[0], selection[1]); // clear brush selection brushArea.call(brush.move, null); @@ -176,6 +202,24 @@ export class ChartBrush extends Component { .on("end.brushed", brushed); brushArea.call(brush); + + backdrop.on("click", function() { + if (event.shiftKey) { + const coords = mouse(this); + const currentClickLocation = coords[0] - axesLeftMargin; + + let leftPoint = currentClickLocation - width * zoomRatio / 2; + if (leftPoint < 0) { + leftPoint = 0; + } + let rightPoint = currentClickLocation + width * zoomRatio / 2; + if (rightPoint > width) { + rightPoint = width; + } + + handleZoomDomain(leftPoint, rightPoint); + } + }); } } } From 2eda6adaf74529fda89029ff4c219dd97ccf4586 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 27 May 2020 15:44:15 +0800 Subject: [PATCH 217/510] feat: initial zoom bar implementation from PR 511 --- packages/core/src/axis-chart.ts | 16 +- packages/core/src/chart.ts | 9 +- packages/core/src/components/axes/axis.ts | 9 +- packages/core/src/components/axes/zoom-bar.ts | 216 ++++++++++++++++++ packages/core/src/components/index.ts | 1 + packages/core/src/model.ts | 10 +- packages/core/src/services/axis-zoom.ts | 60 +++++ packages/core/src/services/index.ts | 1 + .../core/src/services/scales-cartesian.ts | 6 + .../core/src/styles/components/_zoom-bar.scss | 11 + .../core/src/styles/components/index.scss | 1 + packages/core/src/styles/graphs/index.scss | 4 + 12 files changed, 333 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/axes/zoom-bar.ts create mode 100644 packages/core/src/services/axis-zoom.ts create mode 100644 packages/core/src/styles/components/_zoom-bar.scss diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index b9751b3a31..e4d105905f 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -12,7 +12,8 @@ import { Legend, Title, AxisChartsTooltip, - Spacer + Spacer, + ZoomBar } from "./components/index"; import { Tools } from "./tools"; @@ -123,6 +124,17 @@ export class AxisChart extends Chart { } }; + const zoomBarComponent = { + id: "zoom-bar", + components: [ + new ZoomBar(this.model, this.services) + ], + growth: { + x: LayoutGrowth.PREFERRED, + y: LayoutGrowth.FIXED + } + }; + // Add chart title if it exists const topLevelLayoutComponents = []; if (this.model.getOptions().title) { @@ -139,6 +151,8 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } + + topLevelLayoutComponents.push(zoomBarComponent); topLevelLayoutComponents.push(fullFrameComponent); return [ diff --git a/packages/core/src/chart.ts b/packages/core/src/chart.ts index ea65e9f3c3..4e86305488 100644 --- a/packages/core/src/chart.ts +++ b/packages/core/src/chart.ts @@ -37,7 +37,7 @@ export class Chart { // Contains the code that uses properties that are overridable by the super-class init(holder: Element, chartConfigs: ChartConfig) { // Store the holder in the model - this.model.set({ holder }, true); + this.model.set({ holder }, { skipUpdate: true }); // Initialize all services Object.keys(this.services).forEach((serviceName) => { @@ -49,8 +49,9 @@ export class Chart { }); // Call update() when model has been updated - this.services.events.addEventListener(ChartEvents.Model.UPDATE, () => { - this.update(true); + this.services.events.addEventListener(ChartEvents.Model.UPDATE, (e) => { + const animate = !!Tools.getProperty(e, "detail", "animate"); + this.update(animate); }); // Set model data & options @@ -110,7 +111,7 @@ export class Chart { // Remove the chart holder this.services.domUtils.getHolder().remove(); - this.model.set({ destroyed: true }, true); + this.model.set({ destroyed: true }, { skipUpdate: true }); } protected getChartComponents(graphFrameComponents: any[]) { diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 4feebae037..5eab971526 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -114,6 +114,13 @@ export class Axis extends Component { scale.range([startPosition, endPosition]); } + // if zoomDomain is available, update scale domain to Date array. + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { + scale.domain(zoomDomain.map(d => new Date(d))); + } + + // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -426,7 +433,7 @@ export class Axis extends Component { const ticksNumber = isTimeScaleType ? axis.tickValues().length : scale.ticks().length; - const estimatedTickSize = width / ticksNumber / 2; + const estimatedTickSize = width / ticksNumber / 1.6; rotateTicks = estimatedTickSize < minTickSize; } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts new file mode 100644 index 0000000000..dee5a24f55 --- /dev/null +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -0,0 +1,216 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { area, line } from "d3-shape"; +import { extent } from "d3-array"; +import { drag } from "d3-drag"; +import { event, select, selectAll } from "d3-selection"; + +export class ZoomBar extends Component { + type = "zoom-bar"; + + ogXScale: any; + + dragged = Tools.debounce((element, d, e) => { + element = select(element); + const startingHandle = element.attr("class").indexOf("start") !== -1; + + let domain; + if (startingHandle) { + domain = [ + this.ogXScale.invert(e.x), + this.ogXScale.domain()[1] + // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) + ]; + } else { + domain = [ + this.ogXScale.domain()[0], + this.ogXScale.invert(e.x) + // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) + ]; + } + this.model.set({ zoomDomain: domain }, { animate: false }); + }, 2.5); + + render(animate = true) { + const svg = this.getContainerSVG(); + + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); + const mainXScaleType = cartesianScales.getScaleTypeByPosition(mainXAxisPosition); + const mainYScaleType = cartesianScales.getScaleTypeByPosition(mainYAxisPosition); + + const height = 32; + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") + // .attr("transform", "translateX(10)") + .attr("width", "100%") + .attr("height", height) + .attr("opacity", 1); + + const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") + .attr("x", 0) + .attr("y", 32) + .attr("width", "100%") + .attr("height", 20) + .attr("opacity", 1) + .attr("fill", "none"); + + const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") + .attr("x", 0) + .attr("y", 0) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "white") + .attr("stroke", "#e8e8e8"); + + if (mainXScale) { + const displayData = this.model.getDisplayData(); + + if (mainXScaleType === ScaleTypes.TIME) { + // Get all date values provided in data + // TODO - Could be re-used through the model + let allDates = []; + displayData.datasets.forEach(dataset => { + allDates = allDates.concat(dataset.data.map(datum => Number(datum.date))); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + + // Go through all date values + // And get corresponding data from each dataset + const stackDataArray = allDates.map((date) => { + let count = 0; + let correspondingSum = 0; + const correspondingData = {}; + + displayData.datasets.forEach(dataset => { + const correspondingDatum = dataset.data.find(datum => Number(datum.date) === Number(date)); + if (correspondingDatum) { + ++count; + correspondingSum += correspondingDatum.value; + } + }); + correspondingData["label"] = date; + correspondingData["value"] = correspondingSum; + + return correspondingData; + }); + + const xScale = mainXScale.copy(); + if (!this.ogXScale) { + this.ogXScale = xScale; + } + + const yScale = mainYScale.copy(); + + const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); + + xScale.range([0, +width]) + .domain(extent(stackDataArray, (d: any) => d.label)); + + yScale.range([0, height - 6]) + .domain(extent(stackDataArray, (d: any) => d.value)); + + const zoomDomain = this.model.get("zoomDomain"); + + // D3 line generator function + const lineGenerator = line() + .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .y((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)) + .curve(this.services.curves.getD3Curve()); + // .defined((d: any, i) => { + // if (zoomDomain) { + // const dTimestamp = +d.label; + + // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; + // } + + // return true; + // }); + + const lineGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-line") + .attr("stroke", "#8e8e8e") + .attr("stroke-width", 3) + .attr("fill", "none") + .datum(stackDataArray) + .transition(this.services.transitions.getTransition("zoom-pan-line-update", animate)) + .attr("d", lineGenerator); + + const areaGenerator = area() + .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .y0(height) + .y1((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)); + + const areaGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-area") + .attr("fill", "#e0e0e0") + .datum(stackDataArray) + .transition(this.services.transitions.getTransition("zoom-pan-area-update", animate)) + .attr("d", areaGenerator); + + const startHandlePosition = zoomDomain ? xScale(+zoomDomain[0]) : 0; + // Handle #1 + const startHandle = DOMUtils.appendOrSelect(container, "rect.zoom-handle.start") + .attr("x", startHandlePosition) + .attr("width", 5) + .attr("height", "100%") + .attr("fill", "#525252"); + + DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.start") + .attr("x", startHandlePosition + 2) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + const endHandlePosition = zoomDomain ? xScale(+zoomDomain[1]) : xScale.range()[1]; + // console.log("endHandlePosition", endHandlePosition) + + // Handle #2 + const handle2 = DOMUtils.appendOrSelect(container, "rect.zoom-handle.end") + .attr("x", endHandlePosition - 5) + .attr("width", 5) + .attr("height", "100%") + .attr("fill", "#525252"); + + DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.end") + .attr("x", endHandlePosition - 5 + 2) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + + const outboundRangeRight = DOMUtils.appendOrSelect(container, "rect.outbound-range.right") + .attr("x", endHandlePosition) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "#fff") + .attr("fill-opacity", 0.85); + + const self = this; + // handle2.on("click", this.zoomIn.bind(this)); + selectAll("rect.zoom-handle").call( + drag() + .on("start", function() { + select(this).classed("dragging", true); + }) + .on("drag", function(d) { + self.dragged(this, d, event); + }) + .on("end", function() { + select(this).classed("dragging", false); + }) + ); + } + } + } + + zoomIn() { + const mainXScale = this.services.cartesianScales.getMainXScale(); + console.log("zoom in", mainXScale.domain()); + } +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 4142972992..60daf43009 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -34,3 +34,4 @@ export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; +export * from "./axes/zoom-bar"; diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 67f15f296a..999d689680 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -267,11 +267,11 @@ export class ChartModel { return this.state.options; } - set(newState: any, skipUpdate = false) { + set(newState: any, configs?: any) { this.state = Object.assign({}, this.state, newState); - if (!skipUpdate) { - this.update(); + if (!configs || !configs.skipUpdate) { + this.update(configs ? configs.animate : true); } } @@ -298,7 +298,7 @@ export class ChartModel { * Updates miscellanous information within the model * such as the color scales, or the legend data labels */ - update() { + update(animate = true) { if (!this.getDisplayData()) { return; } @@ -306,7 +306,7 @@ export class ChartModel { this.updateAllDataGroups(); this.setColorScale(); - this.services.events.dispatchEvent(Events.Model.UPDATE); + this.services.events.dispatchEvent(Events.Model.UPDATE, { animate }); } setUpdateCallback(cb: Function) { diff --git a/packages/core/src/services/axis-zoom.ts b/packages/core/src/services/axis-zoom.ts new file mode 100644 index 0000000000..8dbb2b3d80 --- /dev/null +++ b/packages/core/src/services/axis-zoom.ts @@ -0,0 +1,60 @@ +// Internal Imports +import { Service } from "./service"; +import { Tools } from "../tools"; + +// D3 Imports +import { brushX } from "d3-brush"; +import { event } from "d3-selection"; + +export class AxisZoom extends Service { + brush: any; + + brushed(e) { + if (event) { + const selectedRange = event.selection; + + const mainXAxis = this.services.axes.getMainXAxis(); + const mainXAxisRangeStart = mainXAxis.scale.range()[0]; + + const newDomain = selectedRange.map(d => d + mainXAxisRangeStart) + .map(mainXAxis.scale.invert, mainXAxis.scale); + + this.model.set({ + zoomDomain: newDomain.map(d => new Date(+d)) + }); + } + } + + // We need a custom debounce function here + // because of the async nature of d3.event + debounceWithD3Event(func, wait) { + let timeout; + return function() { + const e = Object.assign({}, event); + const context = this; + const later = function() { + timeout = null; + func.apply(context, [e]); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } + + getZoomInstance() { + const mainXAxis = this.services.axes.getMainXAxis(); + const mainYAxis = this.services.axes.getMainYAxis(); + const xMaxRange = mainXAxis.scale.range()[1]; + const yMaxRange = mainYAxis.scale.range()[0]; + + this.brush = brushX() + .extent([ + [0, 0], + [xMaxRange, yMaxRange] + ]) + .on("end", this.brushed.bind(this)); + + + return this.brush; + } +} diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index d999a07207..4e9fcc0e17 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -4,5 +4,6 @@ export * from "./essentials/events"; export * from "./essentials/transitions"; // MISC +export * from "./axis-zoom"; export * from "./scales-cartesian"; export * from "./curves"; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 45a2bd142b..e75d6f14e7 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -382,6 +382,12 @@ export class CartesianScales extends Service { return map(displayData, (d) => d[mapsTo]).keys(); } + // If scale is a TIME scale and zoomDomain is available, return Date array as the domain + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && axisOptions && scaleType === ScaleTypes.TIME) { + return zoomDomain.map(d => new Date(d)); + } + // Get the extent of the domain let domain; let allDataValues; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss new file mode 100644 index 0000000000..02fcf7a55f --- /dev/null +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -0,0 +1,11 @@ +g.#{$prefix}--#{$charts-prefix}--zoom-bar { + rect.zoom-handle.dragging, + rect.zoom-handle:hover { + fill: black; + cursor: col-resize; + } + + rect.zoom-handle-bar { + pointer-events: none; + } +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index 3fff851a8b..0b96507fbe 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -10,3 +10,4 @@ @import "./meter-title"; @import "./tooltip"; @import "./threshold"; +@import "./zoom-bar"; diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index 5530eba1a0..bebafaffc3 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -6,3 +6,7 @@ @import "./scatter-stacked"; @import "./radar"; @import "./gauge"; + +svg.#{$prefix}--#{$charts-prefix}--chart-svg svg.graph-frame { + overflow-x: hidden; +} From 3858815c80dc8caebe12eb79994a32e43e1ba42e Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 2 Jun 2020 16:33:50 +0800 Subject: [PATCH 218/510] fix: update ZoomBar to match new data format - fix trailing-comma - update scales-cartesian.getValueFromScale() - update line time-series 15 seconds demo data --- packages/core/demo/data/time-series-axis.ts | 12 +- packages/core/src/components/axes/zoom-bar.ts | 165 +++++++++++++----- .../core/src/services/scales-cartesian.ts | 165 ++++++++++-------- packages/core/tslint.json | 2 +- 4 files changed, 216 insertions(+), 128 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e3a3502c92..c1c7183571 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -8,12 +8,12 @@ export const lineTimeSeriesData15seconds = { label: "Dataset 1", data: [ { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 30), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 45), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 0), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 15), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 30), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 10 } + { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, + { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, + { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, + { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, + { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 }, ] } ] diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index dee5a24f55..f6ac815a7b 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -23,13 +23,13 @@ export class ZoomBar extends Component { if (startingHandle) { domain = [ this.ogXScale.invert(e.x), - this.ogXScale.domain()[1] + this.ogXScale.domain()[1], // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } else { domain = [ this.ogXScale.domain()[0], - this.ogXScale.invert(e.x) + this.ogXScale.invert(e.x), // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } @@ -44,8 +44,12 @@ export class ZoomBar extends Component { const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition(mainXAxisPosition); - const mainYScaleType = cartesianScales.getScaleTypeByPosition(mainYAxisPosition); + const mainXScaleType = cartesianScales.getScaleTypeByPosition( + mainXAxisPosition + ); + const mainYScaleType = cartesianScales.getScaleTypeByPosition( + mainYAxisPosition + ); const height = 32; const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") @@ -77,8 +81,8 @@ export class ZoomBar extends Component { // Get all date values provided in data // TODO - Could be re-used through the model let allDates = []; - displayData.datasets.forEach(dataset => { - allDates = allDates.concat(dataset.data.map(datum => Number(datum.date))); + displayData.forEach((data) => { + allDates = allDates.concat(Number(data.date)); }); allDates = Tools.removeArrayDuplicates(allDates).sort(); @@ -89,14 +93,13 @@ export class ZoomBar extends Component { let correspondingSum = 0; const correspondingData = {}; - displayData.datasets.forEach(dataset => { - const correspondingDatum = dataset.data.find(datum => Number(datum.date) === Number(date)); - if (correspondingDatum) { + displayData.forEach((data) => { + if (Number(data.date) === Number(date)) { ++count; - correspondingSum += correspondingDatum.value; + correspondingSum += data.value; } }); - correspondingData["label"] = date; + correspondingData["date"] = date; correspondingData["value"] = correspondingSum; return correspondingData; @@ -109,53 +112,113 @@ export class ZoomBar extends Component { const yScale = mainYScale.copy(); - const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true, + }); - xScale.range([0, +width]) - .domain(extent(stackDataArray, (d: any) => d.label)); + xScale + .range([0, width]) + .domain(extent(stackDataArray, (d: any) => d.date)); - yScale.range([0, height - 6]) + yScale + .range([0, height - 6]) .domain(extent(stackDataArray, (d: any) => d.value)); const zoomDomain = this.model.get("zoomDomain"); // D3 line generator function const lineGenerator = line() - .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) - .y((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)) + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) + .y( + (d, i) => + height - + cartesianScales.getValueFromScale( + yScale, + mainYScaleType, + mainYAxisPosition, + d, + i + ) + ) .curve(this.services.curves.getD3Curve()); - // .defined((d: any, i) => { - // if (zoomDomain) { - // const dTimestamp = +d.label; - - // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; - // } - - // return true; - // }); - - const lineGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-line") + // .defined((d: any, i) => { + // if (zoomDomain) { + // const dTimestamp = +d.label; + + // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; + // } + + // return true; + // }); + const lineGraph = DOMUtils.appendOrSelect( + container, + "path.zoom-graph-line" + ) .attr("stroke", "#8e8e8e") .attr("stroke-width", 3) .attr("fill", "none") .datum(stackDataArray) - .transition(this.services.transitions.getTransition("zoom-pan-line-update", animate)) + .transition( + this.services.transitions.getTransition( + "zoom-pan-line-update", + animate + ) + ) .attr("d", lineGenerator); const areaGenerator = area() - .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) .y0(height) - .y1((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)); - - const areaGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-area") + .y1( + (d, i) => + height - + cartesianScales.getValueFromScale( + yScale, + mainYScaleType, + mainYAxisPosition, + d, + i + ) + ); + + const areaGraph = DOMUtils.appendOrSelect( + container, + "path.zoom-graph-area" + ) .attr("fill", "#e0e0e0") .datum(stackDataArray) - .transition(this.services.transitions.getTransition("zoom-pan-area-update", animate)) + .transition( + this.services.transitions.getTransition( + "zoom-pan-area-update", + animate + ) + ) .attr("d", areaGenerator); - const startHandlePosition = zoomDomain ? xScale(+zoomDomain[0]) : 0; + const startHandlePosition = zoomDomain + ? xScale(+zoomDomain[0]) + : 0; // Handle #1 - const startHandle = DOMUtils.appendOrSelect(container, "rect.zoom-handle.start") + const startHandle = DOMUtils.appendOrSelect( + container, + "rect.zoom-handle.start" + ) .attr("x", startHandlePosition) .attr("width", 5) .attr("height", "100%") @@ -167,11 +230,16 @@ export class ZoomBar extends Component { .attr("width", 1) .attr("height", 12) .attr("fill", "#fff"); - const endHandlePosition = zoomDomain ? xScale(+zoomDomain[1]) : xScale.range()[1]; + const endHandlePosition = zoomDomain + ? xScale(+zoomDomain[1]) + : xScale.range()[1]; // console.log("endHandlePosition", endHandlePosition) // Handle #2 - const handle2 = DOMUtils.appendOrSelect(container, "rect.zoom-handle.end") + const endHandle = DOMUtils.appendOrSelect( + container, + "rect.zoom-handle.end" + ) .attr("x", endHandlePosition - 5) .attr("width", 5) .attr("height", "100%") @@ -184,24 +252,27 @@ export class ZoomBar extends Component { .attr("height", 12) .attr("fill", "#fff"); - const outboundRangeRight = DOMUtils.appendOrSelect(container, "rect.outbound-range.right") - .attr("x", endHandlePosition) - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "#fff") - .attr("fill-opacity", 0.85); + const outboundRangeRight = DOMUtils.appendOrSelect( + container, + "rect.outbound-range.right" + ) + .attr("x", endHandlePosition) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "#fff") + .attr("fill-opacity", 0.85); const self = this; // handle2.on("click", this.zoomIn.bind(this)); selectAll("rect.zoom-handle").call( drag() - .on("start", function() { + .on("start", function () { select(this).classed("dragging", true); }) - .on("drag", function(d) { + .on("drag", function (d) { self.dragged(this, d, event); }) - .on("end", function() { + .on("end", function () { select(this).classed("dragging", false); }) ); diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index e75d6f14e7..162b0c833f 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -181,33 +181,50 @@ export class CartesianScales extends Service { return this.scales[this.getMainYAxisPosition()]; } - getValueFromScale(axisPosition: AxisPositions, datum: any, index?: number) { + getValueFromScale(scale: any, scaleType: ScaleTypes, axisPosition: AxisPositions, datum: any, index?: number) { const options = this.model.getOptions(); - const axisOptions = Tools.getProperty(options, "axes", axisPosition); - - const scaleType = this.scaleTypes[axisPosition]; - const scale = this.scales[axisPosition]; - + const axesOptions = Tools.getProperty(options, "axes"); + const axisOptions = axesOptions[axisPosition]; const { mapsTo } = axisOptions; const value = datum[mapsTo] !== undefined ? datum[mapsTo] : datum; - - if (scaleType === ScaleTypes.LABELS) { - return scale(value) + scale.step() / 2; + let scaledValue; + switch (scaleType) { + case ScaleTypes.LABELS: + scaledValue = scale(value) + scale.step() / 2; + break; + case ScaleTypes.TIME: + scaledValue = scale(new Date(value)); + break; + default: + scaledValue = scale(value); } + return scaledValue; + } - if (scaleType === ScaleTypes.TIME) { - return scale(new Date(value)); - } + getValueThroughAxisPosition(axisPosition: AxisPositions, datum: any, index?: number) { + const scaleType = this.scaleTypes[axisPosition]; + const scale = this.scales[axisPosition]; - return scale(value); + return this.getValueFromScale(scale, scaleType, axisPosition, datum, index); } + getDomainValue(d, i) { - return this.getValueFromScale(this.domainAxisPosition, d, i); + return this.getValueThroughAxisPosition(this.domainAxisPosition, d, i); } getRangeValue(d, i) { - return this.getValueFromScale(this.rangeAxisPosition, d, i); + return this.getValueThroughAxisPosition(this.rangeAxisPosition, d, i); + } + + getXValue(d, i) { + const mainXAxisPosition = this.getMainXAxisPosition(); + return this.getValueThroughAxisPosition(mainXAxisPosition, d, i); + } + + getYValue(d, i) { + const mainYAxisPosition = this.getMainYAxisPosition(); + return this.getValueThroughAxisPosition(mainYAxisPosition, d, i); } getDomainIdentifier() { @@ -271,6 +288,65 @@ export class CartesianScales extends Service { } } + getHighestDomainThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const domainAxisPosition = this.getDomainAxisPosition(); + + const { thresholds } = axesOptions[domainAxisPosition]; + + if (!thresholds) { + return null; + } + + const domainScale = this.getDomainScale(); + // Find the highest threshold for the domain + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value, + )[0]; + + const scaleType = this.getScaleTypeByPosition(domainAxisPosition); + if ( + scaleType === ScaleTypes.TIME && + (typeof highestThreshold.value === "string" || + highestThreshold.value.getTime === undefined) + ) { + highestThreshold.value = new Date(highestThreshold.value); + } + + return { + threshold: highestThreshold, + scaleValue: domainScale(highestThreshold.value), + }; + } + + getHighestRangeThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const rangeAxisPosition = this.getRangeAxisPosition(); + + const { thresholds } = axesOptions[rangeAxisPosition]; + + if (!thresholds) { + return null; + } + + const rangeScale = this.getRangeScale(); + // Find the highest threshold for the range + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value, + )[0]; + + return { + threshold: highestThreshold, + scaleValue: rangeScale(highestThreshold.value), + }; + } + protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); @@ -438,65 +514,6 @@ export class CartesianScales extends Service { return scale; } - - getHighestDomainThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const domainAxisPosition = this.getDomainAxisPosition(); - - const { thresholds } = axesOptions[domainAxisPosition]; - - if (!thresholds) { - return null; - } - - const domainScale = this.getDomainScale(); - // Find the highest threshold for the domain - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - const scaleType = this.getScaleTypeByPosition(domainAxisPosition); - if ( - scaleType === ScaleTypes.TIME && - (typeof highestThreshold.value === "string" || - highestThreshold.value.getTime === undefined) - ) { - highestThreshold.value = new Date(highestThreshold.value); - } - - return { - threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value) - }; - } - - getHighestRangeThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const rangeAxisPosition = this.getRangeAxisPosition(); - - const { thresholds } = axesOptions[rangeAxisPosition]; - - if (!thresholds) { - return null; - } - - const rangeScale = this.getRangeScale(); - // Find the highest threshold for the range - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - return { - threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value) - }; - } } function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { diff --git a/packages/core/tslint.json b/packages/core/tslint.json index a5a0970b82..91271d283a 100644 --- a/packages/core/tslint.json +++ b/packages/core/tslint.json @@ -92,7 +92,7 @@ "trailing-comma": [ true, { - "multiline": "never", + "multiline": "always", "singleline": "never" } ] From ab4e1dcbf1cd1e03812278bdd6aaf3c673dff836 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 3 Jun 2020 21:56:58 +0800 Subject: [PATCH 219/510] feat: use d3-brush to implement selection --- packages/core/src/components/axes/zoom-bar.ts | 184 +++++++++--------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index f6ac815a7b..8066d0aa08 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -5,20 +5,30 @@ import { ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports -import { area, line } from "d3-shape"; import { extent } from "d3-array"; +import { brushX } from "d3-brush"; import { drag } from "d3-drag"; -import { event, select, selectAll } from "d3-selection"; +import { area, line } from "d3-shape"; +import { event, select, selectAll, BaseType } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + height = 32; + ogXScale: any; dragged = Tools.debounce((element, d, e) => { + console.log("dragged"); + console.log(this.ogXScale.invert(100)); + console.log(d); + console.log(e); element = select(element); const startingHandle = element.attr("class").indexOf("start") !== -1; - + console.log("startingHandle?:" + startingHandle); + const oldDomain = this.model.get("zoomDomain"); + console.log("old domain"); + console.log(oldDomain); let domain; if (startingHandle) { domain = [ @@ -33,9 +43,13 @@ export class ZoomBar extends Component { // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } + console.log("new domain"); + console.log(domain); this.model.set({ zoomDomain: domain }, { animate: false }); }, 2.5); + + render(animate = true) { const svg = this.getContainerSVG(); @@ -45,19 +59,69 @@ export class ZoomBar extends Component { const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition + mainXAxisPosition, ); const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition + mainYAxisPosition, ); - const height = 32; + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") // .attr("transform", "translateX(10)") .attr("width", "100%") - .attr("height", height) + .attr("height", this.height) .attr("opacity", 1); + const brushHandle = (g, selection) => { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{type: "w"}, {type: "e"}]) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32) + .attr("fill", "#525252"); + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{type: "w"}, {type: "e"}]) + .join("rect") + .attr("class", function(d) { return "handle-bar handle-bar--" + d.type; }) + .attr("x", function(d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + + }; + + const brushed = () => { + const selection = event.selection; + if (selection === null) { + // do nothing + console.log("selection === null"); + } else { + // TODO - pass selection to callback function or update scaleDomain + } + // update brush handle position + select(svg).call(brushHandle, selection); + }; + + const brush = brushX() + .extent([[0, 0], [700, this.height]]) + .on("start brush end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") + .call(brush) + .call(brush.move, mainXScale.range()); // TODO -- mainXScale.range() incorrect + const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) .attr("y", 32) @@ -105,11 +169,13 @@ export class ZoomBar extends Component { return correspondingData; }); + // if (!this.ogXScale) { + // this.ogXScale = cartesianScales.getDomainScale(); + // } const xScale = mainXScale.copy(); if (!this.ogXScale) { this.ogXScale = xScale; } - const yScale = mainYScale.copy(); const { width } = DOMUtils.getSVGElementSize(this.parent, { @@ -121,7 +187,7 @@ export class ZoomBar extends Component { .domain(extent(stackDataArray, (d: any) => d.date)); yScale - .range([0, height - 6]) + .range([0, this.height - 6]) .domain(extent(stackDataArray, (d: any) => d.value)); const zoomDomain = this.model.get("zoomDomain"); @@ -134,19 +200,19 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i - ) + i, + ), ) .y( (d, i) => - height - + this.height - cartesianScales.getValueFromScale( yScale, mainYScaleType, mainYAxisPosition, d, - i - ) + i, + ), ) .curve(this.services.curves.getD3Curve()); // .defined((d: any, i) => { @@ -160,7 +226,7 @@ export class ZoomBar extends Component { // }); const lineGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-line" + "path.zoom-graph-line", ) .attr("stroke", "#8e8e8e") .attr("stroke-width", 3) @@ -169,8 +235,8 @@ export class ZoomBar extends Component { .transition( this.services.transitions.getTransition( "zoom-pan-line-update", - animate - ) + animate, + ), ) .attr("d", lineGenerator); @@ -181,101 +247,36 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i - ) + i, + ), ) - .y0(height) + .y0(this.height) .y1( (d, i) => - height - + this.height - cartesianScales.getValueFromScale( yScale, mainYScaleType, mainYAxisPosition, d, - i - ) + i, + ), ); const areaGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-area" + "path.zoom-graph-area", ) .attr("fill", "#e0e0e0") .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-area-update", - animate - ) + animate, + ), ) .attr("d", areaGenerator); - const startHandlePosition = zoomDomain - ? xScale(+zoomDomain[0]) - : 0; - // Handle #1 - const startHandle = DOMUtils.appendOrSelect( - container, - "rect.zoom-handle.start" - ) - .attr("x", startHandlePosition) - .attr("width", 5) - .attr("height", "100%") - .attr("fill", "#525252"); - - DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.start") - .attr("x", startHandlePosition + 2) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - const endHandlePosition = zoomDomain - ? xScale(+zoomDomain[1]) - : xScale.range()[1]; - // console.log("endHandlePosition", endHandlePosition) - - // Handle #2 - const endHandle = DOMUtils.appendOrSelect( - container, - "rect.zoom-handle.end" - ) - .attr("x", endHandlePosition - 5) - .attr("width", 5) - .attr("height", "100%") - .attr("fill", "#525252"); - - DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.end") - .attr("x", endHandlePosition - 5 + 2) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - - const outboundRangeRight = DOMUtils.appendOrSelect( - container, - "rect.outbound-range.right" - ) - .attr("x", endHandlePosition) - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "#fff") - .attr("fill-opacity", 0.85); - - const self = this; - // handle2.on("click", this.zoomIn.bind(this)); - selectAll("rect.zoom-handle").call( - drag() - .on("start", function () { - select(this).classed("dragging", true); - }) - .on("drag", function (d) { - self.dragged(this, d, event); - }) - .on("end", function () { - select(this).classed("dragging", false); - }) - ); } } } @@ -284,4 +285,5 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } + } From 06d282b60426c4369459e3ac79e7b2b334593b63 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 8 Jun 2020 16:04:58 +0800 Subject: [PATCH 220/510] fix: add left margin to align to chart - using .scss to set fill and stroke color --- packages/core/src/components/axes/zoom-bar.ts | 162 +++++++----------- .../core/src/styles/components/_zoom-bar.scss | 26 ++- 2 files changed, 86 insertions(+), 102 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 8066d0aa08..b200d522b2 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -7,9 +7,8 @@ import { DOMUtils } from "../../services"; // D3 Imports import { extent } from "d3-array"; import { brushX } from "d3-brush"; -import { drag } from "d3-drag"; import { area, line } from "d3-shape"; -import { event, select, selectAll, BaseType } from "d3-selection"; +import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; @@ -18,39 +17,9 @@ export class ZoomBar extends Component { ogXScale: any; - dragged = Tools.debounce((element, d, e) => { - console.log("dragged"); - console.log(this.ogXScale.invert(100)); - console.log(d); - console.log(e); - element = select(element); - const startingHandle = element.attr("class").indexOf("start") !== -1; - console.log("startingHandle?:" + startingHandle); - const oldDomain = this.model.get("zoomDomain"); - console.log("old domain"); - console.log(oldDomain); - let domain; - if (startingHandle) { - domain = [ - this.ogXScale.invert(e.x), - this.ogXScale.domain()[1], - // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) - ]; - } else { - domain = [ - this.ogXScale.domain()[0], - this.ogXScale.invert(e.x), - // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) - ]; - } - console.log("new domain"); - console.log(domain); - this.model.set({ zoomDomain: domain }, { animate: false }); - }, 2.5); - - - render(animate = true) { + // TODO - get correct axis left width + const axisLeftWidth = 23; const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -65,63 +34,11 @@ export class ZoomBar extends Component { mainYAxisPosition, ); - const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") - // .attr("transform", "translateX(10)") .attr("width", "100%") .attr("height", this.height) .attr("opacity", 1); - const brushHandle = (g, selection) => { - const handleSize = 5; - // handle - svg.select("g.brush") - .selectAll("rect.handle") - .data([{type: "w"}, {type: "e"}]) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) - .attr("fill", "#525252"); - // handle-bar - svg.select("g.brush") - .selectAll("rect.handle-bar") - .data([{type: "w"}, {type: "e"}]) - .join("rect") - .attr("class", function(d) { return "handle-bar handle-bar--" + d.type; }) - .attr("x", function(d) { - if (d.type === "w") { - return selection[0] - 1; - } else if (d.type === "e") { - return selection[1] - 1; - } - }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - - }; - - const brushed = () => { - const selection = event.selection; - if (selection === null) { - // do nothing - console.log("selection === null"); - } else { - // TODO - pass selection to callback function or update scaleDomain - } - // update brush handle position - select(svg).call(brushHandle, selection); - }; - - const brush = brushX() - .extent([[0, 0], [700, this.height]]) - .on("start brush end", brushed); - - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") - .call(brush) - .call(brush.move, mainXScale.range()); // TODO -- mainXScale.range() incorrect - const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) .attr("y", 32) @@ -131,7 +48,7 @@ export class ZoomBar extends Component { .attr("fill", "none"); const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") - .attr("x", 0) + .attr("x", axisLeftWidth) .attr("y", 0) .attr("width", "100%") .attr("height", "100%") @@ -169,9 +86,9 @@ export class ZoomBar extends Component { return correspondingData; }); - // if (!this.ogXScale) { - // this.ogXScale = cartesianScales.getDomainScale(); - // } + if (!this.ogXScale) { + this.ogXScale = cartesianScales.getDomainScale(); + } const xScale = mainXScale.copy(); if (!this.ogXScale) { this.ogXScale = xScale; @@ -183,7 +100,7 @@ export class ZoomBar extends Component { }); xScale - .range([0, width]) + .range([axisLeftWidth, width]) .domain(extent(stackDataArray, (d: any) => d.date)); yScale @@ -228,9 +145,6 @@ export class ZoomBar extends Component { container, "path.zoom-graph-line", ) - .attr("stroke", "#8e8e8e") - .attr("stroke-width", 3) - .attr("fill", "none") .datum(stackDataArray) .transition( this.services.transitions.getTransition( @@ -267,7 +181,6 @@ export class ZoomBar extends Component { container, "path.zoom-graph-area", ) - .attr("fill", "#e0e0e0") .datum(stackDataArray) .transition( this.services.transitions.getTransition( @@ -277,6 +190,64 @@ export class ZoomBar extends Component { ) .attr("d", areaGenerator); + const updateBrushHandle = (g, selection) => { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{ type: "w" }, { type: "e" }]) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32); + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{ type: "w" }, { type: "e" }]) + .join("rect") + .attr("class", function (d) { + return "handle-bar handle-bar--" + d.type; + }) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12); + }; + + const brushed = () => { + const selection = event.selection; + if (selection === null) { + // do nothing + console.log("selection === null"); + } else { + // TODO - pass selection to callback function or update scaleDomain + } + // update brush handle position + select(svg).call(updateBrushHandle, selection); + + const domain = [ + mainXScale.invert(selection[0]), + mainXScale.invert(selection[1]), + ]; + // TODO -- update zoomDomain in model + // this.model.set({zoomDomain: domain}, {animate: false}); + }; + + const brush = brushX() + .extent([ + [0 + axisLeftWidth, 0], + [700, this.height], + ]) + .on("start brush end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") + .call(brush) + .call(brush.move, xScale.range()); } } } @@ -285,5 +256,4 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } - } diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 02fcf7a55f..59019956a8 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -1,11 +1,25 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { - rect.zoom-handle.dragging, - rect.zoom-handle:hover { - fill: black; - cursor: col-resize; + rect.zoom-bg { + fill: $ui-background; + stroke: $ui-01; } - rect.zoom-handle-bar { - pointer-events: none; + path.zoom-graph-line { + stroke: $ui-04; + stroke-width: 3; + fill: none; + } + + path.zoom-graph-area { + fill: $ui-03; + stroke: $ui-04; + } + + g.brush rect.handle { + fill: $icon-02; + } + + g.brush rect.handle-bar { + fill: $ui-02; } } From 6c0a2ca5f90f6ff6f0277f28dc118cadfbe2427c Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 16:03:16 +0800 Subject: [PATCH 221/510] feat: set brush selection to zoomDomain in Model - use flag to avoid recursive update event --- packages/core/src/components/axes/zoom-bar.ts | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index b200d522b2..12b7b356be 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,7 +17,14 @@ export class ZoomBar extends Component { ogXScale: any; + // use this flag to avoid recursive update events + skipNextUpdate = false; + render(animate = true) { + if (this.skipNextUpdate === true) { + this.skipNextUpdate = false; + return; + } // TODO - get correct axis left width const axisLeftWidth = 23; const svg = this.getContainerSVG(); @@ -28,10 +35,10 @@ export class ZoomBar extends Component { const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition, + mainXAxisPosition ); const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition, + mainYAxisPosition ); const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") @@ -96,7 +103,7 @@ export class ZoomBar extends Component { const yScale = mainYScale.copy(); const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true, + useAttrs: true }); xScale @@ -117,8 +124,8 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i, - ), + i + ) ) .y( (d, i) => @@ -128,8 +135,8 @@ export class ZoomBar extends Component { mainYScaleType, mainYAxisPosition, d, - i, - ), + i + ) ) .curve(this.services.curves.getD3Curve()); // .defined((d: any, i) => { @@ -143,14 +150,14 @@ export class ZoomBar extends Component { // }); const lineGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-line", + "path.zoom-graph-line" ) .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-line-update", - animate, - ), + animate + ) ) .attr("d", lineGenerator); @@ -161,8 +168,8 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i, - ), + i + ) ) .y0(this.height) .y1( @@ -173,20 +180,20 @@ export class ZoomBar extends Component { mainYScaleType, mainYAxisPosition, d, - i, - ), + i + ) ); const areaGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-area", + "path.zoom-graph-area" ) .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-area-update", - animate, - ), + animate + ) ) .attr("d", areaGenerator); @@ -196,9 +203,17 @@ export class ZoomBar extends Component { svg.select("g.brush") .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 3; + } else if (d.type === "e") { + return selection[1] - 3; + } + }) .attr("y", 0) .attr("width", handleSize) - .attr("height", 32); + .attr("height", 32) + .style("display", null); // always display // handle-bar svg.select("g.brush") .selectAll("rect.handle-bar") @@ -220,10 +235,10 @@ export class ZoomBar extends Component { }; const brushed = () => { - const selection = event.selection; + let selection = event.selection; if (selection === null) { - // do nothing - console.log("selection === null"); + // set to default full range + selection = xScale.range(); } else { // TODO - pass selection to callback function or update scaleDomain } @@ -231,23 +246,29 @@ export class ZoomBar extends Component { select(svg).call(updateBrushHandle, selection); const domain = [ - mainXScale.invert(selection[0]), - mainXScale.invert(selection[1]), + xScale.invert(selection[0]), + xScale.invert(selection[1]) ]; - // TODO -- update zoomDomain in model - // this.model.set({zoomDomain: domain}, {animate: false}); + // only if the brush event comes from mouseup event + if (event.sourceEvent != null && event.type === "end") { + this.skipNextUpdate = true; // avoid recursive update + this.model.set( + { zoomDomain: domain }, + { animate: false } + ); + } }; const brush = brushX() .extent([ [0 + axisLeftWidth, 0], - [700, this.height], + [700, this.height] ]) .on("start brush end", brushed); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") .call(brush) - .call(brush.move, xScale.range()); + .call(brush.move, xScale.range()); // default to full range } } } From 8556c4e6c41296322246e91c5937c02e1cccd3d3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:42:26 +0800 Subject: [PATCH 222/510] feat: create ZoomBarOptions and default configuration - default to disable zoombar --- packages/core/src/axis-chart.ts | 9 ++++----- packages/core/src/configuration.ts | 13 +++++++++++-- packages/core/src/interfaces/charts.ts | 7 ++++++- packages/core/src/interfaces/components.ts | 10 ++++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index e4d105905f..04f9bdeeb5 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -126,9 +126,7 @@ export class AxisChart extends Chart { const zoomBarComponent = { id: "zoom-bar", - components: [ - new ZoomBar(this.model, this.services) - ], + components: [new ZoomBar(this.model, this.services)], growth: { x: LayoutGrowth.PREFERRED, y: LayoutGrowth.FIXED @@ -151,8 +149,9 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } - - topLevelLayoutComponents.push(zoomBarComponent); + if (this.model.getOptions().zoomBar.enabled === true) { + topLevelLayoutComponents.push(zoomBarComponent); + } topLevelLayoutComponents.push(fullFrameComponent); return [ diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index f93d747285..d09e139d44 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -23,7 +23,8 @@ import { StackedBarOptions, MeterChartOptions, GaugeTypes, - Alignments + Alignments, + ZoomBarOptions } from "./interfaces"; import enUSLocaleObject from "date-fns/locale/en-US/index"; @@ -127,6 +128,13 @@ export const timeScale: TimeScaleOptions = { } }; +/** + * ZoomBar options + */ +export const zoomBar: ZoomBarOptions = { + enabled: false +}; + /** * Base chart options common to any chart */ @@ -155,7 +163,8 @@ const chart: BaseChartOptions = { const axisChart: AxisChartOptions = Tools.merge({}, chart, { axes, timeScale, - grid + grid, + zoomBar } as AxisChartOptions); /** diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index eceba01b06..5ff08b4f9d 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -8,7 +8,8 @@ import { LegendOptions, TooltipOptions, GridOptions, - AxesOptions + AxesOptions, + ZoomBarOptions } from "./index"; import { BarOptions, StackedBarOptions } from "./components"; import { TimeScaleOptions } from "./axis-scales"; @@ -45,6 +46,10 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 4b56616f59..57199a3dad 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -116,3 +116,13 @@ export interface BarOptions { export interface StackedBarOptions extends BarOptions { dividerSize?: number; } + +/** + * customize the ZoomBar component + */ +export interface ZoomBarOptions { + /** + * is the zoom-bar visible or not + */ + enabled?: boolean; +} From c2ca935ec1604b68fe0433fc2bd122f1b07a63df Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:44:58 +0800 Subject: [PATCH 223/510] feat: create zoombar story in storybook --- packages/core/demo/data/time-series-axis.ts | 34 ++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index c1c7183571..e9519866dc 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -13,7 +13,7 @@ export const lineTimeSeriesData15seconds = { { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } ] } ] @@ -418,3 +418,35 @@ export const lineTimeSeriesAllLabelsInPrimaryFormatOptions = { addSpaceOnEdges: 0 } }; + +// ZoomBar +export const lineTimeSeriesDataZoomBar = { + labels: ["Qty"], + datasets: [ + { + label: "Dataset 1", + data: [ + { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, + { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, + { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, + { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, + { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, + { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } + ] + } + ] +}; + +export const lineTimeSeriesZoomBarOptions = { + title: "Line (time series) - zoom-bar enabled", + axes: { + left: {}, + bottom: { + scaleType: "time" + } + }, + zoomBar: { + enabled: true + } +}; From a24863d8edbf8ed3e8197d77ce7b275f90350205 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 11:17:12 +0800 Subject: [PATCH 224/510] fix: set tslint trailing-comma to never --- packages/core/tslint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/tslint.json b/packages/core/tslint.json index 91271d283a..a5a0970b82 100644 --- a/packages/core/tslint.json +++ b/packages/core/tslint.json @@ -92,7 +92,7 @@ "trailing-comma": [ true, { - "multiline": "always", + "multiline": "never", "singleline": "never" } ] From 665c4335669a73b75bb10b4182e65551d47b2e0f Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 12:23:10 +0800 Subject: [PATCH 225/510] fix: allow zoomDomain to update for every brush event --- packages/core/src/components/axes/zoom-bar.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 12b7b356be..37c68ab870 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,14 +17,7 @@ export class ZoomBar extends Component { ogXScale: any; - // use this flag to avoid recursive update events - skipNextUpdate = false; - render(animate = true) { - if (this.skipNextUpdate === true) { - this.skipNextUpdate = false; - return; - } // TODO - get correct axis left width const axisLeftWidth = 23; const svg = this.getContainerSVG(); @@ -245,17 +238,24 @@ export class ZoomBar extends Component { // update brush handle position select(svg).call(updateBrushHandle, selection); - const domain = [ + const newDomain = [ xScale.invert(selection[0]), xScale.invert(selection[1]) ]; + // only if the brush event comes from mouseup event - if (event.sourceEvent != null && event.type === "end") { - this.skipNextUpdate = true; // avoid recursive update - this.model.set( - { zoomDomain: domain }, - { animate: false } - ); + if (event.sourceEvent != null) { + // only if zoomDomain is never set or needs update + if ( + zoomDomain === undefined || + zoomDomain[0] !== newDomain[0] || + zoomDomain[1] !== newDomain[1] + ) { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + } } }; @@ -266,9 +266,12 @@ export class ZoomBar extends Component { ]) .on("start brush end", brushed); - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") - .call(brush) - .call(brush.move, xScale.range()); // default to full range + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( + brush + ); + if (zoomDomain === undefined) { + brushArea.call(brush.move, xScale.range()); // default to full range + } } } } From 636cf9041f95c7a20979190c196dd8dee1e02a67 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 23:06:38 +0800 Subject: [PATCH 226/510] feat: provide ZoomBar options for brush event callback - selected x range and domain as callback parameters --- packages/core/demo/data/time-series-axis.ts | 20 +++++++++++++++- packages/core/src/components/axes/zoom-bar.ts | 23 +++++++++++++++++++ packages/core/src/configuration.ts | 5 +++- packages/core/src/interfaces/components.ts | 12 ++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e9519866dc..e79de136b6 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -437,6 +437,21 @@ export const lineTimeSeriesDataZoomBar = { } ] }; +const selectionStartFun = (selection, domain) => { + console.log("ZoomBar SelectionStart callback!"); + console.log(selection); + console.log(domain); +}; +const selectionInProgressFun = (selection, domain) => { + console.log("ZoomBar SelectionInProgress callback!"); + console.log(selection); + console.log(domain); +}; +const selectionEndFun = (selection, domain) => { + console.log("ZoomBar SelectionEnd callback!"); + console.log(selection); + console.log(domain); +}; export const lineTimeSeriesZoomBarOptions = { title: "Line (time series) - zoom-bar enabled", @@ -447,6 +462,9 @@ export const lineTimeSeriesZoomBarOptions = { } }, zoomBar: { - enabled: true + enabled: true, + selectionStart: selectionStartFun, + selectionInProgress: selectionInProgressFun, + selectionEnd: selectionEndFun } }; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 37c68ab870..4fdfa2fec5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -256,6 +256,29 @@ export class ZoomBar extends Component { { animate: false } ); } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress( + selection, + newDomain + ); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } } }; diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index d09e139d44..5109d282e1 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -132,7 +132,10 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false + enabled: false, + selectionStart: undefined, + selectionInProgress: undefined, + selectionEnd: undefined }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 57199a3dad..33d100ff45 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -125,4 +125,16 @@ export interface ZoomBarOptions { * is the zoom-bar visible or not */ enabled?: boolean; + /** + * a function to handle selection start event + */ + selectionStart?: Function; + /** + * a function to handle selection in progress event + */ + selectionInProgress?: Function; + /** + * a function to handle selection end event + */ + selectionEnd?: Function; } From 771a731baf215a35da6563d378054155d6d223d6 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Mon, 15 Jun 2020 18:46:54 +0800 Subject: [PATCH 227/510] feat: apply clipPath to line chart - v1 --- packages/core/src/charts/line.ts | 2 + packages/core/src/components/axes/cover.ts | 64 +++++++++++++++++++++ packages/core/src/components/component.ts | 25 ++++++-- packages/core/src/components/graphs/line.ts | 3 +- packages/core/src/components/index.ts | 1 + 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/components/axes/cover.ts diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index 469bf7a28e..d766c7ad5a 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -11,6 +11,7 @@ import { Line, Ruler, Scatter, + Cover, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) Tooltip, @@ -40,6 +41,7 @@ export class LineChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts new file mode 100644 index 0000000000..20b4083e09 --- /dev/null +++ b/packages/core/src/components/axes/cover.ts @@ -0,0 +1,64 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { axisBottom, axisLeft } from "d3-axis"; +import { mouse, select } from "d3-selection"; +import { TooltipTypes, Events } from "../../interfaces"; + +export class Cover extends Component { + type = "cover"; + + coverClipPath: any; + + render(animate = true) { + // Create the cover + this.createCover(); + } + + + createCover() { + const svg = this.parent; + console.log("!!! cover svg: ", svg); + const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainYScale = this.services.cartesianScales.getMainYScale(); + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + // Get height + this.coverClipPath = DOMUtils.appendOrSelect(svg, `clipPath.${this.type}`); + this.coverClipPath + .attr("id", `${this.type}Clip`); + const coverRect = DOMUtils.appendOrSelect( + this.coverClipPath, + "rect.cover" + ); + coverRect + .attr("x", xScaleStart) + .attr("y", yScaleStart) + .attr("width", xScaleEnd - xScaleStart) + .attr("height", yScaleEnd - yScaleStart); + + this.coverClipPath + .merge(coverRect) + .lower(); + + const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); + coverG + .attr("clip-path", `url(#${this.type}Clip)`) + .attr("id", `g-${this.type}Clip`); + + } + + cleanCover(g) { + const options = this.model.getOptions(); + g.selectAll("line").attr("stroke", options.grid.strokeColor); + + // Remove extra elements + g.selectAll("text").remove(); + g.select(".domain").remove(); + } +} diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 7530628ac1..2b1d8d84d0 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -90,10 +90,27 @@ export class Component { "style", "prefix" ); - return DOMUtils.appendOrSelect( - this.parent, - `g.${settings.prefix}--${chartprefix}--${this.type}` - ); + if (this.type === "line" || this.type === "scatter") { + const { width, height } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + return DOMUtils.appendOrSelectForAxisChart( + this.parent, + `clipPath.cover`, + this.type, + 23, + 0, + (width - 23), + height, + ); + + } else { + return DOMUtils.appendOrSelect( + this.parent, + `g.${settings.prefix}--${chartprefix}--${this.type}` + ); + } + } return this.parent; diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 2d2c5c36c7..2be9c949a9 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -131,6 +131,7 @@ export class Line extends Component { this.parent .selectAll("path.line") + .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-hover-line") ) @@ -145,7 +146,7 @@ export class Line extends Component { handleLegendMouseOut = (event: CustomEvent) => { this.parent - .selectAll("path.line") + .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-mouseout-line") ) diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 60daf43009..8be94d9e0e 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -35,3 +35,4 @@ export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; +export * from "./axes/cover"; From 56904a4c0a50ac42db2d76cccf19e80019b6f0e1 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 13:37:58 +0800 Subject: [PATCH 228/510] feat: apply cover to axis chart --- packages/core/src/charts/area-stacked.ts | 2 ++ packages/core/src/charts/area.ts | 2 ++ packages/core/src/charts/bar-grouped.ts | 2 ++ packages/core/src/charts/bar-simple.ts | 2 ++ packages/core/src/charts/bar-stacked.ts | 2 ++ packages/core/src/charts/bubble.ts | 4 +++- packages/core/src/charts/scatter.ts | 2 ++ packages/core/src/components/component.ts | 26 +++++++++++------------ 8 files changed, 28 insertions(+), 14 deletions(-) diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index c89a4f132d..8f18b69cf5 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -7,6 +7,7 @@ import { Tools } from "../tools"; // Components import { Grid, + Cover, StackedArea, TwoDimensionalAxes, Line, @@ -35,6 +36,7 @@ export class StackedAreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new StackedArea(this.model, this.services), diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index aaf6cbad0a..a61e3f669c 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -7,6 +7,7 @@ import { Tools } from "../tools"; // Components import { Grid, + Cover, Area, Line, Ruler, @@ -39,6 +40,7 @@ export class AreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index bba066a001..6a2be76a82 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, GroupedBar, TwoDimensionalAxes, ZeroLine, @@ -39,6 +40,7 @@ export class GroupedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new GroupedBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 0115bd6959..bf2da6772b 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, SimpleBar, TwoDimensionalAxes, ZeroLine, @@ -39,6 +40,7 @@ export class SimpleBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new SimpleBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 5edcdfc9b3..443f483d33 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, StackedBar, StackedBarRuler, TwoDimensionalAxes, @@ -42,6 +43,7 @@ export class StackedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new StackedBarRuler(this.model, this.services), new StackedBar(this.model, this.services), diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 05f1f5e4f1..dcdb8fcced 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -15,7 +15,8 @@ import { Tooltip, Legend, LayoutComponent, - Skeleton + Skeleton, + Cover } from "../components/index"; export class BubbleChart extends AxisChart { @@ -42,6 +43,7 @@ export class BubbleChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Bubble(this.model, this.services), diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 5d2a20c6cb..f3cda814cb 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -9,6 +9,7 @@ import { Skeletons } from "../interfaces/enums"; import { Grid, Ruler, + Cover, Scatter, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) @@ -42,6 +43,7 @@ export class ScatterChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Scatter(this.model, this.services), diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 2b1d8d84d0..c49dbd79fc 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -90,19 +90,19 @@ export class Component { "style", "prefix" ); - if (this.type === "line" || this.type === "scatter") { - const { width, height } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); - return DOMUtils.appendOrSelectForAxisChart( - this.parent, - `clipPath.cover`, - this.type, - 23, - 0, - (width - 23), - height, - ); + + + if ( + this.type === "line" || + this.type === "scatter" || + this.type === "area" || + this.type === "bubble" || + this.type === "area-stacked" || + this.type === "grouped-bar" || + this.type === "simple-bar" || + this.type === "scatter-stacked" + ) { + return DOMUtils.appendOrSelectForAxisChart(this.parent,`clipPath.cover`); } else { return DOMUtils.appendOrSelect( From d2b9b953f0e68053f93dec17f04876ae5544864a Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 13:57:19 +0800 Subject: [PATCH 229/510] refactor: clean code --- packages/core/src/charts/area-stacked.ts | 2 +- packages/core/src/charts/area.ts | 2 +- packages/core/src/charts/bar-grouped.ts | 2 +- packages/core/src/charts/bar-simple.ts | 2 +- packages/core/src/charts/bar-stacked.ts | 2 +- packages/core/src/charts/bubble.ts | 4 ++-- packages/core/src/charts/line.ts | 2 +- packages/core/src/charts/scatter.ts | 2 +- packages/core/src/components/axes/cover.ts | 10 ---------- packages/core/src/components/index.ts | 3 ++- 10 files changed, 11 insertions(+), 20 deletions(-) diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 8f18b69cf5..71cc66c34d 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,8 +6,8 @@ import { Tools } from "../tools"; // Components import { - Grid, Cover, + Grid, StackedArea, TwoDimensionalAxes, Line, diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index a61e3f669c..6d808c893c 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,8 +6,8 @@ import { Tools } from "../tools"; // Components import { - Grid, Cover, + Grid, Area, Line, Ruler, diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 6a2be76a82..63f82294ca 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, GroupedBar, TwoDimensionalAxes, ZeroLine, diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index bf2da6772b..21a1ccecc0 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, SimpleBar, TwoDimensionalAxes, ZeroLine, diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 443f483d33..5816fd62e8 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, StackedBar, StackedBarRuler, TwoDimensionalAxes, diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index dcdb8fcced..11469b0355 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Ruler, Bubble, @@ -15,8 +16,7 @@ import { Tooltip, Legend, LayoutComponent, - Skeleton, - Cover + Skeleton } from "../components/index"; export class BubbleChart extends AxisChart { diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index d766c7ad5a..943686c6c2 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,11 +7,11 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Line, Ruler, Scatter, - Cover, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) Tooltip, diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index f3cda814cb..e38fc66d51 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,9 +7,9 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Ruler, - Cover, Scatter, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts index 20b4083e09..16179b45c5 100644 --- a/packages/core/src/components/axes/cover.ts +++ b/packages/core/src/components/axes/cover.ts @@ -21,7 +21,6 @@ export class Cover extends Component { createCover() { const svg = this.parent; - console.log("!!! cover svg: ", svg); const mainXScale = this.services.cartesianScales.getMainXScale(); const mainYScale = this.services.cartesianScales.getMainYScale(); @@ -52,13 +51,4 @@ export class Cover extends Component { .attr("id", `g-${this.type}Clip`); } - - cleanCover(g) { - const options = this.model.getOptions(); - g.selectAll("line").attr("stroke", options.grid.strokeColor); - - // Remove extra elements - g.selectAll("text").remove(); - g.select(".domain").remove(); - } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 8be94d9e0e..a48f26437e 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,9 +30,10 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; +export * from "./axes/cover"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; -export * from "./axes/cover"; + From a979b919a2773b13af942d1087d11274cc253ce5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 16 Jun 2020 14:57:41 +0800 Subject: [PATCH 230/510] feat: set axes margins as zoom bar left margin --- .../components/axes/two-dimensional-axes.ts | 3 +++ packages/core/src/components/axes/zoom-bar.ts | 20 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/axes/two-dimensional-axes.ts b/packages/core/src/components/axes/two-dimensional-axes.ts index ec843b20ab..d95e70e557 100644 --- a/packages/core/src/components/axes/two-dimensional-axes.ts +++ b/packages/core/src/components/axes/two-dimensional-axes.ts @@ -114,6 +114,9 @@ export class TwoDimensionalAxes extends Component { if (isNotEqual) { this.margins = Object.assign(this.margins, margins); + // also set new margins to model to allow external components to access + this.model.set({ axesMargins: this.margins }, { animate: false }); + Object.keys(this.children).forEach((childKey) => { const child = this.children[childKey]; child.margins = this.margins; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 4fdfa2fec5..0bf64460d9 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -18,10 +18,7 @@ export class ZoomBar extends Component { ogXScale: any; render(animate = true) { - // TODO - get correct axis left width - const axisLeftWidth = 23; const svg = this.getContainerSVG(); - const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); @@ -34,6 +31,13 @@ export class ZoomBar extends Component { mainYAxisPosition ); + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") .attr("width", "100%") .attr("height", this.height) @@ -48,7 +52,7 @@ export class ZoomBar extends Component { .attr("fill", "none"); const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") - .attr("x", axisLeftWidth) + .attr("x", axesLeftMargin) .attr("y", 0) .attr("width", "100%") .attr("height", "100%") @@ -100,7 +104,7 @@ export class ZoomBar extends Component { }); xScale - .range([axisLeftWidth, width]) + .range([axesLeftMargin, width]) .domain(extent(stackDataArray, (d: any) => d.date)); yScale @@ -232,8 +236,6 @@ export class ZoomBar extends Component { if (selection === null) { // set to default full range selection = xScale.range(); - } else { - // TODO - pass selection to callback function or update scaleDomain } // update brush handle position select(svg).call(updateBrushHandle, selection); @@ -284,8 +286,8 @@ export class ZoomBar extends Component { const brush = brushX() .extent([ - [0 + axisLeftWidth, 0], - [700, this.height] + [axesLeftMargin, 0], + [width, this.height] ]) .on("start brush end", brushed); From 7444472e8556b64dd3a980e59450b65de2409ff2 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 16:04:29 +0800 Subject: [PATCH 231/510] refactor: add todo --- packages/core/src/components/component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index c49dbd79fc..5664f29ec8 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -91,18 +91,18 @@ export class Component { "prefix" ); - + // @todo Chart type equals to axis-chart if ( - this.type === "line" || - this.type === "scatter" || - this.type === "area" || + this.type === "line" || + this.type === "scatter" || + this.type === "area" || this.type === "bubble" || this.type === "area-stacked" || this.type === "grouped-bar" || this.type === "simple-bar" || this.type === "scatter-stacked" ) { - return DOMUtils.appendOrSelectForAxisChart(this.parent,`clipPath.cover`); + return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); } else { return DOMUtils.appendOrSelect( From eeb50f0409f8c42ba3eb52940d27574c023dc2ea Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 16 Jun 2020 16:54:51 +0800 Subject: [PATCH 232/510] fix: double the minTickSize for datetime ticks --- packages/core/src/components/axes/axis.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 5eab971526..10953d11cb 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -117,10 +117,9 @@ export class Axis extends Component { // if zoomDomain is available, update scale domain to Date array. const zoomDomain = this.model.get("zoomDomain"); if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain.map(d => new Date(d))); + scale.domain(zoomDomain.map((d) => new Date(d))); } - // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -433,11 +432,11 @@ export class Axis extends Component { const ticksNumber = isTimeScaleType ? axis.tickValues().length : scale.ticks().length; - const estimatedTickSize = width / ticksNumber / 1.6; - - rotateTicks = estimatedTickSize < minTickSize; + const estimatedTickSize = width / ticksNumber / 2; + rotateTicks = isTimeScaleType + ? estimatedTickSize < minTickSize * 2 // datetime tick could be very long + : estimatedTickSize < minTickSize; } - if (rotateTicks) { if (!isNumberOfTicksProvided) { axis.ticks( From de9ac3b4fe3c6f80e5dd9c910831bbaf63fb3a9b Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 18 Jun 2020 13:42:33 +0800 Subject: [PATCH 233/510] feat: add chart brush --- packages/core/src/charts/area-stacked.ts | 5 +- packages/core/src/charts/area.ts | 5 +- packages/core/src/charts/bar-grouped.ts | 5 +- packages/core/src/charts/bar-simple.ts | 5 +- packages/core/src/charts/bar-stacked.ts | 5 +- packages/core/src/charts/bubble.ts | 5 +- packages/core/src/charts/donut.ts | 5 +- packages/core/src/charts/line.ts | 5 +- packages/core/src/charts/pie.ts | 5 +- packages/core/src/charts/radar.ts | 4 +- packages/core/src/charts/scatter.ts | 5 +- packages/core/src/components/axes/brush.ts | 142 +++++++++++++++++++++ packages/core/src/components/index.ts | 1 + 13 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/axes/brush.ts diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 71cc66c34d..b87365a97b 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, Cover, Grid, StackedArea, @@ -34,7 +35,7 @@ export class StackedAreaChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -48,6 +49,8 @@ export class StackedAreaChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 6d808c893c..8d5b94aa49 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, Cover, Grid, Area, @@ -38,7 +39,7 @@ export class AreaChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -51,6 +52,8 @@ export class AreaChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 63f82294ca..5668d302eb 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, GroupedBar, @@ -38,7 +39,7 @@ export class GroupedBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -49,6 +50,8 @@ export class GroupedBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 21a1ccecc0..951cd4509f 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, SimpleBar, @@ -38,7 +39,7 @@ export class SimpleBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -49,6 +50,8 @@ export class SimpleBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 5816fd62e8..e5684fb737 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, StackedBar, @@ -41,7 +42,7 @@ export class StackedBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class StackedBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 11469b0355..fd1c03a354 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Ruler, @@ -41,7 +42,7 @@ export class BubbleChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class BubbleChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/donut.ts b/packages/core/src/charts/donut.ts index 100faa5fac..18e00d2c36 100644 --- a/packages/core/src/charts/donut.ts +++ b/packages/core/src/charts/donut.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Donut, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -33,13 +34,15 @@ export class DonutChart extends PieChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new Donut(this.model, this.services), new Skeleton(this.model, this.services, { skeleton: Skeletons.DONUT }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getChartComponents(graphFrameComponents); return components; } diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index 943686c6c2..e950b1c4af 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Line, @@ -39,7 +40,7 @@ export class LineChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -51,6 +52,8 @@ export class LineChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 0fc4429807..9631b459a8 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Pie, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -46,13 +47,15 @@ export class PieChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new Pie(this.model, this.services), new Skeleton(this.model, this.services, { skeleton: Skeletons.PIE }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 8758bf1591..6a79b1a13b 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, // the imports below are needed because of typescript bug (error TS4029) Legend, LayoutComponent @@ -41,7 +42,8 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [new Radar(this.model, this.services)]; + const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index e38fc66d51..4c5b92d89b 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Ruler, @@ -41,7 +42,7 @@ export class ScatterChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class ScatterChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts new file mode 100644 index 0000000000..60dc0a8945 --- /dev/null +++ b/packages/core/src/components/axes/brush.ts @@ -0,0 +1,142 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { extent } from "d3-array"; +import { brushX } from "d3-brush"; +import { event } from "d3-selection"; +import { scaleTime } from "d3-scale"; + +export class Brush extends Component { + type = "brush"; + + render(animate = true) { + const svg = this.parent; + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const mainXScaleType = cartesianScales.getScaleTypeByPosition( + mainXAxisPosition + ); + + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + + const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainYScale = this.services.cartesianScales.getMainYScale(); + + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + const container = DOMUtils.appendOrSelect(svg, "svg.brush-container") + .attr("width", "100%") + .attr("height", "100%") + .attr("opacity", 1); + + if (mainXScale) { + const displayData = this.model.getDisplayData(); + + if (mainXScaleType === ScaleTypes.TIME) { + // Get all date values provided in data + // TODO - Could be re-used through the model + let allDates = []; + displayData.forEach((data) => { + allDates = allDates.concat(Number(data.date)); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + + // Go through all date values + // And get corresponding data from each dataset + const stackDataArray = allDates.map((date) => { + let count = 0; + let correspondingSum = 0; + const correspondingData = {}; + + displayData.forEach((data) => { + if (Number(data.date) === Number(date)) { + ++count; + correspondingSum += data.value; + } + }); + correspondingData["date"] = date; + correspondingData["value"] = correspondingSum; + + return correspondingData; + }); + + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + + let zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain === undefined) { + zoomDomain = extent(stackDataArray, (d: any) => d.date); // default to full range + this.model.set( + { zoomDomain: zoomDomain }, + { animate: false } + ); + } + + const brushed = () => { + let selection = event.selection; + if (selection !== null) { + // update zoombar handle position + // select(svg).call(updateBrushHandle, selection); + + // get current zoomDomain + zoomDomain = this.model.get("zoomDomain"); + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([axesLeftMargin, width]) + .domain(zoomDomain); + + const newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + // only if the brush event comes from mouseup event + if (event.sourceEvent != null) { + // only if zoomDomain needs update + if (zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1]) { + this.model.set( + { zoomDomain: newDomain, zoomDomainForZoomBar: newDomain }, + { animate: false } + ); + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + } + }; + + const brush = brushX() + .extent([ + [xScaleStart, 0], + [width, yScaleEnd] + ]) + .on("end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.chart-brush").call( + brush + ); + // no need for having default brush selection + // @todo try to hide brush after selection + setTimeout(()=> {brushArea.call(brush.move);}, 0); + } + } + } +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index a48f26437e..4d9959743a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,6 +30,7 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; +export * from "./axes/brush"; export * from "./axes/cover"; export * from "./axes/grid"; export * from "./axes/ruler"; From eb8313873154010b158e9950d3dd215e72fd5006 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 18 Jun 2020 14:19:35 +0800 Subject: [PATCH 234/510] refactor: add brush in axis chart component --- packages/core/src/axis-chart.ts | 3 +++ packages/core/src/charts/area-stacked.ts | 3 --- packages/core/src/charts/area.ts | 3 --- packages/core/src/charts/bar-grouped.ts | 3 --- packages/core/src/charts/bar-simple.ts | 3 --- packages/core/src/charts/bar-stacked.ts | 3 --- packages/core/src/charts/bubble.ts | 3 --- packages/core/src/charts/donut.ts | 3 --- packages/core/src/charts/line.ts | 3 --- packages/core/src/charts/pie.ts | 5 +---- packages/core/src/charts/radar.ts | 4 +--- packages/core/src/charts/scatter.ts | 3 --- 12 files changed, 5 insertions(+), 34 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 04f9bdeeb5..543d0ac6d4 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -8,6 +8,7 @@ import { AxisChartOptions } from "./interfaces/index"; import { + Brush, LayoutComponent, Legend, Title, @@ -48,6 +49,8 @@ export class AxisChart extends Chart { } }; + !!this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ? + graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index b87365a97b..42895bdc6b 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, Cover, Grid, StackedArea, @@ -49,8 +48,6 @@ export class StackedAreaChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 8d5b94aa49..2b313f6cc7 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, Cover, Grid, Area, @@ -52,8 +51,6 @@ export class AreaChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 5668d302eb..59354739e6 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, GroupedBar, @@ -50,8 +49,6 @@ export class GroupedBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 951cd4509f..5709ea0e9b 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, SimpleBar, @@ -50,8 +49,6 @@ export class SimpleBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index e5684fb737..c2510fa7da 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, StackedBar, @@ -53,8 +52,6 @@ export class StackedBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index fd1c03a354..7238de0aa3 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Ruler, @@ -53,8 +52,6 @@ export class BubbleChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/donut.ts b/packages/core/src/charts/donut.ts index 18e00d2c36..a3c9a33a61 100644 --- a/packages/core/src/charts/donut.ts +++ b/packages/core/src/charts/donut.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Donut, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -41,8 +40,6 @@ export class DonutChart extends PieChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getChartComponents(graphFrameComponents); return components; } diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index e950b1c4af..ee4e07f163 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Line, @@ -52,8 +51,6 @@ export class LineChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 9631b459a8..4642ea8f2d 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -8,7 +8,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Pie, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -53,9 +52,7 @@ export class PieChart extends Chart { skeleton: Skeletons.PIE }) ]; - - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 6a79b1a13b..80b8e7888c 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, // the imports below are needed because of typescript bug (error TS4029) Legend, LayoutComponent @@ -43,8 +42,7 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 4c5b92d89b..2acacdf9df 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Ruler, @@ -53,8 +52,6 @@ export class ScatterChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); From 4c78fe585d600a7e35c6f8b8220adf97dc99a85f Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 18 Jun 2020 22:32:35 +0800 Subject: [PATCH 235/510] refactor: move brush functions to ZoomBar class function --- packages/core/src/components/axes/zoom-bar.ts | 180 +++++++++--------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0bf64460d9..8f5d2587e7 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -194,94 +194,8 @@ export class ZoomBar extends Component { ) .attr("d", areaGenerator); - const updateBrushHandle = (g, selection) => { - const handleSize = 5; - // handle - svg.select("g.brush") - .selectAll("rect.handle") - .data([{ type: "w" }, { type: "e" }]) - .attr("x", function (d) { - if (d.type === "w") { - return selection[0] - 3; - } else if (d.type === "e") { - return selection[1] - 3; - } - }) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) - .style("display", null); // always display - // handle-bar - svg.select("g.brush") - .selectAll("rect.handle-bar") - .data([{ type: "w" }, { type: "e" }]) - .join("rect") - .attr("class", function (d) { - return "handle-bar handle-bar--" + d.type; - }) - .attr("x", function (d) { - if (d.type === "w") { - return selection[0] - 1; - } else if (d.type === "e") { - return selection[1] - 1; - } - }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12); - }; - - const brushed = () => { - let selection = event.selection; - if (selection === null) { - // set to default full range - selection = xScale.range(); - } - // update brush handle position - select(svg).call(updateBrushHandle, selection); - - const newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { - // only if zoomDomain is never set or needs update - if ( - zoomDomain === undefined || - zoomDomain[0] !== newDomain[0] || - zoomDomain[1] !== newDomain[1] - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress( - selection, - newDomain - ); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); - } - } + const brushEventListener = () => { + this.brushed(zoomDomain, xScale, event.selection); }; const brush = brushX() @@ -289,13 +203,18 @@ export class ZoomBar extends Component { [axesLeftMargin, 0], [width, this.height] ]) - .on("start brush end", brushed); + .on("start brush end", brushEventListener); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( brush ); if (zoomDomain === undefined) { brushArea.call(brush.move, xScale.range()); // default to full range + } else { + // brushArea.call( + // brush.move, + // zoomDomain.map((domain) => xScale(domain)) //set brush to correct position + // ); } } } @@ -305,4 +224,87 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } + + // brush event listener + brushed(zoomDomain, scale, selection) { + if (selection === null) { + // set to default full range + selection = scale.range(); + } + // update brush handle position + this.updateBrushHandle(this.getContainerSVG(), selection); + + const newDomain = [ + scale.invert(selection[0]), + scale.invert(selection[1]) + ]; + // only if the brush event comes from mouseup event + if (event.sourceEvent != null) { + // only if zoomDomain is never set or needs update + if ( + zoomDomain === undefined || + zoomDomain[0] !== newDomain[0] || + zoomDomain[1] !== newDomain[1] + ) { + this.model.set({ zoomDomain: newDomain }, { animate: false }); + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + } + + updateBrushHandle(svg, selection) { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{ type: "w" }, { type: "e" }]) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 3; + } else if (d.type === "e") { + return selection[1] - 3; + } + }) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32) + .style("display", null); // always display + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{ type: "w" }, { type: "e" }]) + .join("rect") + .attr("class", function (d) { + return "handle-bar handle-bar--" + d.type; + }) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12); + } } From a0417d1fc290469c18c0d7953504d73c789f46ae Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 19 Jun 2020 11:57:00 +0800 Subject: [PATCH 236/510] fix: handel situation of zoom bar selection[0] equals selection[1] --- packages/core/src/components/axes/zoom-bar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 8f5d2587e7..d7aff2470a 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -227,7 +227,9 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - if (selection === null) { + // follow d3 behavior: when selection[0] === selection[1], reset default full range + // @todo find a better way to handel the situation when selection[0] === selection[1] + if (selection === null || selection[0] === selection[1]) { // set to default full range selection = scale.range(); } From 7e49007104c3d7f178b26630845ce4737f697f95 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 12:37:08 +0800 Subject: [PATCH 237/510] feat: ZoomBar could update brush based on zoomDomain - fix some brush handle UI bugs --- packages/core/src/components/axes/zoom-bar.ts | 104 +++++++++++------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d7aff2470a..3c26a897bd 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,6 +17,8 @@ export class ZoomBar extends Component { ogXScale: any; + brush = brushX(); + render(animate = true) { const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -198,23 +200,30 @@ export class ZoomBar extends Component { this.brushed(zoomDomain, xScale, event.selection); }; - const brush = brushX() + this.brush .extent([ [axesLeftMargin, 0], [width, this.height] ]) + .on("start brush end", null) // remove old listener first .on("start brush end", brushEventListener); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( - brush + this.brush ); - if (zoomDomain === undefined) { - brushArea.call(brush.move, xScale.range()); // default to full range + if ( + zoomDomain === undefined || + zoomDomain[0].valueOf() === zoomDomain[1].valueOf() + ) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range() + ); } else { - // brushArea.call( - // brush.move, - // zoomDomain.map((domain) => xScale(domain)) //set brush to correct position - // ); + const selected = zoomDomain.map((domain) => xScale(domain)); + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle(this.getContainerSVG(), selected); } } } @@ -240,8 +249,13 @@ export class ZoomBar extends Component { scale.invert(selection[0]), scale.invert(selection[1]) ]; - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { + + // be aware that the value of d3.event changes during an event! + // update zoomDomain only if the event comes from mousemove event + if ( + event.sourceEvent != null && + event.sourceEvent.type === "mousemove" + ) { // only if zoomDomain is never set or needs update if ( zoomDomain === undefined || @@ -250,45 +264,53 @@ export class ZoomBar extends Component { ) { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); - } + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if (zoomBarOptions.selectionEnd !== undefined && event.type === "end") { + zoomBarOptions.selectionEnd(selection, newDomain); } } updateBrushHandle(svg, selection) { - const handleSize = 5; + // @todo the handle size, height are calculated by d3 library + // need to figure out how to override the value + const handleWidth = 6; + const handleHeight = 38; + const handleXDiff = -handleWidth / 2; + const handleYDiff = -(handleHeight - this.height) / 2; + + const handleBarWidth = 2; + const handleBarHeight = 12; + const handleBarXDiff = -handleBarWidth / 2; + const handleYBarDiff = + (handleHeight - handleBarHeight) / 2 + handleYDiff; // handle svg.select("g.brush") .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { if (d.type === "w") { - return selection[0] - 3; + return selection[0] + handleXDiff; } else if (d.type === "e") { - return selection[1] - 3; + return selection[1] + handleXDiff; } }) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) + .attr("y", handleYDiff) + .attr("width", handleWidth) + .attr("height", handleHeight) .style("display", null); // always display // handle-bar svg.select("g.brush") @@ -300,13 +322,13 @@ export class ZoomBar extends Component { }) .attr("x", function (d) { if (d.type === "w") { - return selection[0] - 1; + return selection[0] + handleBarXDiff; } else if (d.type === "e") { - return selection[1] - 1; + return selection[1] + handleBarXDiff; } }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12); + .attr("y", handleYBarDiff) + .attr("width", handleBarWidth) + .attr("height", handleBarHeight); } } From 498362234398eefe5a7251bb5206f2dfff474b16 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 12:51:20 +0800 Subject: [PATCH 238/510] fix: handle empty selection --- packages/core/src/components/axes/zoom-bar.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 3c26a897bd..04507c90a3 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -197,7 +197,21 @@ export class ZoomBar extends Component { .attr("d", areaGenerator); const brushEventListener = () => { - this.brushed(zoomDomain, xScale, event.selection); + const selection = event.selection; + // follow d3 behavior: when selection is null, reset default full range + // @todo find a better way to handel the situation when selection is null + // select behavior is completed, but nothing selected + if (selection === null) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range() + ); + } else if (selection[0] === selection[1]) { + // select behavior is not completed yet, do nothing + } else { + this.brushed(zoomDomain, xScale, selection); + } }; this.brush @@ -236,12 +250,6 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - // follow d3 behavior: when selection[0] === selection[1], reset default full range - // @todo find a better way to handel the situation when selection[0] === selection[1] - if (selection === null || selection[0] === selection[1]) { - // set to default full range - selection = scale.range(); - } // update brush handle position this.updateBrushHandle(this.getContainerSVG(), selection); From da1c1c1fea68461b93599921380ce8f90481180a Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 19 Jun 2020 14:13:17 +0800 Subject: [PATCH 239/510] fix: set to default full range when same selection of brush --- packages/core/src/components/axes/brush.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 60dc0a8945..0a22fe0d79 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -45,7 +45,7 @@ export class Brush extends Component { if (mainXScaleType === ScaleTypes.TIME) { // Get all date values provided in data - // TODO - Could be re-used through the model + // @todo - Could be re-used through the model let allDates = []; displayData.forEach((data) => { allDates = allDates.concat(Number(data.date)); @@ -86,10 +86,8 @@ export class Brush extends Component { const brushed = () => { let selection = event.selection; - if (selection !== null) { - // update zoombar handle position - // select(svg).call(updateBrushHandle, selection); + if (selection !== null) { // get current zoomDomain zoomDomain = this.model.get("zoomDomain"); // create xScale based on current zoomDomain @@ -97,17 +95,26 @@ export class Brush extends Component { .range([axesLeftMargin, width]) .domain(zoomDomain); - const newDomain = [ + let newDomain = [ xScale.invert(selection[0]), xScale.invert(selection[1]) ]; + + // check if slected start time and end time are the same + if(newDomain[0].valueOf() === newDomain[1].valueOf()) { + // same as d3 behavior and zoombar behavior: set to default full range + newDomain = extent(stackDataArray, (d: any) => d.date); + } // only if the brush event comes from mouseup event if (event.sourceEvent != null) { // only if zoomDomain needs update - if (zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1]) { + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { this.model.set( - { zoomDomain: newDomain, zoomDomainForZoomBar: newDomain }, + { zoomDomain: newDomain }, { animate: false } ); } From 67d78d852026cff8dc050d1058fb509eabec72db Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 13:59:36 +0800 Subject: [PATCH 240/510] fix: fix bug when selection === null --- packages/core/src/components/axes/zoom-bar.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 04507c90a3..f7894985c5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -202,11 +202,7 @@ export class ZoomBar extends Component { // @todo find a better way to handel the situation when selection is null // select behavior is completed, but nothing selected if (selection === null) { - brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle( - this.getContainerSVG(), - xScale.range() - ); + this.brushed(zoomDomain, xScale, xScale.range()); } else if (selection[0] === selection[1]) { // select behavior is not completed yet, do nothing } else { @@ -259,10 +255,12 @@ export class ZoomBar extends Component { ]; // be aware that the value of d3.event changes during an event! - // update zoomDomain only if the event comes from mousemove event + // update zoomDomain only if the event comes from mouse event if ( event.sourceEvent != null && - event.sourceEvent.type === "mousemove" + (event.sourceEvent.type === "mousemove" || + event.sourceEvent.type === "mouseup" || + event.sourceEvent.type === "mousedown") ) { // only if zoomDomain is never set or needs update if ( From b1d2ed5a0ffb2acd313eda165c710b1d2b3c9557 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 14:41:24 +0800 Subject: [PATCH 241/510] feat: add ZoomBarOptions.initZoomDomain - update storybook - add init() and destroy() in ZoomBar --- packages/core/demo/data/time-series-axis.ts | 5 ++++ packages/core/src/components/axes/zoom-bar.ts | 25 ++++++++++++++++--- packages/core/src/configuration.ts | 1 + packages/core/src/interfaces/components.ts | 6 +++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e79de136b6..845e1bd1a7 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -453,6 +453,10 @@ const selectionEndFun = (selection, domain) => { console.log(domain); }; +const initZoomDomain = [ + new Date(2020, 11, 10, 23, 59, 25), + new Date(2020, 11, 11, 0, 0, 25) +]; export const lineTimeSeriesZoomBarOptions = { title: "Line (time series) - zoom-bar enabled", axes: { @@ -463,6 +467,7 @@ export const lineTimeSeriesZoomBarOptions = { }, zoomBar: { enabled: true, + initZoomDomain: initZoomDomain, selectionStart: selectionStartFun, selectionInProgress: selectionInProgressFun, selectionEnd: selectionEndFun diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index f7894985c5..0137960f8d 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -19,6 +19,17 @@ export class ZoomBar extends Component { brush = brushX(); + init() { + // get initZoomDomain + const zoomBarOptions = this.model.getOptions().zoomBar; + if (zoomBarOptions.initZoomDomain !== undefined) { + this.model.set( + { zoomDomain: zoomBarOptions.initZoomDomain }, + { skipUpdate: true } + ); + } + } + render(animate = true) { const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -221,6 +232,7 @@ export class ZoomBar extends Component { const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( this.brush ); + if ( zoomDomain === undefined || zoomDomain[0].valueOf() === zoomDomain[1].valueOf() @@ -239,10 +251,11 @@ export class ZoomBar extends Component { } } - zoomIn() { - const mainXScale = this.services.cartesianScales.getMainXScale(); - console.log("zoom in", mainXScale.domain()); - } + // could be used by Toolbar + // zoomIn() { + // const mainXScale = this.services.cartesianScales.getMainXScale(); + // console.log("zoom in", mainXScale.domain()); + // } // brush event listener brushed(zoomDomain, scale, selection) { @@ -337,4 +350,8 @@ export class ZoomBar extends Component { .attr("width", handleBarWidth) .attr("height", handleBarHeight); } + + destroy() { + this.brush.on("start brush end", null); // remove event listener + } } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 5109d282e1..ad37847348 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -133,6 +133,7 @@ export const timeScale: TimeScaleOptions = { */ export const zoomBar: ZoomBarOptions = { enabled: false, + initZoomDomain: undefined, selectionStart: undefined, selectionInProgress: undefined, selectionEnd: undefined diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 33d100ff45..d81ddce313 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -125,6 +125,12 @@ export interface ZoomBarOptions { * is the zoom-bar visible or not */ enabled?: boolean; + + /** + * an two element array which represents the initial zoom domain + */ + initZoomDomain?: Object[]; + /** * a function to handle selection start event */ From 7e788a48965b2050b29ac909949267c9b35e685e Mon Sep 17 00:00:00 2001 From: EricYang Date: Sat, 20 Jun 2020 18:03:35 +0800 Subject: [PATCH 242/510] feat: create Zoombar storybook demo group --- packages/core/demo/data/index.ts | 54 +++++++++++++ packages/core/demo/data/time-series-axis.ts | 55 ------------- packages/core/demo/data/zoom-bar.ts | 88 +++++++++++++++++++++ 3 files changed, 142 insertions(+), 55 deletions(-) create mode 100644 packages/core/demo/data/zoom-bar.ts diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 46b9693c9b..99a4cbad47 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -10,6 +10,7 @@ import * as stepDemos from "./step"; import * as meterDemos from "./meter"; import * as timeSeriesAxisDemos from "./time-series-axis"; import * as radarDemos from "./radar"; +import * as zoomBarDemos from "./zoom-bar"; export * from "./area"; export * from "./bar"; @@ -749,6 +750,59 @@ let allDemoGroups = [ chartType: chartTypes.RadarChart } ] + }, + { + title: "Zoom bar", + demos: [ + { + options: zoomBarDemos.zoomBarSimpleBarTimeSeriesOptions, + data: zoomBarDemos.zoomBarSimpleBarTimeSeriesData, + chartType: chartTypes.SimpleBarChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarStackedBarTimeSeriesOptions, + data: zoomBarDemos.zoomBarStackedBarTimeSeriesData, + chartType: chartTypes.StackedBarChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarBubbleTimeSeriesOptions, + data: zoomBarDemos.zoomBarBubbleTimeSeriesData, + chartType: chartTypes.BubbleChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeriesOptions, + data: zoomBarDemos.zoomBarLineTimeSeriesData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarScatterTimeSeriesOptions, + data: zoomBarDemos.zoomBarScatterTimeSeriesData, + chartType: chartTypes.ScatterChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarStepTimeSeriesOptions, + data: zoomBarDemos.zoomBarStepTimeSeriesData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeries15secondsOptions, + data: zoomBarDemos.zoomBarLineTimeSeries15secondsData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeriesInitDomainOptions, + data: zoomBarDemos.zoomBarLineTimeSeriesInitDomainData, + chartType: chartTypes.LineChart, + isDemoExample: false + } + ] } ] as any; diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index 845e1bd1a7..4ac497de89 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -418,58 +418,3 @@ export const lineTimeSeriesAllLabelsInPrimaryFormatOptions = { addSpaceOnEdges: 0 } }; - -// ZoomBar -export const lineTimeSeriesDataZoomBar = { - labels: ["Qty"], - datasets: [ - { - label: "Dataset 1", - data: [ - { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, - { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, - { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, - { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, - { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } - ] - } - ] -}; -const selectionStartFun = (selection, domain) => { - console.log("ZoomBar SelectionStart callback!"); - console.log(selection); - console.log(domain); -}; -const selectionInProgressFun = (selection, domain) => { - console.log("ZoomBar SelectionInProgress callback!"); - console.log(selection); - console.log(domain); -}; -const selectionEndFun = (selection, domain) => { - console.log("ZoomBar SelectionEnd callback!"); - console.log(selection); - console.log(domain); -}; - -const initZoomDomain = [ - new Date(2020, 11, 10, 23, 59, 25), - new Date(2020, 11, 11, 0, 0, 25) -]; -export const lineTimeSeriesZoomBarOptions = { - title: "Line (time series) - zoom-bar enabled", - axes: { - left: {}, - bottom: { - scaleType: "time" - } - }, - zoomBar: { - enabled: true, - initZoomDomain: initZoomDomain, - selectionStart: selectionStartFun, - selectionInProgress: selectionInProgressFun, - selectionEnd: selectionEndFun - } -}; diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts new file mode 100644 index 0000000000..5c97e33973 --- /dev/null +++ b/packages/core/demo/data/zoom-bar.ts @@ -0,0 +1,88 @@ +import * as timeSeriesAxisChart from "./time-series-axis"; +import * as barChart from "./bar"; +import * as bubbleChart from "./bubble"; +import * as lineChart from "./line"; +import * as scatterChart from "./scatter"; +import * as stepChart from "./step"; + +// default function for selection callback +const selectionStartFun = (selection, domain) => { + console.log("ZoomBar SelectionStart callback!"); + console.log(selection); + console.log(domain); +}; +const selectionInProgressFun = (selection, domain) => { + console.log("ZoomBar SelectionInProgress callback!"); + console.log(selection); + console.log(domain); +}; +const selectionEndFun = (selection, domain) => { + console.log("ZoomBar SelectionEnd callback!"); + console.log(selection); + console.log(domain); +}; + +const initZoomDomain = [ + new Date(2020, 11, 10, 23, 59, 25), + new Date(2020, 11, 11, 0, 0, 25) +]; + +const defaultZoomBarOptions = { + enabled: true, + initZoomDomain: undefined, + selectionStart: selectionStartFun, + selectionInProgress: selectionInProgressFun, + selectionEnd: selectionEndFun +}; + +// utility function to update title and enable zoomBar option +const updateOptions = (options) => { + options["title"] = options["title"] + " - Zoom bar enabled"; + options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); + return options; +}; + +export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; +export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( + Object.assign({}, barChart.simpleBarTimeSeriesOptions) +); + +export const zoomBarStackedBarTimeSeriesData = + barChart.stackedBarTimeSeriesData; +export const zoomBarStackedBarTimeSeriesOptions = updateOptions( + Object.assign({}, barChart.stackedBarTimeSeriesOptions) +); + +export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; +export const zoomBarBubbleTimeSeriesOptions = updateOptions( + Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) +); + +export const zoomBarLineTimeSeriesData = lineChart.lineTimeSeriesData; +export const zoomBarLineTimeSeriesOptions = updateOptions( + Object.assign({}, lineChart.lineTimeSeriesOptions) +); + +export const zoomBarScatterTimeSeriesData = scatterChart.scatterTimeSeriesData; +export const zoomBarScatterTimeSeriesOptions = updateOptions( + Object.assign({}, scatterChart.scatterTimeSeriesOptions) +); + +export const zoomBarStepTimeSeriesData = stepChart.stepTimeSeriesData; +export const zoomBarStepTimeSeriesOptions = updateOptions( + Object.assign({}, stepChart.stepTimeSeriesOptions) +); + +export const zoomBarLineTimeSeries15secondsData = + timeSeriesAxisChart.lineTimeSeriesData15seconds; +export const zoomBarLineTimeSeries15secondsOptions = updateOptions( + Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) +); + +export const zoomBarLineTimeSeriesInitDomainData = + timeSeriesAxisChart.lineTimeSeriesData15seconds; +export const zoomBarLineTimeSeriesInitDomainOptions = updateOptions( + Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) +); +zoomBarLineTimeSeriesInitDomainOptions["title"] += " with initial zoom domain"; +zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initZoomDomain = initZoomDomain; From 0ebeb1883f90722a3971d266593c624debc5813e Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 23 Jun 2020 10:08:11 +0800 Subject: [PATCH 243/510] fix: apply cover to stacked bar --- packages/core/src/components/component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 5664f29ec8..6aa5e17ee0 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -100,6 +100,7 @@ export class Component { this.type === "area-stacked" || this.type === "grouped-bar" || this.type === "simple-bar" || + this.type === "stacked-bar" || this.type === "scatter-stacked" ) { return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); From 0d76779828377ceb12d18a816f9ac24a86e257c7 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 24 Jun 2020 12:23:11 +0800 Subject: [PATCH 244/510] feat: create ZoomBar event and handler - use Events.ZoomBar.UPDATE to handle axesMargins update --- .../core/src/components/axes/two-dimensional-axes.ts | 4 +++- packages/core/src/components/axes/zoom-bar.ts | 9 ++++++++- packages/core/src/interfaces/events.ts | 7 +++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/axes/two-dimensional-axes.ts b/packages/core/src/components/axes/two-dimensional-axes.ts index d95e70e557..aa8b1cccb3 100644 --- a/packages/core/src/components/axes/two-dimensional-axes.ts +++ b/packages/core/src/components/axes/two-dimensional-axes.ts @@ -5,6 +5,7 @@ import { Axis } from "./axis"; import { Tools } from "../../tools"; import { DOMUtils } from "../../services"; import { Threshold } from "../essentials/threshold"; +import { Events } from "./../../interfaces"; export class TwoDimensionalAxes extends Component { type = "2D-axes"; @@ -115,7 +116,8 @@ export class TwoDimensionalAxes extends Component { this.margins = Object.assign(this.margins, margins); // also set new margins to model to allow external components to access - this.model.set({ axesMargins: this.margins }, { animate: false }); + this.model.set({ axesMargins: this.margins }, { skipUpdate: true }); + this.services.events.dispatchEvent(Events.ZoomBar.UPDATE); Object.keys(this.children).forEach((childKey) => { const child = this.children[childKey]; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0137960f8d..a6d85ed844 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -1,7 +1,7 @@ // Internal Imports import { Component } from "../component"; import { Tools } from "../../tools"; -import { ScaleTypes } from "../../interfaces"; +import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports @@ -20,6 +20,10 @@ export class ZoomBar extends Component { brush = brushX(); init() { + this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); + // get initZoomDomain const zoomBarOptions = this.model.getOptions().zoomBar; if (zoomBarOptions.initZoomDomain !== undefined) { @@ -353,5 +357,8 @@ export class ZoomBar extends Component { destroy() { this.brush.on("start brush end", null); // remove event listener + this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); } } diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 2850204dc5..2a25fd5992 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -17,6 +17,13 @@ export enum Model { UPDATE = "model-update" } +/** + * enum of all events related to the zoom-bar + */ +export enum ZoomBar { + UPDATE = "zoom-bar-update" +} + /** * enum of all axis-related events */ From b63224a78e194eb8aa53b50d148744da2656d698 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 24 Jun 2020 12:48:50 +0800 Subject: [PATCH 245/510] fix: fix merge error - move ZoomBarOptions from BaseChartOptions to AxisChartOptions --- packages/core/src/interfaces/charts.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 5ff08b4f9d..372cd3a9ce 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -46,10 +46,6 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; - /** - * zoombar configuration - */ - zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ @@ -122,6 +118,10 @@ export interface AxisChartOptions extends BaseChartOptions { axes?: AxesOptions; grid?: GridOptions; timeScale?: TimeScaleOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; } /** @@ -218,7 +218,7 @@ export interface StackedAreaChartOptions extends ScatterChartOptions { | string | { name: string; - }; + }; } /** From 2e864c468ac86ddf3b80472786a0d74233dbc884 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 14:26:51 +0800 Subject: [PATCH 246/510] feat: update color for non-highlighted areas - remove zoombar graph line - add zoombar unselected graph area with clip - add zoombar baseline - clear d3.brush selection style --- packages/core/src/components/axes/zoom-bar.ts | 142 +++++++++++------- .../core/src/styles/components/_zoom-bar.scss | 16 +- 2 files changed, 97 insertions(+), 61 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index a6d85ed844..d04c7a4eeb 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,6 +13,8 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + clipId = "zoomBarClip"; + height = 32; ogXScale: any; @@ -72,9 +74,7 @@ export class ZoomBar extends Component { .attr("x", axesLeftMargin) .attr("y", 0) .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "white") - .attr("stroke", "#e8e8e8"); + .attr("height", "100%"); if (mainXScale) { const displayData = this.model.getDisplayData(); @@ -153,63 +153,44 @@ export class ZoomBar extends Component { ) ) .curve(this.services.curves.getD3Curve()); - // .defined((d: any, i) => { - // if (zoomDomain) { - // const dTimestamp = +d.label; - - // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; - // } - - // return true; - // }); - const lineGraph = DOMUtils.appendOrSelect( - container, - "path.zoom-graph-line" - ) - .datum(stackDataArray) - .transition( - this.services.transitions.getTransition( - "zoom-pan-line-update", - animate - ) - ) - .attr("d", lineGenerator); - - const areaGenerator = area() - .x((d, i) => - cartesianScales.getValueFromScale( - xScale, - mainXScaleType, - mainXAxisPosition, + const accessorFunc = (scale, scaleType, axisPosition) => { + return (d, i) => { + return cartesianScales.getValueFromScale( + scale, + scaleType, + axisPosition, d, i - ) - ) - .y0(this.height) - .y1( - (d, i) => - this.height - - cartesianScales.getValueFromScale( - yScale, - mainYScaleType, - mainYAxisPosition, - d, - i - ) - ); - - const areaGraph = DOMUtils.appendOrSelect( + ); + }; + }; + this.renderZoomBarArea( container, - "path.zoom-graph-area" - ) - .datum(stackDataArray) - .transition( - this.services.transitions.getTransition( - "zoom-pan-area-update", - animate - ) - ) - .attr("d", areaGenerator); + "path.zoom-graph-area-unselected", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + stackDataArray, + animate, + undefined + ); + this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); + this.renderZoomBarArea( + container, + "path.zoom-graph-area", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + stackDataArray, + animate, + "zoomBarClip" + ); + const baselineGenerator = line()([ + [axesLeftMargin, this.height], + [width, this.height] + ]); + const zoomBaseline = DOMUtils.appendOrSelect( + container, + "path.zoom-bg-baseline" + ).attr("d", baselineGenerator); const brushEventListener = () => { const selection = event.selection; @@ -352,7 +333,52 @@ export class ZoomBar extends Component { }) .attr("y", handleYBarDiff) .attr("width", handleBarWidth) - .attr("height", handleBarHeight); + .attr("height", handleBarHeight) + .attr("cursor", "ew-resize"); + + this.updateClipPath( + svg, + this.clipId, + selection[0], + 0, + selection[1] - selection[0], + this.height + ); + } + + renderZoomBarArea( + container, + querySelector, + xFunc, + y1Func, + datum, + animate, + clipId + ) { + const areaGenerator = area() + .x((d, i) => xFunc(d, i)) + .y0(this.height) + .y1((d, i) => this.height - y1Func(d, i)); + + const areaGraph = DOMUtils.appendOrSelect(container, querySelector) + .datum(datum) + .attr("d", areaGenerator); + + if (clipId) { + areaGraph.attr("clip-path", `url(#${clipId})`); + } + } + + updateClipPath(svg, clipId, x, y, width, height) { + const zoomBarClipPath = DOMUtils.appendOrSelect(svg, `clipPath`).attr( + "id", + clipId + ); + DOMUtils.appendOrSelect(zoomBarClipPath, "rect") + .attr("x", x) + .attr("y", y) + .attr("width", width) + .attr("height", height); } destroy() { diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 59019956a8..32dfc32842 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -4,15 +4,19 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: $ui-01; } - path.zoom-graph-line { + path.zoom-bg-baseline { stroke: $ui-04; - stroke-width: 3; - fill: none; + stroke-width: 2; } path.zoom-graph-area { fill: $ui-03; stroke: $ui-04; + stroke-width: 1; + } + path.zoom-graph-area-unselected { + fill: $ui-01; + stroke: none; } g.brush rect.handle { @@ -22,4 +26,10 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { g.brush rect.handle-bar { fill: $ui-02; } + + // clear d3.brush selection style + g.brush rect.selection { + fill: none; + stroke: none; + } } From 8ed324059e5d5465141d30a6e188b9d401b5b0b6 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 15:01:58 +0800 Subject: [PATCH 247/510] fix: avoid extra brush handle update --- packages/core/src/components/axes/zoom-bar.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d04c7a4eeb..cc5fd6bfe3 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -218,8 +218,10 @@ export class ZoomBar extends Component { this.brush ); - if ( - zoomDomain === undefined || + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + } else if ( zoomDomain[0].valueOf() === zoomDomain[1].valueOf() ) { brushArea.call(this.brush.move, xScale.range()); // default to full range @@ -229,8 +231,16 @@ export class ZoomBar extends Component { ); } else { const selected = zoomDomain.map((domain) => xScale(domain)); - brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle(this.getContainerSVG(), selected); + if (selected[1] - selected[0] < 1) { + // initialization not completed yet + // don't update brushHandle to avoid flash + } else { + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle( + this.getContainerSVG(), + selected + ); + } } } } From e3e75452e265fd8395bd8963b731465f36fd7b87 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 15:34:34 +0800 Subject: [PATCH 248/510] fix: fix the brush handle style --- packages/core/src/components/axes/zoom-bar.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index cc5fd6bfe3..840d15198f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -299,18 +299,14 @@ export class ZoomBar extends Component { } updateBrushHandle(svg, selection) { - // @todo the handle size, height are calculated by d3 library - // need to figure out how to override the value - const handleWidth = 6; - const handleHeight = 38; + const handleWidth = 5; + const handleHeight = this.height; const handleXDiff = -handleWidth / 2; - const handleYDiff = -(handleHeight - this.height) / 2; - const handleBarWidth = 2; + const handleBarWidth = 1; const handleBarHeight = 12; const handleBarXDiff = -handleBarWidth / 2; - const handleYBarDiff = - (handleHeight - handleBarHeight) / 2 + handleYDiff; + const handleYBarDiff = (handleHeight - handleBarHeight) / 2; // handle svg.select("g.brush") .selectAll("rect.handle") @@ -322,7 +318,7 @@ export class ZoomBar extends Component { return selection[1] + handleXDiff; } }) - .attr("y", handleYDiff) + .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) .style("display", null); // always display From 75eb28d2300336678af9f5431462652d2138e431 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 14:00:53 +0800 Subject: [PATCH 249/510] refactor: remove unnecessary svg.brush-container --- packages/core/src/components/axes/brush.ts | 42 ++++++++++++---------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 0a22fe0d79..517a73eb80 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -30,16 +30,10 @@ export class Brush extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); const mainYScale = this.services.cartesianScales.getMainYScale(); - const [xScaleStart, xScaleEnd] = mainXScale.range(); const [yScaleEnd, yScaleStart] = mainYScale.range(); - const container = DOMUtils.appendOrSelect(svg, "svg.brush-container") - .attr("width", "100%") - .attr("height", "100%") - .attr("opacity", 1); - if (mainXScale) { const displayData = this.model.getDisplayData(); @@ -85,7 +79,7 @@ export class Brush extends Component { } const brushed = () => { - let selection = event.selection; + const selection = event.selection; if (selection !== null) { // get current zoomDomain @@ -99,19 +93,24 @@ export class Brush extends Component { xScale.invert(selection[0]), xScale.invert(selection[1]) ]; - + // check if slected start time and end time are the same - if(newDomain[0].valueOf() === newDomain[1].valueOf()) { + if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoombar behavior: set to default full range - newDomain = extent(stackDataArray, (d: any) => d.date); + newDomain = extent( + stackDataArray, + (d: any) => d.date + ); } // only if the brush event comes from mouseup event if (event.sourceEvent != null) { // only if zoomDomain needs update if ( - zoomDomain[0].valueOf() !== newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== newDomain[1].valueOf() + zoomDomain[0].valueOf() !== + newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== + newDomain[1].valueOf() ) { this.model.set( { zoomDomain: newDomain }, @@ -119,12 +118,16 @@ export class Brush extends Component { ); } // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; + const zoomBarOptions = this.model.getOptions() + .zoomBar; if ( zoomBarOptions.selectionEnd !== undefined && event.type === "end" ) { - zoomBarOptions.selectionEnd(selection, newDomain); + zoomBarOptions.selectionEnd( + selection, + newDomain + ); } } } @@ -137,12 +140,15 @@ export class Brush extends Component { ]) .on("end", brushed); - const brushArea = DOMUtils.appendOrSelect(svg, "g.chart-brush").call( - brush - ); + const brushArea = DOMUtils.appendOrSelect( + svg, + "g.chart-brush" + ).call(brush); // no need for having default brush selection // @todo try to hide brush after selection - setTimeout(()=> {brushArea.call(brush.move);}, 0); + setTimeout(() => { + brushArea.call(brush.move); + }, 0); } } } From 7f5834a8f64a09e6f6e7a7e0c080dc649ca81ff7 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 14:17:17 +0800 Subject: [PATCH 250/510] fix: move brush layer to back to allow tooltips in graph --- packages/core/src/axis-chart.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 543d0ac6d4..3690c7b924 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -6,7 +6,7 @@ import { LegendPositions, ChartConfig, AxisChartOptions -} from "./interfaces/index"; +} from "./interfaces"; import { Brush, LayoutComponent, @@ -15,10 +15,10 @@ import { AxisChartsTooltip, Spacer, ZoomBar -} from "./components/index"; +} from "./components"; import { Tools } from "./tools"; -import { CartesianScales, Curves } from "./services/index"; +import { CartesianScales, Curves } from "./services"; export class AxisChart extends Chart { services: any = Object.assign(this.services, { @@ -49,8 +49,16 @@ export class AxisChart extends Chart { } }; - !!this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ? - graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + if ( + this.model.getOptions().zoomBar && + this.model.getOptions().zoomBar.enabled + ) { + graphFrameComponents.splice( + 1, + 0, + new Brush(this.model, this.services) + ); + } const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, From 7b074b6c0cf48a22e19006d80bdca71358069301 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 16:18:27 +0800 Subject: [PATCH 251/510] feat: add stacked-area chart with zoom bar in storybook --- packages/core/demo/data/index.ts | 6 ++++++ packages/core/demo/data/zoom-bar.ts | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 99a4cbad47..b826eceb22 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -754,6 +754,12 @@ let allDemoGroups = [ { title: "Zoom bar", demos: [ + { + options: zoomBarDemos.zoomBarStackedAreaTimeSeriesOptions, + data: zoomBarDemos.zoomBarStackedAreaTimeSeriesData, + chartType: chartTypes.StackedAreaChart, + isDemoExample: false + }, { options: zoomBarDemos.zoomBarSimpleBarTimeSeriesOptions, data: zoomBarDemos.zoomBarSimpleBarTimeSeriesData, diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 5c97e33973..0016bbcad7 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -1,9 +1,10 @@ -import * as timeSeriesAxisChart from "./time-series-axis"; +import * as areaChart from "./area"; import * as barChart from "./bar"; import * as bubbleChart from "./bubble"; import * as lineChart from "./line"; import * as scatterChart from "./scatter"; import * as stepChart from "./step"; +import * as timeSeriesAxisChart from "./time-series-axis"; // default function for selection callback const selectionStartFun = (selection, domain) => { @@ -42,6 +43,12 @@ const updateOptions = (options) => { return options; }; +export const zoomBarStackedAreaTimeSeriesData = + areaChart.stackedAreaTimeSeriesData; +export const zoomBarStackedAreaTimeSeriesOptions = updateOptions( + Object.assign({}, areaChart.stackedAreaTimeSeriesOptions) +); + export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( Object.assign({}, barChart.simpleBarTimeSeriesOptions) From a1bea49574e6cdff54633aefd9ed135203f756f7 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 14:28:08 +0800 Subject: [PATCH 252/510] refactor: delete unused axis-zoom service --- packages/core/src/services/axis-zoom.ts | 60 ------------------------- packages/core/src/services/index.ts | 1 - 2 files changed, 61 deletions(-) delete mode 100644 packages/core/src/services/axis-zoom.ts diff --git a/packages/core/src/services/axis-zoom.ts b/packages/core/src/services/axis-zoom.ts deleted file mode 100644 index 8dbb2b3d80..0000000000 --- a/packages/core/src/services/axis-zoom.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Internal Imports -import { Service } from "./service"; -import { Tools } from "../tools"; - -// D3 Imports -import { brushX } from "d3-brush"; -import { event } from "d3-selection"; - -export class AxisZoom extends Service { - brush: any; - - brushed(e) { - if (event) { - const selectedRange = event.selection; - - const mainXAxis = this.services.axes.getMainXAxis(); - const mainXAxisRangeStart = mainXAxis.scale.range()[0]; - - const newDomain = selectedRange.map(d => d + mainXAxisRangeStart) - .map(mainXAxis.scale.invert, mainXAxis.scale); - - this.model.set({ - zoomDomain: newDomain.map(d => new Date(+d)) - }); - } - } - - // We need a custom debounce function here - // because of the async nature of d3.event - debounceWithD3Event(func, wait) { - let timeout; - return function() { - const e = Object.assign({}, event); - const context = this; - const later = function() { - timeout = null; - func.apply(context, [e]); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - } - - getZoomInstance() { - const mainXAxis = this.services.axes.getMainXAxis(); - const mainYAxis = this.services.axes.getMainYAxis(); - const xMaxRange = mainXAxis.scale.range()[1]; - const yMaxRange = mainYAxis.scale.range()[0]; - - this.brush = brushX() - .extent([ - [0, 0], - [xMaxRange, yMaxRange] - ]) - .on("end", this.brushed.bind(this)); - - - return this.brush; - } -} diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index 4e9fcc0e17..d999a07207 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -4,6 +4,5 @@ export * from "./essentials/events"; export * from "./essentials/transitions"; // MISC -export * from "./axis-zoom"; export * from "./scales-cartesian"; export * from "./curves"; From 52bb79b3574a71e8830f18396360520a4959bd33 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 14:41:59 +0800 Subject: [PATCH 253/510] feat: allow addSpaceOnEdges to work with zoom bar - extends domain in zoom bar and brush --- packages/core/src/components/axes/brush.ts | 6 ++- packages/core/src/components/axes/zoom-bar.ts | 13 +++++-- .../core/src/services/scales-cartesian.ts | 39 +++++++++++-------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 517a73eb80..efc35a735d 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -71,7 +71,11 @@ export class Brush extends Component { let zoomDomain = this.model.get("zoomDomain"); if (zoomDomain === undefined) { - zoomDomain = extent(stackDataArray, (d: any) => d.date); // default to full range + // default to full range with extended domain + zoomDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) + ); this.model.set( { zoomDomain: zoomDomain }, { animate: false } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 840d15198f..b3b4b2b571 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -120,9 +120,16 @@ export class ZoomBar extends Component { useAttrs: true }); - xScale - .range([axesLeftMargin, width]) - .domain(extent(stackDataArray, (d: any) => d.date)); + // @todo could be a better function to extend domain with default value + const xDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) + ); + // add value 0 to the extended domain for zoom bar area graph + stackDataArray.unshift({ date: xDomain[0], value: 0 }); + stackDataArray.push({ date: xDomain[1], value: 0 }); + + xScale.range([axesLeftMargin, width]).domain(xDomain); yScale .range([0, this.height - 6]) diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 162b0c833f..9614fea1c2 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -5,7 +5,6 @@ import { AxisPositions, CartesianOrientations, ScaleTypes, - AxesOptions, ThresholdOptions } from "../interfaces"; import { Tools } from "../tools"; @@ -181,7 +180,13 @@ export class CartesianScales extends Service { return this.scales[this.getMainYAxisPosition()]; } - getValueFromScale(scale: any, scaleType: ScaleTypes, axisPosition: AxisPositions, datum: any, index?: number) { + getValueFromScale( + scale: any, + scaleType: ScaleTypes, + axisPosition: AxisPositions, + datum: any, + index?: number + ) { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); const axisOptions = axesOptions[axisPosition]; @@ -201,14 +206,23 @@ export class CartesianScales extends Service { return scaledValue; } - getValueThroughAxisPosition(axisPosition: AxisPositions, datum: any, index?: number) { + getValueThroughAxisPosition( + axisPosition: AxisPositions, + datum: any, + index?: number + ) { const scaleType = this.scaleTypes[axisPosition]; const scale = this.scales[axisPosition]; - return this.getValueFromScale(scale, scaleType, axisPosition, datum, index); + return this.getValueFromScale( + scale, + scaleType, + axisPosition, + datum, + index + ); } - getDomainValue(d, i) { return this.getValueThroughAxisPosition(this.domainAxisPosition, d, i); } @@ -304,7 +318,7 @@ export class CartesianScales extends Service { const domainScale = this.getDomainScale(); // Find the highest threshold for the domain const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value, + (a, b) => b.value - a.value )[0]; const scaleType = this.getScaleTypeByPosition(domainAxisPosition); @@ -318,7 +332,7 @@ export class CartesianScales extends Service { return { threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value), + scaleValue: domainScale(highestThreshold.value) }; } @@ -338,12 +352,12 @@ export class CartesianScales extends Service { const rangeScale = this.getRangeScale(); // Find the highest threshold for the range const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value, + (a, b) => b.value - a.value )[0]; return { threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value), + scaleValue: rangeScale(highestThreshold.value) }; } @@ -458,12 +472,6 @@ export class CartesianScales extends Service { return map(displayData, (d) => d[mapsTo]).keys(); } - // If scale is a TIME scale and zoomDomain is available, return Date array as the domain - const zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain && axisOptions && scaleType === ScaleTypes.TIME) { - return zoomDomain.map(d => new Date(d)); - } - // Get the extent of the domain let domain; let allDataValues; @@ -483,7 +491,6 @@ export class CartesianScales extends Service { domain = extent(allDataValues); domain = this.extendsDomain(axisPosition, domain); - return domain; } From 6686b4c57b5053f585a7dba2f5829ee6276136c0 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 16:12:13 +0800 Subject: [PATCH 254/510] refactor: code refactoring - reduce code differences from master branch --- .../core/src/services/scales-cartesian.ts | 119 +++++++++--------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 9614fea1c2..11be646307 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -302,65 +302,6 @@ export class CartesianScales extends Service { } } - getHighestDomainThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const domainAxisPosition = this.getDomainAxisPosition(); - - const { thresholds } = axesOptions[domainAxisPosition]; - - if (!thresholds) { - return null; - } - - const domainScale = this.getDomainScale(); - // Find the highest threshold for the domain - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - const scaleType = this.getScaleTypeByPosition(domainAxisPosition); - if ( - scaleType === ScaleTypes.TIME && - (typeof highestThreshold.value === "string" || - highestThreshold.value.getTime === undefined) - ) { - highestThreshold.value = new Date(highestThreshold.value); - } - - return { - threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value) - }; - } - - getHighestRangeThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const rangeAxisPosition = this.getRangeAxisPosition(); - - const { thresholds } = axesOptions[rangeAxisPosition]; - - if (!thresholds) { - return null; - } - - const rangeScale = this.getRangeScale(); - // Find the highest threshold for the range - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - return { - threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value) - }; - } - protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); @@ -491,6 +432,7 @@ export class CartesianScales extends Service { domain = extent(allDataValues); domain = this.extendsDomain(axisPosition, domain); + return domain; } @@ -521,6 +463,65 @@ export class CartesianScales extends Service { return scale; } + + protected getHighestDomainThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const domainAxisPosition = this.getDomainAxisPosition(); + + const { thresholds } = axesOptions[domainAxisPosition]; + + if (!thresholds) { + return null; + } + + const domainScale = this.getDomainScale(); + // Find the highest threshold for the domain + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value + )[0]; + + const scaleType = this.getScaleTypeByPosition(domainAxisPosition); + if ( + scaleType === ScaleTypes.TIME && + (typeof highestThreshold.value === "string" || + highestThreshold.value.getTime === undefined) + ) { + highestThreshold.value = new Date(highestThreshold.value); + } + + return { + threshold: highestThreshold, + scaleValue: domainScale(highestThreshold.value) + }; + } + + protected getHighestRangeThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const rangeAxisPosition = this.getRangeAxisPosition(); + + const { thresholds } = axesOptions[rangeAxisPosition]; + + if (!thresholds) { + return null; + } + + const rangeScale = this.getRangeScale(); + // Find the highest threshold for the range + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value + )[0]; + + return { + threshold: highestThreshold, + scaleValue: rangeScale(highestThreshold.value) + }; + } } function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { From bd2ac26bdbc9400c97dab90575c02cf16d9e349f Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 23:25:56 +0800 Subject: [PATCH 255/510] fix: display multiline tooltip with zoom bar - use ruler backdrop as brush area - svg.chart-grid-backdrop --- packages/core/src/axis-chart.ts | 6 +----- packages/core/src/components/axes/brush.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 3690c7b924..f5559f6f56 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -53,11 +53,7 @@ export class AxisChart extends Chart { this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ) { - graphFrameComponents.splice( - 1, - 0, - new Brush(this.model, this.services) - ); + graphFrameComponents.push(new Brush(this.model, this.services)); } const graphFrameComponent = { id: "graph-frame", diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index efc35a735d..35f12159b9 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -146,7 +146,7 @@ export class Brush extends Component { const brushArea = DOMUtils.appendOrSelect( svg, - "g.chart-brush" + "svg.chart-grid-backdrop" ).call(brush); // no need for having default brush selection // @todo try to hide brush after selection From a146207690fb71c17c224a8bbf35227333d64c5f Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:42:26 +0800 Subject: [PATCH 256/510] feat: create ZoomBarOptions and default configuration - default to disable zoombar --- packages/core/src/interfaces/charts.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 372cd3a9ce..4956c8895b 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -46,6 +46,10 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ From 190a48d9f8b0bf6a7d053720c05af1233ec6e5c1 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 2 Jul 2020 14:21:12 +0800 Subject: [PATCH 257/510] fix: chart brush with correct range --- packages/core/src/components/axes/brush.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 35f12159b9..c06fdac4c0 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -90,7 +90,7 @@ export class Brush extends Component { zoomDomain = this.model.get("zoomDomain"); // create xScale based on current zoomDomain const xScale = scaleTime() - .range([axesLeftMargin, width]) + .range([0, width]) .domain(zoomDomain); let newDomain = [ @@ -101,9 +101,9 @@ export class Brush extends Component { // check if slected start time and end time are the same if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoombar behavior: set to default full range - newDomain = extent( - stackDataArray, - (d: any) => d.date + newDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) ); } @@ -139,15 +139,19 @@ export class Brush extends Component { const brush = brushX() .extent([ - [xScaleStart, 0], + [0, 0], [width, yScaleEnd] ]) .on("end", brushed); - - const brushArea = DOMUtils.appendOrSelect( + const backdrop = DOMUtils.appendOrSelect( svg, "svg.chart-grid-backdrop" + ); + const brushArea = DOMUtils.appendOrSelect( + backdrop, + "g.chart-brush" ).call(brush); + // no need for having default brush selection // @todo try to hide brush after selection setTimeout(() => { From 42d8a16d4e143a62c2c1d3b220b1362628ad7c5c Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 2 Jul 2020 15:59:02 +0800 Subject: [PATCH 258/510] fix: remove graph out of zoom domain --- .../core/src/components/graphs/bar-grouped.ts | 4 ++++ .../core/src/components/graphs/bar-simple.ts | 5 +++++ .../core/src/components/graphs/bar-stacked.ts | 4 ++++ packages/core/src/components/graphs/bar.ts | 12 ++++++++++++ packages/core/src/components/graphs/scatter.ts | 16 ++++++++++++++++ 5 files changed, 41 insertions(+) diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 48377b5957..07f12e8c64 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -123,6 +123,10 @@ export class GroupedBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(0); const y1 = this.services.cartesianScales.getRangeValue(d.value); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } return Tools.generateSVGPathString( { x0, x1, y0, y1 }, this.services.cartesianScales.getOrientation() diff --git a/packages/core/src/components/graphs/bar-simple.ts b/packages/core/src/components/graphs/bar-simple.ts index 2cbf168934..aeb72950d1 100644 --- a/packages/core/src/components/graphs/bar-simple.ts +++ b/packages/core/src/components/graphs/bar-simple.ts @@ -70,6 +70,11 @@ export class SimpleBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(0); const y1 = this.services.cartesianScales.getRangeValue(d, i); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } + return Tools.generateSVGPathString( { x0, x1, y0, y1 }, this.services.cartesianScales.getOrientation() diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index c49c9f3a59..e2dcbc293a 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -97,6 +97,10 @@ export class StackedBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(d[0], i); let y1 = this.services.cartesianScales.getRangeValue(d[1], i); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } // Add the divider gap if ( Math.abs(y1 - y0) > 0 && diff --git a/packages/core/src/components/graphs/bar.ts b/packages/core/src/components/graphs/bar.ts index dff83021d5..59c18f5685 100644 --- a/packages/core/src/components/graphs/bar.ts +++ b/packages/core/src/components/graphs/bar.ts @@ -16,4 +16,16 @@ export class Bar extends Component { return Math.min(options.bars.maxWidth, mainXScale.step() / 2); } + + protected isOutOfZoomDomain(x0: number, x1: number) { + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain !== undefined) { + const domainScale = this.services.cartesianScales.getDomainScale(); + return ( + x0 < domainScale(zoomDomain[0]) || + x1 > domainScale(zoomDomain[1]) + ); + } + return false; + } } diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index 0ceab80518..2d8074dc49 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -37,6 +37,19 @@ export class Scatter extends Component { } } + filterOutOfDomain(data) { + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain !== undefined) { + return data.filter( + (d) => + d[domainIdentifier] > zoomDomain[0] && + d[domainIdentifier] < zoomDomain[1] + ); + } + return data; + } + render(animate: boolean) { // Grab container SVG const svg = this.getContainerSVG(); @@ -64,6 +77,9 @@ export class Scatter extends Component { ); } + // filter out of domain data + scatterData = this.filterOutOfDomain(scatterData); + // Update data on dot groups const circles = svg .selectAll("circle.dot") From acb9f8d680cd709ed79985696d00ea296209ea44 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 12:44:35 +0800 Subject: [PATCH 259/510] refactor: code refactoring - create getZoomBarData(), getDefaultZoomBarDomain() in model - remove unused code --- packages/core/src/components/axes/brush.ts | 193 ++++------- packages/core/src/components/axes/cover.ts | 25 +- packages/core/src/components/axes/zoom-bar.ts | 312 ++++++++---------- packages/core/src/model.ts | 46 ++- 4 files changed, 256 insertions(+), 320 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index c06fdac4c0..16f21dce44 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -15,149 +15,80 @@ export class Brush extends Component { render(animate = true) { const svg = this.parent; + const backdrop = DOMUtils.appendOrSelect( + svg, + "svg.chart-grid-backdrop" + ); + const { width, height } = DOMUtils.getSVGElementSize(backdrop, { + useAttrs: true + }); + const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( mainXAxisPosition ); - // get axes margins - let axesLeftMargin = 0; - const axesMargins = this.model.get("axesMargins"); - if (axesMargins && axesMargins.left) { - axesLeftMargin = axesMargins.left; - } - const mainXScale = this.services.cartesianScales.getMainXScale(); - const mainYScale = this.services.cartesianScales.getMainYScale(); - - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const [yScaleEnd, yScaleStart] = mainYScale.range(); - - if (mainXScale) { - const displayData = this.model.getDisplayData(); - - if (mainXScaleType === ScaleTypes.TIME) { - // Get all date values provided in data - // @todo - Could be re-used through the model - let allDates = []; - displayData.forEach((data) => { - allDates = allDates.concat(Number(data.date)); - }); - allDates = Tools.removeArrayDuplicates(allDates).sort(); - - // Go through all date values - // And get corresponding data from each dataset - const stackDataArray = allDates.map((date) => { - let count = 0; - let correspondingSum = 0; - const correspondingData = {}; - - displayData.forEach((data) => { - if (Number(data.date) === Number(date)) { - ++count; - correspondingSum += data.value; - } - }); - correspondingData["date"] = date; - correspondingData["value"] = correspondingSum; - - return correspondingData; - }); - const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); - - let zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain === undefined) { - // default to full range with extended domain - zoomDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - this.model.set( - { zoomDomain: zoomDomain }, - { animate: false } - ); - } - - const brushed = () => { - const selection = event.selection; - - if (selection !== null) { - // get current zoomDomain - zoomDomain = this.model.get("zoomDomain"); - // create xScale based on current zoomDomain - const xScale = scaleTime() - .range([0, width]) - .domain(zoomDomain); - - let newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // check if slected start time and end time are the same - if (newDomain[0].valueOf() === newDomain[1].valueOf()) { - // same as d3 behavior and zoombar behavior: set to default full range - newDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - } + 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.model.getDefaultZoomBarDomain(); + this.model.set({ zoomDomain: zoomDomain }, { animate: false }); + } - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { - // only if zoomDomain needs update - if ( - zoomDomain[0].valueOf() !== - newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== - newDomain[1].valueOf() - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } - // call external callback - const zoomBarOptions = this.model.getOptions() - .zoomBar; - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd( - selection, - newDomain - ); - } - } + const brushed = () => { + const selection = event.selection; + + if (selection !== null) { + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([0, width]) + .domain(zoomDomain); + + let newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + // 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.model.getDefaultZoomBarDomain(); } - }; - const brush = brushX() - .extent([ - [0, 0], - [width, yScaleEnd] - ]) - .on("end", brushed); - const backdrop = DOMUtils.appendOrSelect( - svg, - "svg.chart-grid-backdrop" - ); - const brushArea = DOMUtils.appendOrSelect( - backdrop, - "g.chart-brush" - ).call(brush); + // only if zoomDomain needs update + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + } - // no need for having default brush selection - // @todo try to hide brush after selection - setTimeout(() => { - brushArea.call(brush.move); - }, 0); - } + // clear brush selection + brushArea.call(brush.move, null); + } + }; + + // leave some space to display selection strokes besides axis + const brush = brushX() + .extent([ + [2, 0], + [width - 1, height - 1] + ]) + .on("end", brushed); + + const brushArea = DOMUtils.appendOrSelect( + backdrop, + "g.chart-brush" + ).call(brush); } } } diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts index 16179b45c5..2536c9ef40 100644 --- a/packages/core/src/components/axes/cover.ts +++ b/packages/core/src/components/axes/cover.ts @@ -1,13 +1,7 @@ // Internal Imports import { Component } from "../component"; -import { Tools } from "../../tools"; import { DOMUtils } from "../../services"; -// D3 Imports -import { axisBottom, axisLeft } from "d3-axis"; -import { mouse, select } from "d3-selection"; -import { TooltipTypes, Events } from "../../interfaces"; - export class Cover extends Component { type = "cover"; @@ -18,19 +12,21 @@ export class Cover extends Component { this.createCover(); } - createCover() { const svg = this.parent; - const mainXScale = this.services.cartesianScales.getMainXScale(); - const mainYScale = this.services.cartesianScales.getMainYScale(); + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); const [xScaleStart, xScaleEnd] = mainXScale.range(); const [yScaleEnd, yScaleStart] = mainYScale.range(); // Get height - this.coverClipPath = DOMUtils.appendOrSelect(svg, `clipPath.${this.type}`); - this.coverClipPath - .attr("id", `${this.type}Clip`); + this.coverClipPath = DOMUtils.appendOrSelect( + svg, + `clipPath.${this.type}` + ); + this.coverClipPath.attr("id", `${this.type}Clip`); const coverRect = DOMUtils.appendOrSelect( this.coverClipPath, "rect.cover" @@ -41,14 +37,11 @@ export class Cover extends Component { .attr("width", xScaleEnd - xScaleStart) .attr("height", yScaleEnd - yScaleStart); - this.coverClipPath - .merge(coverRect) - .lower(); + this.coverClipPath.merge(coverRect).lower(); const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); coverG .attr("clip-path", `url(#${this.type}Clip)`) .attr("id", `g-${this.type}Clip`); - } } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index b3b4b2b571..6e3d8efe91 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,11 +13,13 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + brushSelector = "g.brush"; // needs to be this value for d3.brush API + clipId = "zoomBarClip"; height = 32; - ogXScale: any; + spacerHeight = 20; brush = brushX(); @@ -64,9 +66,9 @@ export class ZoomBar extends Component { const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) - .attr("y", 32) + .attr("y", this.height) .attr("width", "100%") - .attr("height", 20) + .attr("height", this.spacerHeight) .attr("opacity", 1) .attr("fill", "none"); @@ -76,189 +78,134 @@ export class ZoomBar extends Component { .attr("width", "100%") .attr("height", "100%"); - if (mainXScale) { - const displayData = this.model.getDisplayData(); - - if (mainXScaleType === ScaleTypes.TIME) { - // Get all date values provided in data - // TODO - Could be re-used through the model - let allDates = []; - displayData.forEach((data) => { - allDates = allDates.concat(Number(data.date)); - }); - allDates = Tools.removeArrayDuplicates(allDates).sort(); - - // Go through all date values - // And get corresponding data from each dataset - const stackDataArray = allDates.map((date) => { - let count = 0; - let correspondingSum = 0; - const correspondingData = {}; - - displayData.forEach((data) => { - if (Number(data.date) === Number(date)) { - ++count; - correspondingSum += data.value; - } - }); - correspondingData["date"] = date; - correspondingData["value"] = correspondingSum; - - return correspondingData; - }); - - if (!this.ogXScale) { - this.ogXScale = cartesianScales.getDomainScale(); - } - const xScale = mainXScale.copy(); - if (!this.ogXScale) { - this.ogXScale = xScale; - } - const yScale = mainYScale.copy(); + if (mainXScale && mainXScaleType === ScaleTypes.TIME) { + const zoomBarData = this.model.getZoomBarData(); + const xScale = mainXScale.copy(); + const yScale = mainYScale.copy(); - const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); - // @todo could be a better function to extend domain with default value - const xDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - // add value 0 to the extended domain for zoom bar area graph - stackDataArray.unshift({ date: xDomain[0], value: 0 }); - stackDataArray.push({ date: xDomain[1], value: 0 }); + const defaultDomain = this.model.getDefaultZoomBarDomain(); + // add value 0 to the extended domain for zoom bar area graph + this.compensateDataForDefaultDomain(zoomBarData, defaultDomain, 0); - xScale.range([axesLeftMargin, width]).domain(xDomain); + xScale.range([axesLeftMargin, width]).domain(defaultDomain); - yScale - .range([0, this.height - 6]) - .domain(extent(stackDataArray, (d: any) => d.value)); + yScale + .range([0, this.height - 6]) + .domain(extent(zoomBarData, (d: any) => d.value)); - const zoomDomain = this.model.get("zoomDomain"); + const zoomDomain = this.model.get("zoomDomain"); - // D3 line generator function - const lineGenerator = line() - .x((d, i) => + // D3 line generator function + const lineGenerator = line() + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) + .y( + (d, i) => + this.height - cartesianScales.getValueFromScale( - xScale, - mainXScaleType, - mainXAxisPosition, + yScale, + mainYScaleType, + mainYAxisPosition, d, i ) - ) - .y( - (d, i) => - this.height - - cartesianScales.getValueFromScale( - yScale, - mainYScaleType, - mainYAxisPosition, - d, - i - ) - ) - .curve(this.services.curves.getD3Curve()); - const accessorFunc = (scale, scaleType, axisPosition) => { - return (d, i) => { - return cartesianScales.getValueFromScale( - scale, - scaleType, - axisPosition, - d, - i - ); - }; - }; - this.renderZoomBarArea( - container, - "path.zoom-graph-area-unselected", - accessorFunc(xScale, mainXScaleType, mainXAxisPosition), - accessorFunc(yScale, mainYScaleType, mainYAxisPosition), - stackDataArray, - animate, - undefined - ); - this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); - this.renderZoomBarArea( - container, - "path.zoom-graph-area", - accessorFunc(xScale, mainXScaleType, mainXAxisPosition), - accessorFunc(yScale, mainYScaleType, mainYAxisPosition), - stackDataArray, - animate, - "zoomBarClip" - ); - const baselineGenerator = line()([ - [axesLeftMargin, this.height], - [width, this.height] - ]); - const zoomBaseline = DOMUtils.appendOrSelect( - container, - "path.zoom-bg-baseline" - ).attr("d", baselineGenerator); - - const brushEventListener = () => { - const selection = event.selection; - // follow d3 behavior: when selection is null, reset default full range - // @todo find a better way to handel the situation when selection is null - // select behavior is completed, but nothing selected - if (selection === null) { - this.brushed(zoomDomain, xScale, xScale.range()); - } else if (selection[0] === selection[1]) { - // select behavior is not completed yet, do nothing - } else { - this.brushed(zoomDomain, xScale, selection); - } + ) + .curve(this.services.curves.getD3Curve()); + const accessorFunc = (scale, scaleType, axisPosition) => { + return (d, i) => { + return cartesianScales.getValueFromScale( + scale, + scaleType, + axisPosition, + d, + i + ); }; + }; + this.renderZoomBarArea( + container, + "path.zoom-graph-area-unselected", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + zoomBarData, + animate, + undefined + ); + this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); + this.renderZoomBarArea( + container, + "path.zoom-graph-area", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + zoomBarData, + animate, + this.clipId + ); + const baselineGenerator = line()([ + [axesLeftMargin, this.height], + [width, this.height] + ]); + const zoomBaseline = DOMUtils.appendOrSelect( + container, + "path.zoom-bg-baseline" + ).attr("d", baselineGenerator); + + const brushEventListener = () => { + const selection = event.selection; + // follow d3 behavior: when selection is null, reset default full range + // select behavior is completed, but nothing selected + if (selection === null) { + this.brushed(zoomDomain, xScale, xScale.range()); + } else if (selection[0] === selection[1]) { + // select behavior is not completed yet, do nothing + } else { + this.brushed(zoomDomain, xScale, selection); + } + }; - this.brush - .extent([ - [axesLeftMargin, 0], - [width, this.height] - ]) - .on("start brush end", null) // remove old listener first - .on("start brush end", brushEventListener); - - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( - this.brush - ); - - if (zoomDomain === undefined) { - // do nothing, initialization not completed yet + this.brush + .extent([ + [axesLeftMargin, 0], + [width, this.height] + ]) + .on("start brush end", null) // remove old listener first + .on("start brush end", brushEventListener); + + const brushArea = DOMUtils.appendOrSelect( + svg, + this.brushSelector + ).call(this.brush); + + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + } else if (zoomDomain[0].valueOf() === zoomDomain[1].valueOf()) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle(this.getContainerSVG(), xScale.range()); + } else { + const selected = zoomDomain.map((domain) => xScale(domain)); + if (selected[1] - selected[0] < 1) { + // initialization not completed yet // don't update brushHandle to avoid flash - } else if ( - zoomDomain[0].valueOf() === zoomDomain[1].valueOf() - ) { - brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle( - this.getContainerSVG(), - xScale.range() - ); } else { - const selected = zoomDomain.map((domain) => xScale(domain)); - if (selected[1] - selected[0] < 1) { - // initialization not completed yet - // don't update brushHandle to avoid flash - } else { - brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle( - this.getContainerSVG(), - selected - ); - } + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle(this.getContainerSVG(), selected); } } } } - // could be used by Toolbar - // zoomIn() { - // const mainXScale = this.services.cartesianScales.getMainXScale(); - // console.log("zoom in", mainXScale.domain()); - // } - // brush event listener brushed(zoomDomain, scale, selection) { // update brush handle position @@ -315,7 +262,7 @@ export class ZoomBar extends Component { const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; // handle - svg.select("g.brush") + svg.select(this.brushSelector) .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { @@ -330,7 +277,7 @@ export class ZoomBar extends Component { .attr("height", handleHeight) .style("display", null); // always display // handle-bar - svg.select("g.brush") + svg.select(this.brushSelector) .selectAll("rect.handle-bar") .data([{ type: "w" }, { type: "e" }]) .join("rect") @@ -364,7 +311,7 @@ export class ZoomBar extends Component { querySelector, xFunc, y1Func, - datum, + data, animate, clipId ) { @@ -374,7 +321,7 @@ export class ZoomBar extends Component { .y1((d, i) => this.height - y1Func(d, i)); const areaGraph = DOMUtils.appendOrSelect(container, querySelector) - .datum(datum) + .datum(data) .attr("d", areaGenerator); if (clipId) { @@ -394,6 +341,29 @@ export class ZoomBar extends Component { .attr("height", height); } + // assume the domains in data are already sorted + compensateDataForDefaultDomain(data, defaultDomain, value) { + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); + // if min domain is extended + if (Number(defaultDomain[0]) < Number(data[0][domainIdentifier])) { + const newDatum = {}; + newDatum[domainIdentifier] = defaultDomain[0]; + newDatum[rangeIdentifier] = value; + data.unshift(newDatum); + } + // if max domain is extended + if ( + Number(defaultDomain[1]) > + Number(data[data.length - 1][domainIdentifier]) + ) { + const newDatum = {}; + newDatum[domainIdentifier] = defaultDomain[1]; + newDatum[rangeIdentifier] = value; + data.push(newDatum); + } + } + destroy() { this.brush.on("start brush end", null); // remove event listener this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 999d689680..a795d809e7 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -5,8 +5,9 @@ import * as colorPalettes from "./services/colorPalettes"; import { Events, ScaleTypes } from "./interfaces"; // D3 -import { scaleOrdinal } from "d3-scale"; +import { extent } from "d3-array"; import { map } from "d3-collection"; +import { scaleOrdinal } from "d3-scale"; import { stack } from "d3-shape"; /** The charting model layer which includes mainly the chart data and options, @@ -38,7 +39,48 @@ export class ChartModel { constructor(services: any) { this.services = services; } + // get display data for zoom bar + // basically it's sum of value grouped by time + getZoomBarData() { + const { cartesianScales } = this.services; + const domainIdentifier = cartesianScales.getDomainIdentifier(); + const rangeIdentifier = cartesianScales.getRangeIdentifier(); + + const displayData = this.getDisplayData(); + // get all dates (Number) in displayData + let allDates = []; + displayData.forEach((data) => { + allDates = allDates.concat(Number(data[domainIdentifier])); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + // Go through all date values + // And get corresponding data from each dataset + return allDates.map((date) => { + let sum = 0; + const datum = {}; + + displayData.forEach((data) => { + if (Number(data[domainIdentifier]) === date) { + sum += data[rangeIdentifier]; + } + }); + datum[domainIdentifier] = new Date(date); + datum[rangeIdentifier] = sum; + return datum; + }); + } + getDefaultZoomBarDomain() { + const zoomBarData = this.getZoomBarData(); + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const domainIdentifier = cartesianScales.getDomainIdentifier(); + // default to full range with extended domain + return cartesianScales.extendsDomain( + mainXAxisPosition, + extent(zoomBarData, (d: any) => d[domainIdentifier]) + ); + } getAllDataFromDomain() { if (!this.get("data")) { return null; @@ -95,7 +137,7 @@ export class ChartModel { return allDataFromDomain.filter((datum) => { const group = dataGroups.find( - (group) => group.name === datum[groupMapsTo] + (g) => g.name === datum[groupMapsTo] ); return group.status === ACTIVE; From ef176f597e3843213fa019ee18177afa9fbd9e05 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 16:13:14 +0800 Subject: [PATCH 260/510] fix: set min selection difference threshold --- packages/core/src/components/axes/zoom-bar.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6e3d8efe91..6c1dd80fdb 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,6 +13,11 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + // The minimum selection x range to trigger handler update + // Smaller number may introduce a handler flash during initialization + // Bigger number may not trigger handler update while selection area on chart is very small + MIN_SELECTION_DIFF = 9e-10; + brushSelector = "g.brush"; // needs to be this value for d3.brush API clipId = "zoomBarClip"; @@ -195,7 +200,7 @@ export class ZoomBar extends Component { this.updateBrushHandle(this.getContainerSVG(), xScale.range()); } else { const selected = zoomDomain.map((domain) => xScale(domain)); - if (selected[1] - selected[0] < 1) { + if (selected[1] - selected[0] < this.MIN_SELECTION_DIFF) { // initialization not completed yet // don't update brushHandle to avoid flash } else { From c82973ab6551c502b8ac7cc92c675dbeafbfa5e8 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 16:46:44 +0800 Subject: [PATCH 261/510] refactor: code refactoring - zoom-bar.scss - remove unused scss settings --- packages/core/src/components/axes/axis.ts | 2 +- packages/core/src/components/axes/zoom-bar.ts | 2 +- .../core/src/styles/components/_zoom-bar.scss | 24 +++++++++---------- packages/core/src/styles/graphs/index.scss | 4 ---- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 10953d11cb..209d8463e7 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -117,7 +117,7 @@ export class Axis extends Component { // if zoomDomain is available, update scale domain to Date array. const zoomDomain = this.model.get("zoomDomain"); if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain.map((d) => new Date(d))); + scale.domain(zoomDomain); } // Identify the corresponding d3 axis function diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6c1dd80fdb..edcb1f792b 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -18,7 +18,7 @@ export class ZoomBar extends Component { // Bigger number may not trigger handler update while selection area on chart is very small MIN_SELECTION_DIFF = 9e-10; - brushSelector = "g.brush"; // needs to be this value for d3.brush API + brushSelector = "g.zoom-bar-brush"; // needs to match the style in _zoom-bar.scss clipId = "zoomBarClip"; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 32dfc32842..13c20ecc30 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -19,17 +19,17 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: none; } - g.brush rect.handle { - fill: $icon-02; - } - - g.brush rect.handle-bar { - fill: $ui-02; - } - - // clear d3.brush selection style - g.brush rect.selection { - fill: none; - stroke: none; + g.zoom-bar-brush { + rect.handle { + fill: $icon-02; + } + rect.handle-bar { + fill: $ui-02; + } + // clear d3.brush selection style + rect.selection { + fill: none; + stroke: none; + } } } diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index bebafaffc3..5530eba1a0 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -6,7 +6,3 @@ @import "./scatter-stacked"; @import "./radar"; @import "./gauge"; - -svg.#{$prefix}--#{$charts-prefix}--chart-svg svg.graph-frame { - overflow-x: hidden; -} From caac5125c90192efc5cbb8170f3a2f08f4b24b3b Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 6 Jul 2020 17:09:13 +0800 Subject: [PATCH 262/510] fix: avoid extra/duplicate external callback --- packages/core/src/components/axes/brush.ts | 44 ++++++++++++++++++- packages/core/src/components/axes/zoom-bar.ts | 38 +++++++++------- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 16f21dce44..8067e6aaa8 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -40,6 +40,47 @@ export class Brush extends Component { this.model.set({ zoomDomain: zoomDomain }, { animate: false }); } + const eventHandler = () => { + const selection = event.selection; + const xScale = scaleTime().range([0, width]).domain(zoomDomain); + + const newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + if ( + selection != null && + event.sourceEvent != null && + (event.sourceEvent.type === "mousemove" || + event.sourceEvent.type === "mouseup" || + event.sourceEvent.type === "mousedown") + ) { + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress( + selection, + newDomain + ); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + }; const brushed = () => { const selection = event.selection; @@ -83,7 +124,8 @@ export class Brush extends Component { [2, 0], [width - 1, height - 1] ]) - .on("end", brushed); + .on("start brush end", eventHandler) + .on("end.brushed", brushed); const brushArea = DOMUtils.appendOrSelect( backdrop, diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index edcb1f792b..05722a4116 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -237,23 +237,27 @@ export class ZoomBar extends Component { ) { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if (zoomBarOptions.selectionEnd !== undefined && event.type === "end") { - zoomBarOptions.selectionEnd(selection, newDomain); + + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } } } From 08d7cca8cb9ef67ab22324a4616d94c61ae1cb60 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 7 Jul 2020 12:33:39 +0800 Subject: [PATCH 263/510] fix: remove ZoomBarOptions in BaseChartOptions --- packages/core/src/interfaces/charts.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 4956c8895b..15f49a187f 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -46,10 +46,6 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; - /** - * zoombar configuration - */ - zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ @@ -222,7 +218,7 @@ export interface StackedAreaChartOptions extends ScatterChartOptions { | string | { name: string; - }; + }; } /** From 5ebe3c9618a3b3746f963d6af34555b969a15108 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 11:28:33 +0800 Subject: [PATCH 264/510] refactor: change initZoomDomain to initialZoomDomain - remove unnecessary undefined setting --- packages/core/demo/data/zoom-bar.ts | 27 +++++++++---------- packages/core/src/components/axes/zoom-bar.ts | 4 +-- packages/core/src/configuration.ts | 1 - packages/core/src/interfaces/components.ts | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 0016bbcad7..fbe74fba2a 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -23,21 +23,20 @@ const selectionEndFun = (selection, domain) => { console.log(domain); }; -const initZoomDomain = [ +const initialZoomDomain = [ new Date(2020, 11, 10, 23, 59, 25), new Date(2020, 11, 11, 0, 0, 25) ]; const defaultZoomBarOptions = { enabled: true, - initZoomDomain: undefined, selectionStart: selectionStartFun, selectionInProgress: selectionInProgressFun, selectionEnd: selectionEndFun }; // utility function to update title and enable zoomBar option -const updateOptions = (options) => { +const addZoomBarToOptions = (options) => { options["title"] = options["title"] + " - Zoom bar enabled"; options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); return options; @@ -45,51 +44,51 @@ const updateOptions = (options) => { export const zoomBarStackedAreaTimeSeriesData = areaChart.stackedAreaTimeSeriesData; -export const zoomBarStackedAreaTimeSeriesOptions = updateOptions( +export const zoomBarStackedAreaTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, areaChart.stackedAreaTimeSeriesOptions) ); export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; -export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( +export const zoomBarSimpleBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.simpleBarTimeSeriesOptions) ); export const zoomBarStackedBarTimeSeriesData = barChart.stackedBarTimeSeriesData; -export const zoomBarStackedBarTimeSeriesOptions = updateOptions( +export const zoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.stackedBarTimeSeriesOptions) ); export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; -export const zoomBarBubbleTimeSeriesOptions = updateOptions( +export const zoomBarBubbleTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) ); export const zoomBarLineTimeSeriesData = lineChart.lineTimeSeriesData; -export const zoomBarLineTimeSeriesOptions = updateOptions( +export const zoomBarLineTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, lineChart.lineTimeSeriesOptions) ); export const zoomBarScatterTimeSeriesData = scatterChart.scatterTimeSeriesData; -export const zoomBarScatterTimeSeriesOptions = updateOptions( +export const zoomBarScatterTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, scatterChart.scatterTimeSeriesOptions) ); export const zoomBarStepTimeSeriesData = stepChart.stepTimeSeriesData; -export const zoomBarStepTimeSeriesOptions = updateOptions( +export const zoomBarStepTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, stepChart.stepTimeSeriesOptions) ); export const zoomBarLineTimeSeries15secondsData = timeSeriesAxisChart.lineTimeSeriesData15seconds; -export const zoomBarLineTimeSeries15secondsOptions = updateOptions( +export const zoomBarLineTimeSeries15secondsOptions = addZoomBarToOptions( Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) ); export const zoomBarLineTimeSeriesInitDomainData = timeSeriesAxisChart.lineTimeSeriesData15seconds; -export const zoomBarLineTimeSeriesInitDomainOptions = updateOptions( +export const zoomBarLineTimeSeriesInitDomainOptions = addZoomBarToOptions( Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) ); -zoomBarLineTimeSeriesInitDomainOptions["title"] += " with initial zoom domain"; -zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initZoomDomain = initZoomDomain; +zoomBarLineTimeSeriesInitDomainOptions["title"] += " zoomed domain"; +zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initialZoomDomain = initialZoomDomain; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 05722a4116..2bbd6ad4ea 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -35,9 +35,9 @@ export class ZoomBar extends Component { // get initZoomDomain const zoomBarOptions = this.model.getOptions().zoomBar; - if (zoomBarOptions.initZoomDomain !== undefined) { + if (zoomBarOptions.initialZoomDomain !== undefined) { this.model.set( - { zoomDomain: zoomBarOptions.initZoomDomain }, + { zoomDomain: zoomBarOptions.initialZoomDomain }, { skipUpdate: true } ); } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index ad37847348..5109d282e1 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -133,7 +133,6 @@ export const timeScale: TimeScaleOptions = { */ export const zoomBar: ZoomBarOptions = { enabled: false, - initZoomDomain: undefined, selectionStart: undefined, selectionInProgress: undefined, selectionEnd: undefined diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index d81ddce313..e250197b85 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -129,7 +129,7 @@ export interface ZoomBarOptions { /** * an two element array which represents the initial zoom domain */ - initZoomDomain?: Object[]; + initialZoomDomain?: Object[]; /** * a function to handle selection start event From 6a65a862fc5ff73f33488c7f8127f84321b5c124 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 12:40:27 +0800 Subject: [PATCH 265/510] refactor: use Tools.getProperty to load zoomBarOptions --- packages/core/src/axis-chart.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index f5559f6f56..c7feb56bae 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -31,6 +31,11 @@ export class AxisChart extends Chart { } protected getAxisChartComponents(graphFrameComponents: any[]) { + const zoomBarEnabled = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "enabled" + ); const titleComponent = { id: "title", components: [new Title(this.model, this.services)], @@ -49,12 +54,10 @@ export class AxisChart extends Chart { } }; - if ( - this.model.getOptions().zoomBar && - this.model.getOptions().zoomBar.enabled - ) { + if (zoomBarEnabled) { graphFrameComponents.push(new Brush(this.model, this.services)); } + const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, @@ -156,7 +159,7 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } - if (this.model.getOptions().zoomBar.enabled === true) { + if (zoomBarEnabled) { topLevelLayoutComponents.push(zoomBarComponent); } topLevelLayoutComponents.push(fullFrameComponent); From 97adb6cf775fd619cb08d2021d7c4a2d20615f05 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 13:37:33 +0800 Subject: [PATCH 266/510] refactor: update code format --- packages/core/src/charts/pie.ts | 2 +- packages/core/src/charts/radar.ts | 6 ++++-- packages/core/src/components/component.ts | 7 ++++--- packages/core/src/components/index.ts | 1 - packages/core/src/model.ts | 5 ++++- packages/core/src/styles/components/_zoom-bar.scss | 3 +++ 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 4642ea8f2d..87b74217aa 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -52,7 +52,7 @@ export class PieChart extends Chart { skeleton: Skeletons.PIE }) ]; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 80b8e7888c..c9b3d67e6e 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -41,8 +41,10 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; - + const graphFrameComponents: any[] = [ + new Radar(this.model, this.services) + ]; + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 6aa5e17ee0..b5996e519c 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -103,15 +103,16 @@ export class Component { this.type === "stacked-bar" || this.type === "scatter-stacked" ) { - return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); - + return DOMUtils.appendOrSelectForAxisChart( + this.parent, + `clipPath.cover` + ); } else { return DOMUtils.appendOrSelect( this.parent, `g.${settings.prefix}--${chartprefix}--${this.type}` ); } - } return this.parent; diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 4d9959743a..1534f8db5a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -37,4 +37,3 @@ export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; - diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index a795d809e7..be8b412eb0 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -70,17 +70,20 @@ export class ChartModel { return datum; }); } + getDefaultZoomBarDomain() { const zoomBarData = this.getZoomBarData(); const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const domainIdentifier = cartesianScales.getDomainIdentifier(); + // default to full range with extended domain return cartesianScales.extendsDomain( mainXAxisPosition, extent(zoomBarData, (d: any) => d[domainIdentifier]) ); } + getAllDataFromDomain() { if (!this.get("data")) { return null; @@ -137,7 +140,7 @@ export class ChartModel { return allDataFromDomain.filter((datum) => { const group = dataGroups.find( - (g) => g.name === datum[groupMapsTo] + (dataGroup) => dataGroup.name === datum[groupMapsTo] ); return group.status === ACTIVE; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 13c20ecc30..d52d6c7978 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -14,6 +14,7 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: $ui-04; stroke-width: 1; } + path.zoom-graph-area-unselected { fill: $ui-01; stroke: none; @@ -23,9 +24,11 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { rect.handle { fill: $icon-02; } + rect.handle-bar { fill: $ui-02; } + // clear d3.brush selection style rect.selection { fill: none; From 4f6a06bc626b55d7ab19e2e97ad6c2be9db14343 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 15:38:10 +0800 Subject: [PATCH 267/510] refactor: use Tools.getProperty to get initialZoomDomain --- packages/core/src/components/axes/zoom-bar.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 2bbd6ad4ea..32c4cbb61f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -34,10 +34,14 @@ export class ZoomBar extends Component { }); // get initZoomDomain - const zoomBarOptions = this.model.getOptions().zoomBar; - if (zoomBarOptions.initialZoomDomain !== undefined) { + const initialZoomDomain = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "initialZoomDomain" + ); + if (initialZoomDomain !== null) { this.model.set( - { zoomDomain: zoomBarOptions.initialZoomDomain }, + { zoomDomain: initialZoomDomain }, { skipUpdate: true } ); } From f72a67f0b2efbb776b8e331e9fa5f100c5587a3d Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 12:13:31 +0800 Subject: [PATCH 268/510] refactor: Change Cover to ChartClip --- packages/core/src/charts/area-stacked.ts | 4 +- packages/core/src/charts/area.ts | 4 +- packages/core/src/charts/bar-grouped.ts | 4 +- packages/core/src/charts/bar-simple.ts | 4 +- packages/core/src/charts/bar-stacked.ts | 4 +- packages/core/src/charts/bubble.ts | 4 +- packages/core/src/charts/line.ts | 4 +- packages/core/src/charts/scatter.ts | 4 +- .../core/src/components/axes/chart-clip.ts | 50 +++++++++++++++++++ packages/core/src/components/axes/cover.ts | 47 ----------------- packages/core/src/components/component.ts | 2 +- packages/core/src/components/index.ts | 2 +- 12 files changed, 68 insertions(+), 65 deletions(-) create mode 100644 packages/core/src/components/axes/chart-clip.ts delete mode 100644 packages/core/src/components/axes/cover.ts diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 42895bdc6b..8104c10ef0 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,7 +6,7 @@ import { Tools } from "../tools"; // Components import { - Cover, + ChartClip, Grid, StackedArea, TwoDimensionalAxes, @@ -36,7 +36,7 @@ export class StackedAreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new StackedArea(this.model, this.services), diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 2b313f6cc7..23959cce54 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,7 +6,7 @@ import { Tools } from "../tools"; // Components import { - Cover, + ChartClip, Grid, Area, Line, @@ -40,7 +40,7 @@ export class AreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 59354739e6..0c259fb01e 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, GroupedBar, TwoDimensionalAxes, @@ -40,7 +40,7 @@ export class GroupedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new GroupedBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 5709ea0e9b..4d6d17345a 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, SimpleBar, TwoDimensionalAxes, @@ -40,7 +40,7 @@ export class SimpleBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new SimpleBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index c2510fa7da..a680c9402b 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, StackedBar, StackedBarRuler, @@ -43,7 +43,7 @@ export class StackedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new StackedBarRuler(this.model, this.services), new StackedBar(this.model, this.services), diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 7238de0aa3..090a72ec29 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Ruler, Bubble, @@ -43,7 +43,7 @@ export class BubbleChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Bubble(this.model, this.services), diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index ee4e07f163..3e0c2959b2 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Line, Ruler, @@ -41,7 +41,7 @@ export class LineChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 2acacdf9df..6eebc8a9e0 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Ruler, Scatter, @@ -43,7 +43,7 @@ export class ScatterChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Scatter(this.model, this.services), diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts new file mode 100644 index 0000000000..f7bb54fc21 --- /dev/null +++ b/packages/core/src/components/axes/chart-clip.ts @@ -0,0 +1,50 @@ +// Internal Imports +import { Component } from "../component"; +import { DOMUtils } from "../../services"; + +// This class is used to create the clipPath to clip the chart graphs +// It's necessary for zoom in/out behavior +export class ChartClip extends Component { + type = "chart-clip"; + + chartClipPath: any; + + clipPathId = "id-" + this.type; + + render(animate = true) { + // Create the clipPath + this.createClipPath(); + } + + createClipPath() { + const svg = this.parent; + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + // Get height + this.chartClipPath = DOMUtils.appendOrSelect( + svg, + `clipPath.${this.type}` + ).attr("id", this.clipPathId); + const clipRect = DOMUtils.appendOrSelect( + this.chartClipPath, + `rect.${this.type}` + ); + clipRect + .attr("x", xScaleStart) + .attr("y", yScaleStart) + .attr("width", xScaleEnd - xScaleStart) + .attr("height", yScaleEnd - yScaleStart); + + this.chartClipPath.merge(clipRect).lower(); + + const clipG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); + clipG + .attr("clip-path", `url(#${this.clipPathId})`) + .attr("id", `g-${this.type}`); + } +} diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts deleted file mode 100644 index 2536c9ef40..0000000000 --- a/packages/core/src/components/axes/cover.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Internal Imports -import { Component } from "../component"; -import { DOMUtils } from "../../services"; - -export class Cover extends Component { - type = "cover"; - - coverClipPath: any; - - render(animate = true) { - // Create the cover - this.createCover(); - } - - createCover() { - const svg = this.parent; - const { cartesianScales } = this.services; - const mainXScale = cartesianScales.getMainXScale(); - const mainYScale = cartesianScales.getMainYScale(); - - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const [yScaleEnd, yScaleStart] = mainYScale.range(); - - // Get height - this.coverClipPath = DOMUtils.appendOrSelect( - svg, - `clipPath.${this.type}` - ); - this.coverClipPath.attr("id", `${this.type}Clip`); - const coverRect = DOMUtils.appendOrSelect( - this.coverClipPath, - "rect.cover" - ); - coverRect - .attr("x", xScaleStart) - .attr("y", yScaleStart) - .attr("width", xScaleEnd - xScaleStart) - .attr("height", yScaleEnd - yScaleStart); - - this.coverClipPath.merge(coverRect).lower(); - - const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); - coverG - .attr("clip-path", `url(#${this.type}Clip)`) - .attr("id", `g-${this.type}Clip`); - } -} diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index b5996e519c..f79a5e347f 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -105,7 +105,7 @@ export class Component { ) { return DOMUtils.appendOrSelectForAxisChart( this.parent, - `clipPath.cover` + `clipPath.chart-clip` ); } else { return DOMUtils.appendOrSelect( diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 1534f8db5a..506b51b1fa 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -31,7 +31,7 @@ export * from "./layout/layout"; export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; export * from "./axes/brush"; -export * from "./axes/cover"; +export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; From 9362e63f05600bc296e6af5b686e27530750e87e Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 12:29:05 +0800 Subject: [PATCH 269/510] refactor: Change Brush to ChartBrush --- packages/core/src/axis-chart.ts | 6 ++++-- .../src/components/axes/{brush.ts => chart-brush.ts} | 9 ++++----- packages/core/src/components/index.ts | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) rename packages/core/src/components/axes/{brush.ts => chart-brush.ts} (95%) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index c7feb56bae..b79ca91dfa 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -8,7 +8,7 @@ import { AxisChartOptions } from "./interfaces"; import { - Brush, + ChartBrush, LayoutComponent, Legend, Title, @@ -55,7 +55,9 @@ export class AxisChart extends Chart { }; if (zoomBarEnabled) { - graphFrameComponents.push(new Brush(this.model, this.services)); + graphFrameComponents.push( + new ChartBrush(this.model, this.services) + ); } const graphFrameComponent = { diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/chart-brush.ts similarity index 95% rename from packages/core/src/components/axes/brush.ts rename to packages/core/src/components/axes/chart-brush.ts index 8067e6aaa8..7fa5a1caf3 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -1,17 +1,16 @@ // Internal Imports import { Component } from "../component"; -import { Tools } from "../../tools"; import { ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports -import { extent } from "d3-array"; import { brushX } from "d3-brush"; import { event } from "d3-selection"; import { scaleTime } from "d3-scale"; -export class Brush extends Component { - type = "brush"; +// This class is used for handle brush events in chart +export class ChartBrush extends Component { + type = "chart-brush"; render(animate = true) { const svg = this.parent; @@ -129,7 +128,7 @@ export class Brush extends Component { const brushArea = DOMUtils.appendOrSelect( backdrop, - "g.chart-brush" + `g.${this.type}` ).call(brush); } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 506b51b1fa..369cbbe15a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,7 +30,7 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; -export * from "./axes/brush"; +export * from "./axes/chart-brush"; export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; From 501627867de62dba1ca7fbf2cbe091ef4c6ae8a9 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 14:18:36 +0800 Subject: [PATCH 270/510] refactor: set model.set() function default config --- packages/core/src/model.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index be8b412eb0..63254bb3e7 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -314,9 +314,12 @@ export class ChartModel { set(newState: any, configs?: any) { this.state = Object.assign({}, this.state, newState); - - if (!configs || !configs.skipUpdate) { - this.update(configs ? configs.animate : true); + const newConfig = Object.assign( + { skipUpdate: false, animate: true }, // default configs + configs + ); + if (!newConfig.skipUpdate) { + this.update(newConfig.animate); } } From 3dc2a6831f3c0411287bf05f29534105367bb08a Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 14:30:32 +0800 Subject: [PATCH 271/510] fix: remove unnecessary selector --- packages/core/src/components/graphs/line.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 2be9c949a9..02caebb0be 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -131,7 +131,6 @@ export class Line extends Component { this.parent .selectAll("path.line") - .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-hover-line") ) @@ -142,16 +141,16 @@ export class Line extends Component { return Configuration.lines.opacity.selected; }); - }; + } handleLegendMouseOut = (event: CustomEvent) => { this.parent - .selectAll(`g#coverClip`) + .selectAll("path.line") .transition( this.services.transitions.getTransition("legend-mouseout-line") ) .attr("opacity", Configuration.lines.opacity.selected); - }; + } destroy() { // Remove event listeners From f0335af7e722cfcc593dffc3bfd1e3acd521bc7a Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 15:22:22 +0800 Subject: [PATCH 272/510] refactor: create reusable getMainXScaleType() --- packages/core/src/components/axes/chart-brush.ts | 8 ++------ packages/core/src/components/axes/zoom-bar.ts | 8 ++------ packages/core/src/services/scales-cartesian.ts | 10 ++++------ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 7fa5a1caf3..19b9ee14b9 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -23,12 +23,8 @@ export class ChartBrush extends Component { }); const { cartesianScales } = this.services; - const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition - ); - - const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainXScaleType = cartesianScales.getMainXScaleType(); + const mainXScale = cartesianScales.getMainXScale(); if (mainXScale && mainXScaleType === ScaleTypes.TIME) { // get current zoomDomain diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 32c4cbb61f..897a1b9417 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -54,12 +54,8 @@ export class ZoomBar extends Component { const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition - ); - const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition - ); + const mainXScaleType = cartesianScales.getMainXScaleType(); + const mainYScaleType = cartesianScales.getMainYScaleType(); // get axes margins let axesLeftMargin = 0; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 11be646307..4904006ba5 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -231,14 +231,12 @@ export class CartesianScales extends Service { return this.getValueThroughAxisPosition(this.rangeAxisPosition, d, i); } - getXValue(d, i) { - const mainXAxisPosition = this.getMainXAxisPosition(); - return this.getValueThroughAxisPosition(mainXAxisPosition, d, i); + getMainXScaleType() { + return this.getScaleTypeByPosition(this.getMainXAxisPosition()); } - getYValue(d, i) { - const mainYAxisPosition = this.getMainYAxisPosition(); - return this.getValueThroughAxisPosition(mainYAxisPosition, d, i); + getMainYScaleType() { + return this.getScaleTypeByPosition(this.getMainYAxisPosition()); } getDomainIdentifier() { From 010875abacf6b36449c917f555d0bae2b3d2499c Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 16:22:13 +0800 Subject: [PATCH 273/510] refactor: make sure zoom bar only shows with supported options - zoomBar is available when -- zoomBar is enabled -- main X axis position is bottom -- main X axis scale type is time --- packages/core/src/axis-chart.ts | 21 +++++++++++++++++++-- packages/core/src/components/axes/axis.ts | 13 +++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index b79ca91dfa..6d4d515cba 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -5,7 +5,9 @@ import { LegendOrientations, LegendPositions, ChartConfig, - AxisChartOptions + AxisChartOptions, + AxisPositions, + ScaleTypes } from "./interfaces"; import { ChartBrush, @@ -31,11 +33,26 @@ export class AxisChart extends Chart { } protected getAxisChartComponents(graphFrameComponents: any[]) { - const zoomBarEnabled = Tools.getProperty( + const isZoomBarEnabled = Tools.getProperty( this.model.getOptions(), "zoomBar", "enabled" ); + + this.services.cartesianScales.findDomainAndRangeAxes(); // need to do this before getMainXAxisPosition() + const mainXAxisPosition = this.services.cartesianScales.getMainXAxisPosition(); + const mainXScaleType = Tools.getProperty( + this.model.getOptions(), + "axes", + mainXAxisPosition, + "scaleType" + ); + // @todo - Zoom Bar only supports main axis at BOTTOM axis and time scale for now + const zoomBarEnabled = + isZoomBarEnabled && + mainXAxisPosition === AxisPositions.BOTTOM && + mainXScaleType === ScaleTypes.TIME; + const titleComponent = { id: "title", components: [new Title(this.model, this.services)], diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 209d8463e7..1b1dc86681 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -114,12 +114,6 @@ export class Axis extends Component { scale.range([startPosition, endPosition]); } - // if zoomDomain is available, update scale domain to Date array. - const zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain); - } - // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -178,6 +172,13 @@ export class Axis extends Component { const scaleType = this.scaleType || axisOptions.scaleType || ScaleTypes.LINEAR; + // if zoomDomain is available, scale type is time, and axis position isBOTTOM or TOP + // update scale domain to zoomDomain. + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && isTimeScaleType && !isVerticalAxis) { + scale.domain(zoomDomain); + } + // Initialize axis object const axis = axisFunction(scale).tickSizeOuter(0); From 8930d48f9385ff1c9f7b7ffa1d4214e9302defc6 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 22:15:30 +0800 Subject: [PATCH 274/510] refactor: set clip-path url in containerSVG --- .../core/src/components/axes/chart-clip.ts | 11 ++---- packages/core/src/components/component.ts | 34 +++++++------------ .../src/components/graphs/area-stacked.ts | 2 +- packages/core/src/components/graphs/area.ts | 2 +- .../core/src/components/graphs/bar-grouped.ts | 2 +- .../core/src/components/graphs/bar-simple.ts | 2 +- .../core/src/components/graphs/bar-stacked.ts | 2 +- packages/core/src/components/graphs/line.ts | 6 ++-- .../src/components/graphs/scatter-stacked.ts | 2 +- .../core/src/components/graphs/scatter.ts | 2 +- 10 files changed, 24 insertions(+), 41 deletions(-) diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts index f7bb54fc21..321db115e5 100644 --- a/packages/core/src/components/axes/chart-clip.ts +++ b/packages/core/src/components/axes/chart-clip.ts @@ -2,15 +2,13 @@ import { Component } from "../component"; import { DOMUtils } from "../../services"; -// This class is used to create the clipPath to clip the chart graphs +// This class is used to create the clipPath to clip the chart components // It's necessary for zoom in/out behavior export class ChartClip extends Component { type = "chart-clip"; chartClipPath: any; - clipPathId = "id-" + this.type; - render(animate = true) { // Create the clipPath this.createClipPath(); @@ -29,7 +27,7 @@ export class ChartClip extends Component { this.chartClipPath = DOMUtils.appendOrSelect( svg, `clipPath.${this.type}` - ).attr("id", this.clipPathId); + ).attr("id", this.chartClipId); const clipRect = DOMUtils.appendOrSelect( this.chartClipPath, `rect.${this.type}` @@ -41,10 +39,5 @@ export class ChartClip extends Component { .attr("height", yScaleEnd - yScaleStart); this.chartClipPath.merge(clipRect).lower(); - - const clipG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); - clipG - .attr("clip-path", `url(#${this.clipPathId})`) - .attr("id", `g-${this.type}`); } } diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index f79a5e347f..91ccf82ef8 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -19,6 +19,8 @@ export class Component { protected model: ChartModel; protected services: any; + protected chartClipId = "chart-clip-id"; + constructor(model: ChartModel, services: any, configs?: any) { this.model = model; this.services = services; @@ -83,7 +85,7 @@ export class Component { return this.parent; } - getContainerSVG() { + getContainerSVG(withinChartClip = false) { if (this.type) { const chartprefix = Tools.getProperty( this.model.getOptions(), @@ -91,28 +93,16 @@ export class Component { "prefix" ); - // @todo Chart type equals to axis-chart - if ( - this.type === "line" || - this.type === "scatter" || - this.type === "area" || - this.type === "bubble" || - this.type === "area-stacked" || - this.type === "grouped-bar" || - this.type === "simple-bar" || - this.type === "stacked-bar" || - this.type === "scatter-stacked" - ) { - return DOMUtils.appendOrSelectForAxisChart( - this.parent, - `clipPath.chart-clip` - ); - } else { - return DOMUtils.appendOrSelect( - this.parent, - `g.${settings.prefix}--${chartprefix}--${this.type}` - ); + const svg = DOMUtils.appendOrSelect( + this.parent, + `g.${settings.prefix}--${chartprefix}--${this.type}` + ); + + if (withinChartClip) { + svg.attr("clip-path", `url(#${this.chartClipId})`); } + + return svg; } return this.parent; diff --git a/packages/core/src/components/graphs/area-stacked.ts b/packages/core/src/components/graphs/area-stacked.ts index 5f6609df63..2e086cd958 100644 --- a/packages/core/src/components/graphs/area-stacked.ts +++ b/packages/core/src/components/graphs/area-stacked.ts @@ -28,7 +28,7 @@ export class StackedArea extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const self = this; const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/components/graphs/area.ts b/packages/core/src/components/graphs/area.ts index cd94376aaf..127dab7ac0 100644 --- a/packages/core/src/components/graphs/area.ts +++ b/packages/core/src/components/graphs/area.ts @@ -26,7 +26,7 @@ export class Area extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const { cartesianScales } = this.services; const orientation = cartesianScales.getOrientation(); diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 07f12e8c64..85217ba207 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -44,7 +44,7 @@ export class GroupedBar extends Bar { this.setGroupScale(); // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const allDataLabels = map( displayData, diff --git a/packages/core/src/components/graphs/bar-simple.ts b/packages/core/src/components/graphs/bar-simple.ts index aeb72950d1..2f74a5c922 100644 --- a/packages/core/src/components/graphs/bar-simple.ts +++ b/packages/core/src/components/graphs/bar-simple.ts @@ -31,7 +31,7 @@ export class SimpleBar extends Bar { const { groupMapsTo } = options.data; // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); // Update data on all bars const bars = svg diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index e2dcbc293a..74b5cc9e56 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -28,7 +28,7 @@ export class StackedBar extends Bar { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); // Chart options mixed with the internal configurations const displayData = this.model.getDisplayData(); diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 02caebb0be..3668bc294c 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -25,7 +25,7 @@ export class Line extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const { cartesianScales, curves } = this.services; const getDomainValue = (d, i) => cartesianScales.getDomainValue(d, i); @@ -141,7 +141,7 @@ export class Line extends Component { return Configuration.lines.opacity.selected; }); - } + }; handleLegendMouseOut = (event: CustomEvent) => { this.parent @@ -150,7 +150,7 @@ export class Line extends Component { this.services.transitions.getTransition("legend-mouseout-line") ) .attr("opacity", Configuration.lines.opacity.selected); - } + }; destroy() { // Remove event listeners diff --git a/packages/core/src/components/graphs/scatter-stacked.ts b/packages/core/src/components/graphs/scatter-stacked.ts index e477182786..ca5ad2a4c1 100644 --- a/packages/core/src/components/graphs/scatter-stacked.ts +++ b/packages/core/src/components/graphs/scatter-stacked.ts @@ -7,7 +7,7 @@ export class StackedScatter extends Scatter { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index 2d8074dc49..539d61b7e3 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -52,7 +52,7 @@ export class Scatter extends Component { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const options = this.model.getOptions(); const { groupMapsTo } = options.data; From 327a9937a905914d93f3bca19a287b72d7d88889 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 11:52:45 +0800 Subject: [PATCH 275/510] refactor: dispatch zoombar selection events instead of callback --- packages/core/demo/data/zoom-bar.ts | 26 +------------- .../core/src/components/axes/chart-brush.ts | 36 +++++++------------ packages/core/src/components/axes/zoom-bar.ts | 33 +++++++---------- packages/core/src/configuration.ts | 5 +-- packages/core/src/interfaces/components.ts | 13 ------- packages/core/src/interfaces/events.ts | 5 ++- 6 files changed, 32 insertions(+), 86 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index fbe74fba2a..2dc1b076e1 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -6,39 +6,15 @@ import * as scatterChart from "./scatter"; import * as stepChart from "./step"; import * as timeSeriesAxisChart from "./time-series-axis"; -// default function for selection callback -const selectionStartFun = (selection, domain) => { - console.log("ZoomBar SelectionStart callback!"); - console.log(selection); - console.log(domain); -}; -const selectionInProgressFun = (selection, domain) => { - console.log("ZoomBar SelectionInProgress callback!"); - console.log(selection); - console.log(domain); -}; -const selectionEndFun = (selection, domain) => { - console.log("ZoomBar SelectionEnd callback!"); - console.log(selection); - console.log(domain); -}; - const initialZoomDomain = [ new Date(2020, 11, 10, 23, 59, 25), new Date(2020, 11, 11, 0, 0, 25) ]; -const defaultZoomBarOptions = { - enabled: true, - selectionStart: selectionStartFun, - selectionInProgress: selectionInProgressFun, - selectionEnd: selectionEndFun -}; - // utility function to update title and enable zoomBar option const addZoomBarToOptions = (options) => { options["title"] = options["title"] + " - Zoom bar enabled"; - options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); + options["zoomBar"] = { enabled: true }; return options; }; diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 19b9ee14b9..e8ba9b3f7d 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -1,6 +1,6 @@ // Internal Imports import { Component } from "../component"; -import { ScaleTypes } from "../../interfaces"; +import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports @@ -51,29 +51,19 @@ export class ChartBrush extends Component { event.sourceEvent.type === "mouseup" || event.sourceEvent.type === "mousedown") ) { - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress( - selection, - newDomain - ); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); + // dispatch selection events + let zoomBarEventType; + if (event.type === "start") { + zoomBarEventType = Events.ZoomBar.SELECTION_START; + } else if (event.type === "brush") { + zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; + } else if (event.type === "end") { + zoomBarEventType = Events.ZoomBar.SELECTION_END; } + this.services.events.dispatchEvent(zoomBarEventType, { + selection, + newDomain + }); } }; const brushed = () => { diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 897a1b9417..258d60ea1a 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -8,7 +8,7 @@ import { DOMUtils } from "../../services"; import { extent } from "d3-array"; import { brushX } from "d3-brush"; import { area, line } from "d3-shape"; -import { event, select, selectAll } from "d3-selection"; +import { event } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; @@ -238,26 +238,19 @@ export class ZoomBar extends Component { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); + // dispatch selection events + let zoomBarEventType; + if (event.type === "start") { + zoomBarEventType = Events.ZoomBar.SELECTION_START; + } else if (event.type === "brush") { + zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; + } else if (event.type === "end") { + zoomBarEventType = Events.ZoomBar.SELECTION_END; } + this.services.events.dispatchEvent(zoomBarEventType, { + selection, + newDomain + }); } } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 5109d282e1..d09e139d44 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -132,10 +132,7 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false, - selectionStart: undefined, - selectionInProgress: undefined, - selectionEnd: undefined + enabled: false }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index e250197b85..5bdcf1f302 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -130,17 +130,4 @@ export interface ZoomBarOptions { * an two element array which represents the initial zoom domain */ initialZoomDomain?: Object[]; - - /** - * a function to handle selection start event - */ - selectionStart?: Function; - /** - * a function to handle selection in progress event - */ - selectionInProgress?: Function; - /** - * a function to handle selection end event - */ - selectionEnd?: Function; } diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 2a25fd5992..1da2416ead 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -21,7 +21,10 @@ export enum Model { * enum of all events related to the zoom-bar */ export enum ZoomBar { - UPDATE = "zoom-bar-update" + UPDATE = "zoom-bar-update", + SELECTION_START = "zoom-bar-selection-start", + SELECTION_IN_PROGRESS = "zoom-bar-selection-in-progress", + SELECTION_END = "zoom-bar-selection-end" } /** From 8aa950b65f714ffc75a0cd40d518eb7e85b967d9 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 15:48:41 +0800 Subject: [PATCH 276/510] feat: update chart brush selection storke to dash --- .../core/src/components/axes/chart-brush.ts | 36 +++++++++++++++++-- .../src/styles/components/_chart-brush.scss | 9 +++++ .../core/src/styles/components/index.scss | 1 + 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/styles/components/_chart-brush.scss diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e8ba9b3f7d..e267ae0bda 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -10,8 +10,12 @@ 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 = "chart-brush"; + selectionSelector = "rect.selection"; // needs to match the class name in d3.brush + render(animate = true) { const svg = this.parent; const backdrop = DOMUtils.appendOrSelect( @@ -35,8 +39,36 @@ export class ChartBrush extends Component { 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 + + brushArea + .select(this.selectionSelector) + .attr("stroke-dasharray", dashArray); + }; + const eventHandler = () => { const selection = event.selection; + + updateSelectionDash(selection); + const xScale = scaleTime().range([0, width]).domain(zoomDomain); const newDomain = [ @@ -106,8 +138,8 @@ export class ChartBrush extends Component { // leave some space to display selection strokes besides axis const brush = brushX() .extent([ - [2, 0], - [width - 1, height - 1] + [0, 0], + [width - 1, height] ]) .on("start brush end", eventHandler) .on("end.brushed", brushed); diff --git a/packages/core/src/styles/components/_chart-brush.scss b/packages/core/src/styles/components/_chart-brush.scss new file mode 100644 index 0000000000..f316c7d486 --- /dev/null +++ b/packages/core/src/styles/components/_chart-brush.scss @@ -0,0 +1,9 @@ +.#{$prefix}--#{$charts-prefix}--chart-brush { + g.chart-brush { + rect.selection { + fill: $ui-03; + fill-opacity: 0.3; + stroke: $interactive-03; + } + } +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index 0b96507fbe..e9632f12a1 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -1,5 +1,6 @@ @import "./axis"; @import "./callouts"; +@import "./chart-brush"; @import "./grid"; @import "./ruler"; @import "./skeleton"; From ad2f115c492c273c38f36dab0574d3d5e2a48b7e Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 17:33:44 +0800 Subject: [PATCH 277/510] feat: show tooltip when mouseover zoombar handle --- packages/core/src/components/axes/zoom-bar.ts | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 258d60ea1a..4498bb214f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -197,7 +197,11 @@ export class ZoomBar extends Component { // don't update brushHandle to avoid flash } else if (zoomDomain[0].valueOf() === zoomDomain[1].valueOf()) { brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle(this.getContainerSVG(), xScale.range()); + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range(), + xScale.domain() + ); } else { const selected = zoomDomain.map((domain) => xScale(domain)); if (selected[1] - selected[0] < this.MIN_SELECTION_DIFF) { @@ -205,7 +209,11 @@ export class ZoomBar extends Component { // don't update brushHandle to avoid flash } else { brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle(this.getContainerSVG(), selected); + this.updateBrushHandle( + this.getContainerSVG(), + selected, + zoomDomain + ); } } } @@ -213,14 +221,14 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - // update brush handle position - this.updateBrushHandle(this.getContainerSVG(), selection); - const newDomain = [ scale.invert(selection[0]), scale.invert(selection[1]) ]; + // update brush handle position + this.updateBrushHandle(this.getContainerSVG(), selection, newDomain); + // be aware that the value of d3.event changes during an event! // update zoomDomain only if the event comes from mouse event if ( @@ -254,7 +262,20 @@ export class ZoomBar extends Component { } } - updateBrushHandle(svg, selection) { + updateBrushHandleTooltip(svg, domain) { + // remove old handle tooltip + svg.select("title").remove(); + // add new handle tooltip + svg.append("title").text((d) => { + if (d.type === "w") { + return domain[0]; + } else if (d.type === "e") { + return domain[1]; + } + }); + } + + updateBrushHandle(svg, selection, domain) { const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -263,6 +284,7 @@ export class ZoomBar extends Component { const handleBarHeight = 12; const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; + // handle svg.select(this.brushSelector) .selectAll("rect.handle") @@ -277,7 +299,10 @@ export class ZoomBar extends Component { .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) - .style("display", null); // always display + .attr("cursor", "pointer") + .style("display", null) // always display + .call(this.updateBrushHandleTooltip, domain); + // handle-bar svg.select(this.brushSelector) .selectAll("rect.handle-bar") @@ -296,7 +321,8 @@ export class ZoomBar extends Component { .attr("y", handleYBarDiff) .attr("width", handleBarWidth) .attr("height", handleBarHeight) - .attr("cursor", "ew-resize"); + .attr("cursor", "pointer") + .call(this.updateBrushHandleTooltip, domain); this.updateClipPath( svg, From e903a6adce1768bce11fb5e4abf7fcaf624468ec Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 11:54:28 +0800 Subject: [PATCH 278/510] fix: avoid tick rotation flip during zoom domain changing - always rotate ticks during zoom domain changing --- packages/core/src/components/axes/axis.ts | 33 ++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 1b1dc86681..7f1cc1f3b5 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -29,6 +29,8 @@ export class Axis extends Component { scale: any; scaleType: ScaleTypes; + zoomDomainChanging = false; + constructor(model: ChartModel, services: any, configs?: any) { super(model, services, configs); @@ -37,6 +39,25 @@ export class Axis extends Component { } this.margins = this.configs.margins; + this.init(); + } + + init() { + this.services.events.addEventListener( + Events.ZoomBar.SELECTION_START, + () => { + this.zoomDomainChanging = true; + } + ); + this.services.events.addEventListener( + Events.ZoomBar.SELECTION_END, + () => { + this.zoomDomainChanging = false; + // need another update after zoom bar selection is completed + // to make sure the tick rotation is calculated correctly + this.services.events.dispatchEvent(Events.Model.UPDATE, {}); + } + ); } render(animate = true) { @@ -438,7 +459,9 @@ export class Axis extends Component { ? estimatedTickSize < minTickSize * 2 // datetime tick could be very long : estimatedTickSize < minTickSize; } - if (rotateTicks) { + + // always rotate ticks if zoomDomain is changing to avoid rotation flips during zoomDomain changing + if (rotateTicks || this.zoomDomainChanging) { if (!isNumberOfTicksProvided) { axis.ticks( this.getNumberOfFittingTicks( @@ -653,5 +676,13 @@ export class Axis extends Component { .on("mouseover", null) .on("mousemove", null) .on("mouseout", null); + this.services.events.removeEventListener( + Events.ZoomBar.SELECTION_START, + {} + ); + this.services.events.removeEventListener( + Events.ZoomBar.SELECTION_END, + {} + ); } } From 3b7413eaa181d326843dffa6fe089cbcfd8ebe40 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 18:19:27 +0800 Subject: [PATCH 279/510] fix: move chart brush selection above all graphs --- .../core/src/components/axes/chart-brush.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e267ae0bda..944e2245e6 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -16,6 +16,8 @@ export class ChartBrush extends Component { selectionSelector = "rect.selection"; // needs to match the class name in d3.brush + selectionElementId = "ChartBrushSelectionId"; + render(animate = true) { const svg = this.parent; const backdrop = DOMUtils.appendOrSelect( @@ -66,6 +68,9 @@ export class ChartBrush extends Component { const eventHandler = () => { const selection = event.selection; + if (selection === null) { + return; + } updateSelectionDash(selection); @@ -148,6 +153,26 @@ export class ChartBrush extends Component { backdrop, `g.${this.type}` ).call(brush); + + // set an id for rect.selection to be referred + brushArea + .select(this.selectionSelector) + .attr("id", this.selectionElementId); + + // create the chart brush group + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const selectionArea = this.getContainerSVG().attr( + "transform", + `translate(${xScaleStart},0)` + ); + // clear old svg + selectionArea.selectAll("svg").remove(); + // create a svg referring to d3 brush rect.selection + // this is to draw the selection above all graphs + selectionArea + .append("svg") + .append("use") + .attr("xlink:href", `#${this.selectionElementId}`); } } } From d974b60172fbf8177f23afb6bc3f70f21c843b15 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 20:47:11 +0800 Subject: [PATCH 280/510] fix: use another svg to display front selection --- .../core/src/components/axes/chart-brush.ts | 55 +++++++++---------- .../src/styles/components/_chart-brush.scss | 15 ++++- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 944e2245e6..d07edd0c4e 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -16,14 +16,22 @@ export class ChartBrush extends Component { selectionSelector = "rect.selection"; // needs to match the class name in d3.brush - selectionElementId = "ChartBrushSelectionId"; + frontSelectionSelector = "rect.frontSelection"; // needs to match the class name in _chart-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 = brushArea.select(this.selectionSelector); + const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true }); @@ -31,6 +39,12 @@ export class ChartBrush extends Component { 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 @@ -61,9 +75,7 @@ export class ChartBrush extends Component { dashArray += "," + selectionWidth.toString(); // bottom (invisible) dashArray += "," + height.toString(); // left - brushArea - .select(this.selectionSelector) - .attr("stroke-dasharray", dashArray); + frontSelection.attr("stroke-dasharray", dashArray); }; const eventHandler = () => { @@ -72,6 +84,14 @@ export class ChartBrush extends Component { 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("display", null); + updateSelectionDash(selection); const xScale = scaleTime().range([0, width]).domain(zoomDomain); @@ -137,6 +157,8 @@ export class ChartBrush extends Component { // clear brush selection brushArea.call(brush.move, null); + // hide frontSelection + frontSelection.style("display", "none"); } }; @@ -149,30 +171,7 @@ export class ChartBrush extends Component { .on("start brush end", eventHandler) .on("end.brushed", brushed); - const brushArea = DOMUtils.appendOrSelect( - backdrop, - `g.${this.type}` - ).call(brush); - - // set an id for rect.selection to be referred - brushArea - .select(this.selectionSelector) - .attr("id", this.selectionElementId); - - // create the chart brush group - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const selectionArea = this.getContainerSVG().attr( - "transform", - `translate(${xScaleStart},0)` - ); - // clear old svg - selectionArea.selectAll("svg").remove(); - // create a svg referring to d3 brush rect.selection - // this is to draw the selection above all graphs - selectionArea - .append("svg") - .append("use") - .attr("xlink:href", `#${this.selectionElementId}`); + brushArea.call(brush); } } } diff --git a/packages/core/src/styles/components/_chart-brush.scss b/packages/core/src/styles/components/_chart-brush.scss index f316c7d486..d458f791d4 100644 --- a/packages/core/src/styles/components/_chart-brush.scss +++ b/packages/core/src/styles/components/_chart-brush.scss @@ -1,9 +1,18 @@ .#{$prefix}--#{$charts-prefix}--chart-brush { + // disable default d3 brush selection g.chart-brush { rect.selection { - fill: $ui-03; - fill-opacity: 0.3; - stroke: $interactive-03; + fill: none; + fill-opacity: 0; + stroke: none; } } } + +g.#{$prefix}--#{$charts-prefix}--chart-brush { + rect.frontSelection { + fill: $ui-03; + fill-opacity: 0.3; + stroke: $interactive-03; + } +} From ac6ed5754599fa37f33126c68b0ffcb5c31913ed Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 20:59:47 +0800 Subject: [PATCH 281/510] fix: set cursor for front selection --- packages/core/src/components/axes/chart-brush.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index d07edd0c4e..85e48b3d08 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -90,6 +90,7 @@ export class ChartBrush extends Component { .attr("y", d3Selection.attr("y")) .attr("width", d3Selection.attr("width")) .attr("height", d3Selection.attr("height")) + .style("cursor", "pointer") .style("display", null); updateSelectionDash(selection); From fa1ac37efed02f957e17cfdfeb9fcd4525966aed Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 16 Jul 2020 15:07:40 +0800 Subject: [PATCH 282/510] fix: use unique chartClipId for each chart --- packages/core/src/components/axes/chart-clip.ts | 15 +++++++++++++++ packages/core/src/components/component.ts | 8 +++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts index 321db115e5..4a8a206c2f 100644 --- a/packages/core/src/components/axes/chart-clip.ts +++ b/packages/core/src/components/axes/chart-clip.ts @@ -1,14 +1,29 @@ // Internal Imports import { Component } from "../component"; import { DOMUtils } from "../../services"; +import { ChartModel } from "../../model"; // This class is used to create the clipPath to clip the chart components // It's necessary for zoom in/out behavior export class ChartClip extends Component { type = "chart-clip"; + // Give every chart-clip a distinct ID + // so they don't interfere each other in a page with multiple charts + chartClipId = "chart-clip-id-" + Math.floor(Math.random() * 99999999999); + chartClipPath: any; + constructor(model: ChartModel, services: any, configs?: any) { + super(model, services, configs); + this.init(); + } + + init() { + // set unique chartClipId in this chart to model + this.model.set({ chartClipId: this.chartClipId }, { skipUpdate: true }); + } + render(animate = true) { // Create the clipPath this.createClipPath(); diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 91ccf82ef8..87d55fba62 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -19,8 +19,6 @@ export class Component { protected model: ChartModel; protected services: any; - protected chartClipId = "chart-clip-id"; - constructor(model: ChartModel, services: any, configs?: any) { this.model = model; this.services = services; @@ -99,7 +97,11 @@ export class Component { ); if (withinChartClip) { - svg.attr("clip-path", `url(#${this.chartClipId})`); + // get unique chartClipId int this chart from model + const chartClipId = this.model.get("chartClipId"); + if (chartClipId) { + svg.attr("clip-path", `url(#${chartClipId})`); + } } return svg; From 80324a2e2509f6eb6ee68e2d5f5cc82987650644 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 16 Jul 2020 16:17:58 +0800 Subject: [PATCH 283/510] fix: keep zoom bar handle inside zoom bar range --- packages/core/src/components/axes/zoom-bar.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 4498bb214f..adc38f9bc9 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -20,7 +20,9 @@ export class ZoomBar extends Component { brushSelector = "g.zoom-bar-brush"; // needs to match the style in _zoom-bar.scss - clipId = "zoomBarClip"; + // Give every zoomBarClip a distinct ID + // so they don't interfere the other zoom bars in a page + clipId = "zoomBarClip-" + Math.floor(Math.random() * 99999999999); height = 32; @@ -28,6 +30,9 @@ export class ZoomBar extends Component { brush = brushX(); + // The max allowed selection ragne, will be updated soon in render() + maxSelectionRange: [0, 0]; + init() { this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { this.render(); @@ -97,6 +102,8 @@ export class ZoomBar extends Component { this.compensateDataForDefaultDomain(zoomBarData, defaultDomain, 0); xScale.range([axesLeftMargin, width]).domain(defaultDomain); + // keep max selection range + this.maxSelectionRange = xScale.range(); yScale .range([0, this.height - 6]) @@ -276,6 +283,7 @@ export class ZoomBar extends Component { } updateBrushHandle(svg, selection, domain) { + const self = this; const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -291,9 +299,17 @@ export class ZoomBar extends Component { .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { if (d.type === "w") { - return selection[0] + handleXDiff; + // handle should not exceed zoom bar range + return Math.max( + selection[0] + handleXDiff, + self.maxSelectionRange[0] + ); } else if (d.type === "e") { - return selection[1] + handleXDiff; + // handle should not exceed zoom bar range + return Math.min( + selection[1] + handleXDiff, + self.maxSelectionRange[1] - handleWidth + ); } }) .attr("y", 0) @@ -313,9 +329,15 @@ export class ZoomBar extends Component { }) .attr("x", function (d) { if (d.type === "w") { - return selection[0] + handleBarXDiff; + return Math.max( + selection[0] + handleBarXDiff, + self.maxSelectionRange[0] - handleXDiff + handleBarXDiff + ); } else if (d.type === "e") { - return selection[1] + handleBarXDiff; + return Math.min( + selection[1] + handleBarXDiff, + self.maxSelectionRange[1] + handleXDiff + handleBarXDiff + ); } }) .attr("y", handleYBarDiff) From 3ccef0dc08a6c428578c680f7a486ad081cc2364 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 20 Jul 2020 21:42:26 +0800 Subject: [PATCH 284/510] fix: use DOMUtils.appendOrSelect in case the element does not exist yet --- packages/core/src/components/axes/chart-brush.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 85e48b3d08..e872ec1cf5 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -30,7 +30,10 @@ export class ChartBrush extends Component { const brushArea = DOMUtils.appendOrSelect(backdrop, `g.${this.type}`); // set an id for rect.selection to be referred - const d3Selection = brushArea.select(this.selectionSelector); + const d3Selection = DOMUtils.appendOrSelect( + brushArea, + this.selectionSelector + ); const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true From 68993ec8eb22d1029d121dcf73a6abd6bb5f83aa Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 20 Jul 2020 22:25:10 +0800 Subject: [PATCH 285/510] feat: allow user to set zoom bar data --- packages/core/demo/data/index.ts | 6 +++++ packages/core/demo/data/zoom-bar.ts | 31 +++++++++++++++++++--- packages/core/src/interfaces/components.ts | 4 +++ packages/core/src/model.ts | 19 ++++++++++--- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index b826eceb22..7b0d92cf26 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -772,6 +772,12 @@ let allDemoGroups = [ chartType: chartTypes.StackedBarChart, isDemoExample: false }, + { + options: zoomBarDemos.definedZoomBarStackedBarTimeSeriesOptions, + data: zoomBarDemos.definedZoomBarStackedBarTimeSeriesData, + chartType: chartTypes.StackedBarChart, + isDemoExample: false + }, { options: zoomBarDemos.zoomBarBubbleTimeSeriesOptions, data: zoomBarDemos.zoomBarBubbleTimeSeriesData, diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 2dc1b076e1..98249d52da 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -11,10 +11,28 @@ const initialZoomDomain = [ new Date(2020, 11, 11, 0, 0, 25) ]; +const definedZoomBarData = [ + { date: new Date(2019, 0, 1), value: 10000 }, + { date: new Date(2019, 0, 2), value: 10 }, + { date: new Date(2019, 0, 3), value: 75000 }, + { date: new Date(2019, 0, 5), value: 65000 }, + { date: new Date(2019, 0, 6), value: 57312 }, + { date: new Date(2019, 0, 8), value: 10000 }, + { date: new Date(2019, 0, 13), value: 49213 }, + { date: new Date(2019, 0, 15), value: 70323 }, + { date: new Date(2019, 0, 17), value: 51213 }, + { date: new Date(2019, 0, 19), value: 21300 } +]; + // utility function to update title and enable zoomBar option -const addZoomBarToOptions = (options) => { - options["title"] = options["title"] + " - Zoom bar enabled"; - options["zoomBar"] = { enabled: true }; +const addZoomBarToOptions = (options, includeDefinedZoomBarData = false) => { + if (includeDefinedZoomBarData) { + options["title"] = options["title"] + " - Defined zoom bar enabled"; + options["zoomBar"] = { enabled: true, data: definedZoomBarData }; + } else { + options["title"] = options["title"] + " - Zoom bar enabled"; + options["zoomBar"] = { enabled: true }; + } return options; }; @@ -35,6 +53,13 @@ export const zoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.stackedBarTimeSeriesOptions) ); +export const definedZoomBarStackedBarTimeSeriesData = + barChart.stackedBarTimeSeriesData; +export const definedZoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( + Object.assign({}, barChart.stackedBarTimeSeriesOptions), + true +); + export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; export const zoomBarBubbleTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 5bdcf1f302..1a966db09f 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -130,4 +130,8 @@ export interface ZoomBarOptions { * an two element array which represents the initial zoom domain */ initialZoomDomain?: Object[]; + /** + * options related to zoom bar data + */ + data?: Object[]; } diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 63254bb3e7..843b353b2a 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -46,10 +46,23 @@ export class ChartModel { const domainIdentifier = cartesianScales.getDomainIdentifier(); const rangeIdentifier = cartesianScales.getRangeIdentifier(); - const displayData = this.getDisplayData(); + let zoomBarData; + // check if pre-defined zoom bar data exists + const definedZoomBarData = Tools.getProperty( + this.getOptions(), + "zoomBar", + "data" + ); + // if user already defines zoom bar data, use it + if (definedZoomBarData) { + zoomBarData = definedZoomBarData; + } else { + // use displayData if not defined + zoomBarData = this.getDisplayData(); + } // get all dates (Number) in displayData let allDates = []; - displayData.forEach((data) => { + zoomBarData.forEach((data) => { allDates = allDates.concat(Number(data[domainIdentifier])); }); allDates = Tools.removeArrayDuplicates(allDates).sort(); @@ -59,7 +72,7 @@ export class ChartModel { let sum = 0; const datum = {}; - displayData.forEach((data) => { + zoomBarData.forEach((data) => { if (Number(data[domainIdentifier]) === date) { sum += data[rangeIdentifier]; } From 1b0b9625b360ce240afa0abeaa11bda371349504 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 11:38:02 +0800 Subject: [PATCH 286/510] fix: definedZoomBarData needs at least two elements --- packages/core/src/components/axes/zoom-bar.ts | 3 +++ packages/core/src/model.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index adc38f9bc9..6d134f0534 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -393,6 +393,9 @@ export class ZoomBar extends Component { // assume the domains in data are already sorted compensateDataForDefaultDomain(data, defaultDomain, value) { + if (!data || data.length < 2) { + return; + } const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); // if min domain is extended diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 843b353b2a..b6fa3f9ba4 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -54,7 +54,7 @@ export class ChartModel { "data" ); // if user already defines zoom bar data, use it - if (definedZoomBarData) { + if (definedZoomBarData && definedZoomBarData.length > 1) { zoomBarData = definedZoomBarData; } else { // use displayData if not defined From 93cce4a9845a3a713a7dff89e620cc3bccddbd64 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 13:43:41 +0800 Subject: [PATCH 287/510] fix: axis needs to set opacity if not loading --- packages/core/src/components/axes/axis.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 7f1cc1f3b5..c6a3c33a91 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -494,6 +494,8 @@ export class Axis extends Component { // because the Skeleton component draws them if (isDataLoading) { container.attr("opacity", 0); + } else { + container.attr("opacity", 1); } axisRef.selectAll("g.tick").attr("aria-label", (d) => d); From eebb7802622437bb10a2ffdebb7ea9af4ceb3f45 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 15:15:46 +0800 Subject: [PATCH 288/510] fix: format zoom bar handle tooltip --- packages/core/src/components/axes/zoom-bar.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6d134f0534..0c742f6585 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -3,6 +3,10 @@ import { Component } from "../component"; import { Tools } from "../../tools"; import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; +import { + computeTimeIntervalName, + formatTick +} from "../../services/time-series"; // D3 Imports import { extent } from "d3-array"; @@ -269,21 +273,27 @@ export class ZoomBar extends Component { } } - updateBrushHandleTooltip(svg, domain) { + updateBrushHandleTooltip(svg, domain, timeScaleOptions) { + const timeInterval = computeTimeIntervalName(domain); + // remove old handle tooltip svg.select("title").remove(); // add new handle tooltip svg.append("title").text((d) => { if (d.type === "w") { - return domain[0]; + return formatTick(domain[0], 0, timeInterval, timeScaleOptions); } else if (d.type === "e") { - return domain[1]; + return formatTick(domain[1], 0, timeInterval, timeScaleOptions); } }); } updateBrushHandle(svg, selection, domain) { const self = this; + const timeScaleOptions = Tools.getProperty( + this.model.getOptions(), + "timeScale" + ); const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -317,7 +327,7 @@ export class ZoomBar extends Component { .attr("height", handleHeight) .attr("cursor", "pointer") .style("display", null) // always display - .call(this.updateBrushHandleTooltip, domain); + .call(this.updateBrushHandleTooltip, domain, timeScaleOptions); // handle-bar svg.select(this.brushSelector) @@ -344,7 +354,7 @@ export class ZoomBar extends Component { .attr("width", handleBarWidth) .attr("height", handleBarHeight) .attr("cursor", "pointer") - .call(this.updateBrushHandleTooltip, domain); + .call(this.updateBrushHandleTooltip, domain, timeScaleOptions); this.updateClipPath( svg, From 2e76f0ac8207c29fb5c5a9c8f04daeb5469be782 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 22 Jul 2020 16:39:39 +0800 Subject: [PATCH 289/510] fix: don't set brush handle tooltip if domain is undefined --- packages/core/src/components/axes/zoom-bar.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0c742f6585..d5cea1341c 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -274,10 +274,15 @@ export class ZoomBar extends Component { } updateBrushHandleTooltip(svg, domain, timeScaleOptions) { - const timeInterval = computeTimeIntervalName(domain); - // remove old handle tooltip svg.select("title").remove(); + + // if domain is undefined, do nothing + if (!domain) { + return; + } + + const timeInterval = computeTimeIntervalName(domain); // add new handle tooltip svg.append("title").text((d) => { if (d.type === "w") { From 563f680943198623cf12eacbef061cf41a8231f0 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 27 May 2020 15:44:15 +0800 Subject: [PATCH 290/510] feat: initial zoom bar implementation from PR 511 --- packages/core/src/axis-chart.ts | 16 +- packages/core/src/chart.ts | 9 +- packages/core/src/components/axes/axis.ts | 9 +- packages/core/src/components/axes/zoom-bar.ts | 216 ++++++++++++++++++ packages/core/src/components/index.ts | 1 + packages/core/src/model.ts | 10 +- packages/core/src/services/axis-zoom.ts | 60 +++++ packages/core/src/services/index.ts | 1 + .../core/src/services/scales-cartesian.ts | 6 + .../core/src/styles/components/_zoom-bar.scss | 11 + .../core/src/styles/components/index.scss | 1 + packages/core/src/styles/graphs/index.scss | 4 + 12 files changed, 333 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/axes/zoom-bar.ts create mode 100644 packages/core/src/services/axis-zoom.ts create mode 100644 packages/core/src/styles/components/_zoom-bar.scss diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index b9751b3a31..e4d105905f 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -12,7 +12,8 @@ import { Legend, Title, AxisChartsTooltip, - Spacer + Spacer, + ZoomBar } from "./components/index"; import { Tools } from "./tools"; @@ -123,6 +124,17 @@ export class AxisChart extends Chart { } }; + const zoomBarComponent = { + id: "zoom-bar", + components: [ + new ZoomBar(this.model, this.services) + ], + growth: { + x: LayoutGrowth.PREFERRED, + y: LayoutGrowth.FIXED + } + }; + // Add chart title if it exists const topLevelLayoutComponents = []; if (this.model.getOptions().title) { @@ -139,6 +151,8 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } + + topLevelLayoutComponents.push(zoomBarComponent); topLevelLayoutComponents.push(fullFrameComponent); return [ diff --git a/packages/core/src/chart.ts b/packages/core/src/chart.ts index ea65e9f3c3..4e86305488 100644 --- a/packages/core/src/chart.ts +++ b/packages/core/src/chart.ts @@ -37,7 +37,7 @@ export class Chart { // Contains the code that uses properties that are overridable by the super-class init(holder: Element, chartConfigs: ChartConfig) { // Store the holder in the model - this.model.set({ holder }, true); + this.model.set({ holder }, { skipUpdate: true }); // Initialize all services Object.keys(this.services).forEach((serviceName) => { @@ -49,8 +49,9 @@ export class Chart { }); // Call update() when model has been updated - this.services.events.addEventListener(ChartEvents.Model.UPDATE, () => { - this.update(true); + this.services.events.addEventListener(ChartEvents.Model.UPDATE, (e) => { + const animate = !!Tools.getProperty(e, "detail", "animate"); + this.update(animate); }); // Set model data & options @@ -110,7 +111,7 @@ export class Chart { // Remove the chart holder this.services.domUtils.getHolder().remove(); - this.model.set({ destroyed: true }, true); + this.model.set({ destroyed: true }, { skipUpdate: true }); } protected getChartComponents(graphFrameComponents: any[]) { diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 4feebae037..5eab971526 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -114,6 +114,13 @@ export class Axis extends Component { scale.range([startPosition, endPosition]); } + // if zoomDomain is available, update scale domain to Date array. + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { + scale.domain(zoomDomain.map(d => new Date(d))); + } + + // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -426,7 +433,7 @@ export class Axis extends Component { const ticksNumber = isTimeScaleType ? axis.tickValues().length : scale.ticks().length; - const estimatedTickSize = width / ticksNumber / 2; + const estimatedTickSize = width / ticksNumber / 1.6; rotateTicks = estimatedTickSize < minTickSize; } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts new file mode 100644 index 0000000000..dee5a24f55 --- /dev/null +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -0,0 +1,216 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { area, line } from "d3-shape"; +import { extent } from "d3-array"; +import { drag } from "d3-drag"; +import { event, select, selectAll } from "d3-selection"; + +export class ZoomBar extends Component { + type = "zoom-bar"; + + ogXScale: any; + + dragged = Tools.debounce((element, d, e) => { + element = select(element); + const startingHandle = element.attr("class").indexOf("start") !== -1; + + let domain; + if (startingHandle) { + domain = [ + this.ogXScale.invert(e.x), + this.ogXScale.domain()[1] + // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) + ]; + } else { + domain = [ + this.ogXScale.domain()[0], + this.ogXScale.invert(e.x) + // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) + ]; + } + this.model.set({ zoomDomain: domain }, { animate: false }); + }, 2.5); + + render(animate = true) { + const svg = this.getContainerSVG(); + + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); + const mainXScaleType = cartesianScales.getScaleTypeByPosition(mainXAxisPosition); + const mainYScaleType = cartesianScales.getScaleTypeByPosition(mainYAxisPosition); + + const height = 32; + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") + // .attr("transform", "translateX(10)") + .attr("width", "100%") + .attr("height", height) + .attr("opacity", 1); + + const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") + .attr("x", 0) + .attr("y", 32) + .attr("width", "100%") + .attr("height", 20) + .attr("opacity", 1) + .attr("fill", "none"); + + const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") + .attr("x", 0) + .attr("y", 0) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "white") + .attr("stroke", "#e8e8e8"); + + if (mainXScale) { + const displayData = this.model.getDisplayData(); + + if (mainXScaleType === ScaleTypes.TIME) { + // Get all date values provided in data + // TODO - Could be re-used through the model + let allDates = []; + displayData.datasets.forEach(dataset => { + allDates = allDates.concat(dataset.data.map(datum => Number(datum.date))); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + + // Go through all date values + // And get corresponding data from each dataset + const stackDataArray = allDates.map((date) => { + let count = 0; + let correspondingSum = 0; + const correspondingData = {}; + + displayData.datasets.forEach(dataset => { + const correspondingDatum = dataset.data.find(datum => Number(datum.date) === Number(date)); + if (correspondingDatum) { + ++count; + correspondingSum += correspondingDatum.value; + } + }); + correspondingData["label"] = date; + correspondingData["value"] = correspondingSum; + + return correspondingData; + }); + + const xScale = mainXScale.copy(); + if (!this.ogXScale) { + this.ogXScale = xScale; + } + + const yScale = mainYScale.copy(); + + const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); + + xScale.range([0, +width]) + .domain(extent(stackDataArray, (d: any) => d.label)); + + yScale.range([0, height - 6]) + .domain(extent(stackDataArray, (d: any) => d.value)); + + const zoomDomain = this.model.get("zoomDomain"); + + // D3 line generator function + const lineGenerator = line() + .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .y((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)) + .curve(this.services.curves.getD3Curve()); + // .defined((d: any, i) => { + // if (zoomDomain) { + // const dTimestamp = +d.label; + + // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; + // } + + // return true; + // }); + + const lineGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-line") + .attr("stroke", "#8e8e8e") + .attr("stroke-width", 3) + .attr("fill", "none") + .datum(stackDataArray) + .transition(this.services.transitions.getTransition("zoom-pan-line-update", animate)) + .attr("d", lineGenerator); + + const areaGenerator = area() + .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .y0(height) + .y1((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)); + + const areaGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-area") + .attr("fill", "#e0e0e0") + .datum(stackDataArray) + .transition(this.services.transitions.getTransition("zoom-pan-area-update", animate)) + .attr("d", areaGenerator); + + const startHandlePosition = zoomDomain ? xScale(+zoomDomain[0]) : 0; + // Handle #1 + const startHandle = DOMUtils.appendOrSelect(container, "rect.zoom-handle.start") + .attr("x", startHandlePosition) + .attr("width", 5) + .attr("height", "100%") + .attr("fill", "#525252"); + + DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.start") + .attr("x", startHandlePosition + 2) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + const endHandlePosition = zoomDomain ? xScale(+zoomDomain[1]) : xScale.range()[1]; + // console.log("endHandlePosition", endHandlePosition) + + // Handle #2 + const handle2 = DOMUtils.appendOrSelect(container, "rect.zoom-handle.end") + .attr("x", endHandlePosition - 5) + .attr("width", 5) + .attr("height", "100%") + .attr("fill", "#525252"); + + DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.end") + .attr("x", endHandlePosition - 5 + 2) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + + const outboundRangeRight = DOMUtils.appendOrSelect(container, "rect.outbound-range.right") + .attr("x", endHandlePosition) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "#fff") + .attr("fill-opacity", 0.85); + + const self = this; + // handle2.on("click", this.zoomIn.bind(this)); + selectAll("rect.zoom-handle").call( + drag() + .on("start", function() { + select(this).classed("dragging", true); + }) + .on("drag", function(d) { + self.dragged(this, d, event); + }) + .on("end", function() { + select(this).classed("dragging", false); + }) + ); + } + } + } + + zoomIn() { + const mainXScale = this.services.cartesianScales.getMainXScale(); + console.log("zoom in", mainXScale.domain()); + } +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 4142972992..60daf43009 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -34,3 +34,4 @@ export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; +export * from "./axes/zoom-bar"; diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 67f15f296a..999d689680 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -267,11 +267,11 @@ export class ChartModel { return this.state.options; } - set(newState: any, skipUpdate = false) { + set(newState: any, configs?: any) { this.state = Object.assign({}, this.state, newState); - if (!skipUpdate) { - this.update(); + if (!configs || !configs.skipUpdate) { + this.update(configs ? configs.animate : true); } } @@ -298,7 +298,7 @@ export class ChartModel { * Updates miscellanous information within the model * such as the color scales, or the legend data labels */ - update() { + update(animate = true) { if (!this.getDisplayData()) { return; } @@ -306,7 +306,7 @@ export class ChartModel { this.updateAllDataGroups(); this.setColorScale(); - this.services.events.dispatchEvent(Events.Model.UPDATE); + this.services.events.dispatchEvent(Events.Model.UPDATE, { animate }); } setUpdateCallback(cb: Function) { diff --git a/packages/core/src/services/axis-zoom.ts b/packages/core/src/services/axis-zoom.ts new file mode 100644 index 0000000000..8dbb2b3d80 --- /dev/null +++ b/packages/core/src/services/axis-zoom.ts @@ -0,0 +1,60 @@ +// Internal Imports +import { Service } from "./service"; +import { Tools } from "../tools"; + +// D3 Imports +import { brushX } from "d3-brush"; +import { event } from "d3-selection"; + +export class AxisZoom extends Service { + brush: any; + + brushed(e) { + if (event) { + const selectedRange = event.selection; + + const mainXAxis = this.services.axes.getMainXAxis(); + const mainXAxisRangeStart = mainXAxis.scale.range()[0]; + + const newDomain = selectedRange.map(d => d + mainXAxisRangeStart) + .map(mainXAxis.scale.invert, mainXAxis.scale); + + this.model.set({ + zoomDomain: newDomain.map(d => new Date(+d)) + }); + } + } + + // We need a custom debounce function here + // because of the async nature of d3.event + debounceWithD3Event(func, wait) { + let timeout; + return function() { + const e = Object.assign({}, event); + const context = this; + const later = function() { + timeout = null; + func.apply(context, [e]); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } + + getZoomInstance() { + const mainXAxis = this.services.axes.getMainXAxis(); + const mainYAxis = this.services.axes.getMainYAxis(); + const xMaxRange = mainXAxis.scale.range()[1]; + const yMaxRange = mainYAxis.scale.range()[0]; + + this.brush = brushX() + .extent([ + [0, 0], + [xMaxRange, yMaxRange] + ]) + .on("end", this.brushed.bind(this)); + + + return this.brush; + } +} diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index d999a07207..4e9fcc0e17 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -4,5 +4,6 @@ export * from "./essentials/events"; export * from "./essentials/transitions"; // MISC +export * from "./axis-zoom"; export * from "./scales-cartesian"; export * from "./curves"; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 45a2bd142b..e75d6f14e7 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -382,6 +382,12 @@ export class CartesianScales extends Service { return map(displayData, (d) => d[mapsTo]).keys(); } + // If scale is a TIME scale and zoomDomain is available, return Date array as the domain + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && axisOptions && scaleType === ScaleTypes.TIME) { + return zoomDomain.map(d => new Date(d)); + } + // Get the extent of the domain let domain; let allDataValues; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss new file mode 100644 index 0000000000..02fcf7a55f --- /dev/null +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -0,0 +1,11 @@ +g.#{$prefix}--#{$charts-prefix}--zoom-bar { + rect.zoom-handle.dragging, + rect.zoom-handle:hover { + fill: black; + cursor: col-resize; + } + + rect.zoom-handle-bar { + pointer-events: none; + } +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index 3fff851a8b..0b96507fbe 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -10,3 +10,4 @@ @import "./meter-title"; @import "./tooltip"; @import "./threshold"; +@import "./zoom-bar"; diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index 5530eba1a0..bebafaffc3 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -6,3 +6,7 @@ @import "./scatter-stacked"; @import "./radar"; @import "./gauge"; + +svg.#{$prefix}--#{$charts-prefix}--chart-svg svg.graph-frame { + overflow-x: hidden; +} From 1ff43436c4d8587ac00fba6065dbc0246a62f51c Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 2 Jun 2020 16:33:50 +0800 Subject: [PATCH 291/510] fix: update ZoomBar to match new data format - fix trailing-comma - update scales-cartesian.getValueFromScale() - update line time-series 15 seconds demo data --- packages/core/demo/data/time-series-axis.ts | 12 +- packages/core/src/components/axes/zoom-bar.ts | 165 +++++++++++++----- .../core/src/services/scales-cartesian.ts | 165 ++++++++++-------- packages/core/tslint.json | 2 +- 4 files changed, 216 insertions(+), 128 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e3a3502c92..c1c7183571 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -8,12 +8,12 @@ export const lineTimeSeriesData15seconds = { label: "Dataset 1", data: [ { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 30), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 45), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 0), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 15), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 30), value: 10 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 10 } + { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, + { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, + { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, + { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, + { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 }, ] } ] diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index dee5a24f55..f6ac815a7b 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -23,13 +23,13 @@ export class ZoomBar extends Component { if (startingHandle) { domain = [ this.ogXScale.invert(e.x), - this.ogXScale.domain()[1] + this.ogXScale.domain()[1], // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } else { domain = [ this.ogXScale.domain()[0], - this.ogXScale.invert(e.x) + this.ogXScale.invert(e.x), // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } @@ -44,8 +44,12 @@ export class ZoomBar extends Component { const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition(mainXAxisPosition); - const mainYScaleType = cartesianScales.getScaleTypeByPosition(mainYAxisPosition); + const mainXScaleType = cartesianScales.getScaleTypeByPosition( + mainXAxisPosition + ); + const mainYScaleType = cartesianScales.getScaleTypeByPosition( + mainYAxisPosition + ); const height = 32; const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") @@ -77,8 +81,8 @@ export class ZoomBar extends Component { // Get all date values provided in data // TODO - Could be re-used through the model let allDates = []; - displayData.datasets.forEach(dataset => { - allDates = allDates.concat(dataset.data.map(datum => Number(datum.date))); + displayData.forEach((data) => { + allDates = allDates.concat(Number(data.date)); }); allDates = Tools.removeArrayDuplicates(allDates).sort(); @@ -89,14 +93,13 @@ export class ZoomBar extends Component { let correspondingSum = 0; const correspondingData = {}; - displayData.datasets.forEach(dataset => { - const correspondingDatum = dataset.data.find(datum => Number(datum.date) === Number(date)); - if (correspondingDatum) { + displayData.forEach((data) => { + if (Number(data.date) === Number(date)) { ++count; - correspondingSum += correspondingDatum.value; + correspondingSum += data.value; } }); - correspondingData["label"] = date; + correspondingData["date"] = date; correspondingData["value"] = correspondingSum; return correspondingData; @@ -109,53 +112,113 @@ export class ZoomBar extends Component { const yScale = mainYScale.copy(); - const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true, + }); - xScale.range([0, +width]) - .domain(extent(stackDataArray, (d: any) => d.label)); + xScale + .range([0, width]) + .domain(extent(stackDataArray, (d: any) => d.date)); - yScale.range([0, height - 6]) + yScale + .range([0, height - 6]) .domain(extent(stackDataArray, (d: any) => d.value)); const zoomDomain = this.model.get("zoomDomain"); // D3 line generator function const lineGenerator = line() - .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) - .y((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)) + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) + .y( + (d, i) => + height - + cartesianScales.getValueFromScale( + yScale, + mainYScaleType, + mainYAxisPosition, + d, + i + ) + ) .curve(this.services.curves.getD3Curve()); - // .defined((d: any, i) => { - // if (zoomDomain) { - // const dTimestamp = +d.label; - - // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; - // } - - // return true; - // }); - - const lineGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-line") + // .defined((d: any, i) => { + // if (zoomDomain) { + // const dTimestamp = +d.label; + + // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; + // } + + // return true; + // }); + const lineGraph = DOMUtils.appendOrSelect( + container, + "path.zoom-graph-line" + ) .attr("stroke", "#8e8e8e") .attr("stroke-width", 3) .attr("fill", "none") .datum(stackDataArray) - .transition(this.services.transitions.getTransition("zoom-pan-line-update", animate)) + .transition( + this.services.transitions.getTransition( + "zoom-pan-line-update", + animate + ) + ) .attr("d", lineGenerator); const areaGenerator = area() - .x((d, i) => cartesianScales.getValueFromScale(xScale, mainXScaleType, d, i)) + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) .y0(height) - .y1((d, i) => height - cartesianScales.getValueFromScale(yScale, mainYScaleType, d, i)); - - const areaGraph = DOMUtils.appendOrSelect(container, "path.zoom-graph-area") + .y1( + (d, i) => + height - + cartesianScales.getValueFromScale( + yScale, + mainYScaleType, + mainYAxisPosition, + d, + i + ) + ); + + const areaGraph = DOMUtils.appendOrSelect( + container, + "path.zoom-graph-area" + ) .attr("fill", "#e0e0e0") .datum(stackDataArray) - .transition(this.services.transitions.getTransition("zoom-pan-area-update", animate)) + .transition( + this.services.transitions.getTransition( + "zoom-pan-area-update", + animate + ) + ) .attr("d", areaGenerator); - const startHandlePosition = zoomDomain ? xScale(+zoomDomain[0]) : 0; + const startHandlePosition = zoomDomain + ? xScale(+zoomDomain[0]) + : 0; // Handle #1 - const startHandle = DOMUtils.appendOrSelect(container, "rect.zoom-handle.start") + const startHandle = DOMUtils.appendOrSelect( + container, + "rect.zoom-handle.start" + ) .attr("x", startHandlePosition) .attr("width", 5) .attr("height", "100%") @@ -167,11 +230,16 @@ export class ZoomBar extends Component { .attr("width", 1) .attr("height", 12) .attr("fill", "#fff"); - const endHandlePosition = zoomDomain ? xScale(+zoomDomain[1]) : xScale.range()[1]; + const endHandlePosition = zoomDomain + ? xScale(+zoomDomain[1]) + : xScale.range()[1]; // console.log("endHandlePosition", endHandlePosition) // Handle #2 - const handle2 = DOMUtils.appendOrSelect(container, "rect.zoom-handle.end") + const endHandle = DOMUtils.appendOrSelect( + container, + "rect.zoom-handle.end" + ) .attr("x", endHandlePosition - 5) .attr("width", 5) .attr("height", "100%") @@ -184,24 +252,27 @@ export class ZoomBar extends Component { .attr("height", 12) .attr("fill", "#fff"); - const outboundRangeRight = DOMUtils.appendOrSelect(container, "rect.outbound-range.right") - .attr("x", endHandlePosition) - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "#fff") - .attr("fill-opacity", 0.85); + const outboundRangeRight = DOMUtils.appendOrSelect( + container, + "rect.outbound-range.right" + ) + .attr("x", endHandlePosition) + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "#fff") + .attr("fill-opacity", 0.85); const self = this; // handle2.on("click", this.zoomIn.bind(this)); selectAll("rect.zoom-handle").call( drag() - .on("start", function() { + .on("start", function () { select(this).classed("dragging", true); }) - .on("drag", function(d) { + .on("drag", function (d) { self.dragged(this, d, event); }) - .on("end", function() { + .on("end", function () { select(this).classed("dragging", false); }) ); diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index e75d6f14e7..162b0c833f 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -181,33 +181,50 @@ export class CartesianScales extends Service { return this.scales[this.getMainYAxisPosition()]; } - getValueFromScale(axisPosition: AxisPositions, datum: any, index?: number) { + getValueFromScale(scale: any, scaleType: ScaleTypes, axisPosition: AxisPositions, datum: any, index?: number) { const options = this.model.getOptions(); - const axisOptions = Tools.getProperty(options, "axes", axisPosition); - - const scaleType = this.scaleTypes[axisPosition]; - const scale = this.scales[axisPosition]; - + const axesOptions = Tools.getProperty(options, "axes"); + const axisOptions = axesOptions[axisPosition]; const { mapsTo } = axisOptions; const value = datum[mapsTo] !== undefined ? datum[mapsTo] : datum; - - if (scaleType === ScaleTypes.LABELS) { - return scale(value) + scale.step() / 2; + let scaledValue; + switch (scaleType) { + case ScaleTypes.LABELS: + scaledValue = scale(value) + scale.step() / 2; + break; + case ScaleTypes.TIME: + scaledValue = scale(new Date(value)); + break; + default: + scaledValue = scale(value); } + return scaledValue; + } - if (scaleType === ScaleTypes.TIME) { - return scale(new Date(value)); - } + getValueThroughAxisPosition(axisPosition: AxisPositions, datum: any, index?: number) { + const scaleType = this.scaleTypes[axisPosition]; + const scale = this.scales[axisPosition]; - return scale(value); + return this.getValueFromScale(scale, scaleType, axisPosition, datum, index); } + getDomainValue(d, i) { - return this.getValueFromScale(this.domainAxisPosition, d, i); + return this.getValueThroughAxisPosition(this.domainAxisPosition, d, i); } getRangeValue(d, i) { - return this.getValueFromScale(this.rangeAxisPosition, d, i); + return this.getValueThroughAxisPosition(this.rangeAxisPosition, d, i); + } + + getXValue(d, i) { + const mainXAxisPosition = this.getMainXAxisPosition(); + return this.getValueThroughAxisPosition(mainXAxisPosition, d, i); + } + + getYValue(d, i) { + const mainYAxisPosition = this.getMainYAxisPosition(); + return this.getValueThroughAxisPosition(mainYAxisPosition, d, i); } getDomainIdentifier() { @@ -271,6 +288,65 @@ export class CartesianScales extends Service { } } + getHighestDomainThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const domainAxisPosition = this.getDomainAxisPosition(); + + const { thresholds } = axesOptions[domainAxisPosition]; + + if (!thresholds) { + return null; + } + + const domainScale = this.getDomainScale(); + // Find the highest threshold for the domain + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value, + )[0]; + + const scaleType = this.getScaleTypeByPosition(domainAxisPosition); + if ( + scaleType === ScaleTypes.TIME && + (typeof highestThreshold.value === "string" || + highestThreshold.value.getTime === undefined) + ) { + highestThreshold.value = new Date(highestThreshold.value); + } + + return { + threshold: highestThreshold, + scaleValue: domainScale(highestThreshold.value), + }; + } + + getHighestRangeThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const rangeAxisPosition = this.getRangeAxisPosition(); + + const { thresholds } = axesOptions[rangeAxisPosition]; + + if (!thresholds) { + return null; + } + + const rangeScale = this.getRangeScale(); + // Find the highest threshold for the range + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value, + )[0]; + + return { + threshold: highestThreshold, + scaleValue: rangeScale(highestThreshold.value), + }; + } + protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); @@ -438,65 +514,6 @@ export class CartesianScales extends Service { return scale; } - - getHighestDomainThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const domainAxisPosition = this.getDomainAxisPosition(); - - const { thresholds } = axesOptions[domainAxisPosition]; - - if (!thresholds) { - return null; - } - - const domainScale = this.getDomainScale(); - // Find the highest threshold for the domain - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - const scaleType = this.getScaleTypeByPosition(domainAxisPosition); - if ( - scaleType === ScaleTypes.TIME && - (typeof highestThreshold.value === "string" || - highestThreshold.value.getTime === undefined) - ) { - highestThreshold.value = new Date(highestThreshold.value); - } - - return { - threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value) - }; - } - - getHighestRangeThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const rangeAxisPosition = this.getRangeAxisPosition(); - - const { thresholds } = axesOptions[rangeAxisPosition]; - - if (!thresholds) { - return null; - } - - const rangeScale = this.getRangeScale(); - // Find the highest threshold for the range - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - return { - threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value) - }; - } } function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { diff --git a/packages/core/tslint.json b/packages/core/tslint.json index a5a0970b82..91271d283a 100644 --- a/packages/core/tslint.json +++ b/packages/core/tslint.json @@ -92,7 +92,7 @@ "trailing-comma": [ true, { - "multiline": "never", + "multiline": "always", "singleline": "never" } ] From 6845aff38e5b4d638a47628c5beb5b3fa495800e Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 3 Jun 2020 21:56:58 +0800 Subject: [PATCH 292/510] feat: use d3-brush to implement selection --- packages/core/src/components/axes/zoom-bar.ts | 184 +++++++++--------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index f6ac815a7b..8066d0aa08 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -5,20 +5,30 @@ import { ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports -import { area, line } from "d3-shape"; import { extent } from "d3-array"; +import { brushX } from "d3-brush"; import { drag } from "d3-drag"; -import { event, select, selectAll } from "d3-selection"; +import { area, line } from "d3-shape"; +import { event, select, selectAll, BaseType } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + height = 32; + ogXScale: any; dragged = Tools.debounce((element, d, e) => { + console.log("dragged"); + console.log(this.ogXScale.invert(100)); + console.log(d); + console.log(e); element = select(element); const startingHandle = element.attr("class").indexOf("start") !== -1; - + console.log("startingHandle?:" + startingHandle); + const oldDomain = this.model.get("zoomDomain"); + console.log("old domain"); + console.log(oldDomain); let domain; if (startingHandle) { domain = [ @@ -33,9 +43,13 @@ export class ZoomBar extends Component { // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) ]; } + console.log("new domain"); + console.log(domain); this.model.set({ zoomDomain: domain }, { animate: false }); }, 2.5); + + render(animate = true) { const svg = this.getContainerSVG(); @@ -45,19 +59,69 @@ export class ZoomBar extends Component { const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition + mainXAxisPosition, ); const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition + mainYAxisPosition, ); - const height = 32; + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") // .attr("transform", "translateX(10)") .attr("width", "100%") - .attr("height", height) + .attr("height", this.height) .attr("opacity", 1); + const brushHandle = (g, selection) => { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{type: "w"}, {type: "e"}]) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32) + .attr("fill", "#525252"); + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{type: "w"}, {type: "e"}]) + .join("rect") + .attr("class", function(d) { return "handle-bar handle-bar--" + d.type; }) + .attr("x", function(d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12) + .attr("fill", "#fff"); + + }; + + const brushed = () => { + const selection = event.selection; + if (selection === null) { + // do nothing + console.log("selection === null"); + } else { + // TODO - pass selection to callback function or update scaleDomain + } + // update brush handle position + select(svg).call(brushHandle, selection); + }; + + const brush = brushX() + .extent([[0, 0], [700, this.height]]) + .on("start brush end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") + .call(brush) + .call(brush.move, mainXScale.range()); // TODO -- mainXScale.range() incorrect + const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) .attr("y", 32) @@ -105,11 +169,13 @@ export class ZoomBar extends Component { return correspondingData; }); + // if (!this.ogXScale) { + // this.ogXScale = cartesianScales.getDomainScale(); + // } const xScale = mainXScale.copy(); if (!this.ogXScale) { this.ogXScale = xScale; } - const yScale = mainYScale.copy(); const { width } = DOMUtils.getSVGElementSize(this.parent, { @@ -121,7 +187,7 @@ export class ZoomBar extends Component { .domain(extent(stackDataArray, (d: any) => d.date)); yScale - .range([0, height - 6]) + .range([0, this.height - 6]) .domain(extent(stackDataArray, (d: any) => d.value)); const zoomDomain = this.model.get("zoomDomain"); @@ -134,19 +200,19 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i - ) + i, + ), ) .y( (d, i) => - height - + this.height - cartesianScales.getValueFromScale( yScale, mainYScaleType, mainYAxisPosition, d, - i - ) + i, + ), ) .curve(this.services.curves.getD3Curve()); // .defined((d: any, i) => { @@ -160,7 +226,7 @@ export class ZoomBar extends Component { // }); const lineGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-line" + "path.zoom-graph-line", ) .attr("stroke", "#8e8e8e") .attr("stroke-width", 3) @@ -169,8 +235,8 @@ export class ZoomBar extends Component { .transition( this.services.transitions.getTransition( "zoom-pan-line-update", - animate - ) + animate, + ), ) .attr("d", lineGenerator); @@ -181,101 +247,36 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i - ) + i, + ), ) - .y0(height) + .y0(this.height) .y1( (d, i) => - height - + this.height - cartesianScales.getValueFromScale( yScale, mainYScaleType, mainYAxisPosition, d, - i - ) + i, + ), ); const areaGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-area" + "path.zoom-graph-area", ) .attr("fill", "#e0e0e0") .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-area-update", - animate - ) + animate, + ), ) .attr("d", areaGenerator); - const startHandlePosition = zoomDomain - ? xScale(+zoomDomain[0]) - : 0; - // Handle #1 - const startHandle = DOMUtils.appendOrSelect( - container, - "rect.zoom-handle.start" - ) - .attr("x", startHandlePosition) - .attr("width", 5) - .attr("height", "100%") - .attr("fill", "#525252"); - - DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.start") - .attr("x", startHandlePosition + 2) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - const endHandlePosition = zoomDomain - ? xScale(+zoomDomain[1]) - : xScale.range()[1]; - // console.log("endHandlePosition", endHandlePosition) - - // Handle #2 - const endHandle = DOMUtils.appendOrSelect( - container, - "rect.zoom-handle.end" - ) - .attr("x", endHandlePosition - 5) - .attr("width", 5) - .attr("height", "100%") - .attr("fill", "#525252"); - - DOMUtils.appendOrSelect(container, "rect.zoom-handle-bar.end") - .attr("x", endHandlePosition - 5 + 2) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - - const outboundRangeRight = DOMUtils.appendOrSelect( - container, - "rect.outbound-range.right" - ) - .attr("x", endHandlePosition) - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "#fff") - .attr("fill-opacity", 0.85); - - const self = this; - // handle2.on("click", this.zoomIn.bind(this)); - selectAll("rect.zoom-handle").call( - drag() - .on("start", function () { - select(this).classed("dragging", true); - }) - .on("drag", function (d) { - self.dragged(this, d, event); - }) - .on("end", function () { - select(this).classed("dragging", false); - }) - ); } } } @@ -284,4 +285,5 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } + } From 5efec0d5ff34840378550b8397fb1629510b5aaa Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 8 Jun 2020 16:04:58 +0800 Subject: [PATCH 293/510] fix: add left margin to align to chart - using .scss to set fill and stroke color --- packages/core/src/components/axes/zoom-bar.ts | 162 +++++++----------- .../core/src/styles/components/_zoom-bar.scss | 26 ++- 2 files changed, 86 insertions(+), 102 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 8066d0aa08..b200d522b2 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -7,9 +7,8 @@ import { DOMUtils } from "../../services"; // D3 Imports import { extent } from "d3-array"; import { brushX } from "d3-brush"; -import { drag } from "d3-drag"; import { area, line } from "d3-shape"; -import { event, select, selectAll, BaseType } from "d3-selection"; +import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; @@ -18,39 +17,9 @@ export class ZoomBar extends Component { ogXScale: any; - dragged = Tools.debounce((element, d, e) => { - console.log("dragged"); - console.log(this.ogXScale.invert(100)); - console.log(d); - console.log(e); - element = select(element); - const startingHandle = element.attr("class").indexOf("start") !== -1; - console.log("startingHandle?:" + startingHandle); - const oldDomain = this.model.get("zoomDomain"); - console.log("old domain"); - console.log(oldDomain); - let domain; - if (startingHandle) { - domain = [ - this.ogXScale.invert(e.x), - this.ogXScale.domain()[1], - // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) - ]; - } else { - domain = [ - this.ogXScale.domain()[0], - this.ogXScale.invert(e.x), - // Math.min(this.ogXScale.invert(e.x), this.ogXScale.domain()[1]) - ]; - } - console.log("new domain"); - console.log(domain); - this.model.set({ zoomDomain: domain }, { animate: false }); - }, 2.5); - - - render(animate = true) { + // TODO - get correct axis left width + const axisLeftWidth = 23; const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -65,63 +34,11 @@ export class ZoomBar extends Component { mainYAxisPosition, ); - const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") - // .attr("transform", "translateX(10)") .attr("width", "100%") .attr("height", this.height) .attr("opacity", 1); - const brushHandle = (g, selection) => { - const handleSize = 5; - // handle - svg.select("g.brush") - .selectAll("rect.handle") - .data([{type: "w"}, {type: "e"}]) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) - .attr("fill", "#525252"); - // handle-bar - svg.select("g.brush") - .selectAll("rect.handle-bar") - .data([{type: "w"}, {type: "e"}]) - .join("rect") - .attr("class", function(d) { return "handle-bar handle-bar--" + d.type; }) - .attr("x", function(d) { - if (d.type === "w") { - return selection[0] - 1; - } else if (d.type === "e") { - return selection[1] - 1; - } - }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12) - .attr("fill", "#fff"); - - }; - - const brushed = () => { - const selection = event.selection; - if (selection === null) { - // do nothing - console.log("selection === null"); - } else { - // TODO - pass selection to callback function or update scaleDomain - } - // update brush handle position - select(svg).call(brushHandle, selection); - }; - - const brush = brushX() - .extent([[0, 0], [700, this.height]]) - .on("start brush end", brushed); - - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") - .call(brush) - .call(brush.move, mainXScale.range()); // TODO -- mainXScale.range() incorrect - const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) .attr("y", 32) @@ -131,7 +48,7 @@ export class ZoomBar extends Component { .attr("fill", "none"); const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") - .attr("x", 0) + .attr("x", axisLeftWidth) .attr("y", 0) .attr("width", "100%") .attr("height", "100%") @@ -169,9 +86,9 @@ export class ZoomBar extends Component { return correspondingData; }); - // if (!this.ogXScale) { - // this.ogXScale = cartesianScales.getDomainScale(); - // } + if (!this.ogXScale) { + this.ogXScale = cartesianScales.getDomainScale(); + } const xScale = mainXScale.copy(); if (!this.ogXScale) { this.ogXScale = xScale; @@ -183,7 +100,7 @@ export class ZoomBar extends Component { }); xScale - .range([0, width]) + .range([axisLeftWidth, width]) .domain(extent(stackDataArray, (d: any) => d.date)); yScale @@ -228,9 +145,6 @@ export class ZoomBar extends Component { container, "path.zoom-graph-line", ) - .attr("stroke", "#8e8e8e") - .attr("stroke-width", 3) - .attr("fill", "none") .datum(stackDataArray) .transition( this.services.transitions.getTransition( @@ -267,7 +181,6 @@ export class ZoomBar extends Component { container, "path.zoom-graph-area", ) - .attr("fill", "#e0e0e0") .datum(stackDataArray) .transition( this.services.transitions.getTransition( @@ -277,6 +190,64 @@ export class ZoomBar extends Component { ) .attr("d", areaGenerator); + const updateBrushHandle = (g, selection) => { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{ type: "w" }, { type: "e" }]) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32); + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{ type: "w" }, { type: "e" }]) + .join("rect") + .attr("class", function (d) { + return "handle-bar handle-bar--" + d.type; + }) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12); + }; + + const brushed = () => { + const selection = event.selection; + if (selection === null) { + // do nothing + console.log("selection === null"); + } else { + // TODO - pass selection to callback function or update scaleDomain + } + // update brush handle position + select(svg).call(updateBrushHandle, selection); + + const domain = [ + mainXScale.invert(selection[0]), + mainXScale.invert(selection[1]), + ]; + // TODO -- update zoomDomain in model + // this.model.set({zoomDomain: domain}, {animate: false}); + }; + + const brush = brushX() + .extent([ + [0 + axisLeftWidth, 0], + [700, this.height], + ]) + .on("start brush end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") + .call(brush) + .call(brush.move, xScale.range()); } } } @@ -285,5 +256,4 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } - } diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 02fcf7a55f..59019956a8 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -1,11 +1,25 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { - rect.zoom-handle.dragging, - rect.zoom-handle:hover { - fill: black; - cursor: col-resize; + rect.zoom-bg { + fill: $ui-background; + stroke: $ui-01; } - rect.zoom-handle-bar { - pointer-events: none; + path.zoom-graph-line { + stroke: $ui-04; + stroke-width: 3; + fill: none; + } + + path.zoom-graph-area { + fill: $ui-03; + stroke: $ui-04; + } + + g.brush rect.handle { + fill: $icon-02; + } + + g.brush rect.handle-bar { + fill: $ui-02; } } From b8d7d386f18ee44d722a6345467b049ad6139313 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 16:03:16 +0800 Subject: [PATCH 294/510] feat: set brush selection to zoomDomain in Model - use flag to avoid recursive update event --- packages/core/src/components/axes/zoom-bar.ts | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index b200d522b2..12b7b356be 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,7 +17,14 @@ export class ZoomBar extends Component { ogXScale: any; + // use this flag to avoid recursive update events + skipNextUpdate = false; + render(animate = true) { + if (this.skipNextUpdate === true) { + this.skipNextUpdate = false; + return; + } // TODO - get correct axis left width const axisLeftWidth = 23; const svg = this.getContainerSVG(); @@ -28,10 +35,10 @@ export class ZoomBar extends Component { const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition, + mainXAxisPosition ); const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition, + mainYAxisPosition ); const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") @@ -96,7 +103,7 @@ export class ZoomBar extends Component { const yScale = mainYScale.copy(); const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true, + useAttrs: true }); xScale @@ -117,8 +124,8 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i, - ), + i + ) ) .y( (d, i) => @@ -128,8 +135,8 @@ export class ZoomBar extends Component { mainYScaleType, mainYAxisPosition, d, - i, - ), + i + ) ) .curve(this.services.curves.getD3Curve()); // .defined((d: any, i) => { @@ -143,14 +150,14 @@ export class ZoomBar extends Component { // }); const lineGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-line", + "path.zoom-graph-line" ) .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-line-update", - animate, - ), + animate + ) ) .attr("d", lineGenerator); @@ -161,8 +168,8 @@ export class ZoomBar extends Component { mainXScaleType, mainXAxisPosition, d, - i, - ), + i + ) ) .y0(this.height) .y1( @@ -173,20 +180,20 @@ export class ZoomBar extends Component { mainYScaleType, mainYAxisPosition, d, - i, - ), + i + ) ); const areaGraph = DOMUtils.appendOrSelect( container, - "path.zoom-graph-area", + "path.zoom-graph-area" ) .datum(stackDataArray) .transition( this.services.transitions.getTransition( "zoom-pan-area-update", - animate, - ), + animate + ) ) .attr("d", areaGenerator); @@ -196,9 +203,17 @@ export class ZoomBar extends Component { svg.select("g.brush") .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 3; + } else if (d.type === "e") { + return selection[1] - 3; + } + }) .attr("y", 0) .attr("width", handleSize) - .attr("height", 32); + .attr("height", 32) + .style("display", null); // always display // handle-bar svg.select("g.brush") .selectAll("rect.handle-bar") @@ -220,10 +235,10 @@ export class ZoomBar extends Component { }; const brushed = () => { - const selection = event.selection; + let selection = event.selection; if (selection === null) { - // do nothing - console.log("selection === null"); + // set to default full range + selection = xScale.range(); } else { // TODO - pass selection to callback function or update scaleDomain } @@ -231,23 +246,29 @@ export class ZoomBar extends Component { select(svg).call(updateBrushHandle, selection); const domain = [ - mainXScale.invert(selection[0]), - mainXScale.invert(selection[1]), + xScale.invert(selection[0]), + xScale.invert(selection[1]) ]; - // TODO -- update zoomDomain in model - // this.model.set({zoomDomain: domain}, {animate: false}); + // only if the brush event comes from mouseup event + if (event.sourceEvent != null && event.type === "end") { + this.skipNextUpdate = true; // avoid recursive update + this.model.set( + { zoomDomain: domain }, + { animate: false } + ); + } }; const brush = brushX() .extent([ [0 + axisLeftWidth, 0], - [700, this.height], + [700, this.height] ]) .on("start brush end", brushed); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") .call(brush) - .call(brush.move, xScale.range()); + .call(brush.move, xScale.range()); // default to full range } } } From 22084344cff748d455df351d47c5498d02627d82 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:42:26 +0800 Subject: [PATCH 295/510] feat: create ZoomBarOptions and default configuration - default to disable zoombar --- packages/core/src/axis-chart.ts | 9 ++++----- packages/core/src/configuration.ts | 13 +++++++++++-- packages/core/src/interfaces/charts.ts | 7 ++++++- packages/core/src/interfaces/components.ts | 10 ++++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index e4d105905f..04f9bdeeb5 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -126,9 +126,7 @@ export class AxisChart extends Chart { const zoomBarComponent = { id: "zoom-bar", - components: [ - new ZoomBar(this.model, this.services) - ], + components: [new ZoomBar(this.model, this.services)], growth: { x: LayoutGrowth.PREFERRED, y: LayoutGrowth.FIXED @@ -151,8 +149,9 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } - - topLevelLayoutComponents.push(zoomBarComponent); + if (this.model.getOptions().zoomBar.enabled === true) { + topLevelLayoutComponents.push(zoomBarComponent); + } topLevelLayoutComponents.push(fullFrameComponent); return [ diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index f93d747285..d09e139d44 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -23,7 +23,8 @@ import { StackedBarOptions, MeterChartOptions, GaugeTypes, - Alignments + Alignments, + ZoomBarOptions } from "./interfaces"; import enUSLocaleObject from "date-fns/locale/en-US/index"; @@ -127,6 +128,13 @@ export const timeScale: TimeScaleOptions = { } }; +/** + * ZoomBar options + */ +export const zoomBar: ZoomBarOptions = { + enabled: false +}; + /** * Base chart options common to any chart */ @@ -155,7 +163,8 @@ const chart: BaseChartOptions = { const axisChart: AxisChartOptions = Tools.merge({}, chart, { axes, timeScale, - grid + grid, + zoomBar } as AxisChartOptions); /** diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index eceba01b06..5ff08b4f9d 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -8,7 +8,8 @@ import { LegendOptions, TooltipOptions, GridOptions, - AxesOptions + AxesOptions, + ZoomBarOptions } from "./index"; import { BarOptions, StackedBarOptions } from "./components"; import { TimeScaleOptions } from "./axis-scales"; @@ -45,6 +46,10 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 4b56616f59..57199a3dad 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -116,3 +116,13 @@ export interface BarOptions { export interface StackedBarOptions extends BarOptions { dividerSize?: number; } + +/** + * customize the ZoomBar component + */ +export interface ZoomBarOptions { + /** + * is the zoom-bar visible or not + */ + enabled?: boolean; +} From aed57ea04483209fa7b144bb89d0fab69908dd11 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:44:58 +0800 Subject: [PATCH 296/510] feat: create zoombar story in storybook --- packages/core/demo/data/time-series-axis.ts | 34 ++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index c1c7183571..e9519866dc 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -13,7 +13,7 @@ export const lineTimeSeriesData15seconds = { { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } ] } ] @@ -418,3 +418,35 @@ export const lineTimeSeriesAllLabelsInPrimaryFormatOptions = { addSpaceOnEdges: 0 } }; + +// ZoomBar +export const lineTimeSeriesDataZoomBar = { + labels: ["Qty"], + datasets: [ + { + label: "Dataset 1", + data: [ + { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, + { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, + { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, + { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, + { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, + { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, + { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } + ] + } + ] +}; + +export const lineTimeSeriesZoomBarOptions = { + title: "Line (time series) - zoom-bar enabled", + axes: { + left: {}, + bottom: { + scaleType: "time" + } + }, + zoomBar: { + enabled: true + } +}; From fc1fccc302b530e67b868d51a58e7c000fb86ed5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 11:17:12 +0800 Subject: [PATCH 297/510] fix: set tslint trailing-comma to never --- packages/core/tslint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/tslint.json b/packages/core/tslint.json index 91271d283a..a5a0970b82 100644 --- a/packages/core/tslint.json +++ b/packages/core/tslint.json @@ -92,7 +92,7 @@ "trailing-comma": [ true, { - "multiline": "always", + "multiline": "never", "singleline": "never" } ] From c2c700ea37dd5201a9a7b74eed663a41670c6c6f Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 12:23:10 +0800 Subject: [PATCH 298/510] fix: allow zoomDomain to update for every brush event --- packages/core/src/components/axes/zoom-bar.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 12b7b356be..37c68ab870 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,14 +17,7 @@ export class ZoomBar extends Component { ogXScale: any; - // use this flag to avoid recursive update events - skipNextUpdate = false; - render(animate = true) { - if (this.skipNextUpdate === true) { - this.skipNextUpdate = false; - return; - } // TODO - get correct axis left width const axisLeftWidth = 23; const svg = this.getContainerSVG(); @@ -245,17 +238,24 @@ export class ZoomBar extends Component { // update brush handle position select(svg).call(updateBrushHandle, selection); - const domain = [ + const newDomain = [ xScale.invert(selection[0]), xScale.invert(selection[1]) ]; + // only if the brush event comes from mouseup event - if (event.sourceEvent != null && event.type === "end") { - this.skipNextUpdate = true; // avoid recursive update - this.model.set( - { zoomDomain: domain }, - { animate: false } - ); + if (event.sourceEvent != null) { + // only if zoomDomain is never set or needs update + if ( + zoomDomain === undefined || + zoomDomain[0] !== newDomain[0] || + zoomDomain[1] !== newDomain[1] + ) { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + } } }; @@ -266,9 +266,12 @@ export class ZoomBar extends Component { ]) .on("start brush end", brushed); - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush") - .call(brush) - .call(brush.move, xScale.range()); // default to full range + const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( + brush + ); + if (zoomDomain === undefined) { + brushArea.call(brush.move, xScale.range()); // default to full range + } } } } From d5d512e6c738b2c1ea6c56d28b663fb26b24b8e3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 11 Jun 2020 23:06:38 +0800 Subject: [PATCH 299/510] feat: provide ZoomBar options for brush event callback - selected x range and domain as callback parameters --- packages/core/demo/data/time-series-axis.ts | 20 +++++++++++++++- packages/core/src/components/axes/zoom-bar.ts | 23 +++++++++++++++++++ packages/core/src/configuration.ts | 5 +++- packages/core/src/interfaces/components.ts | 12 ++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e9519866dc..e79de136b6 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -437,6 +437,21 @@ export const lineTimeSeriesDataZoomBar = { } ] }; +const selectionStartFun = (selection, domain) => { + console.log("ZoomBar SelectionStart callback!"); + console.log(selection); + console.log(domain); +}; +const selectionInProgressFun = (selection, domain) => { + console.log("ZoomBar SelectionInProgress callback!"); + console.log(selection); + console.log(domain); +}; +const selectionEndFun = (selection, domain) => { + console.log("ZoomBar SelectionEnd callback!"); + console.log(selection); + console.log(domain); +}; export const lineTimeSeriesZoomBarOptions = { title: "Line (time series) - zoom-bar enabled", @@ -447,6 +462,9 @@ export const lineTimeSeriesZoomBarOptions = { } }, zoomBar: { - enabled: true + enabled: true, + selectionStart: selectionStartFun, + selectionInProgress: selectionInProgressFun, + selectionEnd: selectionEndFun } }; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 37c68ab870..4fdfa2fec5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -256,6 +256,29 @@ export class ZoomBar extends Component { { animate: false } ); } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress( + selection, + newDomain + ); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } } }; diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index d09e139d44..5109d282e1 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -132,7 +132,10 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false + enabled: false, + selectionStart: undefined, + selectionInProgress: undefined, + selectionEnd: undefined }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 57199a3dad..33d100ff45 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -125,4 +125,16 @@ export interface ZoomBarOptions { * is the zoom-bar visible or not */ enabled?: boolean; + /** + * a function to handle selection start event + */ + selectionStart?: Function; + /** + * a function to handle selection in progress event + */ + selectionInProgress?: Function; + /** + * a function to handle selection end event + */ + selectionEnd?: Function; } From 48a313d174b7a8e6bb01a43c93d89a35e8098b4a Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Mon, 15 Jun 2020 18:46:54 +0800 Subject: [PATCH 300/510] feat: apply clipPath to line chart - v1 --- packages/core/src/charts/line.ts | 2 + packages/core/src/components/axes/cover.ts | 64 +++++++++++++++++++++ packages/core/src/components/component.ts | 25 ++++++-- packages/core/src/components/graphs/line.ts | 3 +- packages/core/src/components/index.ts | 1 + 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/components/axes/cover.ts diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index 469bf7a28e..d766c7ad5a 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -11,6 +11,7 @@ import { Line, Ruler, Scatter, + Cover, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) Tooltip, @@ -40,6 +41,7 @@ export class LineChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts new file mode 100644 index 0000000000..20b4083e09 --- /dev/null +++ b/packages/core/src/components/axes/cover.ts @@ -0,0 +1,64 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { axisBottom, axisLeft } from "d3-axis"; +import { mouse, select } from "d3-selection"; +import { TooltipTypes, Events } from "../../interfaces"; + +export class Cover extends Component { + type = "cover"; + + coverClipPath: any; + + render(animate = true) { + // Create the cover + this.createCover(); + } + + + createCover() { + const svg = this.parent; + console.log("!!! cover svg: ", svg); + const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainYScale = this.services.cartesianScales.getMainYScale(); + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + // Get height + this.coverClipPath = DOMUtils.appendOrSelect(svg, `clipPath.${this.type}`); + this.coverClipPath + .attr("id", `${this.type}Clip`); + const coverRect = DOMUtils.appendOrSelect( + this.coverClipPath, + "rect.cover" + ); + coverRect + .attr("x", xScaleStart) + .attr("y", yScaleStart) + .attr("width", xScaleEnd - xScaleStart) + .attr("height", yScaleEnd - yScaleStart); + + this.coverClipPath + .merge(coverRect) + .lower(); + + const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); + coverG + .attr("clip-path", `url(#${this.type}Clip)`) + .attr("id", `g-${this.type}Clip`); + + } + + cleanCover(g) { + const options = this.model.getOptions(); + g.selectAll("line").attr("stroke", options.grid.strokeColor); + + // Remove extra elements + g.selectAll("text").remove(); + g.select(".domain").remove(); + } +} diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 7530628ac1..2b1d8d84d0 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -90,10 +90,27 @@ export class Component { "style", "prefix" ); - return DOMUtils.appendOrSelect( - this.parent, - `g.${settings.prefix}--${chartprefix}--${this.type}` - ); + if (this.type === "line" || this.type === "scatter") { + const { width, height } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + return DOMUtils.appendOrSelectForAxisChart( + this.parent, + `clipPath.cover`, + this.type, + 23, + 0, + (width - 23), + height, + ); + + } else { + return DOMUtils.appendOrSelect( + this.parent, + `g.${settings.prefix}--${chartprefix}--${this.type}` + ); + } + } return this.parent; diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 2d2c5c36c7..2be9c949a9 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -131,6 +131,7 @@ export class Line extends Component { this.parent .selectAll("path.line") + .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-hover-line") ) @@ -145,7 +146,7 @@ export class Line extends Component { handleLegendMouseOut = (event: CustomEvent) => { this.parent - .selectAll("path.line") + .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-mouseout-line") ) diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 60daf43009..8be94d9e0e 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -35,3 +35,4 @@ export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; +export * from "./axes/cover"; From fd250d58e02443043cdee52192f825f5d6793e90 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 13:37:58 +0800 Subject: [PATCH 301/510] feat: apply cover to axis chart --- packages/core/src/charts/area-stacked.ts | 2 ++ packages/core/src/charts/area.ts | 2 ++ packages/core/src/charts/bar-grouped.ts | 2 ++ packages/core/src/charts/bar-simple.ts | 2 ++ packages/core/src/charts/bar-stacked.ts | 2 ++ packages/core/src/charts/bubble.ts | 4 +++- packages/core/src/charts/scatter.ts | 2 ++ packages/core/src/components/component.ts | 26 +++++++++++------------ 8 files changed, 28 insertions(+), 14 deletions(-) diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index c89a4f132d..8f18b69cf5 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -7,6 +7,7 @@ import { Tools } from "../tools"; // Components import { Grid, + Cover, StackedArea, TwoDimensionalAxes, Line, @@ -35,6 +36,7 @@ export class StackedAreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new StackedArea(this.model, this.services), diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index aaf6cbad0a..a61e3f669c 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -7,6 +7,7 @@ import { Tools } from "../tools"; // Components import { Grid, + Cover, Area, Line, Ruler, @@ -39,6 +40,7 @@ export class AreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index bba066a001..6a2be76a82 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, GroupedBar, TwoDimensionalAxes, ZeroLine, @@ -39,6 +40,7 @@ export class GroupedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new GroupedBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 0115bd6959..bf2da6772b 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, SimpleBar, TwoDimensionalAxes, ZeroLine, @@ -39,6 +40,7 @@ export class SimpleBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new SimpleBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 5edcdfc9b3..443f483d33 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { Grid, + Cover, StackedBar, StackedBarRuler, TwoDimensionalAxes, @@ -42,6 +43,7 @@ export class StackedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new StackedBarRuler(this.model, this.services), new StackedBar(this.model, this.services), diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 05f1f5e4f1..dcdb8fcced 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -15,7 +15,8 @@ import { Tooltip, Legend, LayoutComponent, - Skeleton + Skeleton, + Cover } from "../components/index"; export class BubbleChart extends AxisChart { @@ -42,6 +43,7 @@ export class BubbleChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Bubble(this.model, this.services), diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 5d2a20c6cb..f3cda814cb 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -9,6 +9,7 @@ import { Skeletons } from "../interfaces/enums"; import { Grid, Ruler, + Cover, Scatter, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) @@ -42,6 +43,7 @@ export class ScatterChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents = [ new TwoDimensionalAxes(this.model, this.services), + new Cover(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Scatter(this.model, this.services), diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 2b1d8d84d0..c49dbd79fc 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -90,19 +90,19 @@ export class Component { "style", "prefix" ); - if (this.type === "line" || this.type === "scatter") { - const { width, height } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); - return DOMUtils.appendOrSelectForAxisChart( - this.parent, - `clipPath.cover`, - this.type, - 23, - 0, - (width - 23), - height, - ); + + + if ( + this.type === "line" || + this.type === "scatter" || + this.type === "area" || + this.type === "bubble" || + this.type === "area-stacked" || + this.type === "grouped-bar" || + this.type === "simple-bar" || + this.type === "scatter-stacked" + ) { + return DOMUtils.appendOrSelectForAxisChart(this.parent,`clipPath.cover`); } else { return DOMUtils.appendOrSelect( From 15020afe5f8296c1fc19e537ddc2174eaa4beb7c Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 13:57:19 +0800 Subject: [PATCH 302/510] refactor: clean code --- packages/core/src/charts/area-stacked.ts | 2 +- packages/core/src/charts/area.ts | 2 +- packages/core/src/charts/bar-grouped.ts | 2 +- packages/core/src/charts/bar-simple.ts | 2 +- packages/core/src/charts/bar-stacked.ts | 2 +- packages/core/src/charts/bubble.ts | 4 ++-- packages/core/src/charts/line.ts | 2 +- packages/core/src/charts/scatter.ts | 2 +- packages/core/src/components/axes/cover.ts | 10 ---------- packages/core/src/components/index.ts | 3 ++- 10 files changed, 11 insertions(+), 20 deletions(-) diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 8f18b69cf5..71cc66c34d 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,8 +6,8 @@ import { Tools } from "../tools"; // Components import { - Grid, Cover, + Grid, StackedArea, TwoDimensionalAxes, Line, diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index a61e3f669c..6d808c893c 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,8 +6,8 @@ import { Tools } from "../tools"; // Components import { - Grid, Cover, + Grid, Area, Line, Ruler, diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 6a2be76a82..63f82294ca 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, GroupedBar, TwoDimensionalAxes, ZeroLine, diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index bf2da6772b..21a1ccecc0 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, SimpleBar, TwoDimensionalAxes, ZeroLine, diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 443f483d33..5816fd62e8 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,8 +7,8 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Grid, Cover, + Grid, StackedBar, StackedBarRuler, TwoDimensionalAxes, diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index dcdb8fcced..11469b0355 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Ruler, Bubble, @@ -15,8 +16,7 @@ import { Tooltip, Legend, LayoutComponent, - Skeleton, - Cover + Skeleton } from "../components/index"; export class BubbleChart extends AxisChart { diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index d766c7ad5a..943686c6c2 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,11 +7,11 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Line, Ruler, Scatter, - Cover, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) Tooltip, diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index f3cda814cb..e38fc66d51 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,9 +7,9 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Cover, Grid, Ruler, - Cover, Scatter, TwoDimensionalAxes, // the imports below are needed because of typescript bug (error TS4029) diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts index 20b4083e09..16179b45c5 100644 --- a/packages/core/src/components/axes/cover.ts +++ b/packages/core/src/components/axes/cover.ts @@ -21,7 +21,6 @@ export class Cover extends Component { createCover() { const svg = this.parent; - console.log("!!! cover svg: ", svg); const mainXScale = this.services.cartesianScales.getMainXScale(); const mainYScale = this.services.cartesianScales.getMainYScale(); @@ -52,13 +51,4 @@ export class Cover extends Component { .attr("id", `g-${this.type}Clip`); } - - cleanCover(g) { - const options = this.model.getOptions(); - g.selectAll("line").attr("stroke", options.grid.strokeColor); - - // Remove extra elements - g.selectAll("text").remove(); - g.select(".domain").remove(); - } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 8be94d9e0e..a48f26437e 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,9 +30,10 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; +export * from "./axes/cover"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; -export * from "./axes/cover"; + From fc203252cb19e9ce71f260d9b5b3fc071784d1dd Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 16 Jun 2020 14:57:41 +0800 Subject: [PATCH 303/510] feat: set axes margins as zoom bar left margin --- .../components/axes/two-dimensional-axes.ts | 3 +++ packages/core/src/components/axes/zoom-bar.ts | 20 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/axes/two-dimensional-axes.ts b/packages/core/src/components/axes/two-dimensional-axes.ts index ec843b20ab..d95e70e557 100644 --- a/packages/core/src/components/axes/two-dimensional-axes.ts +++ b/packages/core/src/components/axes/two-dimensional-axes.ts @@ -114,6 +114,9 @@ export class TwoDimensionalAxes extends Component { if (isNotEqual) { this.margins = Object.assign(this.margins, margins); + // also set new margins to model to allow external components to access + this.model.set({ axesMargins: this.margins }, { animate: false }); + Object.keys(this.children).forEach((childKey) => { const child = this.children[childKey]; child.margins = this.margins; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 4fdfa2fec5..0bf64460d9 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -18,10 +18,7 @@ export class ZoomBar extends Component { ogXScale: any; render(animate = true) { - // TODO - get correct axis left width - const axisLeftWidth = 23; const svg = this.getContainerSVG(); - const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); @@ -34,6 +31,13 @@ export class ZoomBar extends Component { mainYAxisPosition ); + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + const container = DOMUtils.appendOrSelect(svg, "svg.zoom-container") .attr("width", "100%") .attr("height", this.height) @@ -48,7 +52,7 @@ export class ZoomBar extends Component { .attr("fill", "none"); const zoomBG = DOMUtils.appendOrSelect(container, "rect.zoom-bg") - .attr("x", axisLeftWidth) + .attr("x", axesLeftMargin) .attr("y", 0) .attr("width", "100%") .attr("height", "100%") @@ -100,7 +104,7 @@ export class ZoomBar extends Component { }); xScale - .range([axisLeftWidth, width]) + .range([axesLeftMargin, width]) .domain(extent(stackDataArray, (d: any) => d.date)); yScale @@ -232,8 +236,6 @@ export class ZoomBar extends Component { if (selection === null) { // set to default full range selection = xScale.range(); - } else { - // TODO - pass selection to callback function or update scaleDomain } // update brush handle position select(svg).call(updateBrushHandle, selection); @@ -284,8 +286,8 @@ export class ZoomBar extends Component { const brush = brushX() .extent([ - [0 + axisLeftWidth, 0], - [700, this.height] + [axesLeftMargin, 0], + [width, this.height] ]) .on("start brush end", brushed); From 22a9478142e530b814f7a8cb51ad78cf3f4f10eb Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 16 Jun 2020 16:04:29 +0800 Subject: [PATCH 304/510] refactor: add todo --- packages/core/src/components/component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index c49dbd79fc..5664f29ec8 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -91,18 +91,18 @@ export class Component { "prefix" ); - + // @todo Chart type equals to axis-chart if ( - this.type === "line" || - this.type === "scatter" || - this.type === "area" || + this.type === "line" || + this.type === "scatter" || + this.type === "area" || this.type === "bubble" || this.type === "area-stacked" || this.type === "grouped-bar" || this.type === "simple-bar" || this.type === "scatter-stacked" ) { - return DOMUtils.appendOrSelectForAxisChart(this.parent,`clipPath.cover`); + return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); } else { return DOMUtils.appendOrSelect( From feea0d96e45ccb3f038cb1b5a87a07ae59fc4bc9 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 16 Jun 2020 16:54:51 +0800 Subject: [PATCH 305/510] fix: double the minTickSize for datetime ticks --- packages/core/src/components/axes/axis.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 5eab971526..10953d11cb 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -117,10 +117,9 @@ export class Axis extends Component { // if zoomDomain is available, update scale domain to Date array. const zoomDomain = this.model.get("zoomDomain"); if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain.map(d => new Date(d))); + scale.domain(zoomDomain.map((d) => new Date(d))); } - // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -433,11 +432,11 @@ export class Axis extends Component { const ticksNumber = isTimeScaleType ? axis.tickValues().length : scale.ticks().length; - const estimatedTickSize = width / ticksNumber / 1.6; - - rotateTicks = estimatedTickSize < minTickSize; + const estimatedTickSize = width / ticksNumber / 2; + rotateTicks = isTimeScaleType + ? estimatedTickSize < minTickSize * 2 // datetime tick could be very long + : estimatedTickSize < minTickSize; } - if (rotateTicks) { if (!isNumberOfTicksProvided) { axis.ticks( From 89e8af6c217543378ecd701f23e53e490b72377a Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 18 Jun 2020 13:42:33 +0800 Subject: [PATCH 306/510] feat: add chart brush --- packages/core/src/charts/area-stacked.ts | 5 +- packages/core/src/charts/area.ts | 5 +- packages/core/src/charts/bar-grouped.ts | 5 +- packages/core/src/charts/bar-simple.ts | 5 +- packages/core/src/charts/bar-stacked.ts | 5 +- packages/core/src/charts/bubble.ts | 5 +- packages/core/src/charts/donut.ts | 5 +- packages/core/src/charts/line.ts | 5 +- packages/core/src/charts/pie.ts | 5 +- packages/core/src/charts/radar.ts | 4 +- packages/core/src/charts/scatter.ts | 5 +- packages/core/src/components/axes/brush.ts | 142 +++++++++++++++++++++ packages/core/src/components/index.ts | 1 + 13 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/axes/brush.ts diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 71cc66c34d..b87365a97b 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, Cover, Grid, StackedArea, @@ -34,7 +35,7 @@ export class StackedAreaChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -48,6 +49,8 @@ export class StackedAreaChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 6d808c893c..8d5b94aa49 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, Cover, Grid, Area, @@ -38,7 +39,7 @@ export class AreaChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -51,6 +52,8 @@ export class AreaChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 63f82294ca..5668d302eb 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, GroupedBar, @@ -38,7 +39,7 @@ export class GroupedBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -49,6 +50,8 @@ export class GroupedBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 21a1ccecc0..951cd4509f 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, SimpleBar, @@ -38,7 +39,7 @@ export class SimpleBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -49,6 +50,8 @@ export class SimpleBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index 5816fd62e8..e5684fb737 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, StackedBar, @@ -41,7 +42,7 @@ export class StackedBarChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class StackedBarChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 11469b0355..fd1c03a354 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Ruler, @@ -41,7 +42,7 @@ export class BubbleChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class BubbleChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/donut.ts b/packages/core/src/charts/donut.ts index 100faa5fac..18e00d2c36 100644 --- a/packages/core/src/charts/donut.ts +++ b/packages/core/src/charts/donut.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Donut, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -33,13 +34,15 @@ export class DonutChart extends PieChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new Donut(this.model, this.services), new Skeleton(this.model, this.services, { skeleton: Skeletons.DONUT }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getChartComponents(graphFrameComponents); return components; } diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index 943686c6c2..e950b1c4af 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Line, @@ -39,7 +40,7 @@ export class LineChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -51,6 +52,8 @@ export class LineChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 0fc4429807..9631b459a8 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -8,6 +8,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Pie, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -46,13 +47,15 @@ export class PieChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new Pie(this.model, this.services), new Skeleton(this.model, this.services, { skeleton: Skeletons.PIE }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 8758bf1591..6a79b1a13b 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -6,6 +6,7 @@ import { Tools } from "../tools"; // Components import { + Brush, // the imports below are needed because of typescript bug (error TS4029) Legend, LayoutComponent @@ -41,7 +42,8 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [new Radar(this.model, this.services)]; + const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index e38fc66d51..4c5b92d89b 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,6 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { + Brush, Cover, Grid, Ruler, @@ -41,7 +42,7 @@ export class ScatterChart extends AxisChart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents = [ + const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), new Cover(this.model, this.services), new Grid(this.model, this.services), @@ -52,6 +53,8 @@ export class ScatterChart extends AxisChart { }) ]; + this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts new file mode 100644 index 0000000000..60dc0a8945 --- /dev/null +++ b/packages/core/src/components/axes/brush.ts @@ -0,0 +1,142 @@ +// Internal Imports +import { Component } from "../component"; +import { Tools } from "../../tools"; +import { ScaleTypes } from "../../interfaces"; +import { DOMUtils } from "../../services"; + +// D3 Imports +import { extent } from "d3-array"; +import { brushX } from "d3-brush"; +import { event } from "d3-selection"; +import { scaleTime } from "d3-scale"; + +export class Brush extends Component { + type = "brush"; + + render(animate = true) { + const svg = this.parent; + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const mainXScaleType = cartesianScales.getScaleTypeByPosition( + mainXAxisPosition + ); + + // get axes margins + let axesLeftMargin = 0; + const axesMargins = this.model.get("axesMargins"); + if (axesMargins && axesMargins.left) { + axesLeftMargin = axesMargins.left; + } + + const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainYScale = this.services.cartesianScales.getMainYScale(); + + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + const container = DOMUtils.appendOrSelect(svg, "svg.brush-container") + .attr("width", "100%") + .attr("height", "100%") + .attr("opacity", 1); + + if (mainXScale) { + const displayData = this.model.getDisplayData(); + + if (mainXScaleType === ScaleTypes.TIME) { + // Get all date values provided in data + // TODO - Could be re-used through the model + let allDates = []; + displayData.forEach((data) => { + allDates = allDates.concat(Number(data.date)); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + + // Go through all date values + // And get corresponding data from each dataset + const stackDataArray = allDates.map((date) => { + let count = 0; + let correspondingSum = 0; + const correspondingData = {}; + + displayData.forEach((data) => { + if (Number(data.date) === Number(date)) { + ++count; + correspondingSum += data.value; + } + }); + correspondingData["date"] = date; + correspondingData["value"] = correspondingSum; + + return correspondingData; + }); + + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); + + let zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain === undefined) { + zoomDomain = extent(stackDataArray, (d: any) => d.date); // default to full range + this.model.set( + { zoomDomain: zoomDomain }, + { animate: false } + ); + } + + const brushed = () => { + let selection = event.selection; + if (selection !== null) { + // update zoombar handle position + // select(svg).call(updateBrushHandle, selection); + + // get current zoomDomain + zoomDomain = this.model.get("zoomDomain"); + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([axesLeftMargin, width]) + .domain(zoomDomain); + + const newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + // only if the brush event comes from mouseup event + if (event.sourceEvent != null) { + // only if zoomDomain needs update + if (zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1]) { + this.model.set( + { zoomDomain: newDomain, zoomDomainForZoomBar: newDomain }, + { animate: false } + ); + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + } + }; + + const brush = brushX() + .extent([ + [xScaleStart, 0], + [width, yScaleEnd] + ]) + .on("end", brushed); + + const brushArea = DOMUtils.appendOrSelect(svg, "g.chart-brush").call( + brush + ); + // no need for having default brush selection + // @todo try to hide brush after selection + setTimeout(()=> {brushArea.call(brush.move);}, 0); + } + } + } +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index a48f26437e..4d9959743a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,6 +30,7 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; +export * from "./axes/brush"; export * from "./axes/cover"; export * from "./axes/grid"; export * from "./axes/ruler"; From b45a8ddcd4f4fb6d3ab7cedeef37140ac9e06058 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 18 Jun 2020 14:19:35 +0800 Subject: [PATCH 307/510] refactor: add brush in axis chart component --- packages/core/src/axis-chart.ts | 3 +++ packages/core/src/charts/area-stacked.ts | 3 --- packages/core/src/charts/area.ts | 3 --- packages/core/src/charts/bar-grouped.ts | 3 --- packages/core/src/charts/bar-simple.ts | 3 --- packages/core/src/charts/bar-stacked.ts | 3 --- packages/core/src/charts/bubble.ts | 3 --- packages/core/src/charts/donut.ts | 3 --- packages/core/src/charts/line.ts | 3 --- packages/core/src/charts/pie.ts | 5 +---- packages/core/src/charts/radar.ts | 4 +--- packages/core/src/charts/scatter.ts | 3 --- 12 files changed, 5 insertions(+), 34 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 04f9bdeeb5..543d0ac6d4 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -8,6 +8,7 @@ import { AxisChartOptions } from "./interfaces/index"; import { + Brush, LayoutComponent, Legend, Title, @@ -48,6 +49,8 @@ export class AxisChart extends Chart { } }; + !!this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ? + graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index b87365a97b..42895bdc6b 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, Cover, Grid, StackedArea, @@ -49,8 +48,6 @@ export class StackedAreaChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 8d5b94aa49..2b313f6cc7 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, Cover, Grid, Area, @@ -52,8 +51,6 @@ export class AreaChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 5668d302eb..59354739e6 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, GroupedBar, @@ -50,8 +49,6 @@ export class GroupedBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 951cd4509f..5709ea0e9b 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, SimpleBar, @@ -50,8 +49,6 @@ export class SimpleBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index e5684fb737..c2510fa7da 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, StackedBar, @@ -53,8 +52,6 @@ export class StackedBarChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index fd1c03a354..7238de0aa3 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Ruler, @@ -53,8 +52,6 @@ export class BubbleChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/donut.ts b/packages/core/src/charts/donut.ts index 18e00d2c36..a3c9a33a61 100644 --- a/packages/core/src/charts/donut.ts +++ b/packages/core/src/charts/donut.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Donut, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -41,8 +40,6 @@ export class DonutChart extends PieChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getChartComponents(graphFrameComponents); return components; } diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index e950b1c4af..ee4e07f163 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Line, @@ -52,8 +51,6 @@ export class LineChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 9631b459a8..4642ea8f2d 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -8,7 +8,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Pie, // the imports below are needed because of typescript bug (error TS4029) Legend, @@ -53,9 +52,7 @@ export class PieChart extends Chart { skeleton: Skeletons.PIE }) ]; - - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 6a79b1a13b..80b8e7888c 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -6,7 +6,6 @@ import { Tools } from "../tools"; // Components import { - Brush, // the imports below are needed because of typescript bug (error TS4029) Legend, LayoutComponent @@ -43,8 +42,7 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 4c5b92d89b..2acacdf9df 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,7 +7,6 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Brush, Cover, Grid, Ruler, @@ -53,8 +52,6 @@ export class ScatterChart extends AxisChart { }) ]; - this.model.getOptions().zoomBar.enabled ? graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; - const components: any[] = this.getAxisChartComponents( graphFrameComponents ); From a8034333826fbb1eb17daffcec7f260ca37add70 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 18 Jun 2020 22:32:35 +0800 Subject: [PATCH 308/510] refactor: move brush functions to ZoomBar class function --- packages/core/src/components/axes/zoom-bar.ts | 180 +++++++++--------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0bf64460d9..8f5d2587e7 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -194,94 +194,8 @@ export class ZoomBar extends Component { ) .attr("d", areaGenerator); - const updateBrushHandle = (g, selection) => { - const handleSize = 5; - // handle - svg.select("g.brush") - .selectAll("rect.handle") - .data([{ type: "w" }, { type: "e" }]) - .attr("x", function (d) { - if (d.type === "w") { - return selection[0] - 3; - } else if (d.type === "e") { - return selection[1] - 3; - } - }) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) - .style("display", null); // always display - // handle-bar - svg.select("g.brush") - .selectAll("rect.handle-bar") - .data([{ type: "w" }, { type: "e" }]) - .join("rect") - .attr("class", function (d) { - return "handle-bar handle-bar--" + d.type; - }) - .attr("x", function (d) { - if (d.type === "w") { - return selection[0] - 1; - } else if (d.type === "e") { - return selection[1] - 1; - } - }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12); - }; - - const brushed = () => { - let selection = event.selection; - if (selection === null) { - // set to default full range - selection = xScale.range(); - } - // update brush handle position - select(svg).call(updateBrushHandle, selection); - - const newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { - // only if zoomDomain is never set or needs update - if ( - zoomDomain === undefined || - zoomDomain[0] !== newDomain[0] || - zoomDomain[1] !== newDomain[1] - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress( - selection, - newDomain - ); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); - } - } + const brushEventListener = () => { + this.brushed(zoomDomain, xScale, event.selection); }; const brush = brushX() @@ -289,13 +203,18 @@ export class ZoomBar extends Component { [axesLeftMargin, 0], [width, this.height] ]) - .on("start brush end", brushed); + .on("start brush end", brushEventListener); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( brush ); if (zoomDomain === undefined) { brushArea.call(brush.move, xScale.range()); // default to full range + } else { + // brushArea.call( + // brush.move, + // zoomDomain.map((domain) => xScale(domain)) //set brush to correct position + // ); } } } @@ -305,4 +224,87 @@ export class ZoomBar extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); console.log("zoom in", mainXScale.domain()); } + + // brush event listener + brushed(zoomDomain, scale, selection) { + if (selection === null) { + // set to default full range + selection = scale.range(); + } + // update brush handle position + this.updateBrushHandle(this.getContainerSVG(), selection); + + const newDomain = [ + scale.invert(selection[0]), + scale.invert(selection[1]) + ]; + // only if the brush event comes from mouseup event + if (event.sourceEvent != null) { + // only if zoomDomain is never set or needs update + if ( + zoomDomain === undefined || + zoomDomain[0] !== newDomain[0] || + zoomDomain[1] !== newDomain[1] + ) { + this.model.set({ zoomDomain: newDomain }, { animate: false }); + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + } + + updateBrushHandle(svg, selection) { + const handleSize = 5; + // handle + svg.select("g.brush") + .selectAll("rect.handle") + .data([{ type: "w" }, { type: "e" }]) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 3; + } else if (d.type === "e") { + return selection[1] - 3; + } + }) + .attr("y", 0) + .attr("width", handleSize) + .attr("height", 32) + .style("display", null); // always display + // handle-bar + svg.select("g.brush") + .selectAll("rect.handle-bar") + .data([{ type: "w" }, { type: "e" }]) + .join("rect") + .attr("class", function (d) { + return "handle-bar handle-bar--" + d.type; + }) + .attr("x", function (d) { + if (d.type === "w") { + return selection[0] - 1; + } else if (d.type === "e") { + return selection[1] - 1; + } + }) + .attr("y", 10) + .attr("width", 1) + .attr("height", 12); + } } From 3c23df62d857b75a2008aec87c4aeafe584df13c Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 19 Jun 2020 11:57:00 +0800 Subject: [PATCH 309/510] fix: handel situation of zoom bar selection[0] equals selection[1] --- packages/core/src/components/axes/zoom-bar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 8f5d2587e7..d7aff2470a 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -227,7 +227,9 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - if (selection === null) { + // follow d3 behavior: when selection[0] === selection[1], reset default full range + // @todo find a better way to handel the situation when selection[0] === selection[1] + if (selection === null || selection[0] === selection[1]) { // set to default full range selection = scale.range(); } From 4575327892cc93fcdfe98a46e3a945bfa0e681ca Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 12:37:08 +0800 Subject: [PATCH 310/510] feat: ZoomBar could update brush based on zoomDomain - fix some brush handle UI bugs --- packages/core/src/components/axes/zoom-bar.ts | 104 +++++++++++------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d7aff2470a..3c26a897bd 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -17,6 +17,8 @@ export class ZoomBar extends Component { ogXScale: any; + brush = brushX(); + render(animate = true) { const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -198,23 +200,30 @@ export class ZoomBar extends Component { this.brushed(zoomDomain, xScale, event.selection); }; - const brush = brushX() + this.brush .extent([ [axesLeftMargin, 0], [width, this.height] ]) + .on("start brush end", null) // remove old listener first .on("start brush end", brushEventListener); const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( - brush + this.brush ); - if (zoomDomain === undefined) { - brushArea.call(brush.move, xScale.range()); // default to full range + if ( + zoomDomain === undefined || + zoomDomain[0].valueOf() === zoomDomain[1].valueOf() + ) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range() + ); } else { - // brushArea.call( - // brush.move, - // zoomDomain.map((domain) => xScale(domain)) //set brush to correct position - // ); + const selected = zoomDomain.map((domain) => xScale(domain)); + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle(this.getContainerSVG(), selected); } } } @@ -240,8 +249,13 @@ export class ZoomBar extends Component { scale.invert(selection[0]), scale.invert(selection[1]) ]; - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { + + // be aware that the value of d3.event changes during an event! + // update zoomDomain only if the event comes from mousemove event + if ( + event.sourceEvent != null && + event.sourceEvent.type === "mousemove" + ) { // only if zoomDomain is never set or needs update if ( zoomDomain === undefined || @@ -250,45 +264,53 @@ export class ZoomBar extends Component { ) { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); - } + } + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if (zoomBarOptions.selectionEnd !== undefined && event.type === "end") { + zoomBarOptions.selectionEnd(selection, newDomain); } } updateBrushHandle(svg, selection) { - const handleSize = 5; + // @todo the handle size, height are calculated by d3 library + // need to figure out how to override the value + const handleWidth = 6; + const handleHeight = 38; + const handleXDiff = -handleWidth / 2; + const handleYDiff = -(handleHeight - this.height) / 2; + + const handleBarWidth = 2; + const handleBarHeight = 12; + const handleBarXDiff = -handleBarWidth / 2; + const handleYBarDiff = + (handleHeight - handleBarHeight) / 2 + handleYDiff; // handle svg.select("g.brush") .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { if (d.type === "w") { - return selection[0] - 3; + return selection[0] + handleXDiff; } else if (d.type === "e") { - return selection[1] - 3; + return selection[1] + handleXDiff; } }) - .attr("y", 0) - .attr("width", handleSize) - .attr("height", 32) + .attr("y", handleYDiff) + .attr("width", handleWidth) + .attr("height", handleHeight) .style("display", null); // always display // handle-bar svg.select("g.brush") @@ -300,13 +322,13 @@ export class ZoomBar extends Component { }) .attr("x", function (d) { if (d.type === "w") { - return selection[0] - 1; + return selection[0] + handleBarXDiff; } else if (d.type === "e") { - return selection[1] - 1; + return selection[1] + handleBarXDiff; } }) - .attr("y", 10) - .attr("width", 1) - .attr("height", 12); + .attr("y", handleYBarDiff) + .attr("width", handleBarWidth) + .attr("height", handleBarHeight); } } From c7770e3200e431d79df420f7f971101a2d5c823e Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 12:51:20 +0800 Subject: [PATCH 311/510] fix: handle empty selection --- packages/core/src/components/axes/zoom-bar.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 3c26a897bd..04507c90a3 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -197,7 +197,21 @@ export class ZoomBar extends Component { .attr("d", areaGenerator); const brushEventListener = () => { - this.brushed(zoomDomain, xScale, event.selection); + const selection = event.selection; + // follow d3 behavior: when selection is null, reset default full range + // @todo find a better way to handel the situation when selection is null + // select behavior is completed, but nothing selected + if (selection === null) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range() + ); + } else if (selection[0] === selection[1]) { + // select behavior is not completed yet, do nothing + } else { + this.brushed(zoomDomain, xScale, selection); + } }; this.brush @@ -236,12 +250,6 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - // follow d3 behavior: when selection[0] === selection[1], reset default full range - // @todo find a better way to handel the situation when selection[0] === selection[1] - if (selection === null || selection[0] === selection[1]) { - // set to default full range - selection = scale.range(); - } // update brush handle position this.updateBrushHandle(this.getContainerSVG(), selection); From 4f3cc996fd8af96ba92dff8a33902684caf13775 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Fri, 19 Jun 2020 14:13:17 +0800 Subject: [PATCH 312/510] fix: set to default full range when same selection of brush --- packages/core/src/components/axes/brush.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 60dc0a8945..0a22fe0d79 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -45,7 +45,7 @@ export class Brush extends Component { if (mainXScaleType === ScaleTypes.TIME) { // Get all date values provided in data - // TODO - Could be re-used through the model + // @todo - Could be re-used through the model let allDates = []; displayData.forEach((data) => { allDates = allDates.concat(Number(data.date)); @@ -86,10 +86,8 @@ export class Brush extends Component { const brushed = () => { let selection = event.selection; - if (selection !== null) { - // update zoombar handle position - // select(svg).call(updateBrushHandle, selection); + if (selection !== null) { // get current zoomDomain zoomDomain = this.model.get("zoomDomain"); // create xScale based on current zoomDomain @@ -97,17 +95,26 @@ export class Brush extends Component { .range([axesLeftMargin, width]) .domain(zoomDomain); - const newDomain = [ + let newDomain = [ xScale.invert(selection[0]), xScale.invert(selection[1]) ]; + + // check if slected start time and end time are the same + if(newDomain[0].valueOf() === newDomain[1].valueOf()) { + // same as d3 behavior and zoombar behavior: set to default full range + newDomain = extent(stackDataArray, (d: any) => d.date); + } // only if the brush event comes from mouseup event if (event.sourceEvent != null) { // only if zoomDomain needs update - if (zoomDomain[0] !== newDomain[0] || zoomDomain[1] !== newDomain[1]) { + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { this.model.set( - { zoomDomain: newDomain, zoomDomainForZoomBar: newDomain }, + { zoomDomain: newDomain }, { animate: false } ); } From 85b056e8cfe24d8c48aa77d8753d5492e807ab4d Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 13:59:36 +0800 Subject: [PATCH 313/510] fix: fix bug when selection === null --- packages/core/src/components/axes/zoom-bar.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 04507c90a3..f7894985c5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -202,11 +202,7 @@ export class ZoomBar extends Component { // @todo find a better way to handel the situation when selection is null // select behavior is completed, but nothing selected if (selection === null) { - brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle( - this.getContainerSVG(), - xScale.range() - ); + this.brushed(zoomDomain, xScale, xScale.range()); } else if (selection[0] === selection[1]) { // select behavior is not completed yet, do nothing } else { @@ -259,10 +255,12 @@ export class ZoomBar extends Component { ]; // be aware that the value of d3.event changes during an event! - // update zoomDomain only if the event comes from mousemove event + // update zoomDomain only if the event comes from mouse event if ( event.sourceEvent != null && - event.sourceEvent.type === "mousemove" + (event.sourceEvent.type === "mousemove" || + event.sourceEvent.type === "mouseup" || + event.sourceEvent.type === "mousedown") ) { // only if zoomDomain is never set or needs update if ( From 604577e75d80a5d65a59bd329b501609df85c169 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 19 Jun 2020 14:41:24 +0800 Subject: [PATCH 314/510] feat: add ZoomBarOptions.initZoomDomain - update storybook - add init() and destroy() in ZoomBar --- packages/core/demo/data/time-series-axis.ts | 5 ++++ packages/core/src/components/axes/zoom-bar.ts | 25 ++++++++++++++++--- packages/core/src/configuration.ts | 1 + packages/core/src/interfaces/components.ts | 6 +++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index e79de136b6..845e1bd1a7 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -453,6 +453,10 @@ const selectionEndFun = (selection, domain) => { console.log(domain); }; +const initZoomDomain = [ + new Date(2020, 11, 10, 23, 59, 25), + new Date(2020, 11, 11, 0, 0, 25) +]; export const lineTimeSeriesZoomBarOptions = { title: "Line (time series) - zoom-bar enabled", axes: { @@ -463,6 +467,7 @@ export const lineTimeSeriesZoomBarOptions = { }, zoomBar: { enabled: true, + initZoomDomain: initZoomDomain, selectionStart: selectionStartFun, selectionInProgress: selectionInProgressFun, selectionEnd: selectionEndFun diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index f7894985c5..0137960f8d 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -19,6 +19,17 @@ export class ZoomBar extends Component { brush = brushX(); + init() { + // get initZoomDomain + const zoomBarOptions = this.model.getOptions().zoomBar; + if (zoomBarOptions.initZoomDomain !== undefined) { + this.model.set( + { zoomDomain: zoomBarOptions.initZoomDomain }, + { skipUpdate: true } + ); + } + } + render(animate = true) { const svg = this.getContainerSVG(); const { cartesianScales } = this.services; @@ -221,6 +232,7 @@ export class ZoomBar extends Component { const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( this.brush ); + if ( zoomDomain === undefined || zoomDomain[0].valueOf() === zoomDomain[1].valueOf() @@ -239,10 +251,11 @@ export class ZoomBar extends Component { } } - zoomIn() { - const mainXScale = this.services.cartesianScales.getMainXScale(); - console.log("zoom in", mainXScale.domain()); - } + // could be used by Toolbar + // zoomIn() { + // const mainXScale = this.services.cartesianScales.getMainXScale(); + // console.log("zoom in", mainXScale.domain()); + // } // brush event listener brushed(zoomDomain, scale, selection) { @@ -337,4 +350,8 @@ export class ZoomBar extends Component { .attr("width", handleBarWidth) .attr("height", handleBarHeight); } + + destroy() { + this.brush.on("start brush end", null); // remove event listener + } } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 5109d282e1..ad37847348 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -133,6 +133,7 @@ export const timeScale: TimeScaleOptions = { */ export const zoomBar: ZoomBarOptions = { enabled: false, + initZoomDomain: undefined, selectionStart: undefined, selectionInProgress: undefined, selectionEnd: undefined diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 33d100ff45..d81ddce313 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -125,6 +125,12 @@ export interface ZoomBarOptions { * is the zoom-bar visible or not */ enabled?: boolean; + + /** + * an two element array which represents the initial zoom domain + */ + initZoomDomain?: Object[]; + /** * a function to handle selection start event */ From 75c8d3646b6c20e1ed2393144103ce724a96a640 Mon Sep 17 00:00:00 2001 From: EricYang Date: Sat, 20 Jun 2020 18:03:35 +0800 Subject: [PATCH 315/510] feat: create Zoombar storybook demo group --- packages/core/demo/data/index.ts | 54 +++++++++++++ packages/core/demo/data/time-series-axis.ts | 55 ------------- packages/core/demo/data/zoom-bar.ts | 88 +++++++++++++++++++++ 3 files changed, 142 insertions(+), 55 deletions(-) create mode 100644 packages/core/demo/data/zoom-bar.ts diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 49313db1e4..a266d4352c 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -10,6 +10,7 @@ import * as stepDemos from "./step"; import * as meterDemos from "./meter"; import * as timeSeriesAxisDemos from "./time-series-axis"; import * as radarDemos from "./radar"; +import * as zoomBarDemos from "./zoom-bar"; export * from "./area"; export * from "./bar"; @@ -755,6 +756,59 @@ let allDemoGroups = [ chartType: chartTypes.RadarChart } ] + }, + { + title: "Zoom bar", + demos: [ + { + options: zoomBarDemos.zoomBarSimpleBarTimeSeriesOptions, + data: zoomBarDemos.zoomBarSimpleBarTimeSeriesData, + chartType: chartTypes.SimpleBarChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarStackedBarTimeSeriesOptions, + data: zoomBarDemos.zoomBarStackedBarTimeSeriesData, + chartType: chartTypes.StackedBarChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarBubbleTimeSeriesOptions, + data: zoomBarDemos.zoomBarBubbleTimeSeriesData, + chartType: chartTypes.BubbleChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeriesOptions, + data: zoomBarDemos.zoomBarLineTimeSeriesData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarScatterTimeSeriesOptions, + data: zoomBarDemos.zoomBarScatterTimeSeriesData, + chartType: chartTypes.ScatterChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarStepTimeSeriesOptions, + data: zoomBarDemos.zoomBarStepTimeSeriesData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeries15secondsOptions, + data: zoomBarDemos.zoomBarLineTimeSeries15secondsData, + chartType: chartTypes.LineChart, + isDemoExample: false + }, + { + options: zoomBarDemos.zoomBarLineTimeSeriesInitDomainOptions, + data: zoomBarDemos.zoomBarLineTimeSeriesInitDomainData, + chartType: chartTypes.LineChart, + isDemoExample: false + } + ] } ] as any; diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index 845e1bd1a7..4ac497de89 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -418,58 +418,3 @@ export const lineTimeSeriesAllLabelsInPrimaryFormatOptions = { addSpaceOnEdges: 0 } }; - -// ZoomBar -export const lineTimeSeriesDataZoomBar = { - labels: ["Qty"], - datasets: [ - { - label: "Dataset 1", - data: [ - { date: new Date(2020, 11, 10, 23, 59, 15), value: 10 }, - { date: new Date(2020, 11, 10, 23, 59, 30), value: 15 }, - { date: new Date(2020, 11, 10, 23, 59, 45), value: 7 }, - { date: new Date(2020, 11, 11, 0, 0, 0), value: 2 }, - { date: new Date(2020, 11, 11, 0, 0, 15), value: 9 }, - { date: new Date(2020, 11, 11, 0, 0, 30), value: 13 }, - { date: new Date(2020, 11, 11, 0, 0, 45), value: 8 } - ] - } - ] -}; -const selectionStartFun = (selection, domain) => { - console.log("ZoomBar SelectionStart callback!"); - console.log(selection); - console.log(domain); -}; -const selectionInProgressFun = (selection, domain) => { - console.log("ZoomBar SelectionInProgress callback!"); - console.log(selection); - console.log(domain); -}; -const selectionEndFun = (selection, domain) => { - console.log("ZoomBar SelectionEnd callback!"); - console.log(selection); - console.log(domain); -}; - -const initZoomDomain = [ - new Date(2020, 11, 10, 23, 59, 25), - new Date(2020, 11, 11, 0, 0, 25) -]; -export const lineTimeSeriesZoomBarOptions = { - title: "Line (time series) - zoom-bar enabled", - axes: { - left: {}, - bottom: { - scaleType: "time" - } - }, - zoomBar: { - enabled: true, - initZoomDomain: initZoomDomain, - selectionStart: selectionStartFun, - selectionInProgress: selectionInProgressFun, - selectionEnd: selectionEndFun - } -}; diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts new file mode 100644 index 0000000000..5c97e33973 --- /dev/null +++ b/packages/core/demo/data/zoom-bar.ts @@ -0,0 +1,88 @@ +import * as timeSeriesAxisChart from "./time-series-axis"; +import * as barChart from "./bar"; +import * as bubbleChart from "./bubble"; +import * as lineChart from "./line"; +import * as scatterChart from "./scatter"; +import * as stepChart from "./step"; + +// default function for selection callback +const selectionStartFun = (selection, domain) => { + console.log("ZoomBar SelectionStart callback!"); + console.log(selection); + console.log(domain); +}; +const selectionInProgressFun = (selection, domain) => { + console.log("ZoomBar SelectionInProgress callback!"); + console.log(selection); + console.log(domain); +}; +const selectionEndFun = (selection, domain) => { + console.log("ZoomBar SelectionEnd callback!"); + console.log(selection); + console.log(domain); +}; + +const initZoomDomain = [ + new Date(2020, 11, 10, 23, 59, 25), + new Date(2020, 11, 11, 0, 0, 25) +]; + +const defaultZoomBarOptions = { + enabled: true, + initZoomDomain: undefined, + selectionStart: selectionStartFun, + selectionInProgress: selectionInProgressFun, + selectionEnd: selectionEndFun +}; + +// utility function to update title and enable zoomBar option +const updateOptions = (options) => { + options["title"] = options["title"] + " - Zoom bar enabled"; + options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); + return options; +}; + +export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; +export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( + Object.assign({}, barChart.simpleBarTimeSeriesOptions) +); + +export const zoomBarStackedBarTimeSeriesData = + barChart.stackedBarTimeSeriesData; +export const zoomBarStackedBarTimeSeriesOptions = updateOptions( + Object.assign({}, barChart.stackedBarTimeSeriesOptions) +); + +export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; +export const zoomBarBubbleTimeSeriesOptions = updateOptions( + Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) +); + +export const zoomBarLineTimeSeriesData = lineChart.lineTimeSeriesData; +export const zoomBarLineTimeSeriesOptions = updateOptions( + Object.assign({}, lineChart.lineTimeSeriesOptions) +); + +export const zoomBarScatterTimeSeriesData = scatterChart.scatterTimeSeriesData; +export const zoomBarScatterTimeSeriesOptions = updateOptions( + Object.assign({}, scatterChart.scatterTimeSeriesOptions) +); + +export const zoomBarStepTimeSeriesData = stepChart.stepTimeSeriesData; +export const zoomBarStepTimeSeriesOptions = updateOptions( + Object.assign({}, stepChart.stepTimeSeriesOptions) +); + +export const zoomBarLineTimeSeries15secondsData = + timeSeriesAxisChart.lineTimeSeriesData15seconds; +export const zoomBarLineTimeSeries15secondsOptions = updateOptions( + Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) +); + +export const zoomBarLineTimeSeriesInitDomainData = + timeSeriesAxisChart.lineTimeSeriesData15seconds; +export const zoomBarLineTimeSeriesInitDomainOptions = updateOptions( + Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) +); +zoomBarLineTimeSeriesInitDomainOptions["title"] += " with initial zoom domain"; +zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initZoomDomain = initZoomDomain; From 338f29820cc269e571b9223139b8550a9a5c806f Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 23 Jun 2020 10:08:11 +0800 Subject: [PATCH 316/510] fix: apply cover to stacked bar --- packages/core/src/components/component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 5664f29ec8..6aa5e17ee0 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -100,6 +100,7 @@ export class Component { this.type === "area-stacked" || this.type === "grouped-bar" || this.type === "simple-bar" || + this.type === "stacked-bar" || this.type === "scatter-stacked" ) { return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); From 708a1cb1719e48aae797756091bed48d7b10ca11 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 24 Jun 2020 12:23:11 +0800 Subject: [PATCH 317/510] feat: create ZoomBar event and handler - use Events.ZoomBar.UPDATE to handle axesMargins update --- .../core/src/components/axes/two-dimensional-axes.ts | 4 +++- packages/core/src/components/axes/zoom-bar.ts | 9 ++++++++- packages/core/src/interfaces/events.ts | 7 +++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/axes/two-dimensional-axes.ts b/packages/core/src/components/axes/two-dimensional-axes.ts index d95e70e557..aa8b1cccb3 100644 --- a/packages/core/src/components/axes/two-dimensional-axes.ts +++ b/packages/core/src/components/axes/two-dimensional-axes.ts @@ -5,6 +5,7 @@ import { Axis } from "./axis"; import { Tools } from "../../tools"; import { DOMUtils } from "../../services"; import { Threshold } from "../essentials/threshold"; +import { Events } from "./../../interfaces"; export class TwoDimensionalAxes extends Component { type = "2D-axes"; @@ -115,7 +116,8 @@ export class TwoDimensionalAxes extends Component { this.margins = Object.assign(this.margins, margins); // also set new margins to model to allow external components to access - this.model.set({ axesMargins: this.margins }, { animate: false }); + this.model.set({ axesMargins: this.margins }, { skipUpdate: true }); + this.services.events.dispatchEvent(Events.ZoomBar.UPDATE); Object.keys(this.children).forEach((childKey) => { const child = this.children[childKey]; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0137960f8d..a6d85ed844 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -1,7 +1,7 @@ // Internal Imports import { Component } from "../component"; import { Tools } from "../../tools"; -import { ScaleTypes } from "../../interfaces"; +import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports @@ -20,6 +20,10 @@ export class ZoomBar extends Component { brush = brushX(); init() { + this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); + // get initZoomDomain const zoomBarOptions = this.model.getOptions().zoomBar; if (zoomBarOptions.initZoomDomain !== undefined) { @@ -353,5 +357,8 @@ export class ZoomBar extends Component { destroy() { this.brush.on("start brush end", null); // remove event listener + this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { + this.render(); + }); } } diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 2850204dc5..2a25fd5992 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -17,6 +17,13 @@ export enum Model { UPDATE = "model-update" } +/** + * enum of all events related to the zoom-bar + */ +export enum ZoomBar { + UPDATE = "zoom-bar-update" +} + /** * enum of all axis-related events */ From e71c7dbc8143769c39b7bc9b6cfe8e30dacd4389 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 24 Jun 2020 12:48:50 +0800 Subject: [PATCH 318/510] fix: fix merge error - move ZoomBarOptions from BaseChartOptions to AxisChartOptions --- packages/core/src/interfaces/charts.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 5ff08b4f9d..372cd3a9ce 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -46,10 +46,6 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; - /** - * zoombar configuration - */ - zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ @@ -122,6 +118,10 @@ export interface AxisChartOptions extends BaseChartOptions { axes?: AxesOptions; grid?: GridOptions; timeScale?: TimeScaleOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; } /** @@ -218,7 +218,7 @@ export interface StackedAreaChartOptions extends ScatterChartOptions { | string | { name: string; - }; + }; } /** From beefd109554e3df4525f75d62657fd5fe86e73e7 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 14:26:51 +0800 Subject: [PATCH 319/510] feat: update color for non-highlighted areas - remove zoombar graph line - add zoombar unselected graph area with clip - add zoombar baseline - clear d3.brush selection style --- packages/core/src/components/axes/zoom-bar.ts | 142 +++++++++++------- .../core/src/styles/components/_zoom-bar.scss | 16 +- 2 files changed, 97 insertions(+), 61 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index a6d85ed844..d04c7a4eeb 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,6 +13,8 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + clipId = "zoomBarClip"; + height = 32; ogXScale: any; @@ -72,9 +74,7 @@ export class ZoomBar extends Component { .attr("x", axesLeftMargin) .attr("y", 0) .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "white") - .attr("stroke", "#e8e8e8"); + .attr("height", "100%"); if (mainXScale) { const displayData = this.model.getDisplayData(); @@ -153,63 +153,44 @@ export class ZoomBar extends Component { ) ) .curve(this.services.curves.getD3Curve()); - // .defined((d: any, i) => { - // if (zoomDomain) { - // const dTimestamp = +d.label; - - // return dTimestamp >= +zoomDomain[0] && dTimestamp <= +zoomDomain[1]; - // } - - // return true; - // }); - const lineGraph = DOMUtils.appendOrSelect( - container, - "path.zoom-graph-line" - ) - .datum(stackDataArray) - .transition( - this.services.transitions.getTransition( - "zoom-pan-line-update", - animate - ) - ) - .attr("d", lineGenerator); - - const areaGenerator = area() - .x((d, i) => - cartesianScales.getValueFromScale( - xScale, - mainXScaleType, - mainXAxisPosition, + const accessorFunc = (scale, scaleType, axisPosition) => { + return (d, i) => { + return cartesianScales.getValueFromScale( + scale, + scaleType, + axisPosition, d, i - ) - ) - .y0(this.height) - .y1( - (d, i) => - this.height - - cartesianScales.getValueFromScale( - yScale, - mainYScaleType, - mainYAxisPosition, - d, - i - ) - ); - - const areaGraph = DOMUtils.appendOrSelect( + ); + }; + }; + this.renderZoomBarArea( container, - "path.zoom-graph-area" - ) - .datum(stackDataArray) - .transition( - this.services.transitions.getTransition( - "zoom-pan-area-update", - animate - ) - ) - .attr("d", areaGenerator); + "path.zoom-graph-area-unselected", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + stackDataArray, + animate, + undefined + ); + this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); + this.renderZoomBarArea( + container, + "path.zoom-graph-area", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + stackDataArray, + animate, + "zoomBarClip" + ); + const baselineGenerator = line()([ + [axesLeftMargin, this.height], + [width, this.height] + ]); + const zoomBaseline = DOMUtils.appendOrSelect( + container, + "path.zoom-bg-baseline" + ).attr("d", baselineGenerator); const brushEventListener = () => { const selection = event.selection; @@ -352,7 +333,52 @@ export class ZoomBar extends Component { }) .attr("y", handleYBarDiff) .attr("width", handleBarWidth) - .attr("height", handleBarHeight); + .attr("height", handleBarHeight) + .attr("cursor", "ew-resize"); + + this.updateClipPath( + svg, + this.clipId, + selection[0], + 0, + selection[1] - selection[0], + this.height + ); + } + + renderZoomBarArea( + container, + querySelector, + xFunc, + y1Func, + datum, + animate, + clipId + ) { + const areaGenerator = area() + .x((d, i) => xFunc(d, i)) + .y0(this.height) + .y1((d, i) => this.height - y1Func(d, i)); + + const areaGraph = DOMUtils.appendOrSelect(container, querySelector) + .datum(datum) + .attr("d", areaGenerator); + + if (clipId) { + areaGraph.attr("clip-path", `url(#${clipId})`); + } + } + + updateClipPath(svg, clipId, x, y, width, height) { + const zoomBarClipPath = DOMUtils.appendOrSelect(svg, `clipPath`).attr( + "id", + clipId + ); + DOMUtils.appendOrSelect(zoomBarClipPath, "rect") + .attr("x", x) + .attr("y", y) + .attr("width", width) + .attr("height", height); } destroy() { diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 59019956a8..32dfc32842 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -4,15 +4,19 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: $ui-01; } - path.zoom-graph-line { + path.zoom-bg-baseline { stroke: $ui-04; - stroke-width: 3; - fill: none; + stroke-width: 2; } path.zoom-graph-area { fill: $ui-03; stroke: $ui-04; + stroke-width: 1; + } + path.zoom-graph-area-unselected { + fill: $ui-01; + stroke: none; } g.brush rect.handle { @@ -22,4 +26,10 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { g.brush rect.handle-bar { fill: $ui-02; } + + // clear d3.brush selection style + g.brush rect.selection { + fill: none; + stroke: none; + } } From ed3bf3f2c334c896036107986bb55ccf7a1ce628 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 15:01:58 +0800 Subject: [PATCH 320/510] fix: avoid extra brush handle update --- packages/core/src/components/axes/zoom-bar.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d04c7a4eeb..cc5fd6bfe3 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -218,8 +218,10 @@ export class ZoomBar extends Component { this.brush ); - if ( - zoomDomain === undefined || + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + } else if ( zoomDomain[0].valueOf() === zoomDomain[1].valueOf() ) { brushArea.call(this.brush.move, xScale.range()); // default to full range @@ -229,8 +231,16 @@ export class ZoomBar extends Component { ); } else { const selected = zoomDomain.map((domain) => xScale(domain)); - brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle(this.getContainerSVG(), selected); + if (selected[1] - selected[0] < 1) { + // initialization not completed yet + // don't update brushHandle to avoid flash + } else { + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle( + this.getContainerSVG(), + selected + ); + } } } } From a7535211a414ba89701544a543582633a4bfa988 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 29 Jun 2020 15:34:34 +0800 Subject: [PATCH 321/510] fix: fix the brush handle style --- packages/core/src/components/axes/zoom-bar.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index cc5fd6bfe3..840d15198f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -299,18 +299,14 @@ export class ZoomBar extends Component { } updateBrushHandle(svg, selection) { - // @todo the handle size, height are calculated by d3 library - // need to figure out how to override the value - const handleWidth = 6; - const handleHeight = 38; + const handleWidth = 5; + const handleHeight = this.height; const handleXDiff = -handleWidth / 2; - const handleYDiff = -(handleHeight - this.height) / 2; - const handleBarWidth = 2; + const handleBarWidth = 1; const handleBarHeight = 12; const handleBarXDiff = -handleBarWidth / 2; - const handleYBarDiff = - (handleHeight - handleBarHeight) / 2 + handleYDiff; + const handleYBarDiff = (handleHeight - handleBarHeight) / 2; // handle svg.select("g.brush") .selectAll("rect.handle") @@ -322,7 +318,7 @@ export class ZoomBar extends Component { return selection[1] + handleXDiff; } }) - .attr("y", handleYDiff) + .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) .style("display", null); // always display From 7d2a57a03e3bc78d30f6d9c426f87f4dede10292 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 14:00:53 +0800 Subject: [PATCH 322/510] refactor: remove unnecessary svg.brush-container --- packages/core/src/components/axes/brush.ts | 42 ++++++++++++---------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 0a22fe0d79..517a73eb80 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -30,16 +30,10 @@ export class Brush extends Component { const mainXScale = this.services.cartesianScales.getMainXScale(); const mainYScale = this.services.cartesianScales.getMainYScale(); - const [xScaleStart, xScaleEnd] = mainXScale.range(); const [yScaleEnd, yScaleStart] = mainYScale.range(); - const container = DOMUtils.appendOrSelect(svg, "svg.brush-container") - .attr("width", "100%") - .attr("height", "100%") - .attr("opacity", 1); - if (mainXScale) { const displayData = this.model.getDisplayData(); @@ -85,7 +79,7 @@ export class Brush extends Component { } const brushed = () => { - let selection = event.selection; + const selection = event.selection; if (selection !== null) { // get current zoomDomain @@ -99,19 +93,24 @@ export class Brush extends Component { xScale.invert(selection[0]), xScale.invert(selection[1]) ]; - + // check if slected start time and end time are the same - if(newDomain[0].valueOf() === newDomain[1].valueOf()) { + if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoombar behavior: set to default full range - newDomain = extent(stackDataArray, (d: any) => d.date); + newDomain = extent( + stackDataArray, + (d: any) => d.date + ); } // only if the brush event comes from mouseup event if (event.sourceEvent != null) { // only if zoomDomain needs update if ( - zoomDomain[0].valueOf() !== newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== newDomain[1].valueOf() + zoomDomain[0].valueOf() !== + newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== + newDomain[1].valueOf() ) { this.model.set( { zoomDomain: newDomain }, @@ -119,12 +118,16 @@ export class Brush extends Component { ); } // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; + const zoomBarOptions = this.model.getOptions() + .zoomBar; if ( zoomBarOptions.selectionEnd !== undefined && event.type === "end" ) { - zoomBarOptions.selectionEnd(selection, newDomain); + zoomBarOptions.selectionEnd( + selection, + newDomain + ); } } } @@ -137,12 +140,15 @@ export class Brush extends Component { ]) .on("end", brushed); - const brushArea = DOMUtils.appendOrSelect(svg, "g.chart-brush").call( - brush - ); + const brushArea = DOMUtils.appendOrSelect( + svg, + "g.chart-brush" + ).call(brush); // no need for having default brush selection // @todo try to hide brush after selection - setTimeout(()=> {brushArea.call(brush.move);}, 0); + setTimeout(() => { + brushArea.call(brush.move); + }, 0); } } } From 9373977d4959e4e638b8fa175ef219f2b146dd68 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 14:17:17 +0800 Subject: [PATCH 323/510] fix: move brush layer to back to allow tooltips in graph --- packages/core/src/axis-chart.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 543d0ac6d4..3690c7b924 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -6,7 +6,7 @@ import { LegendPositions, ChartConfig, AxisChartOptions -} from "./interfaces/index"; +} from "./interfaces"; import { Brush, LayoutComponent, @@ -15,10 +15,10 @@ import { AxisChartsTooltip, Spacer, ZoomBar -} from "./components/index"; +} from "./components"; import { Tools } from "./tools"; -import { CartesianScales, Curves } from "./services/index"; +import { CartesianScales, Curves } from "./services"; export class AxisChart extends Chart { services: any = Object.assign(this.services, { @@ -49,8 +49,16 @@ export class AxisChart extends Chart { } }; - !!this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ? - graphFrameComponents.push(new Brush(this.model, this.services)) : graphFrameComponents; + if ( + this.model.getOptions().zoomBar && + this.model.getOptions().zoomBar.enabled + ) { + graphFrameComponents.splice( + 1, + 0, + new Brush(this.model, this.services) + ); + } const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, From 8247969745e501a5af7400c9e8ae402f61d02784 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 30 Jun 2020 16:18:27 +0800 Subject: [PATCH 324/510] feat: add stacked-area chart with zoom bar in storybook --- packages/core/demo/data/index.ts | 6 ++++++ packages/core/demo/data/zoom-bar.ts | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index a266d4352c..8f830ed5a8 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -760,6 +760,12 @@ let allDemoGroups = [ { title: "Zoom bar", demos: [ + { + options: zoomBarDemos.zoomBarStackedAreaTimeSeriesOptions, + data: zoomBarDemos.zoomBarStackedAreaTimeSeriesData, + chartType: chartTypes.StackedAreaChart, + isDemoExample: false + }, { options: zoomBarDemos.zoomBarSimpleBarTimeSeriesOptions, data: zoomBarDemos.zoomBarSimpleBarTimeSeriesData, diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 5c97e33973..0016bbcad7 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -1,9 +1,10 @@ -import * as timeSeriesAxisChart from "./time-series-axis"; +import * as areaChart from "./area"; import * as barChart from "./bar"; import * as bubbleChart from "./bubble"; import * as lineChart from "./line"; import * as scatterChart from "./scatter"; import * as stepChart from "./step"; +import * as timeSeriesAxisChart from "./time-series-axis"; // default function for selection callback const selectionStartFun = (selection, domain) => { @@ -42,6 +43,12 @@ const updateOptions = (options) => { return options; }; +export const zoomBarStackedAreaTimeSeriesData = + areaChart.stackedAreaTimeSeriesData; +export const zoomBarStackedAreaTimeSeriesOptions = updateOptions( + Object.assign({}, areaChart.stackedAreaTimeSeriesOptions) +); + export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( Object.assign({}, barChart.simpleBarTimeSeriesOptions) From c8f3f2e33355bbf9030903942793b002ee4d24d5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 14:28:08 +0800 Subject: [PATCH 325/510] refactor: delete unused axis-zoom service --- packages/core/src/services/axis-zoom.ts | 60 ------------------------- packages/core/src/services/index.ts | 1 - 2 files changed, 61 deletions(-) delete mode 100644 packages/core/src/services/axis-zoom.ts diff --git a/packages/core/src/services/axis-zoom.ts b/packages/core/src/services/axis-zoom.ts deleted file mode 100644 index 8dbb2b3d80..0000000000 --- a/packages/core/src/services/axis-zoom.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Internal Imports -import { Service } from "./service"; -import { Tools } from "../tools"; - -// D3 Imports -import { brushX } from "d3-brush"; -import { event } from "d3-selection"; - -export class AxisZoom extends Service { - brush: any; - - brushed(e) { - if (event) { - const selectedRange = event.selection; - - const mainXAxis = this.services.axes.getMainXAxis(); - const mainXAxisRangeStart = mainXAxis.scale.range()[0]; - - const newDomain = selectedRange.map(d => d + mainXAxisRangeStart) - .map(mainXAxis.scale.invert, mainXAxis.scale); - - this.model.set({ - zoomDomain: newDomain.map(d => new Date(+d)) - }); - } - } - - // We need a custom debounce function here - // because of the async nature of d3.event - debounceWithD3Event(func, wait) { - let timeout; - return function() { - const e = Object.assign({}, event); - const context = this; - const later = function() { - timeout = null; - func.apply(context, [e]); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - } - - getZoomInstance() { - const mainXAxis = this.services.axes.getMainXAxis(); - const mainYAxis = this.services.axes.getMainYAxis(); - const xMaxRange = mainXAxis.scale.range()[1]; - const yMaxRange = mainYAxis.scale.range()[0]; - - this.brush = brushX() - .extent([ - [0, 0], - [xMaxRange, yMaxRange] - ]) - .on("end", this.brushed.bind(this)); - - - return this.brush; - } -} diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index 4e9fcc0e17..d999a07207 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -4,6 +4,5 @@ export * from "./essentials/events"; export * from "./essentials/transitions"; // MISC -export * from "./axis-zoom"; export * from "./scales-cartesian"; export * from "./curves"; From 874e7c2a824fe1b4870447686ade6efc7ad663d2 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 14:41:59 +0800 Subject: [PATCH 326/510] feat: allow addSpaceOnEdges to work with zoom bar - extends domain in zoom bar and brush --- packages/core/src/components/axes/brush.ts | 6 ++- packages/core/src/components/axes/zoom-bar.ts | 13 +++++-- .../core/src/services/scales-cartesian.ts | 39 +++++++++++-------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 517a73eb80..efc35a735d 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -71,7 +71,11 @@ export class Brush extends Component { let zoomDomain = this.model.get("zoomDomain"); if (zoomDomain === undefined) { - zoomDomain = extent(stackDataArray, (d: any) => d.date); // default to full range + // default to full range with extended domain + zoomDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) + ); this.model.set( { zoomDomain: zoomDomain }, { animate: false } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 840d15198f..b3b4b2b571 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -120,9 +120,16 @@ export class ZoomBar extends Component { useAttrs: true }); - xScale - .range([axesLeftMargin, width]) - .domain(extent(stackDataArray, (d: any) => d.date)); + // @todo could be a better function to extend domain with default value + const xDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) + ); + // add value 0 to the extended domain for zoom bar area graph + stackDataArray.unshift({ date: xDomain[0], value: 0 }); + stackDataArray.push({ date: xDomain[1], value: 0 }); + + xScale.range([axesLeftMargin, width]).domain(xDomain); yScale .range([0, this.height - 6]) diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 162b0c833f..9614fea1c2 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -5,7 +5,6 @@ import { AxisPositions, CartesianOrientations, ScaleTypes, - AxesOptions, ThresholdOptions } from "../interfaces"; import { Tools } from "../tools"; @@ -181,7 +180,13 @@ export class CartesianScales extends Service { return this.scales[this.getMainYAxisPosition()]; } - getValueFromScale(scale: any, scaleType: ScaleTypes, axisPosition: AxisPositions, datum: any, index?: number) { + getValueFromScale( + scale: any, + scaleType: ScaleTypes, + axisPosition: AxisPositions, + datum: any, + index?: number + ) { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); const axisOptions = axesOptions[axisPosition]; @@ -201,14 +206,23 @@ export class CartesianScales extends Service { return scaledValue; } - getValueThroughAxisPosition(axisPosition: AxisPositions, datum: any, index?: number) { + getValueThroughAxisPosition( + axisPosition: AxisPositions, + datum: any, + index?: number + ) { const scaleType = this.scaleTypes[axisPosition]; const scale = this.scales[axisPosition]; - return this.getValueFromScale(scale, scaleType, axisPosition, datum, index); + return this.getValueFromScale( + scale, + scaleType, + axisPosition, + datum, + index + ); } - getDomainValue(d, i) { return this.getValueThroughAxisPosition(this.domainAxisPosition, d, i); } @@ -304,7 +318,7 @@ export class CartesianScales extends Service { const domainScale = this.getDomainScale(); // Find the highest threshold for the domain const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value, + (a, b) => b.value - a.value )[0]; const scaleType = this.getScaleTypeByPosition(domainAxisPosition); @@ -318,7 +332,7 @@ export class CartesianScales extends Service { return { threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value), + scaleValue: domainScale(highestThreshold.value) }; } @@ -338,12 +352,12 @@ export class CartesianScales extends Service { const rangeScale = this.getRangeScale(); // Find the highest threshold for the range const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value, + (a, b) => b.value - a.value )[0]; return { threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value), + scaleValue: rangeScale(highestThreshold.value) }; } @@ -458,12 +472,6 @@ export class CartesianScales extends Service { return map(displayData, (d) => d[mapsTo]).keys(); } - // If scale is a TIME scale and zoomDomain is available, return Date array as the domain - const zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain && axisOptions && scaleType === ScaleTypes.TIME) { - return zoomDomain.map(d => new Date(d)); - } - // Get the extent of the domain let domain; let allDataValues; @@ -483,7 +491,6 @@ export class CartesianScales extends Service { domain = extent(allDataValues); domain = this.extendsDomain(axisPosition, domain); - return domain; } From 4fd15a0c76a11f450b38b6713a5db51eae7ac230 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 16:12:13 +0800 Subject: [PATCH 327/510] refactor: code refactoring - reduce code differences from master branch --- .../core/src/services/scales-cartesian.ts | 119 +++++++++--------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 9614fea1c2..11be646307 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -302,65 +302,6 @@ export class CartesianScales extends Service { } } - getHighestDomainThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const domainAxisPosition = this.getDomainAxisPosition(); - - const { thresholds } = axesOptions[domainAxisPosition]; - - if (!thresholds) { - return null; - } - - const domainScale = this.getDomainScale(); - // Find the highest threshold for the domain - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - const scaleType = this.getScaleTypeByPosition(domainAxisPosition); - if ( - scaleType === ScaleTypes.TIME && - (typeof highestThreshold.value === "string" || - highestThreshold.value.getTime === undefined) - ) { - highestThreshold.value = new Date(highestThreshold.value); - } - - return { - threshold: highestThreshold, - scaleValue: domainScale(highestThreshold.value) - }; - } - - getHighestRangeThreshold(): null | { - threshold: ThresholdOptions; - scaleValue: number; - } { - const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); - const rangeAxisPosition = this.getRangeAxisPosition(); - - const { thresholds } = axesOptions[rangeAxisPosition]; - - if (!thresholds) { - return null; - } - - const rangeScale = this.getRangeScale(); - // Find the highest threshold for the range - const highestThreshold = thresholds.sort( - (a, b) => b.value - a.value - )[0]; - - return { - threshold: highestThreshold, - scaleValue: rangeScale(highestThreshold.value) - }; - } - protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axesOptions = Tools.getProperty(options, "axes"); @@ -491,6 +432,7 @@ export class CartesianScales extends Service { domain = extent(allDataValues); domain = this.extendsDomain(axisPosition, domain); + return domain; } @@ -521,6 +463,65 @@ export class CartesianScales extends Service { return scale; } + + protected getHighestDomainThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const domainAxisPosition = this.getDomainAxisPosition(); + + const { thresholds } = axesOptions[domainAxisPosition]; + + if (!thresholds) { + return null; + } + + const domainScale = this.getDomainScale(); + // Find the highest threshold for the domain + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value + )[0]; + + const scaleType = this.getScaleTypeByPosition(domainAxisPosition); + if ( + scaleType === ScaleTypes.TIME && + (typeof highestThreshold.value === "string" || + highestThreshold.value.getTime === undefined) + ) { + highestThreshold.value = new Date(highestThreshold.value); + } + + return { + threshold: highestThreshold, + scaleValue: domainScale(highestThreshold.value) + }; + } + + protected getHighestRangeThreshold(): null | { + threshold: ThresholdOptions; + scaleValue: number; + } { + const axesOptions = Tools.getProperty(this.model.getOptions(), "axes"); + const rangeAxisPosition = this.getRangeAxisPosition(); + + const { thresholds } = axesOptions[rangeAxisPosition]; + + if (!thresholds) { + return null; + } + + const rangeScale = this.getRangeScale(); + // Find the highest threshold for the range + const highestThreshold = thresholds.sort( + (a, b) => b.value - a.value + )[0]; + + return { + threshold: highestThreshold, + scaleValue: rangeScale(highestThreshold.value) + }; + } } function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { From 7a238083afb7e355b2691a5590ce585f1569e6fe Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 1 Jul 2020 23:25:56 +0800 Subject: [PATCH 328/510] fix: display multiline tooltip with zoom bar - use ruler backdrop as brush area - svg.chart-grid-backdrop --- packages/core/src/axis-chart.ts | 6 +----- packages/core/src/components/axes/brush.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 3690c7b924..f5559f6f56 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -53,11 +53,7 @@ export class AxisChart extends Chart { this.model.getOptions().zoomBar && this.model.getOptions().zoomBar.enabled ) { - graphFrameComponents.splice( - 1, - 0, - new Brush(this.model, this.services) - ); + graphFrameComponents.push(new Brush(this.model, this.services)); } const graphFrameComponent = { id: "graph-frame", diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index efc35a735d..35f12159b9 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -146,7 +146,7 @@ export class Brush extends Component { const brushArea = DOMUtils.appendOrSelect( svg, - "g.chart-brush" + "svg.chart-grid-backdrop" ).call(brush); // no need for having default brush selection // @todo try to hide brush after selection From 935988882f2d10fce6aab3b55f5a26bcb143d00e Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 10 Jun 2020 17:42:26 +0800 Subject: [PATCH 329/510] feat: create ZoomBarOptions and default configuration - default to disable zoombar --- packages/core/src/interfaces/charts.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 372cd3a9ce..4956c8895b 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -46,6 +46,10 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; + /** + * zoombar configuration + */ + zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ From 529ecb5e65451a21f71803642d9c2460c43778f1 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 2 Jul 2020 14:21:12 +0800 Subject: [PATCH 330/510] fix: chart brush with correct range --- packages/core/src/components/axes/brush.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 35f12159b9..c06fdac4c0 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -90,7 +90,7 @@ export class Brush extends Component { zoomDomain = this.model.get("zoomDomain"); // create xScale based on current zoomDomain const xScale = scaleTime() - .range([axesLeftMargin, width]) + .range([0, width]) .domain(zoomDomain); let newDomain = [ @@ -101,9 +101,9 @@ export class Brush extends Component { // check if slected start time and end time are the same if (newDomain[0].valueOf() === newDomain[1].valueOf()) { // same as d3 behavior and zoombar behavior: set to default full range - newDomain = extent( - stackDataArray, - (d: any) => d.date + newDomain = cartesianScales.extendsDomain( + mainXAxisPosition, + extent(stackDataArray, (d: any) => d.date) ); } @@ -139,15 +139,19 @@ export class Brush extends Component { const brush = brushX() .extent([ - [xScaleStart, 0], + [0, 0], [width, yScaleEnd] ]) .on("end", brushed); - - const brushArea = DOMUtils.appendOrSelect( + const backdrop = DOMUtils.appendOrSelect( svg, "svg.chart-grid-backdrop" + ); + const brushArea = DOMUtils.appendOrSelect( + backdrop, + "g.chart-brush" ).call(brush); + // no need for having default brush selection // @todo try to hide brush after selection setTimeout(() => { From af8519ccf0525792ce6131ed4e923195661a155f Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 2 Jul 2020 15:59:02 +0800 Subject: [PATCH 331/510] fix: remove graph out of zoom domain --- .../core/src/components/graphs/bar-grouped.ts | 4 ++++ .../core/src/components/graphs/bar-simple.ts | 5 +++++ .../core/src/components/graphs/bar-stacked.ts | 4 ++++ packages/core/src/components/graphs/bar.ts | 12 ++++++++++++ packages/core/src/components/graphs/scatter.ts | 16 ++++++++++++++++ 5 files changed, 41 insertions(+) diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 48377b5957..07f12e8c64 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -123,6 +123,10 @@ export class GroupedBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(0); const y1 = this.services.cartesianScales.getRangeValue(d.value); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } return Tools.generateSVGPathString( { x0, x1, y0, y1 }, this.services.cartesianScales.getOrientation() diff --git a/packages/core/src/components/graphs/bar-simple.ts b/packages/core/src/components/graphs/bar-simple.ts index 2cbf168934..aeb72950d1 100644 --- a/packages/core/src/components/graphs/bar-simple.ts +++ b/packages/core/src/components/graphs/bar-simple.ts @@ -70,6 +70,11 @@ export class SimpleBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(0); const y1 = this.services.cartesianScales.getRangeValue(d, i); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } + return Tools.generateSVGPathString( { x0, x1, y0, y1 }, this.services.cartesianScales.getOrientation() diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index c49c9f3a59..e2dcbc293a 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -97,6 +97,10 @@ export class StackedBar extends Bar { const y0 = this.services.cartesianScales.getRangeValue(d[0], i); let y1 = this.services.cartesianScales.getRangeValue(d[1], i); + // don't show if part of bar is out of zoom domain + if (this.isOutOfZoomDomain(x0, x1)) { + return; + } // Add the divider gap if ( Math.abs(y1 - y0) > 0 && diff --git a/packages/core/src/components/graphs/bar.ts b/packages/core/src/components/graphs/bar.ts index 90c3dff8dd..1794d822e9 100644 --- a/packages/core/src/components/graphs/bar.ts +++ b/packages/core/src/components/graphs/bar.ts @@ -21,4 +21,16 @@ export class Bar extends Component { return Math.min(options.bars.maxWidth, mainXScale.step() / 2); } + + protected isOutOfZoomDomain(x0: number, x1: number) { + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain !== undefined) { + const domainScale = this.services.cartesianScales.getDomainScale(); + return ( + x0 < domainScale(zoomDomain[0]) || + x1 > domainScale(zoomDomain[1]) + ); + } + return false; + } } diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index 0ceab80518..2d8074dc49 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -37,6 +37,19 @@ export class Scatter extends Component { } } + filterOutOfDomain(data) { + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain !== undefined) { + return data.filter( + (d) => + d[domainIdentifier] > zoomDomain[0] && + d[domainIdentifier] < zoomDomain[1] + ); + } + return data; + } + render(animate: boolean) { // Grab container SVG const svg = this.getContainerSVG(); @@ -64,6 +77,9 @@ export class Scatter extends Component { ); } + // filter out of domain data + scatterData = this.filterOutOfDomain(scatterData); + // Update data on dot groups const circles = svg .selectAll("circle.dot") From 1dbd716b7bf697fccb367fc054ea73aa5eb247cd Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 12:44:35 +0800 Subject: [PATCH 332/510] refactor: code refactoring - create getZoomBarData(), getDefaultZoomBarDomain() in model - remove unused code --- packages/core/src/components/axes/brush.ts | 193 ++++------- packages/core/src/components/axes/cover.ts | 25 +- packages/core/src/components/axes/zoom-bar.ts | 312 ++++++++---------- packages/core/src/model.ts | 46 ++- 4 files changed, 256 insertions(+), 320 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index c06fdac4c0..16f21dce44 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -15,149 +15,80 @@ export class Brush extends Component { render(animate = true) { const svg = this.parent; + const backdrop = DOMUtils.appendOrSelect( + svg, + "svg.chart-grid-backdrop" + ); + const { width, height } = DOMUtils.getSVGElementSize(backdrop, { + useAttrs: true + }); + const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const mainXScaleType = cartesianScales.getScaleTypeByPosition( mainXAxisPosition ); - // get axes margins - let axesLeftMargin = 0; - const axesMargins = this.model.get("axesMargins"); - if (axesMargins && axesMargins.left) { - axesLeftMargin = axesMargins.left; - } - const mainXScale = this.services.cartesianScales.getMainXScale(); - const mainYScale = this.services.cartesianScales.getMainYScale(); - - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const [yScaleEnd, yScaleStart] = mainYScale.range(); - - if (mainXScale) { - const displayData = this.model.getDisplayData(); - - if (mainXScaleType === ScaleTypes.TIME) { - // Get all date values provided in data - // @todo - Could be re-used through the model - let allDates = []; - displayData.forEach((data) => { - allDates = allDates.concat(Number(data.date)); - }); - allDates = Tools.removeArrayDuplicates(allDates).sort(); - - // Go through all date values - // And get corresponding data from each dataset - const stackDataArray = allDates.map((date) => { - let count = 0; - let correspondingSum = 0; - const correspondingData = {}; - - displayData.forEach((data) => { - if (Number(data.date) === Number(date)) { - ++count; - correspondingSum += data.value; - } - }); - correspondingData["date"] = date; - correspondingData["value"] = correspondingSum; - - return correspondingData; - }); - const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); - - let zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain === undefined) { - // default to full range with extended domain - zoomDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - this.model.set( - { zoomDomain: zoomDomain }, - { animate: false } - ); - } - - const brushed = () => { - const selection = event.selection; - - if (selection !== null) { - // get current zoomDomain - zoomDomain = this.model.get("zoomDomain"); - // create xScale based on current zoomDomain - const xScale = scaleTime() - .range([0, width]) - .domain(zoomDomain); - - let newDomain = [ - xScale.invert(selection[0]), - xScale.invert(selection[1]) - ]; - - // check if slected start time and end time are the same - if (newDomain[0].valueOf() === newDomain[1].valueOf()) { - // same as d3 behavior and zoombar behavior: set to default full range - newDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - } + 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.model.getDefaultZoomBarDomain(); + this.model.set({ zoomDomain: zoomDomain }, { animate: false }); + } - // only if the brush event comes from mouseup event - if (event.sourceEvent != null) { - // only if zoomDomain needs update - if ( - zoomDomain[0].valueOf() !== - newDomain[0].valueOf() || - zoomDomain[1].valueOf() !== - newDomain[1].valueOf() - ) { - this.model.set( - { zoomDomain: newDomain }, - { animate: false } - ); - } - // call external callback - const zoomBarOptions = this.model.getOptions() - .zoomBar; - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd( - selection, - newDomain - ); - } - } + const brushed = () => { + const selection = event.selection; + + if (selection !== null) { + // create xScale based on current zoomDomain + const xScale = scaleTime() + .range([0, width]) + .domain(zoomDomain); + + let newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + // 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.model.getDefaultZoomBarDomain(); } - }; - const brush = brushX() - .extent([ - [0, 0], - [width, yScaleEnd] - ]) - .on("end", brushed); - const backdrop = DOMUtils.appendOrSelect( - svg, - "svg.chart-grid-backdrop" - ); - const brushArea = DOMUtils.appendOrSelect( - backdrop, - "g.chart-brush" - ).call(brush); + // only if zoomDomain needs update + if ( + zoomDomain[0].valueOf() !== newDomain[0].valueOf() || + zoomDomain[1].valueOf() !== newDomain[1].valueOf() + ) { + this.model.set( + { zoomDomain: newDomain }, + { animate: false } + ); + } - // no need for having default brush selection - // @todo try to hide brush after selection - setTimeout(() => { - brushArea.call(brush.move); - }, 0); - } + // clear brush selection + brushArea.call(brush.move, null); + } + }; + + // leave some space to display selection strokes besides axis + const brush = brushX() + .extent([ + [2, 0], + [width - 1, height - 1] + ]) + .on("end", brushed); + + const brushArea = DOMUtils.appendOrSelect( + backdrop, + "g.chart-brush" + ).call(brush); } } } diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts index 16179b45c5..2536c9ef40 100644 --- a/packages/core/src/components/axes/cover.ts +++ b/packages/core/src/components/axes/cover.ts @@ -1,13 +1,7 @@ // Internal Imports import { Component } from "../component"; -import { Tools } from "../../tools"; import { DOMUtils } from "../../services"; -// D3 Imports -import { axisBottom, axisLeft } from "d3-axis"; -import { mouse, select } from "d3-selection"; -import { TooltipTypes, Events } from "../../interfaces"; - export class Cover extends Component { type = "cover"; @@ -18,19 +12,21 @@ export class Cover extends Component { this.createCover(); } - createCover() { const svg = this.parent; - const mainXScale = this.services.cartesianScales.getMainXScale(); - const mainYScale = this.services.cartesianScales.getMainYScale(); + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); const [xScaleStart, xScaleEnd] = mainXScale.range(); const [yScaleEnd, yScaleStart] = mainYScale.range(); // Get height - this.coverClipPath = DOMUtils.appendOrSelect(svg, `clipPath.${this.type}`); - this.coverClipPath - .attr("id", `${this.type}Clip`); + this.coverClipPath = DOMUtils.appendOrSelect( + svg, + `clipPath.${this.type}` + ); + this.coverClipPath.attr("id", `${this.type}Clip`); const coverRect = DOMUtils.appendOrSelect( this.coverClipPath, "rect.cover" @@ -41,14 +37,11 @@ export class Cover extends Component { .attr("width", xScaleEnd - xScaleStart) .attr("height", yScaleEnd - yScaleStart); - this.coverClipPath - .merge(coverRect) - .lower(); + this.coverClipPath.merge(coverRect).lower(); const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); coverG .attr("clip-path", `url(#${this.type}Clip)`) .attr("id", `g-${this.type}Clip`); - } } diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index b3b4b2b571..6e3d8efe91 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,11 +13,13 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + brushSelector = "g.brush"; // needs to be this value for d3.brush API + clipId = "zoomBarClip"; height = 32; - ogXScale: any; + spacerHeight = 20; brush = brushX(); @@ -64,9 +66,9 @@ export class ZoomBar extends Component { const spacer = DOMUtils.appendOrSelect(svg, "rect.zoom-spacer") .attr("x", 0) - .attr("y", 32) + .attr("y", this.height) .attr("width", "100%") - .attr("height", 20) + .attr("height", this.spacerHeight) .attr("opacity", 1) .attr("fill", "none"); @@ -76,189 +78,134 @@ export class ZoomBar extends Component { .attr("width", "100%") .attr("height", "100%"); - if (mainXScale) { - const displayData = this.model.getDisplayData(); - - if (mainXScaleType === ScaleTypes.TIME) { - // Get all date values provided in data - // TODO - Could be re-used through the model - let allDates = []; - displayData.forEach((data) => { - allDates = allDates.concat(Number(data.date)); - }); - allDates = Tools.removeArrayDuplicates(allDates).sort(); - - // Go through all date values - // And get corresponding data from each dataset - const stackDataArray = allDates.map((date) => { - let count = 0; - let correspondingSum = 0; - const correspondingData = {}; - - displayData.forEach((data) => { - if (Number(data.date) === Number(date)) { - ++count; - correspondingSum += data.value; - } - }); - correspondingData["date"] = date; - correspondingData["value"] = correspondingSum; - - return correspondingData; - }); - - if (!this.ogXScale) { - this.ogXScale = cartesianScales.getDomainScale(); - } - const xScale = mainXScale.copy(); - if (!this.ogXScale) { - this.ogXScale = xScale; - } - const yScale = mainYScale.copy(); + if (mainXScale && mainXScaleType === ScaleTypes.TIME) { + const zoomBarData = this.model.getZoomBarData(); + const xScale = mainXScale.copy(); + const yScale = mainYScale.copy(); - const { width } = DOMUtils.getSVGElementSize(this.parent, { - useAttrs: true - }); + const { width } = DOMUtils.getSVGElementSize(this.parent, { + useAttrs: true + }); - // @todo could be a better function to extend domain with default value - const xDomain = cartesianScales.extendsDomain( - mainXAxisPosition, - extent(stackDataArray, (d: any) => d.date) - ); - // add value 0 to the extended domain for zoom bar area graph - stackDataArray.unshift({ date: xDomain[0], value: 0 }); - stackDataArray.push({ date: xDomain[1], value: 0 }); + const defaultDomain = this.model.getDefaultZoomBarDomain(); + // add value 0 to the extended domain for zoom bar area graph + this.compensateDataForDefaultDomain(zoomBarData, defaultDomain, 0); - xScale.range([axesLeftMargin, width]).domain(xDomain); + xScale.range([axesLeftMargin, width]).domain(defaultDomain); - yScale - .range([0, this.height - 6]) - .domain(extent(stackDataArray, (d: any) => d.value)); + yScale + .range([0, this.height - 6]) + .domain(extent(zoomBarData, (d: any) => d.value)); - const zoomDomain = this.model.get("zoomDomain"); + const zoomDomain = this.model.get("zoomDomain"); - // D3 line generator function - const lineGenerator = line() - .x((d, i) => + // D3 line generator function + const lineGenerator = line() + .x((d, i) => + cartesianScales.getValueFromScale( + xScale, + mainXScaleType, + mainXAxisPosition, + d, + i + ) + ) + .y( + (d, i) => + this.height - cartesianScales.getValueFromScale( - xScale, - mainXScaleType, - mainXAxisPosition, + yScale, + mainYScaleType, + mainYAxisPosition, d, i ) - ) - .y( - (d, i) => - this.height - - cartesianScales.getValueFromScale( - yScale, - mainYScaleType, - mainYAxisPosition, - d, - i - ) - ) - .curve(this.services.curves.getD3Curve()); - const accessorFunc = (scale, scaleType, axisPosition) => { - return (d, i) => { - return cartesianScales.getValueFromScale( - scale, - scaleType, - axisPosition, - d, - i - ); - }; - }; - this.renderZoomBarArea( - container, - "path.zoom-graph-area-unselected", - accessorFunc(xScale, mainXScaleType, mainXAxisPosition), - accessorFunc(yScale, mainYScaleType, mainYAxisPosition), - stackDataArray, - animate, - undefined - ); - this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); - this.renderZoomBarArea( - container, - "path.zoom-graph-area", - accessorFunc(xScale, mainXScaleType, mainXAxisPosition), - accessorFunc(yScale, mainYScaleType, mainYAxisPosition), - stackDataArray, - animate, - "zoomBarClip" - ); - const baselineGenerator = line()([ - [axesLeftMargin, this.height], - [width, this.height] - ]); - const zoomBaseline = DOMUtils.appendOrSelect( - container, - "path.zoom-bg-baseline" - ).attr("d", baselineGenerator); - - const brushEventListener = () => { - const selection = event.selection; - // follow d3 behavior: when selection is null, reset default full range - // @todo find a better way to handel the situation when selection is null - // select behavior is completed, but nothing selected - if (selection === null) { - this.brushed(zoomDomain, xScale, xScale.range()); - } else if (selection[0] === selection[1]) { - // select behavior is not completed yet, do nothing - } else { - this.brushed(zoomDomain, xScale, selection); - } + ) + .curve(this.services.curves.getD3Curve()); + const accessorFunc = (scale, scaleType, axisPosition) => { + return (d, i) => { + return cartesianScales.getValueFromScale( + scale, + scaleType, + axisPosition, + d, + i + ); }; + }; + this.renderZoomBarArea( + container, + "path.zoom-graph-area-unselected", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + zoomBarData, + animate, + undefined + ); + this.updateClipPath(svg, this.clipId, 0, 0, 0, 0); + this.renderZoomBarArea( + container, + "path.zoom-graph-area", + accessorFunc(xScale, mainXScaleType, mainXAxisPosition), + accessorFunc(yScale, mainYScaleType, mainYAxisPosition), + zoomBarData, + animate, + this.clipId + ); + const baselineGenerator = line()([ + [axesLeftMargin, this.height], + [width, this.height] + ]); + const zoomBaseline = DOMUtils.appendOrSelect( + container, + "path.zoom-bg-baseline" + ).attr("d", baselineGenerator); + + const brushEventListener = () => { + const selection = event.selection; + // follow d3 behavior: when selection is null, reset default full range + // select behavior is completed, but nothing selected + if (selection === null) { + this.brushed(zoomDomain, xScale, xScale.range()); + } else if (selection[0] === selection[1]) { + // select behavior is not completed yet, do nothing + } else { + this.brushed(zoomDomain, xScale, selection); + } + }; - this.brush - .extent([ - [axesLeftMargin, 0], - [width, this.height] - ]) - .on("start brush end", null) // remove old listener first - .on("start brush end", brushEventListener); - - const brushArea = DOMUtils.appendOrSelect(svg, "g.brush").call( - this.brush - ); - - if (zoomDomain === undefined) { - // do nothing, initialization not completed yet + this.brush + .extent([ + [axesLeftMargin, 0], + [width, this.height] + ]) + .on("start brush end", null) // remove old listener first + .on("start brush end", brushEventListener); + + const brushArea = DOMUtils.appendOrSelect( + svg, + this.brushSelector + ).call(this.brush); + + if (zoomDomain === undefined) { + // do nothing, initialization not completed yet + // don't update brushHandle to avoid flash + } else if (zoomDomain[0].valueOf() === zoomDomain[1].valueOf()) { + brushArea.call(this.brush.move, xScale.range()); // default to full range + this.updateBrushHandle(this.getContainerSVG(), xScale.range()); + } else { + const selected = zoomDomain.map((domain) => xScale(domain)); + if (selected[1] - selected[0] < 1) { + // initialization not completed yet // don't update brushHandle to avoid flash - } else if ( - zoomDomain[0].valueOf() === zoomDomain[1].valueOf() - ) { - brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle( - this.getContainerSVG(), - xScale.range() - ); } else { - const selected = zoomDomain.map((domain) => xScale(domain)); - if (selected[1] - selected[0] < 1) { - // initialization not completed yet - // don't update brushHandle to avoid flash - } else { - brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle( - this.getContainerSVG(), - selected - ); - } + brushArea.call(this.brush.move, selected); // set brush to correct position + this.updateBrushHandle(this.getContainerSVG(), selected); } } } } - // could be used by Toolbar - // zoomIn() { - // const mainXScale = this.services.cartesianScales.getMainXScale(); - // console.log("zoom in", mainXScale.domain()); - // } - // brush event listener brushed(zoomDomain, scale, selection) { // update brush handle position @@ -315,7 +262,7 @@ export class ZoomBar extends Component { const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; // handle - svg.select("g.brush") + svg.select(this.brushSelector) .selectAll("rect.handle") .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { @@ -330,7 +277,7 @@ export class ZoomBar extends Component { .attr("height", handleHeight) .style("display", null); // always display // handle-bar - svg.select("g.brush") + svg.select(this.brushSelector) .selectAll("rect.handle-bar") .data([{ type: "w" }, { type: "e" }]) .join("rect") @@ -364,7 +311,7 @@ export class ZoomBar extends Component { querySelector, xFunc, y1Func, - datum, + data, animate, clipId ) { @@ -374,7 +321,7 @@ export class ZoomBar extends Component { .y1((d, i) => this.height - y1Func(d, i)); const areaGraph = DOMUtils.appendOrSelect(container, querySelector) - .datum(datum) + .datum(data) .attr("d", areaGenerator); if (clipId) { @@ -394,6 +341,29 @@ export class ZoomBar extends Component { .attr("height", height); } + // assume the domains in data are already sorted + compensateDataForDefaultDomain(data, defaultDomain, value) { + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); + // if min domain is extended + if (Number(defaultDomain[0]) < Number(data[0][domainIdentifier])) { + const newDatum = {}; + newDatum[domainIdentifier] = defaultDomain[0]; + newDatum[rangeIdentifier] = value; + data.unshift(newDatum); + } + // if max domain is extended + if ( + Number(defaultDomain[1]) > + Number(data[data.length - 1][domainIdentifier]) + ) { + const newDatum = {}; + newDatum[domainIdentifier] = defaultDomain[1]; + newDatum[rangeIdentifier] = value; + data.push(newDatum); + } + } + destroy() { this.brush.on("start brush end", null); // remove event listener this.services.events.removeEventListener(Events.ZoomBar.UPDATE, () => { diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 999d689680..a795d809e7 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -5,8 +5,9 @@ import * as colorPalettes from "./services/colorPalettes"; import { Events, ScaleTypes } from "./interfaces"; // D3 -import { scaleOrdinal } from "d3-scale"; +import { extent } from "d3-array"; import { map } from "d3-collection"; +import { scaleOrdinal } from "d3-scale"; import { stack } from "d3-shape"; /** The charting model layer which includes mainly the chart data and options, @@ -38,7 +39,48 @@ export class ChartModel { constructor(services: any) { this.services = services; } + // get display data for zoom bar + // basically it's sum of value grouped by time + getZoomBarData() { + const { cartesianScales } = this.services; + const domainIdentifier = cartesianScales.getDomainIdentifier(); + const rangeIdentifier = cartesianScales.getRangeIdentifier(); + + const displayData = this.getDisplayData(); + // get all dates (Number) in displayData + let allDates = []; + displayData.forEach((data) => { + allDates = allDates.concat(Number(data[domainIdentifier])); + }); + allDates = Tools.removeArrayDuplicates(allDates).sort(); + // Go through all date values + // And get corresponding data from each dataset + return allDates.map((date) => { + let sum = 0; + const datum = {}; + + displayData.forEach((data) => { + if (Number(data[domainIdentifier]) === date) { + sum += data[rangeIdentifier]; + } + }); + datum[domainIdentifier] = new Date(date); + datum[rangeIdentifier] = sum; + return datum; + }); + } + getDefaultZoomBarDomain() { + const zoomBarData = this.getZoomBarData(); + const { cartesianScales } = this.services; + const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); + const domainIdentifier = cartesianScales.getDomainIdentifier(); + // default to full range with extended domain + return cartesianScales.extendsDomain( + mainXAxisPosition, + extent(zoomBarData, (d: any) => d[domainIdentifier]) + ); + } getAllDataFromDomain() { if (!this.get("data")) { return null; @@ -95,7 +137,7 @@ export class ChartModel { return allDataFromDomain.filter((datum) => { const group = dataGroups.find( - (group) => group.name === datum[groupMapsTo] + (g) => g.name === datum[groupMapsTo] ); return group.status === ACTIVE; From ca01a852f7fc9be72591a12c7e29da331eb0f3ec Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 16:13:14 +0800 Subject: [PATCH 333/510] fix: set min selection difference threshold --- packages/core/src/components/axes/zoom-bar.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6e3d8efe91..6c1dd80fdb 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -13,6 +13,11 @@ import { event, select, selectAll } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; + // The minimum selection x range to trigger handler update + // Smaller number may introduce a handler flash during initialization + // Bigger number may not trigger handler update while selection area on chart is very small + MIN_SELECTION_DIFF = 9e-10; + brushSelector = "g.brush"; // needs to be this value for d3.brush API clipId = "zoomBarClip"; @@ -195,7 +200,7 @@ export class ZoomBar extends Component { this.updateBrushHandle(this.getContainerSVG(), xScale.range()); } else { const selected = zoomDomain.map((domain) => xScale(domain)); - if (selected[1] - selected[0] < 1) { + if (selected[1] - selected[0] < this.MIN_SELECTION_DIFF) { // initialization not completed yet // don't update brushHandle to avoid flash } else { From bf98e62c92a91fbc88288037a9438892da65ebb3 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 3 Jul 2020 16:46:44 +0800 Subject: [PATCH 334/510] refactor: code refactoring - zoom-bar.scss - remove unused scss settings --- packages/core/src/components/axes/axis.ts | 2 +- packages/core/src/components/axes/zoom-bar.ts | 2 +- .../core/src/styles/components/_zoom-bar.scss | 24 +++++++++---------- packages/core/src/styles/graphs/index.scss | 4 ---- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 10953d11cb..209d8463e7 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -117,7 +117,7 @@ export class Axis extends Component { // if zoomDomain is available, update scale domain to Date array. const zoomDomain = this.model.get("zoomDomain"); if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain.map((d) => new Date(d))); + scale.domain(zoomDomain); } // Identify the corresponding d3 axis function diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6c1dd80fdb..edcb1f792b 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -18,7 +18,7 @@ export class ZoomBar extends Component { // Bigger number may not trigger handler update while selection area on chart is very small MIN_SELECTION_DIFF = 9e-10; - brushSelector = "g.brush"; // needs to be this value for d3.brush API + brushSelector = "g.zoom-bar-brush"; // needs to match the style in _zoom-bar.scss clipId = "zoomBarClip"; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 32dfc32842..13c20ecc30 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -19,17 +19,17 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: none; } - g.brush rect.handle { - fill: $icon-02; - } - - g.brush rect.handle-bar { - fill: $ui-02; - } - - // clear d3.brush selection style - g.brush rect.selection { - fill: none; - stroke: none; + g.zoom-bar-brush { + rect.handle { + fill: $icon-02; + } + rect.handle-bar { + fill: $ui-02; + } + // clear d3.brush selection style + rect.selection { + fill: none; + stroke: none; + } } } diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index bebafaffc3..5530eba1a0 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -6,7 +6,3 @@ @import "./scatter-stacked"; @import "./radar"; @import "./gauge"; - -svg.#{$prefix}--#{$charts-prefix}--chart-svg svg.graph-frame { - overflow-x: hidden; -} From 8faf4413b2026a16241f0e3841467238ea9d15a2 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 6 Jul 2020 17:09:13 +0800 Subject: [PATCH 335/510] fix: avoid extra/duplicate external callback --- packages/core/src/components/axes/brush.ts | 44 ++++++++++++++++++- packages/core/src/components/axes/zoom-bar.ts | 38 +++++++++------- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/brush.ts index 16f21dce44..8067e6aaa8 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/brush.ts @@ -40,6 +40,47 @@ export class Brush extends Component { this.model.set({ zoomDomain: zoomDomain }, { animate: false }); } + const eventHandler = () => { + const selection = event.selection; + const xScale = scaleTime().range([0, width]).domain(zoomDomain); + + const newDomain = [ + xScale.invert(selection[0]), + xScale.invert(selection[1]) + ]; + + if ( + selection != null && + event.sourceEvent != null && + (event.sourceEvent.type === "mousemove" || + event.sourceEvent.type === "mouseup" || + event.sourceEvent.type === "mousedown") + ) { + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress( + selection, + newDomain + ); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } + } + }; const brushed = () => { const selection = event.selection; @@ -83,7 +124,8 @@ export class Brush extends Component { [2, 0], [width - 1, height - 1] ]) - .on("end", brushed); + .on("start brush end", eventHandler) + .on("end.brushed", brushed); const brushArea = DOMUtils.appendOrSelect( backdrop, diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index edcb1f792b..05722a4116 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -237,23 +237,27 @@ export class ZoomBar extends Component { ) { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if (zoomBarOptions.selectionEnd !== undefined && event.type === "end") { - zoomBarOptions.selectionEnd(selection, newDomain); + + // call external callback + const zoomBarOptions = this.model.getOptions().zoomBar; + if ( + zoomBarOptions.selectionStart !== undefined && + event.type === "start" + ) { + zoomBarOptions.selectionStart(selection, newDomain); + } + if ( + zoomBarOptions.selectionInProgress !== undefined && + event.type === "brush" + ) { + zoomBarOptions.selectionInProgress(selection, newDomain); + } + if ( + zoomBarOptions.selectionEnd !== undefined && + event.type === "end" + ) { + zoomBarOptions.selectionEnd(selection, newDomain); + } } } From d1a0697d5594b1a65e7246a8dba19ac310a530cd Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 7 Jul 2020 12:33:39 +0800 Subject: [PATCH 336/510] fix: remove ZoomBarOptions in BaseChartOptions --- packages/core/src/interfaces/charts.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 4956c8895b..15f49a187f 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -46,10 +46,6 @@ export interface BaseChartOptions { * legend configuration */ legend?: LegendOptions; - /** - * zoombar configuration - */ - zoomBar?: ZoomBarOptions; /** * Optional function to determine whether is filled based on datasetLabel, label, and/or data */ @@ -222,7 +218,7 @@ export interface StackedAreaChartOptions extends ScatterChartOptions { | string | { name: string; - }; + }; } /** From 5a1372eb85313f53c7504a4b46af4b89c1e9751a Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 11:28:33 +0800 Subject: [PATCH 337/510] refactor: change initZoomDomain to initialZoomDomain - remove unnecessary undefined setting --- packages/core/demo/data/zoom-bar.ts | 27 +++++++++---------- packages/core/src/components/axes/zoom-bar.ts | 4 +-- packages/core/src/configuration.ts | 1 - packages/core/src/interfaces/components.ts | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 0016bbcad7..fbe74fba2a 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -23,21 +23,20 @@ const selectionEndFun = (selection, domain) => { console.log(domain); }; -const initZoomDomain = [ +const initialZoomDomain = [ new Date(2020, 11, 10, 23, 59, 25), new Date(2020, 11, 11, 0, 0, 25) ]; const defaultZoomBarOptions = { enabled: true, - initZoomDomain: undefined, selectionStart: selectionStartFun, selectionInProgress: selectionInProgressFun, selectionEnd: selectionEndFun }; // utility function to update title and enable zoomBar option -const updateOptions = (options) => { +const addZoomBarToOptions = (options) => { options["title"] = options["title"] + " - Zoom bar enabled"; options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); return options; @@ -45,51 +44,51 @@ const updateOptions = (options) => { export const zoomBarStackedAreaTimeSeriesData = areaChart.stackedAreaTimeSeriesData; -export const zoomBarStackedAreaTimeSeriesOptions = updateOptions( +export const zoomBarStackedAreaTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, areaChart.stackedAreaTimeSeriesOptions) ); export const zoomBarSimpleBarTimeSeriesData = barChart.simpleBarTimeSeriesData; -export const zoomBarSimpleBarTimeSeriesOptions = updateOptions( +export const zoomBarSimpleBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.simpleBarTimeSeriesOptions) ); export const zoomBarStackedBarTimeSeriesData = barChart.stackedBarTimeSeriesData; -export const zoomBarStackedBarTimeSeriesOptions = updateOptions( +export const zoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.stackedBarTimeSeriesOptions) ); export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; -export const zoomBarBubbleTimeSeriesOptions = updateOptions( +export const zoomBarBubbleTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) ); export const zoomBarLineTimeSeriesData = lineChart.lineTimeSeriesData; -export const zoomBarLineTimeSeriesOptions = updateOptions( +export const zoomBarLineTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, lineChart.lineTimeSeriesOptions) ); export const zoomBarScatterTimeSeriesData = scatterChart.scatterTimeSeriesData; -export const zoomBarScatterTimeSeriesOptions = updateOptions( +export const zoomBarScatterTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, scatterChart.scatterTimeSeriesOptions) ); export const zoomBarStepTimeSeriesData = stepChart.stepTimeSeriesData; -export const zoomBarStepTimeSeriesOptions = updateOptions( +export const zoomBarStepTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, stepChart.stepTimeSeriesOptions) ); export const zoomBarLineTimeSeries15secondsData = timeSeriesAxisChart.lineTimeSeriesData15seconds; -export const zoomBarLineTimeSeries15secondsOptions = updateOptions( +export const zoomBarLineTimeSeries15secondsOptions = addZoomBarToOptions( Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) ); export const zoomBarLineTimeSeriesInitDomainData = timeSeriesAxisChart.lineTimeSeriesData15seconds; -export const zoomBarLineTimeSeriesInitDomainOptions = updateOptions( +export const zoomBarLineTimeSeriesInitDomainOptions = addZoomBarToOptions( Object.assign({}, timeSeriesAxisChart.lineTimeSeries15secondsOptions) ); -zoomBarLineTimeSeriesInitDomainOptions["title"] += " with initial zoom domain"; -zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initZoomDomain = initZoomDomain; +zoomBarLineTimeSeriesInitDomainOptions["title"] += " zoomed domain"; +zoomBarLineTimeSeriesInitDomainOptions.zoomBar.initialZoomDomain = initialZoomDomain; diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 05722a4116..2bbd6ad4ea 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -35,9 +35,9 @@ export class ZoomBar extends Component { // get initZoomDomain const zoomBarOptions = this.model.getOptions().zoomBar; - if (zoomBarOptions.initZoomDomain !== undefined) { + if (zoomBarOptions.initialZoomDomain !== undefined) { this.model.set( - { zoomDomain: zoomBarOptions.initZoomDomain }, + { zoomDomain: zoomBarOptions.initialZoomDomain }, { skipUpdate: true } ); } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index ad37847348..5109d282e1 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -133,7 +133,6 @@ export const timeScale: TimeScaleOptions = { */ export const zoomBar: ZoomBarOptions = { enabled: false, - initZoomDomain: undefined, selectionStart: undefined, selectionInProgress: undefined, selectionEnd: undefined diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index d81ddce313..e250197b85 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -129,7 +129,7 @@ export interface ZoomBarOptions { /** * an two element array which represents the initial zoom domain */ - initZoomDomain?: Object[]; + initialZoomDomain?: Object[]; /** * a function to handle selection start event From f5574f6c76ee80ebabc1652c11cb859386fdf51e Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 12:40:27 +0800 Subject: [PATCH 338/510] refactor: use Tools.getProperty to load zoomBarOptions --- packages/core/src/axis-chart.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index f5559f6f56..c7feb56bae 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -31,6 +31,11 @@ export class AxisChart extends Chart { } protected getAxisChartComponents(graphFrameComponents: any[]) { + const zoomBarEnabled = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "enabled" + ); const titleComponent = { id: "title", components: [new Title(this.model, this.services)], @@ -49,12 +54,10 @@ export class AxisChart extends Chart { } }; - if ( - this.model.getOptions().zoomBar && - this.model.getOptions().zoomBar.enabled - ) { + if (zoomBarEnabled) { graphFrameComponents.push(new Brush(this.model, this.services)); } + const graphFrameComponent = { id: "graph-frame", components: graphFrameComponents, @@ -156,7 +159,7 @@ export class AxisChart extends Chart { topLevelLayoutComponents.push(titleSpacerComponent); } - if (this.model.getOptions().zoomBar.enabled === true) { + if (zoomBarEnabled) { topLevelLayoutComponents.push(zoomBarComponent); } topLevelLayoutComponents.push(fullFrameComponent); From fad73e633c2d5601c4ca64cd5631728f86092abd Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 13:37:33 +0800 Subject: [PATCH 339/510] refactor: update code format --- packages/core/src/charts/pie.ts | 2 +- packages/core/src/charts/radar.ts | 6 ++++-- packages/core/src/components/component.ts | 7 ++++--- packages/core/src/components/index.ts | 1 - packages/core/src/model.ts | 5 ++++- packages/core/src/styles/components/_zoom-bar.scss | 3 +++ 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/core/src/charts/pie.ts b/packages/core/src/charts/pie.ts index 4642ea8f2d..87b74217aa 100644 --- a/packages/core/src/charts/pie.ts +++ b/packages/core/src/charts/pie.ts @@ -52,7 +52,7 @@ export class PieChart extends Chart { skeleton: Skeletons.PIE }) ]; - + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 80b8e7888c..c9b3d67e6e 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -41,8 +41,10 @@ export class RadarChart extends Chart { getComponents() { // Specify what to render inside the graph-frame - const graphFrameComponents: any[] = [new Radar(this.model, this.services)]; - + const graphFrameComponents: any[] = [ + new Radar(this.model, this.services) + ]; + // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); return components; diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 6aa5e17ee0..b5996e519c 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -103,15 +103,16 @@ export class Component { this.type === "stacked-bar" || this.type === "scatter-stacked" ) { - return DOMUtils.appendOrSelectForAxisChart(this.parent, `clipPath.cover`); - + return DOMUtils.appendOrSelectForAxisChart( + this.parent, + `clipPath.cover` + ); } else { return DOMUtils.appendOrSelect( this.parent, `g.${settings.prefix}--${chartprefix}--${this.type}` ); } - } return this.parent; diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 4d9959743a..1534f8db5a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -37,4 +37,3 @@ export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; export * from "./axes/zero-line"; export * from "./axes/zoom-bar"; - diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index a795d809e7..be8b412eb0 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -70,17 +70,20 @@ export class ChartModel { return datum; }); } + getDefaultZoomBarDomain() { const zoomBarData = this.getZoomBarData(); const { cartesianScales } = this.services; const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); const domainIdentifier = cartesianScales.getDomainIdentifier(); + // default to full range with extended domain return cartesianScales.extendsDomain( mainXAxisPosition, extent(zoomBarData, (d: any) => d[domainIdentifier]) ); } + getAllDataFromDomain() { if (!this.get("data")) { return null; @@ -137,7 +140,7 @@ export class ChartModel { return allDataFromDomain.filter((datum) => { const group = dataGroups.find( - (g) => g.name === datum[groupMapsTo] + (dataGroup) => dataGroup.name === datum[groupMapsTo] ); return group.status === ACTIVE; diff --git a/packages/core/src/styles/components/_zoom-bar.scss b/packages/core/src/styles/components/_zoom-bar.scss index 13c20ecc30..d52d6c7978 100644 --- a/packages/core/src/styles/components/_zoom-bar.scss +++ b/packages/core/src/styles/components/_zoom-bar.scss @@ -14,6 +14,7 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { stroke: $ui-04; stroke-width: 1; } + path.zoom-graph-area-unselected { fill: $ui-01; stroke: none; @@ -23,9 +24,11 @@ g.#{$prefix}--#{$charts-prefix}--zoom-bar { rect.handle { fill: $icon-02; } + rect.handle-bar { fill: $ui-02; } + // clear d3.brush selection style rect.selection { fill: none; From 7b83c721d2d3e6dacc0d8e5b3bef4ba891e17e76 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 9 Jul 2020 15:38:10 +0800 Subject: [PATCH 340/510] refactor: use Tools.getProperty to get initialZoomDomain --- packages/core/src/components/axes/zoom-bar.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 2bbd6ad4ea..32c4cbb61f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -34,10 +34,14 @@ export class ZoomBar extends Component { }); // get initZoomDomain - const zoomBarOptions = this.model.getOptions().zoomBar; - if (zoomBarOptions.initialZoomDomain !== undefined) { + const initialZoomDomain = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "initialZoomDomain" + ); + if (initialZoomDomain !== null) { this.model.set( - { zoomDomain: zoomBarOptions.initialZoomDomain }, + { zoomDomain: initialZoomDomain }, { skipUpdate: true } ); } From 4e6097eeb3a5023e1f5131b4a928e98a6a0e5c1d Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 12:13:31 +0800 Subject: [PATCH 341/510] refactor: Change Cover to ChartClip --- packages/core/src/charts/area-stacked.ts | 4 +- packages/core/src/charts/area.ts | 4 +- packages/core/src/charts/bar-grouped.ts | 4 +- packages/core/src/charts/bar-simple.ts | 4 +- packages/core/src/charts/bar-stacked.ts | 4 +- packages/core/src/charts/bubble.ts | 4 +- packages/core/src/charts/line.ts | 4 +- packages/core/src/charts/scatter.ts | 4 +- .../core/src/components/axes/chart-clip.ts | 50 +++++++++++++++++++ packages/core/src/components/axes/cover.ts | 47 ----------------- packages/core/src/components/component.ts | 2 +- packages/core/src/components/index.ts | 2 +- 12 files changed, 68 insertions(+), 65 deletions(-) create mode 100644 packages/core/src/components/axes/chart-clip.ts delete mode 100644 packages/core/src/components/axes/cover.ts diff --git a/packages/core/src/charts/area-stacked.ts b/packages/core/src/charts/area-stacked.ts index 42895bdc6b..8104c10ef0 100644 --- a/packages/core/src/charts/area-stacked.ts +++ b/packages/core/src/charts/area-stacked.ts @@ -6,7 +6,7 @@ import { Tools } from "../tools"; // Components import { - Cover, + ChartClip, Grid, StackedArea, TwoDimensionalAxes, @@ -36,7 +36,7 @@ export class StackedAreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new StackedArea(this.model, this.services), diff --git a/packages/core/src/charts/area.ts b/packages/core/src/charts/area.ts index 2b313f6cc7..23959cce54 100644 --- a/packages/core/src/charts/area.ts +++ b/packages/core/src/charts/area.ts @@ -6,7 +6,7 @@ import { Tools } from "../tools"; // Components import { - Cover, + ChartClip, Grid, Area, Line, @@ -40,7 +40,7 @@ export class AreaChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/bar-grouped.ts b/packages/core/src/charts/bar-grouped.ts index 59354739e6..0c259fb01e 100644 --- a/packages/core/src/charts/bar-grouped.ts +++ b/packages/core/src/charts/bar-grouped.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, GroupedBar, TwoDimensionalAxes, @@ -40,7 +40,7 @@ export class GroupedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new GroupedBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-simple.ts b/packages/core/src/charts/bar-simple.ts index 5709ea0e9b..4d6d17345a 100644 --- a/packages/core/src/charts/bar-simple.ts +++ b/packages/core/src/charts/bar-simple.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, SimpleBar, TwoDimensionalAxes, @@ -40,7 +40,7 @@ export class SimpleBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new SimpleBar(this.model, this.services), new ZeroLine(this.model, this.services), diff --git a/packages/core/src/charts/bar-stacked.ts b/packages/core/src/charts/bar-stacked.ts index c2510fa7da..a680c9402b 100644 --- a/packages/core/src/charts/bar-stacked.ts +++ b/packages/core/src/charts/bar-stacked.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, StackedBar, StackedBarRuler, @@ -43,7 +43,7 @@ export class StackedBarChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new StackedBarRuler(this.model, this.services), new StackedBar(this.model, this.services), diff --git a/packages/core/src/charts/bubble.ts b/packages/core/src/charts/bubble.ts index 7238de0aa3..090a72ec29 100644 --- a/packages/core/src/charts/bubble.ts +++ b/packages/core/src/charts/bubble.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Ruler, Bubble, @@ -43,7 +43,7 @@ export class BubbleChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Bubble(this.model, this.services), diff --git a/packages/core/src/charts/line.ts b/packages/core/src/charts/line.ts index ee4e07f163..3e0c2959b2 100644 --- a/packages/core/src/charts/line.ts +++ b/packages/core/src/charts/line.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Line, Ruler, @@ -41,7 +41,7 @@ export class LineChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Line(this.model, this.services), diff --git a/packages/core/src/charts/scatter.ts b/packages/core/src/charts/scatter.ts index 2acacdf9df..6eebc8a9e0 100644 --- a/packages/core/src/charts/scatter.ts +++ b/packages/core/src/charts/scatter.ts @@ -7,7 +7,7 @@ import { Skeletons } from "../interfaces/enums"; // Components import { - Cover, + ChartClip, Grid, Ruler, Scatter, @@ -43,7 +43,7 @@ export class ScatterChart extends AxisChart { // Specify what to render inside the graph-frame const graphFrameComponents: any[] = [ new TwoDimensionalAxes(this.model, this.services), - new Cover(this.model, this.services), + new ChartClip(this.model, this.services), new Grid(this.model, this.services), new Ruler(this.model, this.services), new Scatter(this.model, this.services), diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts new file mode 100644 index 0000000000..f7bb54fc21 --- /dev/null +++ b/packages/core/src/components/axes/chart-clip.ts @@ -0,0 +1,50 @@ +// Internal Imports +import { Component } from "../component"; +import { DOMUtils } from "../../services"; + +// This class is used to create the clipPath to clip the chart graphs +// It's necessary for zoom in/out behavior +export class ChartClip extends Component { + type = "chart-clip"; + + chartClipPath: any; + + clipPathId = "id-" + this.type; + + render(animate = true) { + // Create the clipPath + this.createClipPath(); + } + + createClipPath() { + const svg = this.parent; + const { cartesianScales } = this.services; + const mainXScale = cartesianScales.getMainXScale(); + const mainYScale = cartesianScales.getMainYScale(); + + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const [yScaleEnd, yScaleStart] = mainYScale.range(); + + // Get height + this.chartClipPath = DOMUtils.appendOrSelect( + svg, + `clipPath.${this.type}` + ).attr("id", this.clipPathId); + const clipRect = DOMUtils.appendOrSelect( + this.chartClipPath, + `rect.${this.type}` + ); + clipRect + .attr("x", xScaleStart) + .attr("y", yScaleStart) + .attr("width", xScaleEnd - xScaleStart) + .attr("height", yScaleEnd - yScaleStart); + + this.chartClipPath.merge(clipRect).lower(); + + const clipG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); + clipG + .attr("clip-path", `url(#${this.clipPathId})`) + .attr("id", `g-${this.type}`); + } +} diff --git a/packages/core/src/components/axes/cover.ts b/packages/core/src/components/axes/cover.ts deleted file mode 100644 index 2536c9ef40..0000000000 --- a/packages/core/src/components/axes/cover.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Internal Imports -import { Component } from "../component"; -import { DOMUtils } from "../../services"; - -export class Cover extends Component { - type = "cover"; - - coverClipPath: any; - - render(animate = true) { - // Create the cover - this.createCover(); - } - - createCover() { - const svg = this.parent; - const { cartesianScales } = this.services; - const mainXScale = cartesianScales.getMainXScale(); - const mainYScale = cartesianScales.getMainYScale(); - - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const [yScaleEnd, yScaleStart] = mainYScale.range(); - - // Get height - this.coverClipPath = DOMUtils.appendOrSelect( - svg, - `clipPath.${this.type}` - ); - this.coverClipPath.attr("id", `${this.type}Clip`); - const coverRect = DOMUtils.appendOrSelect( - this.coverClipPath, - "rect.cover" - ); - coverRect - .attr("x", xScaleStart) - .attr("y", yScaleStart) - .attr("width", xScaleEnd - xScaleStart) - .attr("height", yScaleEnd - yScaleStart); - - this.coverClipPath.merge(coverRect).lower(); - - const coverG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); - coverG - .attr("clip-path", `url(#${this.type}Clip)`) - .attr("id", `g-${this.type}Clip`); - } -} diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index b5996e519c..f79a5e347f 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -105,7 +105,7 @@ export class Component { ) { return DOMUtils.appendOrSelectForAxisChart( this.parent, - `clipPath.cover` + `clipPath.chart-clip` ); } else { return DOMUtils.appendOrSelect( diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 1534f8db5a..506b51b1fa 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -31,7 +31,7 @@ export * from "./layout/layout"; export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; export * from "./axes/brush"; -export * from "./axes/cover"; +export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; export * from "./axes/ruler-stacked-bar"; From 6604563c0c2933ace9e441132c9f7d69fe061d6b Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 12:29:05 +0800 Subject: [PATCH 342/510] refactor: Change Brush to ChartBrush --- packages/core/src/axis-chart.ts | 6 ++++-- .../src/components/axes/{brush.ts => chart-brush.ts} | 9 ++++----- packages/core/src/components/index.ts | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) rename packages/core/src/components/axes/{brush.ts => chart-brush.ts} (95%) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index c7feb56bae..b79ca91dfa 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -8,7 +8,7 @@ import { AxisChartOptions } from "./interfaces"; import { - Brush, + ChartBrush, LayoutComponent, Legend, Title, @@ -55,7 +55,9 @@ export class AxisChart extends Chart { }; if (zoomBarEnabled) { - graphFrameComponents.push(new Brush(this.model, this.services)); + graphFrameComponents.push( + new ChartBrush(this.model, this.services) + ); } const graphFrameComponent = { diff --git a/packages/core/src/components/axes/brush.ts b/packages/core/src/components/axes/chart-brush.ts similarity index 95% rename from packages/core/src/components/axes/brush.ts rename to packages/core/src/components/axes/chart-brush.ts index 8067e6aaa8..7fa5a1caf3 100644 --- a/packages/core/src/components/axes/brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -1,17 +1,16 @@ // Internal Imports import { Component } from "../component"; -import { Tools } from "../../tools"; import { ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports -import { extent } from "d3-array"; import { brushX } from "d3-brush"; import { event } from "d3-selection"; import { scaleTime } from "d3-scale"; -export class Brush extends Component { - type = "brush"; +// This class is used for handle brush events in chart +export class ChartBrush extends Component { + type = "chart-brush"; render(animate = true) { const svg = this.parent; @@ -129,7 +128,7 @@ export class Brush extends Component { const brushArea = DOMUtils.appendOrSelect( backdrop, - "g.chart-brush" + `g.${this.type}` ).call(brush); } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 506b51b1fa..369cbbe15a 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -30,7 +30,7 @@ export * from "./layout/layout"; // MISC export * from "./axes/two-dimensional-axes"; export * from "./axes/axis"; -export * from "./axes/brush"; +export * from "./axes/chart-brush"; export * from "./axes/chart-clip"; export * from "./axes/grid"; export * from "./axes/ruler"; From 4b2bcce37fea111a6e41b84ea2128380afba0b6c Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 14:18:36 +0800 Subject: [PATCH 343/510] refactor: set model.set() function default config --- packages/core/src/model.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index be8b412eb0..63254bb3e7 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -314,9 +314,12 @@ export class ChartModel { set(newState: any, configs?: any) { this.state = Object.assign({}, this.state, newState); - - if (!configs || !configs.skipUpdate) { - this.update(configs ? configs.animate : true); + const newConfig = Object.assign( + { skipUpdate: false, animate: true }, // default configs + configs + ); + if (!newConfig.skipUpdate) { + this.update(newConfig.animate); } } From b4399d98077afdae2ee2ea31ea6006dfeead3cf4 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 14:30:32 +0800 Subject: [PATCH 344/510] fix: remove unnecessary selector --- packages/core/src/components/graphs/line.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 2be9c949a9..02caebb0be 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -131,7 +131,6 @@ export class Line extends Component { this.parent .selectAll("path.line") - .selectAll(`g#coverClip`) .transition( this.services.transitions.getTransition("legend-hover-line") ) @@ -142,16 +141,16 @@ export class Line extends Component { return Configuration.lines.opacity.selected; }); - }; + } handleLegendMouseOut = (event: CustomEvent) => { this.parent - .selectAll(`g#coverClip`) + .selectAll("path.line") .transition( this.services.transitions.getTransition("legend-mouseout-line") ) .attr("opacity", Configuration.lines.opacity.selected); - }; + } destroy() { // Remove event listeners From 53d1ee2a5e829ebe65629f593be5546fa99b38df Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 15:22:22 +0800 Subject: [PATCH 345/510] refactor: create reusable getMainXScaleType() --- packages/core/src/components/axes/chart-brush.ts | 8 ++------ packages/core/src/components/axes/zoom-bar.ts | 8 ++------ packages/core/src/services/scales-cartesian.ts | 10 ++++------ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 7fa5a1caf3..19b9ee14b9 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -23,12 +23,8 @@ export class ChartBrush extends Component { }); const { cartesianScales } = this.services; - const mainXAxisPosition = cartesianScales.getMainXAxisPosition(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition - ); - - const mainXScale = this.services.cartesianScales.getMainXScale(); + const mainXScaleType = cartesianScales.getMainXScaleType(); + const mainXScale = cartesianScales.getMainXScale(); if (mainXScale && mainXScaleType === ScaleTypes.TIME) { // get current zoomDomain diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 32c4cbb61f..897a1b9417 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -54,12 +54,8 @@ export class ZoomBar extends Component { const mainYAxisPosition = cartesianScales.getMainYAxisPosition(); const mainXScale = cartesianScales.getMainXScale(); const mainYScale = cartesianScales.getMainYScale(); - const mainXScaleType = cartesianScales.getScaleTypeByPosition( - mainXAxisPosition - ); - const mainYScaleType = cartesianScales.getScaleTypeByPosition( - mainYAxisPosition - ); + const mainXScaleType = cartesianScales.getMainXScaleType(); + const mainYScaleType = cartesianScales.getMainYScaleType(); // get axes margins let axesLeftMargin = 0; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 11be646307..4904006ba5 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -231,14 +231,12 @@ export class CartesianScales extends Service { return this.getValueThroughAxisPosition(this.rangeAxisPosition, d, i); } - getXValue(d, i) { - const mainXAxisPosition = this.getMainXAxisPosition(); - return this.getValueThroughAxisPosition(mainXAxisPosition, d, i); + getMainXScaleType() { + return this.getScaleTypeByPosition(this.getMainXAxisPosition()); } - getYValue(d, i) { - const mainYAxisPosition = this.getMainYAxisPosition(); - return this.getValueThroughAxisPosition(mainYAxisPosition, d, i); + getMainYScaleType() { + return this.getScaleTypeByPosition(this.getMainYAxisPosition()); } getDomainIdentifier() { From 58263834e0530120beb8e5b87204746b0d504a5d Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 16:22:13 +0800 Subject: [PATCH 346/510] refactor: make sure zoom bar only shows with supported options - zoomBar is available when -- zoomBar is enabled -- main X axis position is bottom -- main X axis scale type is time --- packages/core/src/axis-chart.ts | 21 +++++++++++++++++++-- packages/core/src/components/axes/axis.ts | 13 +++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index b79ca91dfa..6d4d515cba 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -5,7 +5,9 @@ import { LegendOrientations, LegendPositions, ChartConfig, - AxisChartOptions + AxisChartOptions, + AxisPositions, + ScaleTypes } from "./interfaces"; import { ChartBrush, @@ -31,11 +33,26 @@ export class AxisChart extends Chart { } protected getAxisChartComponents(graphFrameComponents: any[]) { - const zoomBarEnabled = Tools.getProperty( + const isZoomBarEnabled = Tools.getProperty( this.model.getOptions(), "zoomBar", "enabled" ); + + this.services.cartesianScales.findDomainAndRangeAxes(); // need to do this before getMainXAxisPosition() + const mainXAxisPosition = this.services.cartesianScales.getMainXAxisPosition(); + const mainXScaleType = Tools.getProperty( + this.model.getOptions(), + "axes", + mainXAxisPosition, + "scaleType" + ); + // @todo - Zoom Bar only supports main axis at BOTTOM axis and time scale for now + const zoomBarEnabled = + isZoomBarEnabled && + mainXAxisPosition === AxisPositions.BOTTOM && + mainXScaleType === ScaleTypes.TIME; + const titleComponent = { id: "title", components: [new Title(this.model, this.services)], diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 209d8463e7..1b1dc86681 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -114,12 +114,6 @@ export class Axis extends Component { scale.range([startPosition, endPosition]); } - // if zoomDomain is available, update scale domain to Date array. - const zoomDomain = this.model.get("zoomDomain"); - if (zoomDomain && axisPosition === AxisPositions.BOTTOM) { - scale.domain(zoomDomain); - } - // Identify the corresponding d3 axis function let axisFunction; switch (axisPosition) { @@ -178,6 +172,13 @@ export class Axis extends Component { const scaleType = this.scaleType || axisOptions.scaleType || ScaleTypes.LINEAR; + // if zoomDomain is available, scale type is time, and axis position isBOTTOM or TOP + // update scale domain to zoomDomain. + const zoomDomain = this.model.get("zoomDomain"); + if (zoomDomain && isTimeScaleType && !isVerticalAxis) { + scale.domain(zoomDomain); + } + // Initialize axis object const axis = axisFunction(scale).tickSizeOuter(0); From 877c013764c74d45746d4ea5eb0bfba7ba96d294 Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 10 Jul 2020 22:15:30 +0800 Subject: [PATCH 347/510] refactor: set clip-path url in containerSVG --- .../core/src/components/axes/chart-clip.ts | 11 ++---- packages/core/src/components/component.ts | 34 +++++++------------ .../src/components/graphs/area-stacked.ts | 2 +- packages/core/src/components/graphs/area.ts | 2 +- .../core/src/components/graphs/bar-grouped.ts | 2 +- .../core/src/components/graphs/bar-simple.ts | 2 +- .../core/src/components/graphs/bar-stacked.ts | 2 +- packages/core/src/components/graphs/line.ts | 6 ++-- .../src/components/graphs/scatter-stacked.ts | 2 +- .../core/src/components/graphs/scatter.ts | 2 +- 10 files changed, 24 insertions(+), 41 deletions(-) diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts index f7bb54fc21..321db115e5 100644 --- a/packages/core/src/components/axes/chart-clip.ts +++ b/packages/core/src/components/axes/chart-clip.ts @@ -2,15 +2,13 @@ import { Component } from "../component"; import { DOMUtils } from "../../services"; -// This class is used to create the clipPath to clip the chart graphs +// This class is used to create the clipPath to clip the chart components // It's necessary for zoom in/out behavior export class ChartClip extends Component { type = "chart-clip"; chartClipPath: any; - clipPathId = "id-" + this.type; - render(animate = true) { // Create the clipPath this.createClipPath(); @@ -29,7 +27,7 @@ export class ChartClip extends Component { this.chartClipPath = DOMUtils.appendOrSelect( svg, `clipPath.${this.type}` - ).attr("id", this.clipPathId); + ).attr("id", this.chartClipId); const clipRect = DOMUtils.appendOrSelect( this.chartClipPath, `rect.${this.type}` @@ -41,10 +39,5 @@ export class ChartClip extends Component { .attr("height", yScaleEnd - yScaleStart); this.chartClipPath.merge(clipRect).lower(); - - const clipG = DOMUtils.appendOrSelect(svg, `g.${this.type}`); - clipG - .attr("clip-path", `url(#${this.clipPathId})`) - .attr("id", `g-${this.type}`); } } diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index f79a5e347f..91ccf82ef8 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -19,6 +19,8 @@ export class Component { protected model: ChartModel; protected services: any; + protected chartClipId = "chart-clip-id"; + constructor(model: ChartModel, services: any, configs?: any) { this.model = model; this.services = services; @@ -83,7 +85,7 @@ export class Component { return this.parent; } - getContainerSVG() { + getContainerSVG(withinChartClip = false) { if (this.type) { const chartprefix = Tools.getProperty( this.model.getOptions(), @@ -91,28 +93,16 @@ export class Component { "prefix" ); - // @todo Chart type equals to axis-chart - if ( - this.type === "line" || - this.type === "scatter" || - this.type === "area" || - this.type === "bubble" || - this.type === "area-stacked" || - this.type === "grouped-bar" || - this.type === "simple-bar" || - this.type === "stacked-bar" || - this.type === "scatter-stacked" - ) { - return DOMUtils.appendOrSelectForAxisChart( - this.parent, - `clipPath.chart-clip` - ); - } else { - return DOMUtils.appendOrSelect( - this.parent, - `g.${settings.prefix}--${chartprefix}--${this.type}` - ); + const svg = DOMUtils.appendOrSelect( + this.parent, + `g.${settings.prefix}--${chartprefix}--${this.type}` + ); + + if (withinChartClip) { + svg.attr("clip-path", `url(#${this.chartClipId})`); } + + return svg; } return this.parent; diff --git a/packages/core/src/components/graphs/area-stacked.ts b/packages/core/src/components/graphs/area-stacked.ts index 5f6609df63..2e086cd958 100644 --- a/packages/core/src/components/graphs/area-stacked.ts +++ b/packages/core/src/components/graphs/area-stacked.ts @@ -28,7 +28,7 @@ export class StackedArea extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const self = this; const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/components/graphs/area.ts b/packages/core/src/components/graphs/area.ts index cd94376aaf..127dab7ac0 100644 --- a/packages/core/src/components/graphs/area.ts +++ b/packages/core/src/components/graphs/area.ts @@ -26,7 +26,7 @@ export class Area extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const { cartesianScales } = this.services; const orientation = cartesianScales.getOrientation(); diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 07f12e8c64..85217ba207 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -44,7 +44,7 @@ export class GroupedBar extends Bar { this.setGroupScale(); // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const allDataLabels = map( displayData, diff --git a/packages/core/src/components/graphs/bar-simple.ts b/packages/core/src/components/graphs/bar-simple.ts index aeb72950d1..2f74a5c922 100644 --- a/packages/core/src/components/graphs/bar-simple.ts +++ b/packages/core/src/components/graphs/bar-simple.ts @@ -31,7 +31,7 @@ export class SimpleBar extends Bar { const { groupMapsTo } = options.data; // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); // Update data on all bars const bars = svg diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index e2dcbc293a..74b5cc9e56 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -28,7 +28,7 @@ export class StackedBar extends Bar { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); // Chart options mixed with the internal configurations const displayData = this.model.getDisplayData(); diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 02caebb0be..3668bc294c 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -25,7 +25,7 @@ export class Line extends Component { } render(animate = true) { - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const { cartesianScales, curves } = this.services; const getDomainValue = (d, i) => cartesianScales.getDomainValue(d, i); @@ -141,7 +141,7 @@ export class Line extends Component { return Configuration.lines.opacity.selected; }); - } + }; handleLegendMouseOut = (event: CustomEvent) => { this.parent @@ -150,7 +150,7 @@ export class Line extends Component { this.services.transitions.getTransition("legend-mouseout-line") ) .attr("opacity", Configuration.lines.opacity.selected); - } + }; destroy() { // Remove event listeners diff --git a/packages/core/src/components/graphs/scatter-stacked.ts b/packages/core/src/components/graphs/scatter-stacked.ts index e477182786..ca5ad2a4c1 100644 --- a/packages/core/src/components/graphs/scatter-stacked.ts +++ b/packages/core/src/components/graphs/scatter-stacked.ts @@ -7,7 +7,7 @@ export class StackedScatter extends Scatter { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const options = this.model.getOptions(); const { groupMapsTo } = options.data; diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index 2d8074dc49..539d61b7e3 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -52,7 +52,7 @@ export class Scatter extends Component { render(animate: boolean) { // Grab container SVG - const svg = this.getContainerSVG(); + const svg = this.getContainerSVG(true); const options = this.model.getOptions(); const { groupMapsTo } = options.data; From b7239cd2f224a03c33625e3faa1b8f954ec54e8f Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 11:52:45 +0800 Subject: [PATCH 348/510] refactor: dispatch zoombar selection events instead of callback --- packages/core/demo/data/zoom-bar.ts | 26 +------------- .../core/src/components/axes/chart-brush.ts | 36 +++++++------------ packages/core/src/components/axes/zoom-bar.ts | 33 +++++++---------- packages/core/src/configuration.ts | 5 +-- packages/core/src/interfaces/components.ts | 13 ------- packages/core/src/interfaces/events.ts | 5 ++- 6 files changed, 32 insertions(+), 86 deletions(-) diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index fbe74fba2a..2dc1b076e1 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -6,39 +6,15 @@ import * as scatterChart from "./scatter"; import * as stepChart from "./step"; import * as timeSeriesAxisChart from "./time-series-axis"; -// default function for selection callback -const selectionStartFun = (selection, domain) => { - console.log("ZoomBar SelectionStart callback!"); - console.log(selection); - console.log(domain); -}; -const selectionInProgressFun = (selection, domain) => { - console.log("ZoomBar SelectionInProgress callback!"); - console.log(selection); - console.log(domain); -}; -const selectionEndFun = (selection, domain) => { - console.log("ZoomBar SelectionEnd callback!"); - console.log(selection); - console.log(domain); -}; - const initialZoomDomain = [ new Date(2020, 11, 10, 23, 59, 25), new Date(2020, 11, 11, 0, 0, 25) ]; -const defaultZoomBarOptions = { - enabled: true, - selectionStart: selectionStartFun, - selectionInProgress: selectionInProgressFun, - selectionEnd: selectionEndFun -}; - // utility function to update title and enable zoomBar option const addZoomBarToOptions = (options) => { options["title"] = options["title"] + " - Zoom bar enabled"; - options["zoomBar"] = Object.assign({}, defaultZoomBarOptions); + options["zoomBar"] = { enabled: true }; return options; }; diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 19b9ee14b9..e8ba9b3f7d 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -1,6 +1,6 @@ // Internal Imports import { Component } from "../component"; -import { ScaleTypes } from "../../interfaces"; +import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; // D3 Imports @@ -51,29 +51,19 @@ export class ChartBrush extends Component { event.sourceEvent.type === "mouseup" || event.sourceEvent.type === "mousedown") ) { - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress( - selection, - newDomain - ); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); + // dispatch selection events + let zoomBarEventType; + if (event.type === "start") { + zoomBarEventType = Events.ZoomBar.SELECTION_START; + } else if (event.type === "brush") { + zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; + } else if (event.type === "end") { + zoomBarEventType = Events.ZoomBar.SELECTION_END; } + this.services.events.dispatchEvent(zoomBarEventType, { + selection, + newDomain + }); } }; const brushed = () => { diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 897a1b9417..258d60ea1a 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -8,7 +8,7 @@ import { DOMUtils } from "../../services"; import { extent } from "d3-array"; import { brushX } from "d3-brush"; import { area, line } from "d3-shape"; -import { event, select, selectAll } from "d3-selection"; +import { event } from "d3-selection"; export class ZoomBar extends Component { type = "zoom-bar"; @@ -238,26 +238,19 @@ export class ZoomBar extends Component { this.model.set({ zoomDomain: newDomain }, { animate: false }); } - // call external callback - const zoomBarOptions = this.model.getOptions().zoomBar; - if ( - zoomBarOptions.selectionStart !== undefined && - event.type === "start" - ) { - zoomBarOptions.selectionStart(selection, newDomain); - } - if ( - zoomBarOptions.selectionInProgress !== undefined && - event.type === "brush" - ) { - zoomBarOptions.selectionInProgress(selection, newDomain); - } - if ( - zoomBarOptions.selectionEnd !== undefined && - event.type === "end" - ) { - zoomBarOptions.selectionEnd(selection, newDomain); + // dispatch selection events + let zoomBarEventType; + if (event.type === "start") { + zoomBarEventType = Events.ZoomBar.SELECTION_START; + } else if (event.type === "brush") { + zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; + } else if (event.type === "end") { + zoomBarEventType = Events.ZoomBar.SELECTION_END; } + this.services.events.dispatchEvent(zoomBarEventType, { + selection, + newDomain + }); } } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 5109d282e1..d09e139d44 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -132,10 +132,7 @@ export const timeScale: TimeScaleOptions = { * ZoomBar options */ export const zoomBar: ZoomBarOptions = { - enabled: false, - selectionStart: undefined, - selectionInProgress: undefined, - selectionEnd: undefined + enabled: false }; /** diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index e250197b85..5bdcf1f302 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -130,17 +130,4 @@ export interface ZoomBarOptions { * an two element array which represents the initial zoom domain */ initialZoomDomain?: Object[]; - - /** - * a function to handle selection start event - */ - selectionStart?: Function; - /** - * a function to handle selection in progress event - */ - selectionInProgress?: Function; - /** - * a function to handle selection end event - */ - selectionEnd?: Function; } diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 2a25fd5992..1da2416ead 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -21,7 +21,10 @@ export enum Model { * enum of all events related to the zoom-bar */ export enum ZoomBar { - UPDATE = "zoom-bar-update" + UPDATE = "zoom-bar-update", + SELECTION_START = "zoom-bar-selection-start", + SELECTION_IN_PROGRESS = "zoom-bar-selection-in-progress", + SELECTION_END = "zoom-bar-selection-end" } /** From 8a947167d36295c2869409375218e59a555395cd Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 15:48:41 +0800 Subject: [PATCH 349/510] feat: update chart brush selection storke to dash --- .../core/src/components/axes/chart-brush.ts | 36 +++++++++++++++++-- .../src/styles/components/_chart-brush.scss | 9 +++++ .../core/src/styles/components/index.scss | 1 + 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/styles/components/_chart-brush.scss diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e8ba9b3f7d..e267ae0bda 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -10,8 +10,12 @@ 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 = "chart-brush"; + selectionSelector = "rect.selection"; // needs to match the class name in d3.brush + render(animate = true) { const svg = this.parent; const backdrop = DOMUtils.appendOrSelect( @@ -35,8 +39,36 @@ export class ChartBrush extends Component { 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 + + brushArea + .select(this.selectionSelector) + .attr("stroke-dasharray", dashArray); + }; + const eventHandler = () => { const selection = event.selection; + + updateSelectionDash(selection); + const xScale = scaleTime().range([0, width]).domain(zoomDomain); const newDomain = [ @@ -106,8 +138,8 @@ export class ChartBrush extends Component { // leave some space to display selection strokes besides axis const brush = brushX() .extent([ - [2, 0], - [width - 1, height - 1] + [0, 0], + [width - 1, height] ]) .on("start brush end", eventHandler) .on("end.brushed", brushed); diff --git a/packages/core/src/styles/components/_chart-brush.scss b/packages/core/src/styles/components/_chart-brush.scss new file mode 100644 index 0000000000..f316c7d486 --- /dev/null +++ b/packages/core/src/styles/components/_chart-brush.scss @@ -0,0 +1,9 @@ +.#{$prefix}--#{$charts-prefix}--chart-brush { + g.chart-brush { + rect.selection { + fill: $ui-03; + fill-opacity: 0.3; + stroke: $interactive-03; + } + } +} diff --git a/packages/core/src/styles/components/index.scss b/packages/core/src/styles/components/index.scss index 0b96507fbe..e9632f12a1 100644 --- a/packages/core/src/styles/components/index.scss +++ b/packages/core/src/styles/components/index.scss @@ -1,5 +1,6 @@ @import "./axis"; @import "./callouts"; +@import "./chart-brush"; @import "./grid"; @import "./ruler"; @import "./skeleton"; From 21d2d28edae985bd6eb34fd0d5257aa369a6c5a5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 13 Jul 2020 17:33:44 +0800 Subject: [PATCH 350/510] feat: show tooltip when mouseover zoombar handle --- packages/core/src/components/axes/zoom-bar.ts | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 258d60ea1a..4498bb214f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -197,7 +197,11 @@ export class ZoomBar extends Component { // don't update brushHandle to avoid flash } else if (zoomDomain[0].valueOf() === zoomDomain[1].valueOf()) { brushArea.call(this.brush.move, xScale.range()); // default to full range - this.updateBrushHandle(this.getContainerSVG(), xScale.range()); + this.updateBrushHandle( + this.getContainerSVG(), + xScale.range(), + xScale.domain() + ); } else { const selected = zoomDomain.map((domain) => xScale(domain)); if (selected[1] - selected[0] < this.MIN_SELECTION_DIFF) { @@ -205,7 +209,11 @@ export class ZoomBar extends Component { // don't update brushHandle to avoid flash } else { brushArea.call(this.brush.move, selected); // set brush to correct position - this.updateBrushHandle(this.getContainerSVG(), selected); + this.updateBrushHandle( + this.getContainerSVG(), + selected, + zoomDomain + ); } } } @@ -213,14 +221,14 @@ export class ZoomBar extends Component { // brush event listener brushed(zoomDomain, scale, selection) { - // update brush handle position - this.updateBrushHandle(this.getContainerSVG(), selection); - const newDomain = [ scale.invert(selection[0]), scale.invert(selection[1]) ]; + // update brush handle position + this.updateBrushHandle(this.getContainerSVG(), selection, newDomain); + // be aware that the value of d3.event changes during an event! // update zoomDomain only if the event comes from mouse event if ( @@ -254,7 +262,20 @@ export class ZoomBar extends Component { } } - updateBrushHandle(svg, selection) { + updateBrushHandleTooltip(svg, domain) { + // remove old handle tooltip + svg.select("title").remove(); + // add new handle tooltip + svg.append("title").text((d) => { + if (d.type === "w") { + return domain[0]; + } else if (d.type === "e") { + return domain[1]; + } + }); + } + + updateBrushHandle(svg, selection, domain) { const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -263,6 +284,7 @@ export class ZoomBar extends Component { const handleBarHeight = 12; const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; + // handle svg.select(this.brushSelector) .selectAll("rect.handle") @@ -277,7 +299,10 @@ export class ZoomBar extends Component { .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) - .style("display", null); // always display + .attr("cursor", "pointer") + .style("display", null) // always display + .call(this.updateBrushHandleTooltip, domain); + // handle-bar svg.select(this.brushSelector) .selectAll("rect.handle-bar") @@ -296,7 +321,8 @@ export class ZoomBar extends Component { .attr("y", handleYBarDiff) .attr("width", handleBarWidth) .attr("height", handleBarHeight) - .attr("cursor", "ew-resize"); + .attr("cursor", "pointer") + .call(this.updateBrushHandleTooltip, domain); this.updateClipPath( svg, From d3483b13bd8707d60d957690c546fef1fc474c3a Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 11:54:28 +0800 Subject: [PATCH 351/510] fix: avoid tick rotation flip during zoom domain changing - always rotate ticks during zoom domain changing --- packages/core/src/components/axes/axis.ts | 33 ++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 1b1dc86681..7f1cc1f3b5 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -29,6 +29,8 @@ export class Axis extends Component { scale: any; scaleType: ScaleTypes; + zoomDomainChanging = false; + constructor(model: ChartModel, services: any, configs?: any) { super(model, services, configs); @@ -37,6 +39,25 @@ export class Axis extends Component { } this.margins = this.configs.margins; + this.init(); + } + + init() { + this.services.events.addEventListener( + Events.ZoomBar.SELECTION_START, + () => { + this.zoomDomainChanging = true; + } + ); + this.services.events.addEventListener( + Events.ZoomBar.SELECTION_END, + () => { + this.zoomDomainChanging = false; + // need another update after zoom bar selection is completed + // to make sure the tick rotation is calculated correctly + this.services.events.dispatchEvent(Events.Model.UPDATE, {}); + } + ); } render(animate = true) { @@ -438,7 +459,9 @@ export class Axis extends Component { ? estimatedTickSize < minTickSize * 2 // datetime tick could be very long : estimatedTickSize < minTickSize; } - if (rotateTicks) { + + // always rotate ticks if zoomDomain is changing to avoid rotation flips during zoomDomain changing + if (rotateTicks || this.zoomDomainChanging) { if (!isNumberOfTicksProvided) { axis.ticks( this.getNumberOfFittingTicks( @@ -653,5 +676,13 @@ export class Axis extends Component { .on("mouseover", null) .on("mousemove", null) .on("mouseout", null); + this.services.events.removeEventListener( + Events.ZoomBar.SELECTION_START, + {} + ); + this.services.events.removeEventListener( + Events.ZoomBar.SELECTION_END, + {} + ); } } From 7821866afa59fe3bbd852467b0cb33a28385c2cc Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 18:19:27 +0800 Subject: [PATCH 352/510] fix: move chart brush selection above all graphs --- .../core/src/components/axes/chart-brush.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index e267ae0bda..944e2245e6 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -16,6 +16,8 @@ export class ChartBrush extends Component { selectionSelector = "rect.selection"; // needs to match the class name in d3.brush + selectionElementId = "ChartBrushSelectionId"; + render(animate = true) { const svg = this.parent; const backdrop = DOMUtils.appendOrSelect( @@ -66,6 +68,9 @@ export class ChartBrush extends Component { const eventHandler = () => { const selection = event.selection; + if (selection === null) { + return; + } updateSelectionDash(selection); @@ -148,6 +153,26 @@ export class ChartBrush extends Component { backdrop, `g.${this.type}` ).call(brush); + + // set an id for rect.selection to be referred + brushArea + .select(this.selectionSelector) + .attr("id", this.selectionElementId); + + // create the chart brush group + const [xScaleStart, xScaleEnd] = mainXScale.range(); + const selectionArea = this.getContainerSVG().attr( + "transform", + `translate(${xScaleStart},0)` + ); + // clear old svg + selectionArea.selectAll("svg").remove(); + // create a svg referring to d3 brush rect.selection + // this is to draw the selection above all graphs + selectionArea + .append("svg") + .append("use") + .attr("xlink:href", `#${this.selectionElementId}`); } } } From 274799d1554102b6011e1ddf8f1575dcaaa42c29 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 20:47:11 +0800 Subject: [PATCH 353/510] fix: use another svg to display front selection --- .../core/src/components/axes/chart-brush.ts | 55 +++++++++---------- .../src/styles/components/_chart-brush.scss | 15 ++++- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 944e2245e6..d07edd0c4e 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -16,14 +16,22 @@ export class ChartBrush extends Component { selectionSelector = "rect.selection"; // needs to match the class name in d3.brush - selectionElementId = "ChartBrushSelectionId"; + frontSelectionSelector = "rect.frontSelection"; // needs to match the class name in _chart-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 = brushArea.select(this.selectionSelector); + const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true }); @@ -31,6 +39,12 @@ export class ChartBrush extends Component { 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 @@ -61,9 +75,7 @@ export class ChartBrush extends Component { dashArray += "," + selectionWidth.toString(); // bottom (invisible) dashArray += "," + height.toString(); // left - brushArea - .select(this.selectionSelector) - .attr("stroke-dasharray", dashArray); + frontSelection.attr("stroke-dasharray", dashArray); }; const eventHandler = () => { @@ -72,6 +84,14 @@ export class ChartBrush extends Component { 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("display", null); + updateSelectionDash(selection); const xScale = scaleTime().range([0, width]).domain(zoomDomain); @@ -137,6 +157,8 @@ export class ChartBrush extends Component { // clear brush selection brushArea.call(brush.move, null); + // hide frontSelection + frontSelection.style("display", "none"); } }; @@ -149,30 +171,7 @@ export class ChartBrush extends Component { .on("start brush end", eventHandler) .on("end.brushed", brushed); - const brushArea = DOMUtils.appendOrSelect( - backdrop, - `g.${this.type}` - ).call(brush); - - // set an id for rect.selection to be referred - brushArea - .select(this.selectionSelector) - .attr("id", this.selectionElementId); - - // create the chart brush group - const [xScaleStart, xScaleEnd] = mainXScale.range(); - const selectionArea = this.getContainerSVG().attr( - "transform", - `translate(${xScaleStart},0)` - ); - // clear old svg - selectionArea.selectAll("svg").remove(); - // create a svg referring to d3 brush rect.selection - // this is to draw the selection above all graphs - selectionArea - .append("svg") - .append("use") - .attr("xlink:href", `#${this.selectionElementId}`); + brushArea.call(brush); } } } diff --git a/packages/core/src/styles/components/_chart-brush.scss b/packages/core/src/styles/components/_chart-brush.scss index f316c7d486..d458f791d4 100644 --- a/packages/core/src/styles/components/_chart-brush.scss +++ b/packages/core/src/styles/components/_chart-brush.scss @@ -1,9 +1,18 @@ .#{$prefix}--#{$charts-prefix}--chart-brush { + // disable default d3 brush selection g.chart-brush { rect.selection { - fill: $ui-03; - fill-opacity: 0.3; - stroke: $interactive-03; + fill: none; + fill-opacity: 0; + stroke: none; } } } + +g.#{$prefix}--#{$charts-prefix}--chart-brush { + rect.frontSelection { + fill: $ui-03; + fill-opacity: 0.3; + stroke: $interactive-03; + } +} From df80f1c43c8b6bd8d651176e5f026ac5901816e2 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 14 Jul 2020 20:59:47 +0800 Subject: [PATCH 354/510] fix: set cursor for front selection --- packages/core/src/components/axes/chart-brush.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index d07edd0c4e..85e48b3d08 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -90,6 +90,7 @@ export class ChartBrush extends Component { .attr("y", d3Selection.attr("y")) .attr("width", d3Selection.attr("width")) .attr("height", d3Selection.attr("height")) + .style("cursor", "pointer") .style("display", null); updateSelectionDash(selection); From e74427fceb2d2fb46cc421edb347c09f79e929de Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 16 Jul 2020 15:07:40 +0800 Subject: [PATCH 355/510] fix: use unique chartClipId for each chart --- packages/core/src/components/axes/chart-clip.ts | 15 +++++++++++++++ packages/core/src/components/component.ts | 8 +++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/axes/chart-clip.ts b/packages/core/src/components/axes/chart-clip.ts index 321db115e5..4a8a206c2f 100644 --- a/packages/core/src/components/axes/chart-clip.ts +++ b/packages/core/src/components/axes/chart-clip.ts @@ -1,14 +1,29 @@ // Internal Imports import { Component } from "../component"; import { DOMUtils } from "../../services"; +import { ChartModel } from "../../model"; // This class is used to create the clipPath to clip the chart components // It's necessary for zoom in/out behavior export class ChartClip extends Component { type = "chart-clip"; + // Give every chart-clip a distinct ID + // so they don't interfere each other in a page with multiple charts + chartClipId = "chart-clip-id-" + Math.floor(Math.random() * 99999999999); + chartClipPath: any; + constructor(model: ChartModel, services: any, configs?: any) { + super(model, services, configs); + this.init(); + } + + init() { + // set unique chartClipId in this chart to model + this.model.set({ chartClipId: this.chartClipId }, { skipUpdate: true }); + } + render(animate = true) { // Create the clipPath this.createClipPath(); diff --git a/packages/core/src/components/component.ts b/packages/core/src/components/component.ts index 91ccf82ef8..87d55fba62 100644 --- a/packages/core/src/components/component.ts +++ b/packages/core/src/components/component.ts @@ -19,8 +19,6 @@ export class Component { protected model: ChartModel; protected services: any; - protected chartClipId = "chart-clip-id"; - constructor(model: ChartModel, services: any, configs?: any) { this.model = model; this.services = services; @@ -99,7 +97,11 @@ export class Component { ); if (withinChartClip) { - svg.attr("clip-path", `url(#${this.chartClipId})`); + // get unique chartClipId int this chart from model + const chartClipId = this.model.get("chartClipId"); + if (chartClipId) { + svg.attr("clip-path", `url(#${chartClipId})`); + } } return svg; From e05fc0cab3996bf4ba754488670dc5ecd007bf67 Mon Sep 17 00:00:00 2001 From: EricYang Date: Thu, 16 Jul 2020 16:17:58 +0800 Subject: [PATCH 356/510] fix: keep zoom bar handle inside zoom bar range --- packages/core/src/components/axes/zoom-bar.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 4498bb214f..adc38f9bc9 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -20,7 +20,9 @@ export class ZoomBar extends Component { brushSelector = "g.zoom-bar-brush"; // needs to match the style in _zoom-bar.scss - clipId = "zoomBarClip"; + // Give every zoomBarClip a distinct ID + // so they don't interfere the other zoom bars in a page + clipId = "zoomBarClip-" + Math.floor(Math.random() * 99999999999); height = 32; @@ -28,6 +30,9 @@ export class ZoomBar extends Component { brush = brushX(); + // The max allowed selection ragne, will be updated soon in render() + maxSelectionRange: [0, 0]; + init() { this.services.events.addEventListener(Events.ZoomBar.UPDATE, () => { this.render(); @@ -97,6 +102,8 @@ export class ZoomBar extends Component { this.compensateDataForDefaultDomain(zoomBarData, defaultDomain, 0); xScale.range([axesLeftMargin, width]).domain(defaultDomain); + // keep max selection range + this.maxSelectionRange = xScale.range(); yScale .range([0, this.height - 6]) @@ -276,6 +283,7 @@ export class ZoomBar extends Component { } updateBrushHandle(svg, selection, domain) { + const self = this; const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -291,9 +299,17 @@ export class ZoomBar extends Component { .data([{ type: "w" }, { type: "e" }]) .attr("x", function (d) { if (d.type === "w") { - return selection[0] + handleXDiff; + // handle should not exceed zoom bar range + return Math.max( + selection[0] + handleXDiff, + self.maxSelectionRange[0] + ); } else if (d.type === "e") { - return selection[1] + handleXDiff; + // handle should not exceed zoom bar range + return Math.min( + selection[1] + handleXDiff, + self.maxSelectionRange[1] - handleWidth + ); } }) .attr("y", 0) @@ -313,9 +329,15 @@ export class ZoomBar extends Component { }) .attr("x", function (d) { if (d.type === "w") { - return selection[0] + handleBarXDiff; + return Math.max( + selection[0] + handleBarXDiff, + self.maxSelectionRange[0] - handleXDiff + handleBarXDiff + ); } else if (d.type === "e") { - return selection[1] + handleBarXDiff; + return Math.min( + selection[1] + handleBarXDiff, + self.maxSelectionRange[1] + handleXDiff + handleBarXDiff + ); } }) .attr("y", handleYBarDiff) From e25b8a3d7197bc84b216e52e01418f8db2ee42b5 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 20 Jul 2020 21:42:26 +0800 Subject: [PATCH 357/510] fix: use DOMUtils.appendOrSelect in case the element does not exist yet --- packages/core/src/components/axes/chart-brush.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 85e48b3d08..e872ec1cf5 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -30,7 +30,10 @@ export class ChartBrush extends Component { const brushArea = DOMUtils.appendOrSelect(backdrop, `g.${this.type}`); // set an id for rect.selection to be referred - const d3Selection = brushArea.select(this.selectionSelector); + const d3Selection = DOMUtils.appendOrSelect( + brushArea, + this.selectionSelector + ); const { width, height } = DOMUtils.getSVGElementSize(backdrop, { useAttrs: true From 3cdad63d5fafde1c9052422bd52c82faf4a664f1 Mon Sep 17 00:00:00 2001 From: EricYang Date: Mon, 20 Jul 2020 22:25:10 +0800 Subject: [PATCH 358/510] feat: allow user to set zoom bar data --- packages/core/demo/data/index.ts | 6 +++++ packages/core/demo/data/zoom-bar.ts | 31 +++++++++++++++++++--- packages/core/src/interfaces/components.ts | 4 +++ packages/core/src/model.ts | 19 ++++++++++--- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 8f830ed5a8..adf45f2d1b 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -778,6 +778,12 @@ let allDemoGroups = [ chartType: chartTypes.StackedBarChart, isDemoExample: false }, + { + options: zoomBarDemos.definedZoomBarStackedBarTimeSeriesOptions, + data: zoomBarDemos.definedZoomBarStackedBarTimeSeriesData, + chartType: chartTypes.StackedBarChart, + isDemoExample: false + }, { options: zoomBarDemos.zoomBarBubbleTimeSeriesOptions, data: zoomBarDemos.zoomBarBubbleTimeSeriesData, diff --git a/packages/core/demo/data/zoom-bar.ts b/packages/core/demo/data/zoom-bar.ts index 2dc1b076e1..98249d52da 100644 --- a/packages/core/demo/data/zoom-bar.ts +++ b/packages/core/demo/data/zoom-bar.ts @@ -11,10 +11,28 @@ const initialZoomDomain = [ new Date(2020, 11, 11, 0, 0, 25) ]; +const definedZoomBarData = [ + { date: new Date(2019, 0, 1), value: 10000 }, + { date: new Date(2019, 0, 2), value: 10 }, + { date: new Date(2019, 0, 3), value: 75000 }, + { date: new Date(2019, 0, 5), value: 65000 }, + { date: new Date(2019, 0, 6), value: 57312 }, + { date: new Date(2019, 0, 8), value: 10000 }, + { date: new Date(2019, 0, 13), value: 49213 }, + { date: new Date(2019, 0, 15), value: 70323 }, + { date: new Date(2019, 0, 17), value: 51213 }, + { date: new Date(2019, 0, 19), value: 21300 } +]; + // utility function to update title and enable zoomBar option -const addZoomBarToOptions = (options) => { - options["title"] = options["title"] + " - Zoom bar enabled"; - options["zoomBar"] = { enabled: true }; +const addZoomBarToOptions = (options, includeDefinedZoomBarData = false) => { + if (includeDefinedZoomBarData) { + options["title"] = options["title"] + " - Defined zoom bar enabled"; + options["zoomBar"] = { enabled: true, data: definedZoomBarData }; + } else { + options["title"] = options["title"] + " - Zoom bar enabled"; + options["zoomBar"] = { enabled: true }; + } return options; }; @@ -35,6 +53,13 @@ export const zoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, barChart.stackedBarTimeSeriesOptions) ); +export const definedZoomBarStackedBarTimeSeriesData = + barChart.stackedBarTimeSeriesData; +export const definedZoomBarStackedBarTimeSeriesOptions = addZoomBarToOptions( + Object.assign({}, barChart.stackedBarTimeSeriesOptions), + true +); + export const zoomBarBubbleTimeSeriesData = bubbleChart.bubbleTimeSeriesData; export const zoomBarBubbleTimeSeriesOptions = addZoomBarToOptions( Object.assign({}, bubbleChart.bubbleTimeSeriesOptions) diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index 5bdcf1f302..1a966db09f 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -130,4 +130,8 @@ export interface ZoomBarOptions { * an two element array which represents the initial zoom domain */ initialZoomDomain?: Object[]; + /** + * options related to zoom bar data + */ + data?: Object[]; } diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 63254bb3e7..843b353b2a 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -46,10 +46,23 @@ export class ChartModel { const domainIdentifier = cartesianScales.getDomainIdentifier(); const rangeIdentifier = cartesianScales.getRangeIdentifier(); - const displayData = this.getDisplayData(); + let zoomBarData; + // check if pre-defined zoom bar data exists + const definedZoomBarData = Tools.getProperty( + this.getOptions(), + "zoomBar", + "data" + ); + // if user already defines zoom bar data, use it + if (definedZoomBarData) { + zoomBarData = definedZoomBarData; + } else { + // use displayData if not defined + zoomBarData = this.getDisplayData(); + } // get all dates (Number) in displayData let allDates = []; - displayData.forEach((data) => { + zoomBarData.forEach((data) => { allDates = allDates.concat(Number(data[domainIdentifier])); }); allDates = Tools.removeArrayDuplicates(allDates).sort(); @@ -59,7 +72,7 @@ export class ChartModel { let sum = 0; const datum = {}; - displayData.forEach((data) => { + zoomBarData.forEach((data) => { if (Number(data[domainIdentifier]) === date) { sum += data[rangeIdentifier]; } From 55c4fa2821e05a70fb9877475ed6978dd4c968ec Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 11:38:02 +0800 Subject: [PATCH 359/510] fix: definedZoomBarData needs at least two elements --- packages/core/src/components/axes/zoom-bar.ts | 3 +++ packages/core/src/model.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index adc38f9bc9..6d134f0534 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -393,6 +393,9 @@ export class ZoomBar extends Component { // assume the domains in data are already sorted compensateDataForDefaultDomain(data, defaultDomain, value) { + if (!data || data.length < 2) { + return; + } const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); // if min domain is extended diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 843b353b2a..b6fa3f9ba4 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -54,7 +54,7 @@ export class ChartModel { "data" ); // if user already defines zoom bar data, use it - if (definedZoomBarData) { + if (definedZoomBarData && definedZoomBarData.length > 1) { zoomBarData = definedZoomBarData; } else { // use displayData if not defined From 20a3dcabcd36f647e66be8e8e68c92baf11117a8 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 13:43:41 +0800 Subject: [PATCH 360/510] fix: axis needs to set opacity if not loading --- packages/core/src/components/axes/axis.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 7f1cc1f3b5..c6a3c33a91 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -494,6 +494,8 @@ export class Axis extends Component { // because the Skeleton component draws them if (isDataLoading) { container.attr("opacity", 0); + } else { + container.attr("opacity", 1); } axisRef.selectAll("g.tick").attr("aria-label", (d) => d); From a1252ba13f28fe77e1ac91d524fd49754d45be63 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 21 Jul 2020 15:15:46 +0800 Subject: [PATCH 361/510] fix: format zoom bar handle tooltip --- packages/core/src/components/axes/zoom-bar.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 6d134f0534..0c742f6585 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -3,6 +3,10 @@ import { Component } from "../component"; import { Tools } from "../../tools"; import { Events, ScaleTypes } from "../../interfaces"; import { DOMUtils } from "../../services"; +import { + computeTimeIntervalName, + formatTick +} from "../../services/time-series"; // D3 Imports import { extent } from "d3-array"; @@ -269,21 +273,27 @@ export class ZoomBar extends Component { } } - updateBrushHandleTooltip(svg, domain) { + updateBrushHandleTooltip(svg, domain, timeScaleOptions) { + const timeInterval = computeTimeIntervalName(domain); + // remove old handle tooltip svg.select("title").remove(); // add new handle tooltip svg.append("title").text((d) => { if (d.type === "w") { - return domain[0]; + return formatTick(domain[0], 0, timeInterval, timeScaleOptions); } else if (d.type === "e") { - return domain[1]; + return formatTick(domain[1], 0, timeInterval, timeScaleOptions); } }); } updateBrushHandle(svg, selection, domain) { const self = this; + const timeScaleOptions = Tools.getProperty( + this.model.getOptions(), + "timeScale" + ); const handleWidth = 5; const handleHeight = this.height; const handleXDiff = -handleWidth / 2; @@ -317,7 +327,7 @@ export class ZoomBar extends Component { .attr("height", handleHeight) .attr("cursor", "pointer") .style("display", null) // always display - .call(this.updateBrushHandleTooltip, domain); + .call(this.updateBrushHandleTooltip, domain, timeScaleOptions); // handle-bar svg.select(this.brushSelector) @@ -344,7 +354,7 @@ export class ZoomBar extends Component { .attr("width", handleBarWidth) .attr("height", handleBarHeight) .attr("cursor", "pointer") - .call(this.updateBrushHandleTooltip, domain); + .call(this.updateBrushHandleTooltip, domain, timeScaleOptions); this.updateClipPath( svg, From 3f3092d6704bfe550e6bb1951a926dd792023af1 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 22 Jul 2020 16:39:39 +0800 Subject: [PATCH 362/510] fix: don't set brush handle tooltip if domain is undefined --- packages/core/src/components/axes/zoom-bar.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 0c742f6585..d5cea1341c 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -274,10 +274,15 @@ export class ZoomBar extends Component { } updateBrushHandleTooltip(svg, domain, timeScaleOptions) { - const timeInterval = computeTimeIntervalName(domain); - // remove old handle tooltip svg.select("title").remove(); + + // if domain is undefined, do nothing + if (!domain) { + return; + } + + const timeInterval = computeTimeIntervalName(domain); // add new handle tooltip svg.append("title").text((d) => { if (d.type === "w") { From 6eb41d2ea31759b2d3fb164664fc0ec00a0e9589 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 28 Jul 2020 12:32:05 +0800 Subject: [PATCH 363/510] fix: filter out invalid domain value to avoid exception --- packages/core/src/components/axes/zoom-bar.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index d5cea1341c..015a15a8e5 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -274,11 +274,14 @@ export class ZoomBar extends Component { } updateBrushHandleTooltip(svg, domain, timeScaleOptions) { + const isValidDate = (d) => { + return d instanceof Date && !isNaN(d.getTime()); + }; // remove old handle tooltip svg.select("title").remove(); // if domain is undefined, do nothing - if (!domain) { + if (!domain || !isValidDate(domain[0]) || !isValidDate(domain[1])) { return; } From a01f087c32bcf4000e7d308624ba71959e0bd46d Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Tue, 28 Jul 2020 15:22:30 +0800 Subject: [PATCH 364/510] fix: zoomDomain event and overflow menu option position --- packages/core/src/components/axes/axis.ts | 4 ++-- .../core/src/components/axes/chart-brush.ts | 3 ++- packages/core/src/components/axes/tool-bar.ts | 18 +++++++----------- packages/core/src/components/axes/zoom-bar.ts | 6 +++++- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index de2b966a09..c6a3c33a91 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -50,7 +50,7 @@ export class Axis extends Component { } ); this.services.events.addEventListener( - Events.ZoomDomain.CHANGE, + Events.ZoomBar.SELECTION_END, () => { this.zoomDomainChanging = false; // need another update after zoom bar selection is completed @@ -683,7 +683,7 @@ export class Axis extends Component { {} ); this.services.events.removeEventListener( - Events.ZoomDomain.CHANGE, + Events.ZoomBar.SELECTION_END, {} ); } diff --git a/packages/core/src/components/axes/chart-brush.ts b/packages/core/src/components/axes/chart-brush.ts index 30eaca8b14..c6f8908af3 100644 --- a/packages/core/src/components/axes/chart-brush.ts +++ b/packages/core/src/components/axes/chart-brush.ts @@ -63,6 +63,7 @@ export class ChartBrush extends Component { ); const setDomain = (newDomain) => { + this.services.events.dispatchEvent(Events.ZoomDomain.CHANGE, { newDomain }); this.model.set( { zoomDomain: newDomain }, { animate: false } @@ -143,7 +144,7 @@ export class ChartBrush extends Component { } else if (event.type === "brush") { zoomBarEventType = Events.ZoomBar.SELECTION_IN_PROGRESS; } else if (event.type === "end") { - zoomBarEventType = Events.ZoomDomain.CHANGE; + zoomBarEventType = Events.ZoomBar.SELECTION_END; } this.services.events.dispatchEvent(zoomBarEventType, { selection, diff --git a/packages/core/src/components/axes/tool-bar.ts b/packages/core/src/components/axes/tool-bar.ts index e08691a45b..892239f780 100644 --- a/packages/core/src/components/axes/tool-bar.ts +++ b/packages/core/src/components/axes/tool-bar.ts @@ -31,6 +31,8 @@ export class ToolBar extends Component { overflowMenuOptions; + overflowMenuIconBottom = 0; + overflowMenuId = "overflowMenu-" + Math.floor(Math.random() * 99999999999); @@ -156,9 +158,7 @@ export class ToolBar extends Component { { zoomDomain: newDomain, selectionRange: [startPoint, endPoint] }, { animate: false } ); - self.services.events.dispatchEvent(Events.ZoomDomain.CHANGE, { - newDomain - }); + self.services.events.dispatchEvent(Events.ZoomDomain.CHANGE, { newDomain }); } }); const zoomOutContainer = DOMUtils.appendOrSelect(container, "g.toolbar-zoomOut"); @@ -198,10 +198,7 @@ export class ToolBar extends Component { { zoomDomain: newDomain, selectionRange: [startPoint, endPoint] }, { animate: false } ); - self.services.events.dispatchEvent(Events.ZoomDomain.CHANGE, { - newDomain - }); - + self.services.events.dispatchEvent(Events.ZoomDomain.CHANGE, { newDomain }); }); const overflowMenuContainer = DOMUtils.appendOrSelect(container, "g.toolbar-overflow-menu"); @@ -213,6 +210,7 @@ export class ToolBar extends Component { // Hide toolbar self.services.events.dispatchEvent(Events.Toolbar.HIDE); } else { + self.overflowMenuIconBottom = parseFloat(self.parent.node().getAttribute("y")) + this.parentNode.getBBox().height; self.services.events.dispatchEvent(Events.Toolbar.SHOW); document.getElementById("reset-Btn").addEventListener("click", function () { const newDomain = self.model.getDefaultZoomBarDomain(); @@ -220,9 +218,7 @@ export class ToolBar extends Component { { zoomDomain: newDomain, selectionRange: [axesLeftMargin, width] }, { animate: false } ); - self.services.events.dispatchEvent(Events.ZoomDomain.CHANGE, { - newDomain - }); + self.services.events.dispatchEvent(Events.ZoomDomain.CHANGE, { newDomain }); self.services.events.dispatchEvent(Events.Toolbar.HIDE); }, true); } @@ -293,7 +289,7 @@ export class ToolBar extends Component { data-floating-menu-direction="bottom" role="main"> `; + return overflowMenuHtml; } - getResetZoomMenuItem() { + getOverflowMenuItems() { + const overflowMenuItems = []; const isZoomBarEnabled = this.services.zoom.isZoomBarEnabled() && !this.services.zoom.isEmptyState(); - const resetZoomOption = Tools.getProperty( + const isResetZoomEnabled = Tools.getProperty( this.model.getOptions(), "toolbar", "overflowMenuItems", - "resetZoom" + "resetZoom", + "enabled" ); - if (!resetZoomOption.enabled || !isZoomBarEnabled) { - return ""; - } else { - return `
  • - -
  • `; + if (isZoomBarEnabled && isResetZoomEnabled) { + overflowMenuItems.push(this.getResetZoomMenuItemConfig()); } + + return overflowMenuItems; } - handleOverflowMenuEvent() { - if ( - this.overflowMenu - .selectAll("ul.bx--overflow-menu-options--open") - .size() > 0 - ) { - // hide overflow menu - this.overflowIcon.classed("icon-overflowRect-hovered", false); - this.services.events.dispatchEvent( - Events.Toolbar.HIDE_OVERFLOW_MENU - ); - } else { - // show overflow menu - this.overflowIcon.classed("icon-overflowRect-hovered", true); - this.services.events.dispatchEvent( - Events.Toolbar.SHOW_OVERFLOW_MENU - ); - const self = this; - const resetZoomButtonElement = document.getElementById( - this.resetZoomMenuItemId - ); - if (resetZoomButtonElement !== null) { - resetZoomButtonElement.addEventListener( - "click", - function () { - self.services.zoom.resetZoomDomain(); - - self.overflowIcon.classed( - "icon-overflowRect-hovered", - false - ); - - self.services.events.dispatchEvent( - Events.Toolbar.HIDE_OVERFLOW_MENU - ); - }, - true - ); - } - } - event.stopImmediatePropagation(); + getResetZoomMenuItemConfig() { + const resetZoomText = Tools.getProperty( + this.model.getOptions(), + "toolbar", + "overflowMenuItems", + "resetZoom", + "text" + ); + return { + elementId: this.resetZoomMenuItemId, // this id needs to be unique in the whole web page + text: resetZoomText, + clickFunction: () => this.services.zoom.resetZoomDomain() + }; } getZoomInButtonConfig() { @@ -269,7 +282,7 @@ export class Toolbar extends Component { id: "toolbar-overflow-menu", iconId: "overflowRect", iconSVG: (startPosition) => this.getOverflowIcon(startPosition), - clickFunction: () => this.handleOverflowMenuEvent() + clickFunction: () => this.toggleOverflowMenu() }; } From c3d15a616d43e18bdd67ac0d5d7af1d747713222 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 11 Aug 2020 16:32:25 +0800 Subject: [PATCH 504/510] fix: hide overflow icon if no overflow menu item --- packages/core/src/components/axes/toolbar.ts | 42 ++++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/core/src/components/axes/toolbar.ts b/packages/core/src/components/axes/toolbar.ts index 348c3cf9f5..c500ebe4e3 100644 --- a/packages/core/src/components/axes/toolbar.ts +++ b/packages/core/src/components/axes/toolbar.ts @@ -62,9 +62,7 @@ export class Toolbar extends Component { render(animate = true) { const isDataLoading = this.services.zoom.isDataLoading(); - const isZoomBarEnabled = - this.services.zoom.isZoomBarEnabled() && - !this.services.zoom.isEmptyState(); + // size of overflow menu icon with background const iconSize = Configuration.toolbar.iconSize; @@ -93,8 +91,12 @@ export class Toolbar extends Component { // clean children first container.html(null); + + // get the toolbar buttons + const toolbarButtonList = this.getToolbarButtons(); + // loading or empty state - if (isDataLoading) { + if (isDataLoading || toolbarButtonList.length === 0) { // put an empty rect to keep space unchanged DOMUtils.appendOrSelect(container, "svg.toolbar-loading-spacer") .append("rect") @@ -104,15 +106,7 @@ export class Toolbar extends Component { } else { const self = this; - const toolbarButtonList = []; - // add zoom in/out button only if zoom bar is enabled - if (isZoomBarEnabled) { - toolbarButtonList.push(this.getZoomInButtonConfig()); - toolbarButtonList.push(this.getZoomOutButtonConfig()); - } - toolbarButtonList.push(this.getOverflowButtonConfig()); - - // render buttons sequentially + // render toolbar buttons sequentially let buttonXPosition = 0; toolbarButtonList.forEach((button) => { // zoom in icon and event @@ -242,6 +236,8 @@ export class Toolbar extends Component { overflowMenuItems.push(this.getResetZoomMenuItemConfig()); } + // add more overflow menu item configurations here + return overflowMenuItems; } @@ -260,6 +256,26 @@ export class Toolbar extends Component { }; } + getToolbarButtons() { + const toolbarButtonList = []; + const isZoomBarEnabled = + this.services.zoom.isZoomBarEnabled() && + !this.services.zoom.isEmptyState(); + // add zoom in/out button only if zoom bar is enabled + if (isZoomBarEnabled) { + toolbarButtonList.push(this.getZoomInButtonConfig()); + toolbarButtonList.push(this.getZoomOutButtonConfig()); + } + // add overflow icon button only if overflow menu item is available + if (this.getOverflowMenuItems().length > 0) { + toolbarButtonList.push(this.getOverflowButtonConfig()); + } + + // add more toolbar button configurations here + + return toolbarButtonList; + } + getZoomInButtonConfig() { return { id: "toolbar-zoomIn", From e4e07b90addf2e4e9c6a918b0454869f5a151f50 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 11 Aug 2020 16:42:48 +0800 Subject: [PATCH 505/510] refactor: change defaultZoomBarDomain to initialZoomBarDomain --- packages/core/src/components/axes/zoom-bar.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 041d1a17ea..99e41e69b2 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -34,7 +34,7 @@ export class ZoomBar extends Component { yScale: any; // keep the initial zoomBar domain to avoid the incorrect domain due to data changes - defaultZoomBarDomain: any; + initialZoomBarDomain: any; init() { this.services.events.addEventListener( @@ -120,14 +120,14 @@ export class ZoomBar extends Component { return; } // save defaultZoomBarDomain if not set yet - if (!this.defaultZoomBarDomain) { - this.defaultZoomBarDomain = defaultDomain; + if (!this.initialZoomBarDomain) { + this.initialZoomBarDomain = defaultDomain; } // add value 0 to the extended domain for zoom bar area graph this.compensateDataForDefaultDomain( zoomBarData, - this.defaultZoomBarDomain + this.initialZoomBarDomain ); const handleWidth = Configuration.zoomBar.handleWidth; @@ -136,7 +136,7 @@ export class ZoomBar extends Component { axesLeftMargin + handleWidth / 2, width - handleWidth / 2 ]) - .domain(this.defaultZoomBarDomain); + .domain(this.initialZoomBarDomain); // keep max selection range this.maxSelectionRange = this.xScale.range(); From 4594c1bab894fd0eaf5285563a5ba5c4110d7a07 Mon Sep 17 00:00:00 2001 From: EricYang Date: Tue, 11 Aug 2020 16:59:11 +0800 Subject: [PATCH 506/510] refactor: move addEventListener to init() to avoid duplication --- packages/core/src/components/axes/toolbar.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/axes/toolbar.ts b/packages/core/src/components/axes/toolbar.ts index c500ebe4e3..1821fea093 100644 --- a/packages/core/src/components/axes/toolbar.ts +++ b/packages/core/src/components/axes/toolbar.ts @@ -58,6 +58,10 @@ export class Toolbar extends Component { this.overflowMenu.html(null); } ); + // hide overflow menu if user clicks on somewhere in web page + document.body.addEventListener("click", () => + this.showOverflowMenu(false) + ); } render(animate = true) { @@ -128,11 +132,6 @@ export class Toolbar extends Component { "rect.icon-overflowRect" ); - document.body.addEventListener("click", function () { - // hide overflow menu if user clicks on somewhere in web page - self.showOverflowMenu(false); - }); - if (this.isOverflowMenuOpen()) { // keep overflow menu displayed this.showOverflowMenu(true); From fa0f2fec01328ed9929934d52baa5c3e3d950db9 Mon Sep 17 00:00:00 2001 From: EricYang Date: Wed, 12 Aug 2020 09:58:10 +0800 Subject: [PATCH 507/510] fix: keep title component if toolbar is enabled for alignment --- packages/core/src/axis-chart.ts | 3 ++- packages/core/src/components/axes/toolbar.ts | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/src/axis-chart.ts b/packages/core/src/axis-chart.ts index 7c35083b8f..0c6be9e65c 100644 --- a/packages/core/src/axis-chart.ts +++ b/packages/core/src/axis-chart.ts @@ -69,7 +69,8 @@ export class AxisChart extends Chart { this.model, this.services, [ - ...(titleAvailable ? [titleComponent] : []), + // always add title to keep layout correct + titleComponent, ...(toolbarEnabled ? [toolbarComponent] : []) ], { diff --git a/packages/core/src/components/axes/toolbar.ts b/packages/core/src/components/axes/toolbar.ts index 1821fea093..fbb0664b16 100644 --- a/packages/core/src/components/axes/toolbar.ts +++ b/packages/core/src/components/axes/toolbar.ts @@ -108,8 +108,6 @@ export class Toolbar extends Component { .attr("width", iconSize * 3) // value doesn't matter but can't be empty .attr("opacity", 0); } else { - const self = this; - // render toolbar buttons sequentially let buttonXPosition = 0; toolbarButtonList.forEach((button) => { @@ -150,7 +148,9 @@ export class Toolbar extends Component { // show/hide overflow menu showOverflowMenu(show: boolean) { // update overflow icon background - this.overflowIcon.classed("icon-overflowRect-hovered", show); + if (this.overflowIcon) { + this.overflowIcon.classed("icon-overflowRect-hovered", show); + } if (show) { this.services.events.dispatchEvent( Events.Toolbar.SHOW_OVERFLOW_MENU From 9732c8aefc7cc0dd1ce1790226475362b87682dc Mon Sep 17 00:00:00 2001 From: EricYang Date: Fri, 14 Aug 2020 15:01:05 +0800 Subject: [PATCH 508/510] feat: change zoom bar handle cursor from "pointer" to "ew-resize" --- packages/core/src/components/axes/zoom-bar.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/axes/zoom-bar.ts b/packages/core/src/components/axes/zoom-bar.ts index 21d8c8ebbb..8065bba15f 100644 --- a/packages/core/src/components/axes/zoom-bar.ts +++ b/packages/core/src/components/axes/zoom-bar.ts @@ -331,6 +331,7 @@ export class ZoomBar extends Component { const handleBarXDiff = -handleBarWidth / 2; const handleYBarDiff = (handleHeight - handleBarHeight) / 2; + const ewHandleCursor = "ew-resize"; const displayStyle = isDataLoading || isNaN(selection[0]) || isNaN(selection[1]) ? "none" @@ -358,7 +359,7 @@ export class ZoomBar extends Component { .attr("y", 0) .attr("width", handleWidth) .attr("height", handleHeight) - .attr("cursor", "pointer") + .attr("cursor", ewHandleCursor) .style("display", displayStyle); // handle-bar @@ -391,7 +392,7 @@ export class ZoomBar extends Component { .attr("y", handleYBarDiff) .attr("width", handleBarWidth) .attr("height", handleBarHeight) - .attr("cursor", "pointer") + .attr("cursor", ewHandleCursor) .style("display", displayStyle); // Update slider selected area From bc597f1e3b04b94d1f63d36ac0c74e5e9715cbaa Mon Sep 17 00:00:00 2001 From: EricYang Date: Sun, 16 Aug 2020 15:32:34 +0800 Subject: [PATCH 509/510] feat: provide option to refresh range axis label --- packages/core/src/configuration.ts | 3 +- packages/core/src/interfaces/components.ts | 5 ++++ .../core/src/services/scales-cartesian.ts | 11 +++++-- packages/core/src/services/zoom.ts | 29 +++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index ef4ee0e6e9..595a685b5a 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -168,7 +168,8 @@ const axisChart: AxisChartOptions = Tools.merge({}, chart, { top: { enabled: false, type: ZoomBarTypes.GRAPH_VIEW, - zoomRatio: 0.4 + zoomRatio: 0.4, + refreshRangeAxisLabel: false } as ZoomBarOptions } as ZoomBarsOptions, toolbar: { diff --git a/packages/core/src/interfaces/components.ts b/packages/core/src/interfaces/components.ts index aa2076f1ec..dbfa9537cf 100644 --- a/packages/core/src/interfaces/components.ts +++ b/packages/core/src/interfaces/components.ts @@ -153,6 +153,11 @@ export interface ZoomBarOptions { * options related to zoom bar data */ data?: Object[]; + + /** + * whether keep refreshing range axis label while zoom domain is changing + */ + refreshRangeAxisLabel?: boolean; } /** diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 4904006ba5..94bc55f71f 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -414,14 +414,21 @@ export class CartesianScales extends Service { // Get the extent of the domain let domain; let allDataValues; + const zoomService = this.services.zoom; // If the scale is stacked if (axisOptions.stacked) { - const dataValuesGroupedByKeys = this.model.getDataValuesGroupedByKeys(); + const dataValuesGroupedByKeys = zoomService.filterDataForRangeAxisLabel( + this.model.getDataValuesGroupedByKeys(), + true + ); + allDataValues = dataValuesGroupedByKeys.map((dataValues) => sum(values(dataValues) as any) ); } else { - allDataValues = displayData.map((datum) => datum[mapsTo]); + allDataValues = zoomService + .filterDataForRangeAxisLabel(displayData) + .map((datum) => datum[mapsTo]); } if (scaleType !== ScaleTypes.TIME && includeZero) { diff --git a/packages/core/src/services/zoom.ts b/packages/core/src/services/zoom.ts index 9da3cd6e6d..a4221f72c9 100644 --- a/packages/core/src/services/zoom.ts +++ b/packages/core/src/services/zoom.ts @@ -8,6 +8,35 @@ import { AxisPositions, Events, ScaleTypes } from "../interfaces"; import * as Configuration from "../configuration"; export class Zoom extends Service { + // filter out data not inside zoom domain + // to get better range value for axis label + filterDataForRangeAxisLabel(displayData: object[], stacked = false) { + const zoomDomain = this.model.get("zoomDomain"); + const isRefreshRangeAxisLabel = Tools.getProperty( + this.model.getOptions(), + "zoomBar", + "top", + "refreshRangeAxisLabel" + ); + if (this.isZoomBarEnabled() && isRefreshRangeAxisLabel && zoomDomain) { + const domainIdentifier = stacked + ? "sharedStackKey" + : this.services.cartesianScales.getDomainIdentifier(); + const filteredData = displayData.filter( + (datum) => + new Date(datum[domainIdentifier]) >= zoomDomain[0] && + new Date(datum[domainIdentifier]) <= zoomDomain[1] + ); + // if no data in zoom domain, use all data to get full range value + // so only return filteredData if length > 0 + if (filteredData.length > 0) { + return filteredData; + } + } + // return original data by default + return displayData; + } + // get display data for zoom bar // basically it's sum of value grouped by time getZoomBarData() { From 0bee757ce536ab328e509bcdb41966ecc1e66ec6 Mon Sep 17 00:00:00 2001 From: Jennifer Chao Date: Thu, 27 Aug 2020 17:31:06 +0800 Subject: [PATCH 510/510] fix: duplicated import --- packages/core/src/components/layout/spacer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/components/layout/spacer.ts b/packages/core/src/components/layout/spacer.ts index 761fe1d93d..d9ec13cf76 100644 --- a/packages/core/src/components/layout/spacer.ts +++ b/packages/core/src/components/layout/spacer.ts @@ -2,7 +2,6 @@ import { Component } from "../component"; import { DOMUtils } from "../../services"; import * as Configuration from "../../configuration"; -import { DOMUtils } from "../../services"; export class Spacer extends Component { type = "spacer";