From edf6077eb469df10725bfb632ed8153a40ae62b0 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Sep 2019 14:49:30 +0530 Subject: [PATCH] feat: kick start funnel Chart --- src/js/charts/FunnelChart.js | 91 ++++++++++++++++++++++++++++++++++++ src/js/utils/draw.js | 26 +++++++++++ 2 files changed, 117 insertions(+) create mode 100644 src/js/charts/FunnelChart.js diff --git a/src/js/charts/FunnelChart.js b/src/js/charts/FunnelChart.js new file mode 100644 index 0000000..509b258 --- /dev/null +++ b/src/js/charts/FunnelChart.js @@ -0,0 +1,91 @@ +import AggregationChart from './AggregationChart'; +import { getOffset } from '../utils/dom'; +import { getComponent } from '../objects/ChartComponents'; +import { PERCENTAGE_BAR_DEFAULT_HEIGHT, PERCENTAGE_BAR_DEFAULT_DEPTH } from '../utils/constants'; + +export default class FunnelChart extends AggregationChart { + constructor(parent, args) { + super(parent, args); + this.type = 'funnel'; + this.setup(); + } + + setMeasures(options) { + let m = this.measures; + this.funnelOptions = options.funnelOptions || {}; + + let opts = this.funnelOptions; + opts.height = opts.height || PERCENTAGE_BAR_DEFAULT_HEIGHT; + + m.paddings.right = 30; + m.legendHeight = 60; + m.baseHeight = (opts.height + opts.depth * 0.5) * 8; + } + + setupComponents() { + let s = this.state; + + let componentConfigs = [ + [ + 'percentageBars', + { + barHeight: this.funnelOptions.height, + barDepth: this.funnelOptions.depth, + }, + function() { + return { + xPositions: s.xPositions, + widths: s.widths, + colors: this.colors + }; + }.bind(this) + ] + ]; + + this.components = new Map(componentConfigs + .map(args => { + let component = getComponent(...args); + return [args[0], component]; + })); + } + + calc() { + super.calc(); + let s = this.state; + + s.xPositions = []; + s.widths = []; + + let xPos = 0; + s.sliceTotals.map((value) => { + let width = this.width * value / s.grandTotal; + s.widths.push(width); + s.xPositions.push(xPos); + xPos += width; + }); + } + + makeDataByIndex() { } + + bindTooltip() { + let s = this.state; + this.container.addEventListener('mousemove', (e) => { + let bars = this.components.get('percentageBars').store; + let bar = e.target; + if(bars.includes(bar)) { + + let i = bars.indexOf(bar); + let gOff = getOffset(this.container), pOff = getOffset(bar); + + let x = pOff.left - gOff.left + parseInt(bar.getAttribute('width'))/2; + let y = pOff.top - gOff.top; + let title = (this.formattedLabels && this.formattedLabels.length>0 + ? this.formattedLabels[i] : this.state.labels[i]) + ': '; + let fraction = s.sliceTotals[i]/s.grandTotal; + + this.tip.setValues(x, y, {name: title, value: (fraction*100).toFixed(1) + "%"}); + this.tip.showTip(); + } + }); + } +} diff --git a/src/js/utils/draw.js b/src/js/utils/draw.js index d7e26cd..173951e 100644 --- a/src/js/utils/draw.js +++ b/src/js/utils/draw.js @@ -190,6 +190,32 @@ export function percentageBar(x, y, width, height, return createSVG("rect", args); } +export function funnelSlice(className, startPositions, height, fill='none') { + const endPosition = [] + let [point_a, point_b] = startPositions[0] + + // For an equilateral triangle, the angles are always 60 deg. + // The end points on the polygons can be created using the following formula + // + // end_point_x = start_x + height + // end_point_y = start_y +/- height * 1/2 + // + // b + // _______________________________ + // \ |_| / + // \ | / + // \ | h / + // \ | / + // \|____________________/ + // + // b = h * cos(60 deg) + // + + endPosition[0] = [point_a[0] + height, point_a[1] + height * 0.5] + endPosition[1] = [point_b[0] + height, point_b[1] - height * 0.5] + +} + export function heatSquare(className, x, y, size, fill='none', data={}) { let args = { className: className,