From 50b44c00ce795d0ce6313287fd0f17227b65f90f Mon Sep 17 00:00:00 2001 From: Eliad Moosavi Date: Mon, 12 Nov 2018 01:01:55 -0500 Subject: [PATCH] fix(core): Refactor thresholds to support a range of values, closes #85 --- packages/core/demo/demo-data/line.ts | 10 +- packages/core/src/base-axis-chart.ts | 140 ++++++++++++--------------- 2 files changed, 69 insertions(+), 81 deletions(-) diff --git a/packages/core/demo/demo-data/line.ts b/packages/core/demo/demo-data/line.ts index fd0d9874d8..d3ad3cdec4 100644 --- a/packages/core/demo/demo-data/line.ts +++ b/packages/core/demo/demo-data/line.ts @@ -46,6 +46,8 @@ export const curvedLineOptions = { title: "2018 Annual Sales Figures", }, y: { + yMaxAdjuster: yMax => yMax * 1.2, + yMinAdjuster: yMin => yMin * 1.2, formatter: axisValue => `${axisValue / 1000}k` }, y2: { @@ -107,18 +109,20 @@ export const lineOptions = { title: "2018 Annual Sales Figures", }, y: { + yMaxAdjuster: yMax => yMax * 1.2, + yMinAdjuster: yMin => yMin * 1.2, formatter: axisValue => `${axisValue / 1000}k`, thresholds: [ { - value : 10000, + range: [0, 30000], theme: "success" }, { - value : 40000, + range: [30000, 40000], theme: "danger" }, { - value: 50000, + range: [40000, 70000], theme: "warning" } ] diff --git a/packages/core/src/base-axis-chart.ts b/packages/core/src/base-axis-chart.ts index 95d347fbf1..38f8904290 100644 --- a/packages/core/src/base-axis-chart.ts +++ b/packages/core/src/base-axis-chart.ts @@ -408,18 +408,6 @@ export class BaseAxisChart extends BaseChart { .tickSizeInner(-this.getChartSize().width) .tickSizeOuter(0); - if (thresholds && thresholds.length > 0) { - const thresholdTickValues = thresholds.map(e => { - if (e.value) { return e.value; } - console.error("Missing threshold value: ", e); - }); - - // TickValues seem to ignore the first element of the array - // This workaround is a temporary solution - thresholdTickValues.unshift(0); - yGrid.tickValues(thresholdTickValues); - } - yGrid.ticks(scales.y.numberOfTicks || Configuration.scales.y.numberOfTicks); const g = this.innerWrap.select(".y.grid") @@ -429,67 +417,74 @@ export class BaseAxisChart extends BaseChart { this.cleanGrid(g); if (thresholds && thresholds.length > 0) { - this.addOrUpdateThresholds(g); + this.addOrUpdateThresholds(g, false); } } addOrUpdateThresholds(yGrid, animate?) { - const { thresholds } = this.options.scales.y; - const thresholdDimensions = []; + const t = animate === false ? this.getInstantTransition() : this.getDefaultTransition(); + const width = this.getChartSize().width; + const { thresholds } = this.options.scales.y; - let prevBase = this.y(0); - let gThresholdContainer = (yGrid.select("g.threshold-container").nodes().length > 0) - ? yGrid.select("g.threshold-container") - : yGrid.append("g").attr("class", "threshold-container"); - - // iterate ticks to find y offset, and height - yGrid.selectAll(".tick") - .each(function(d, i) { - const y = Tools.getTranformOffsets(select(this).attr("transform")).y; - // draw rectangle between previous tick and the current tick - const height = Math.abs(prevBase - y); - const theme = thresholds[i].theme; - - thresholdDimensions.push({ - height: height, - width: width, - y: y, - theme: theme - }); - // rectangles are drawn stacked ontop of each other, so y of the current rect - // is used as the base of the next one - prevBase = y; - }); + // Check if the thresholds container exists + const thresholdContainerExists = this.innerWrap.select("g.thresholds").nodes().length > 0; + const thresholdRects = thresholdContainerExists + ? this.innerWrap.selectAll("g.thresholds rect") + : this.innerWrap.append("g").classed("thresholds", true).selectAll("rect").data(thresholds); + + const calculateHeight = d => { + const height = Math.abs(this.y(d.range[1]) - this.y(d.range[0])); + + // If the threshold is getting cropped because it is extending beyond + // the top of the chart, update its height to reflect the crop + if (this.y(d.range[1]) < 0) { + return Math.max(0, height + this.y(d.range[1])); + } - // bind rect to rectangle dimensions - gThresholdContainer = gThresholdContainer - .selectAll("rect") - .data(thresholdDimensions); - - // update - gThresholdContainer - .transition(animate === false ? this.getInstantTransition() : this.getDefaultTransition()) - .attr("height", d => d.height) - .attr("width", d => d.width) - .attr("y", d => d.y ) - .style("fill", d => Configuration.scales.y.thresholds.colors[d.theme]); - - // enter - gThresholdContainer - .enter() - .append("rect") - .attr("height", d => d.height) - .attr("width", d => d.width) - .attr("y", d => d.y ) - .style("fill", d => Configuration.scales.y.thresholds.colors[d.theme]); - - // exit - gThresholdContainer - .exit() - .transition(this.getDefaultTransition()) - .style("opacity", 0) - .remove(); + return Math.max(0, height); + }; + + const calculateOpacity = d => { + const height = Math.abs(this.y(d.range[1]) - this.y(d.range[0])); + + // If the threshold is to be shown anywhere outside of the top edge of the chart + // Hide it + if (this.y(d.range[1]) + height <= 0) { + return 0; + } + + return 1; + }; + + // Applies to thresholds being added + thresholdRects.enter() + .append("rect") + .classed("bar", true) + .attr("x", 0) + .attr("y", d => Math.max(0, this.y(d.range[1]))) + .attr("width", width) + .attr("height", d => calculateHeight(d)) + .attr("fill", d => Configuration.scales.y.thresholds.colors[d.theme]) + .attr("opacity", 0) + .transition(t) + .attr("opacity", d => calculateOpacity(d)); + + // Update thresholds + thresholdRects + .transition(t) + .attr("x", 0) + .attr("y", d => Math.max(0, this.y(d.range[1]))) + .attr("width", width) + .attr("height", d => calculateHeight(d)) + .attr("opacity", d => calculateOpacity(d)) + .attr("fill", d => Configuration.scales.y.thresholds.colors[d.theme]); + + // Applies to thresholds getting removed + thresholdRects.exit() + .transition(t) + .style("opacity", 0) + .remove(); } updateXandYGrid(noAnimation?: boolean) { @@ -520,18 +515,8 @@ export class BaseAxisChart extends BaseChart { .tickSizeOuter(0) .tickFormat("" as any); - if (thresholds && thresholds.length > 0) { - const thresholdTickValues = thresholds.map(e => { - if (e.value) { return e.value; } - console.error("Missing threshold value: ", e); - }); - // for some reason tickValues ignore the first element of the array - // passed to it so this workaround - thresholdTickValues.unshift(0); - yGrid.tickValues(thresholdTickValues); - } - // .ticks(10); const g_yGrid = this.innerWrap.select(".y.grid") + .transition(t) .attr("transform", `translate(0, 0)`) .call(yGrid); @@ -550,7 +535,6 @@ export class BaseAxisChart extends BaseChart { .attr("stroke", Configuration.grid.strokeColor); g.selectAll("text").style("display", "none").remove(); g.select(".domain").style("stroke", "none"); - g.select(".tick").remove(); } // TODO - Refactor