Compare commits

...

9 Commits

Author SHA1 Message Date
Shivam Mishra
999d5acc74 style: linting fixes 2019-10-30 15:55:23 +05:30
Shivam Mishra
ce67836445 chore: revert frappe-charts.esm.js 2019-10-30 15:41:03 +05:30
Shivam Mishra
2c35a2f35b
refactor: remove window assignment 2019-10-30 15:37:23 +05:30
Shivam Mishra
f32cf4bde7 feat: centered chart 2019-10-30 15:09:25 +05:30
Shivam Mishra
b099ffe1c9 feat: fixed funnel chart tooltip 2019-09-28 19:34:37 +05:30
Shivam Mishra
fff25ecf4f feat: generated funnel chart 2019-09-27 19:42:36 +05:30
Shivam Mishra
9053b01462 refactor: move endPoint creation to draw-utils 2019-09-27 15:00:43 +05:30
Shivam Mishra
2451e58df9 chore: enabled funnel option 2019-09-27 15:00:26 +05:30
Shivam Mishra
edf6077eb4 feat: kick start funnel Chart 2019-09-27 14:49:30 +05:30
7 changed files with 147 additions and 8 deletions

View File

@ -83,6 +83,7 @@ redirect_to: "https://frappe.io/charts"
<button type="button" class="btn btn-sm btn-secondary active" data-type='axis-mixed'>Mixed</button> <button type="button" class="btn btn-sm btn-secondary active" data-type='axis-mixed'>Mixed</button>
<button type="button" class="btn btn-sm btn-secondary" data-type='pie'>Pie Chart</button> <button type="button" class="btn btn-sm btn-secondary" data-type='pie'>Pie Chart</button>
<button type="button" class="btn btn-sm btn-secondary" data-type='donut'>Donut Chart</button> <button type="button" class="btn btn-sm btn-secondary" data-type='donut'>Donut Chart</button>
<button type="button" class="btn btn-sm btn-secondary" data-type='funnel'>Funnel Chart</button>
<button type="button" class="btn btn-sm btn-secondary" data-type='percentage'>Percentage Chart</button> <button type="button" class="btn btn-sm btn-secondary" data-type='percentage'>Percentage Chart</button>
</div> </div>
<div class="btn-group export-buttons margin-top mx-auto" role="group"> <div class="btn-group export-buttons margin-top mx-auto" role="group">

View File

@ -6,6 +6,7 @@ import PieChart from './charts/PieChart';
import Heatmap from './charts/Heatmap'; import Heatmap from './charts/Heatmap';
import AxisChart from './charts/AxisChart'; import AxisChart from './charts/AxisChart';
import DonutChart from './charts/DonutChart'; import DonutChart from './charts/DonutChart';
import FunnelChart from './charts/FunnelChart';
const chartTypes = { const chartTypes = {
bar: AxisChart, bar: AxisChart,
@ -15,6 +16,7 @@ const chartTypes = {
heatmap: Heatmap, heatmap: Heatmap,
pie: PieChart, pie: PieChart,
donut: DonutChart, donut: DonutChart,
funnel: FunnelChart,
}; };
function getChartByType(chartType = 'line', parent, options) { function getChartByType(chartType = 'line', parent, options) {

View File

@ -0,0 +1,85 @@
import AggregationChart from './AggregationChart';
import { getOffset } from '../utils/dom';
import { getComponent } from '../objects/ChartComponents';
import { getEndpointsForTrapezoid } from '../utils/draw-utils';
export default class FunnelChart extends AggregationChart {
constructor(parent, args) {
super(parent, args);
this.type = 'funnel';
this.setup();
}
calc() {
super.calc();
let s = this.state;
// calculate width and height options
const totalheight = this.height * 0.9;
const baseWidth = (2 * totalheight) / Math.sqrt(3);
const reducer = (accumulator, currentValue) => accumulator + currentValue;
const weightage = s.sliceTotals.reduce(reducer, 0.0);
const center_x_offset = this.center.x - baseWidth / 2;
const center_y_offset = this.center.y - totalheight / 2;
let slicePoints = [];
let startPoint = [[center_x_offset, center_y_offset], [center_x_offset + baseWidth, center_y_offset]];
s.sliceTotals.forEach(d => {
let height = totalheight * d / weightage;
let endPoint = getEndpointsForTrapezoid(startPoint, height);
slicePoints.push([startPoint, endPoint]);
startPoint = endPoint;
});
s.slicePoints = slicePoints;
}
setupComponents() {
let s = this.state;
let componentConfigs = [
[
'funnelSlices',
{ },
function() {
return {
slicePoints: s.slicePoints,
colors: this.colors
};
}.bind(this)
]
];
this.components = new Map(componentConfigs
.map(args => {
let component = getComponent(...args);
return [args[0], component];
}));
}
bindTooltip() {
function getPolygonWidth(slice) {
const points = slice.points;
return points[1].x - points[0].x;
}
this.container.addEventListener('mousemove', (e) => {
let slices = this.components.get('funnelSlices').store;
let slice = e.target;
if(slices.includes(slice)) {
let i = slices.indexOf(slice);
let gOff = getOffset(this.container), pOff = getOffset(slice);
let x = pOff.left - gOff.left + getPolygonWidth(slice)/2;
let y = pOff.top - gOff.top;
let title = (this.formatted_labels && this.formatted_labels.length > 0
? this.formatted_labels[i] : this.state.labels[i]) + ': ';
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1);
this.tip.setValues(x, y, {name: title, value: percent + "%"});
this.tip.showTip();
}
});
}
}

View File

@ -1,5 +1,5 @@
import { makeSVGGroup } from '../utils/draw'; import { makeSVGGroup } from '../utils/draw';
import { makeText, makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, percentageBar, getPaths, heatSquare } from '../utils/draw'; import { makeText, makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, percentageBar, getPaths, heatSquare, funnelSlice } from '../utils/draw';
import { equilizeNoOfElements } from '../utils/draw-utils'; import { equilizeNoOfElements } from '../utils/draw-utils';
import { translateHoriLine, translateVertLine, animateRegion, animateBar, import { translateHoriLine, translateVertLine, animateRegion, animateBar,
animateDot, animatePath, animatePathStr } from '../utils/animate'; animateDot, animatePath, animatePathStr } from '../utils/animate';
@ -114,6 +114,18 @@ let componentConfigs = {
if(newData) return []; if(newData) return [];
} }
}, },
funnelSlices: {
layerClass: 'funnel-slices',
makeElements(data) {
return data.slicePoints.map((p, i) => {
return funnelSlice('funnel-slice', p[0], p[1], data.colors[i]);
});
},
animateElements(newData) {
if(newData) return [];
}
},
yAxis: { yAxis: {
layerClass: 'y axis', layerClass: 'y axis',
makeElements(data) { makeElements(data) {

View File

@ -99,7 +99,8 @@ export const DEFAULT_COLORS = {
pie: DEFAULT_CHART_COLORS, pie: DEFAULT_CHART_COLORS,
percentage: DEFAULT_CHART_COLORS, percentage: DEFAULT_CHART_COLORS,
heatmap: HEATMAP_COLORS_GREEN, heatmap: HEATMAP_COLORS_GREEN,
donut: DEFAULT_CHART_COLORS donut: DEFAULT_CHART_COLORS,
funnel: DEFAULT_CHART_COLORS,
}; };
// Universal constants // Universal constants

View File

@ -25,6 +25,34 @@ export function equilizeNoOfElements(array1, array2,
return [array1, array2]; return [array1, array2];
} }
export function getEndpointsForTrapezoid(startPositions, height) {
const endPosition = [];
let [point_a, point_b] = startPositions;
// 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 * 1/√3
// end_point_y = start_y + height
//
// b
// _______________________________
// \ |_| /
// \ | /
// \ | h /
// \ | /
// \|____________________/
//
// b = h * tan(30 deg)
//
let multiplicationFactor = 1.0/Math.sqrt(3);
endPosition[0] = [point_a[0] + height * multiplicationFactor, point_a[1] + height];
endPosition[1] = [point_b[0] - height * multiplicationFactor, point_b[1] + height];
return endPosition;
}
export function truncateString(txt, len) { export function truncateString(txt, len) {
if (!txt) { if (!txt) {
return; return;

View File

@ -190,6 +190,16 @@ export function percentageBar(x, y, width, height,
return createSVG("rect", args); return createSVG("rect", args);
} }
export function funnelSlice(className, start, end, fill='none') {
const points = `${start[0].join()} ${start[1].join()} ${end[1].join()} ${end[0].join()}`;
let args = {
className: 'funnel-slice',
points: points,
fill: fill
};
return createSVG("polygon", args);
}
export function heatSquare(className, x, y, size, fill='none', data={}) { export function heatSquare(className, x, y, size, fill='none', data={}) {
let args = { let args = {
className: className, className: className,