[animate] lines, cleanup
This commit is contained in:
parent
dc1fa4d373
commit
02a1c4c5cf
459
dist/frappe-charts.esm.js
vendored
459
dist/frappe-charts.esm.js
vendored
@ -279,7 +279,7 @@ function equilizeNoOfElements(array1, array2,
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
const UNIT_ANIM_DUR = 350;
|
const UNIT_ANIM_DUR = 350;
|
||||||
|
const PATH_ANIM_DUR = 350;
|
||||||
const MARKER_LINE_ANIM_DUR = UNIT_ANIM_DUR;
|
const MARKER_LINE_ANIM_DUR = UNIT_ANIM_DUR;
|
||||||
const REPLACE_ALL_NEW_DUR = 250;
|
const REPLACE_ALL_NEW_DUR = 250;
|
||||||
|
|
||||||
@ -331,8 +331,8 @@ function animateBar(bar, x, yTop, width, index=0, meta={}) {
|
|||||||
STD_EASING
|
STD_EASING
|
||||||
];
|
];
|
||||||
|
|
||||||
let old = bar.getAttribute("transform").split("(")[1].slice(0, -1);
|
let oldCoordStr = bar.getAttribute("transform").split("(")[1].slice(0, -1);
|
||||||
let groupAnim = translate(bar, old, [x, y], MARKER_LINE_ANIM_DUR);
|
let groupAnim = translate(bar, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR);
|
||||||
return [rectAnim, groupAnim];
|
return [rectAnim, groupAnim];
|
||||||
} else {
|
} else {
|
||||||
return [[bar, {width: width, height: height, x: x, y: y}, UNIT_ANIM_DUR, STD_EASING]];
|
return [[bar, {width: width, height: height, x: x, y: y}, UNIT_ANIM_DUR, STD_EASING]];
|
||||||
@ -340,21 +340,41 @@ function animateBar(bar, x, yTop, width, index=0, meta={}) {
|
|||||||
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
|
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
function animateDot(dot, x, y) {
|
||||||
|
if(dot.nodeName !== 'circle') {
|
||||||
|
let oldCoordStr = dot.getAttribute("transform").split("(")[1].slice(0, -1);
|
||||||
|
let groupAnim = translate(dot, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR);
|
||||||
|
return [groupAnim];
|
||||||
|
} else {
|
||||||
|
return [[dot, {cx: x, cy: y}, UNIT_ANIM_DUR, STD_EASING]];
|
||||||
|
}
|
||||||
|
// dot.animate({cy: yTop}, UNIT_ANIM_DUR, mina.easein);
|
||||||
|
}
|
||||||
|
|
||||||
<filter id="glow" x="-10%" y="-10%" width="120%" height="120%">
|
function animatePath(paths, newXList, newYList, zeroLine) {
|
||||||
<feGaussianBlur stdDeviation="0.5 0.5" result="glow"></feGaussianBlur>
|
let pathComponents = [];
|
||||||
<feMerge>
|
|
||||||
<feMergeNode in="glow"></feMergeNode>
|
|
||||||
<feMergeNode in="glow"></feMergeNode>
|
|
||||||
<feMergeNode in="glow"></feMergeNode>
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
|
|
||||||
filter: url(#glow);
|
let pointsStr = newYList.map((y, i) => (newXList[i] + ',' + y));
|
||||||
fill: #fff;
|
let pathStr = pointsStr.join("L");
|
||||||
|
|
||||||
*/
|
const animPath = [paths.path, {d:"M"+pathStr}, PATH_ANIM_DUR, STD_EASING];
|
||||||
|
pathComponents.push(animPath);
|
||||||
|
|
||||||
|
if(paths.region) {
|
||||||
|
let regStartPt = `${newXList[0]},${zeroLine}L`;
|
||||||
|
let regEndPt = `L${newXList.slice(-1)[0]}, ${zeroLine}`;
|
||||||
|
|
||||||
|
const animRegion = [
|
||||||
|
paths.region,
|
||||||
|
{d:"M" + regStartPt + pathStr + regEndPt},
|
||||||
|
PATH_ANIM_DUR,
|
||||||
|
STD_EASING
|
||||||
|
];
|
||||||
|
pathComponents.push(animRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathComponents;
|
||||||
|
}
|
||||||
|
|
||||||
const AXIS_TICK_LENGTH = 6;
|
const AXIS_TICK_LENGTH = 6;
|
||||||
const LABEL_MARGIN = 4;
|
const LABEL_MARGIN = 4;
|
||||||
@ -397,6 +417,26 @@ function createSVG(tag, o) {
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderVerticalGradient(svgDefElem, gradientId) {
|
||||||
|
return createSVG('linearGradient', {
|
||||||
|
inside: svgDefElem,
|
||||||
|
id: gradientId,
|
||||||
|
x1: 0,
|
||||||
|
x2: 0,
|
||||||
|
y1: 0,
|
||||||
|
y2: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGradientStop(gradElem, offset, color, opacity) {
|
||||||
|
return createSVG('stop', {
|
||||||
|
'inside': gradElem,
|
||||||
|
'style': `stop-color: ${color}`,
|
||||||
|
'offset': offset,
|
||||||
|
'stop-opacity': opacity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function makeSVGContainer(parent, className, width, height) {
|
function makeSVGContainer(parent, className, width, height) {
|
||||||
return createSVG('svg', {
|
return createSVG('svg', {
|
||||||
className: className,
|
className: className,
|
||||||
@ -433,7 +473,20 @@ function makePath(pathStr, className='', stroke='none', fill='none') {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeGradient(svgDefElem, color, lighter = false) {
|
||||||
|
let gradientId ='path-fill-gradient' + '-' + color + '-' +(lighter ? 'lighter' : 'default');
|
||||||
|
let gradientDef = renderVerticalGradient(svgDefElem, gradientId);
|
||||||
|
let opacities = [1, 0.6, 0.2];
|
||||||
|
if(lighter) {
|
||||||
|
opacities = [0.4, 0.2, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
setGradientStop(gradientDef, "0%", color, opacities[0]);
|
||||||
|
setGradientStop(gradientDef, "50%", color, opacities[1]);
|
||||||
|
setGradientStop(gradientDef, "100%", color, opacities[2]);
|
||||||
|
|
||||||
|
return gradientId;
|
||||||
|
}
|
||||||
|
|
||||||
function makeHeatSquare(className, x, y, size, fill='none', data={}) {
|
function makeHeatSquare(className, x, y, size, fill='none', data={}) {
|
||||||
let args = {
|
let args = {
|
||||||
@ -695,6 +748,68 @@ function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={})
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function datasetDot(x, y, radius, color, label='', index=0, meta={}) {
|
||||||
|
let dot = createSVG('circle', {
|
||||||
|
style: `fill: ${color}`,
|
||||||
|
'data-point-index': index,
|
||||||
|
cx: x,
|
||||||
|
cy: y,
|
||||||
|
r: radius
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!label && !label.length) {
|
||||||
|
return dot;
|
||||||
|
} else {
|
||||||
|
dot.setAttribute('cy', 0);
|
||||||
|
dot.setAttribute('cx', 0);
|
||||||
|
|
||||||
|
let text = createSVG('text', {
|
||||||
|
className: 'data-point-value',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
dy: (FONT_SIZE / 2 * -1 - radius) + 'px',
|
||||||
|
'font-size': FONT_SIZE + 'px',
|
||||||
|
'text-anchor': 'middle',
|
||||||
|
innerHTML: label
|
||||||
|
});
|
||||||
|
|
||||||
|
let group = createSVG('g', {
|
||||||
|
transform: `translate(${x}, ${y})`
|
||||||
|
});
|
||||||
|
group.appendChild(dot);
|
||||||
|
group.appendChild(text);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPaths(xList, yList, color, options={}, meta={}) {
|
||||||
|
let pointsList = yList.map((y, i) => (xList[i] + ',' + y));
|
||||||
|
let pointsStr = pointsList.join("L");
|
||||||
|
let path = makePath("M"+pointsStr, 'line-graph-path', color);
|
||||||
|
|
||||||
|
// HeatLine
|
||||||
|
if(options.heatline) {
|
||||||
|
let gradient_id = makeGradient(meta.svgDefs, color);
|
||||||
|
path.style.stroke = `url(#${gradient_id})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let paths = {
|
||||||
|
path: path
|
||||||
|
};
|
||||||
|
|
||||||
|
// Region
|
||||||
|
if(options.regionFill) {
|
||||||
|
let gradient_id_region = makeGradient(meta.svgDefs, color, true);
|
||||||
|
|
||||||
|
// TODO: use zeroLine OR minimum
|
||||||
|
let pathStr = "M" + `${xList[0]},${meta.zeroLine}L` + pointsStr + `L${xList.slice(-1)[0]},${meta.zeroLine}`;
|
||||||
|
paths.region = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id_region})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
const PRESET_COLOR_MAP = {
|
const PRESET_COLOR_MAP = {
|
||||||
'light-blue': '#7cd6fd',
|
'light-blue': '#7cd6fd',
|
||||||
'blue': '#5e64ff',
|
'blue': '#5e64ff',
|
||||||
@ -1047,17 +1162,16 @@ class BaseChart {
|
|||||||
|
|
||||||
draw(init=false) {
|
draw(init=false) {
|
||||||
this.calcWidth();
|
this.calcWidth();
|
||||||
this.makeChartArea();
|
|
||||||
|
|
||||||
this.calc();
|
this.calc();
|
||||||
this.initComponents(); // Only depend on the drawArea made in makeChartArea
|
this.makeChartArea();
|
||||||
|
this.initComponents();
|
||||||
|
|
||||||
this.setupComponents();
|
this.setupComponents();
|
||||||
|
|
||||||
this.components.forEach(c => c.setup(this.drawArea)); // or c.build()
|
this.components.forEach(c => c.setup(this.drawArea)); // or c.build()
|
||||||
this.components.forEach(c => c.make()); // or c.build()
|
this.components.forEach(c => c.make()); // or c.build()
|
||||||
this.renderLegend();
|
|
||||||
|
|
||||||
|
this.renderLegend();
|
||||||
this.setupNavigation(init);
|
this.setupNavigation(init);
|
||||||
|
|
||||||
// TODO: remove timeout and decrease post animate time in chart component
|
// TODO: remove timeout and decrease post animate time in chart component
|
||||||
@ -1199,10 +1313,13 @@ class BaseChart {
|
|||||||
|
|
||||||
const Y_AXIS_MARGIN = 60;
|
const Y_AXIS_MARGIN = 60;
|
||||||
|
|
||||||
const MIN_BAR_PERCENT_HEIGHT = 0.01;
|
|
||||||
const DEFAULT_AXIS_CHART_TYPE = 'line';
|
const DEFAULT_AXIS_CHART_TYPE = 'line';
|
||||||
const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
|
const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
|
||||||
|
|
||||||
const BAR_CHART_SPACE_RATIO = 0.5;
|
const BAR_CHART_SPACE_RATIO = 0.5;
|
||||||
|
const MIN_BAR_PERCENT_HEIGHT = 0.01;
|
||||||
|
|
||||||
|
const LINE_CHART_DOT_SIZE = 4;
|
||||||
|
|
||||||
function dataPrep(data, type) {
|
function dataPrep(data, type) {
|
||||||
data.labels = data.labels || [];
|
data.labels = data.labels || [];
|
||||||
@ -1520,7 +1637,6 @@ let componentConfigs = {
|
|||||||
let newValues = newData.values;
|
let newValues = newData.values;
|
||||||
let newCYs = newData.cumulativeYs;
|
let newCYs = newData.cumulativeYs;
|
||||||
|
|
||||||
|
|
||||||
let oldXPos = this.oldData.xPositions;
|
let oldXPos = this.oldData.xPositions;
|
||||||
let oldYPos = this.oldData.yPositions;
|
let oldYPos = this.oldData.yPositions;
|
||||||
let oldCYPos = this.oldData.cumulativeYPos;
|
let oldCYPos = this.oldData.cumulativeYPos;
|
||||||
@ -1560,7 +1676,77 @@ let componentConfigs = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
lineGraph: {
|
lineGraph: {
|
||||||
|
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; },
|
||||||
|
makeElements(data) {
|
||||||
|
let c = this.constants;
|
||||||
|
this.paths = getPaths(
|
||||||
|
data.xPositions,
|
||||||
|
data.yPositions,
|
||||||
|
c.color,
|
||||||
|
{
|
||||||
|
heatline: c.heatline,
|
||||||
|
regionFill: c.regionFill
|
||||||
|
},
|
||||||
|
{
|
||||||
|
svgDefs: c.svgDefs,
|
||||||
|
zeroLine: data.zeroLine
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.dots = [];
|
||||||
|
|
||||||
|
if(!c.hideDots) {
|
||||||
|
this.dots = data.yPositions.map((y, j) => {
|
||||||
|
return datasetDot(
|
||||||
|
data.xPositions[j],
|
||||||
|
y,
|
||||||
|
data.radius,
|
||||||
|
c.color,
|
||||||
|
(c.valuesOverPoints ? data.values[j] : ''),
|
||||||
|
j
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.values(this.paths).concat(this.dots);
|
||||||
|
},
|
||||||
|
animateElements(newData) {
|
||||||
|
let newXPos = newData.xPositions;
|
||||||
|
let newYPos = newData.yPositions;
|
||||||
|
let newValues = newData.values;
|
||||||
|
|
||||||
|
|
||||||
|
let oldXPos = this.oldData.xPositions;
|
||||||
|
let oldYPos = this.oldData.yPositions;
|
||||||
|
let oldValues = this.oldData.values;
|
||||||
|
|
||||||
|
[oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
|
||||||
|
[oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
|
||||||
|
[oldValues, newValues] = equilizeNoOfElements(oldValues, newValues);
|
||||||
|
|
||||||
|
this.render({
|
||||||
|
xPositions: oldXPos,
|
||||||
|
yPositions: oldYPos,
|
||||||
|
values: newValues,
|
||||||
|
|
||||||
|
zeroLine: this.oldData.zeroLine,
|
||||||
|
radius: this.oldData.radius,
|
||||||
|
});
|
||||||
|
|
||||||
|
let animateElements = [];
|
||||||
|
|
||||||
|
animateElements = animateElements.concat(animatePath(
|
||||||
|
this.paths, newXPos, newYPos, newData.zeroLine));
|
||||||
|
|
||||||
|
if(this.dots.length) {
|
||||||
|
this.dots.map((dot, i) => {
|
||||||
|
animateElements = animateElements.concat(animateDot(
|
||||||
|
dot, newXPos[i], newYPos[i]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return animateElements;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1840,6 +2026,10 @@ class AxisChart extends BaseChart {
|
|||||||
// Default, as per bar, and mixed. Only line will be a special case
|
// Default, as per bar, and mixed. Only line will be a special case
|
||||||
s.xOffset = s.unitWidth/2;
|
s.xOffset = s.unitWidth/2;
|
||||||
|
|
||||||
|
// // For a pure Line Chart
|
||||||
|
// s.unitWidth = this.width/(s.datasetLength - 1);
|
||||||
|
// s.xOffset = 0;
|
||||||
|
|
||||||
s.xAxis = {
|
s.xAxis = {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
positions: labels.map((d, i) =>
|
positions: labels.map((d, i) =>
|
||||||
@ -1979,8 +2169,6 @@ class AxisChart extends BaseChart {
|
|||||||
|
|
||||||
// console.log('barDatasets', barDatasets, this.state.datasets);
|
// console.log('barDatasets', barDatasets, this.state.datasets);
|
||||||
|
|
||||||
// Bars
|
|
||||||
|
|
||||||
let barsConfigs = barDatasets.map(d => {
|
let barsConfigs = barDatasets.map(d => {
|
||||||
let index = d.index;
|
let index = d.index;
|
||||||
return [
|
return [
|
||||||
@ -2019,6 +2207,38 @@ class AxisChart extends BaseChart {
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let lineConfigs = lineDatasets.map(d => {
|
||||||
|
let index = d.index;
|
||||||
|
return [
|
||||||
|
'lineGraph' + '-' + d.index,
|
||||||
|
{
|
||||||
|
index: index,
|
||||||
|
color: this.colors[index],
|
||||||
|
svgDefs: this.svgDefs,
|
||||||
|
heatline: this.lineOptions.heatline,
|
||||||
|
regionFill: this.lineOptions.regionFill,
|
||||||
|
hideDots: this.lineOptions.hideDots,
|
||||||
|
|
||||||
|
// same for all datasets
|
||||||
|
valuesOverPoints: this.valuesOverPoints,
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
let s = this.state;
|
||||||
|
let d = s.datasets[index];
|
||||||
|
|
||||||
|
return {
|
||||||
|
xPositions: s.xAxis.positions,
|
||||||
|
yPositions: d.yPositions,
|
||||||
|
|
||||||
|
values: d.values,
|
||||||
|
|
||||||
|
zeroLine: s.yAxis.zeroLine,
|
||||||
|
radius: this.lineOptions.dotSize || LINE_CHART_DOT_SIZE,
|
||||||
|
};
|
||||||
|
}.bind(this)
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
let markerConfigs = [
|
let markerConfigs = [
|
||||||
[
|
[
|
||||||
'yMarkers',
|
'yMarkers',
|
||||||
@ -2037,7 +2257,7 @@ class AxisChart extends BaseChart {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.componentConfigs = this.componentConfigs.concat(barsConfigs, markerConfigs);
|
this.componentConfigs = this.componentConfigs.concat(barsConfigs, lineConfigs, markerConfigs);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupComponents() {
|
setupComponents() {
|
||||||
@ -2049,130 +2269,6 @@ class AxisChart extends BaseChart {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
getChartComponents() {
|
|
||||||
let dataUnitsComponents = [];
|
|
||||||
// this.state is not defined at this stage
|
|
||||||
this.data.datasets.forEach((d, index) => {
|
|
||||||
if(d.chartType === 'line') {
|
|
||||||
dataUnitsComponents.push(this.getPathComponent(d, index));
|
|
||||||
}
|
|
||||||
|
|
||||||
let renderer = this.unitRenderers[d.chartType];
|
|
||||||
dataUnitsComponents.push(this.getDataUnitComponent(
|
|
||||||
index, renderer
|
|
||||||
));
|
|
||||||
});
|
|
||||||
return dataUnitsComponents;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDataUnitComponent(index, unitRenderer) {
|
|
||||||
return new ChartComponent({
|
|
||||||
layerClass: 'dataset-units dataset-' + index,
|
|
||||||
makeElements: () => {
|
|
||||||
// yPositions, xPostions, color, valuesOverPoints,
|
|
||||||
let d = this.data.datasets[index];
|
|
||||||
|
|
||||||
return d.positions.map((y, j) => {
|
|
||||||
return unitRenderer.draw(
|
|
||||||
this.state.xAxis.positions[j],
|
|
||||||
y,
|
|
||||||
this.colors[index],
|
|
||||||
(this.valuesOverPoints ? (this.barOptions &&
|
|
||||||
this.barOptions.stacked ? d.cumulativeYs[j] : d.values[j]) : ''),
|
|
||||||
j,
|
|
||||||
y - (d.cumulativePositions ? d.cumulativePositions[j] : y)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
postMake: function() {
|
|
||||||
let translate_layer = () => {
|
|
||||||
this.layer.setAttribute('transform', `translate(${unitRenderer.consts.width * index}, 0)`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// let d = this.data.datasets[index];
|
|
||||||
|
|
||||||
if(this.meta.type === 'bar' && (!this.meta.barOptions
|
|
||||||
|| !this.meta.barOptions.stacked)) {
|
|
||||||
|
|
||||||
translate_layer();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
animate: (svgUnits) => {
|
|
||||||
// have been updated in axis render;
|
|
||||||
let newX = this.state.xAxis.positions;
|
|
||||||
let newY = this.data.datasets[index].positions;
|
|
||||||
|
|
||||||
let lastUnit = svgUnits[svgUnits.length - 1];
|
|
||||||
let parentNode = lastUnit.parentNode;
|
|
||||||
|
|
||||||
if(this.oldState.xExtra > 0) {
|
|
||||||
for(var i = 0; i<this.oldState.xExtra; i++) {
|
|
||||||
let unit = lastUnit.cloneNode(true);
|
|
||||||
parentNode.appendChild(unit);
|
|
||||||
svgUnits.push(unit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svgUnits.map((unit, i) => {
|
|
||||||
if(newX[i] === undefined || newY[i] === undefined) return;
|
|
||||||
this.elementsToAnimate.push(unitRenderer.animate(
|
|
||||||
unit, // unit, with info to replace where it came from in the data
|
|
||||||
newX[i],
|
|
||||||
newY[i],
|
|
||||||
index,
|
|
||||||
this.data.datasets.length
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getPathComponent(d, index) {
|
|
||||||
return new ChartComponent({
|
|
||||||
layerClass: 'path dataset-path',
|
|
||||||
setData: () => {},
|
|
||||||
makeElements: () => {
|
|
||||||
let d = this.data.datasets[index];
|
|
||||||
let color = this.colors[index];
|
|
||||||
|
|
||||||
return getPaths(
|
|
||||||
d.positions,
|
|
||||||
this.state.xAxis.positions,
|
|
||||||
color,
|
|
||||||
this.config.heatline,
|
|
||||||
this.config.regionFill
|
|
||||||
);
|
|
||||||
},
|
|
||||||
animate: (paths) => {
|
|
||||||
let newX = this.state.xAxis.positions;
|
|
||||||
let newY = this.data.datasets[index].positions;
|
|
||||||
|
|
||||||
let oldX = this.oldState.xAxis.positions;
|
|
||||||
let oldY = this.oldState.datasets[index].positions;
|
|
||||||
|
|
||||||
|
|
||||||
let parentNode = paths[0].parentNode;
|
|
||||||
|
|
||||||
[oldX, newX] = equilizeNoOfElements(oldX, newX);
|
|
||||||
[oldY, newY] = equilizeNoOfElements(oldY, newY);
|
|
||||||
|
|
||||||
if(this.oldState.xExtra > 0) {
|
|
||||||
paths = getPaths(
|
|
||||||
oldY, oldX, this.colors[index],
|
|
||||||
this.config.heatline,
|
|
||||||
this.config.regionFill
|
|
||||||
);
|
|
||||||
parentNode.textContent = '';
|
|
||||||
paths.map(path => parentNode.appendChild(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
const newPointsList = newY.map((y, i) => (newX[i] + ',' + y));
|
|
||||||
this.elementsToAnimate = this.elementsToAnimate
|
|
||||||
.concat(this.renderer.animatepath(paths, newPointsList.join("L")));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bindTooltip() {
|
bindTooltip() {
|
||||||
// TODO: could be in tooltip itself, as it is a given functionality for its parent
|
// TODO: could be in tooltip itself, as it is a given functionality for its parent
|
||||||
this.chartWrapper.addEventListener('mousemove', (e) => {
|
this.chartWrapper.addEventListener('mousemove', (e) => {
|
||||||
@ -2277,72 +2373,6 @@ class AxisChart extends BaseChart {
|
|||||||
|
|
||||||
// keep a binding at the end of chart
|
// keep a binding at the end of chart
|
||||||
|
|
||||||
class LineChart extends AxisChart {
|
|
||||||
constructor(args) {
|
|
||||||
super(args);
|
|
||||||
this.type = 'line';
|
|
||||||
|
|
||||||
if(Object.getPrototypeOf(this) !== LineChart.prototype) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
configure(args) {
|
|
||||||
super.configure(args);
|
|
||||||
this.config.xAxisMode = args.xAxisMode || 'span';
|
|
||||||
this.config.yAxisMode = args.yAxisMode || 'span';
|
|
||||||
|
|
||||||
this.config.dotRadius = args.dotRadius || 4;
|
|
||||||
|
|
||||||
this.config.heatline = args.heatline || 0;
|
|
||||||
this.config.regionFill = args.regionFill || 0;
|
|
||||||
this.config.showDots = args.showDots || 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
configUnits() {
|
|
||||||
this.unitArgs = {
|
|
||||||
type: 'dot',
|
|
||||||
args: { radius: this.config.dotRadius }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// temp commented
|
|
||||||
setUnitWidthAndXOffset() {
|
|
||||||
this.state.unitWidth = this.width/(this.state.datasetLength - 1);
|
|
||||||
this.state.xOffset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScatterChart extends LineChart {
|
|
||||||
constructor(args) {
|
|
||||||
super(args);
|
|
||||||
|
|
||||||
this.type = 'scatter';
|
|
||||||
|
|
||||||
if(!args.dotRadius) {
|
|
||||||
this.dotRadius = 8;
|
|
||||||
} else {
|
|
||||||
this.dotRadius = args.dotRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_values() {
|
|
||||||
super.setup_values();
|
|
||||||
this.unit_args = {
|
|
||||||
type: 'dot',
|
|
||||||
args: { radius: this.dotRadius }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
make_paths() {}
|
|
||||||
make_path() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MultiAxisChart extends AxisChart {
|
class MultiAxisChart extends AxisChart {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
super(args);
|
super(args);
|
||||||
@ -3123,7 +3153,6 @@ class Heatmap extends BaseChart {
|
|||||||
const chartTypes = {
|
const chartTypes = {
|
||||||
mixed: AxisChart,
|
mixed: AxisChart,
|
||||||
multiaxis: MultiAxisChart,
|
multiaxis: MultiAxisChart,
|
||||||
scatter: ScatterChart,
|
|
||||||
percentage: PercentageChart,
|
percentage: PercentageChart,
|
||||||
heatmap: Heatmap,
|
heatmap: Heatmap,
|
||||||
pie: PieChart
|
pie: PieChart
|
||||||
|
|||||||
2
dist/frappe-charts.min.cjs.js
vendored
2
dist/frappe-charts.min.cjs.js
vendored
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.esm.js
vendored
2
dist/frappe-charts.min.esm.js
vendored
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.iife.js
vendored
2
dist/frappe-charts.min.iife.js
vendored
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.iife.js.map
vendored
2
dist/frappe-charts.min.iife.js.map
vendored
File diff suppressed because one or more lines are too long
2
docs/assets/js/frappe-charts.min.js
vendored
2
docs/assets/js/frappe-charts.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -80,7 +80,7 @@ let line_composite_chart = new Chart ({
|
|||||||
parent: c2,
|
parent: c2,
|
||||||
data: line_composite_data,
|
data: line_composite_data,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
options: {
|
lineOptions: {
|
||||||
dotSize: 10
|
dotSize: 10
|
||||||
},
|
},
|
||||||
height: 180,
|
height: 180,
|
||||||
@ -208,7 +208,7 @@ let type_chart = new Chart({
|
|||||||
yAxisMode: 'span',
|
yAxisMode: 'span',
|
||||||
valuesOverPoints: 1,
|
valuesOverPoints: 1,
|
||||||
barOptions: {
|
barOptions: {
|
||||||
// stacked: 1
|
stacked: 1
|
||||||
}
|
}
|
||||||
// formatTooltipX: d => (d + '').toUpperCase(),
|
// formatTooltipX: d => (d + '').toUpperCase(),
|
||||||
// formatTooltipY: d => d + ' pts'
|
// formatTooltipY: d => d + ' pts'
|
||||||
@ -261,7 +261,7 @@ let plot_chart_args = {
|
|||||||
colors: ['blue'],
|
colors: ['blue'],
|
||||||
isSeries: 1,
|
isSeries: 1,
|
||||||
lineOptions: {
|
lineOptions: {
|
||||||
showDots: 0,
|
hideDots: 1,
|
||||||
heatline: 1,
|
heatline: 1,
|
||||||
},
|
},
|
||||||
xAxisMode: 'tick',
|
xAxisMode: 'tick',
|
||||||
@ -286,7 +286,7 @@ Array.prototype.slice.call(
|
|||||||
config = [0, 1, 0];
|
config = [0, 1, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
plot_chart_args.showDots = config[0];
|
plot_chart_args.hideDots = config[0];
|
||||||
plot_chart_args.heatline = config[1];
|
plot_chart_args.heatline = config[1];
|
||||||
plot_chart_args.regionFill = config[2];
|
plot_chart_args.regionFill = config[2];
|
||||||
|
|
||||||
@ -339,7 +339,9 @@ let update_chart = new Chart({
|
|||||||
height: 250,
|
height: 250,
|
||||||
colors: ['red'],
|
colors: ['red'],
|
||||||
isSeries: 1,
|
isSeries: 1,
|
||||||
regionFill: 1
|
lineOptions: {
|
||||||
|
regionFill: 1
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let chart_update_buttons = document.querySelector('.chart-update-buttons');
|
let chart_update_buttons = document.querySelector('.chart-update-buttons');
|
||||||
|
|||||||
@ -167,13 +167,14 @@
|
|||||||
<div id="chart-trends" class="border"></div>
|
<div id="chart-trends" class="border"></div>
|
||||||
<div class="btn-group chart-plot-buttons mt-1 mx-auto" role="group">
|
<div class="btn-group chart-plot-buttons mt-1 mx-auto" role="group">
|
||||||
<button type="button" class="btn btn-sm btn-secondary" data-type="line">Line</button>
|
<button type="button" class="btn btn-sm btn-secondary" data-type="line">Line</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" data-type="dots">Dots</button>
|
||||||
<button type="button" class="btn btn-sm btn-secondary active" data-type="heatline">HeatLine</button>
|
<button type="button" class="btn btn-sm btn-secondary active" data-type="heatline">HeatLine</button>
|
||||||
<button type="button" class="btn btn-sm btn-secondary" data-type="region">Region</button>
|
<button type="button" class="btn btn-sm btn-secondary" data-type="region">Region</button>
|
||||||
</div>
|
</div>
|
||||||
<pre><code class="hljs javascript margin-vertical-px"> ...
|
<pre><code class="hljs javascript margin-vertical-px"> ...
|
||||||
type: 'line', // Line Chart specific properties:
|
type: 'line', // Line Chart specific properties:
|
||||||
|
|
||||||
showDots: 0, // Show data points on the line; default 1
|
hideDots: 1, // Hide data points on the line; default 0
|
||||||
heatline: 1, // Show a value-wise line gradient; default 0
|
heatline: 1, // Show a value-wise line gradient; default 0
|
||||||
regionFill: 1, // Fill the area under the graph; default 0
|
regionFill: 1, // Fill the area under the graph; default 0
|
||||||
...</code></pre>
|
...</code></pre>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import '../scss/charts.scss';
|
import '../scss/charts.scss';
|
||||||
|
|
||||||
import ScatterChart from './charts/ScatterChart';
|
|
||||||
import MultiAxisChart from './charts/MultiAxisChart';
|
import MultiAxisChart from './charts/MultiAxisChart';
|
||||||
import PercentageChart from './charts/PercentageChart';
|
import PercentageChart from './charts/PercentageChart';
|
||||||
import PieChart from './charts/PieChart';
|
import PieChart from './charts/PieChart';
|
||||||
@ -23,7 +22,6 @@ import AxisChart from './charts/AxisChart';
|
|||||||
const chartTypes = {
|
const chartTypes = {
|
||||||
mixed: AxisChart,
|
mixed: AxisChart,
|
||||||
multiaxis: MultiAxisChart,
|
multiaxis: MultiAxisChart,
|
||||||
scatter: ScatterChart,
|
|
||||||
percentage: PercentageChart,
|
percentage: PercentageChart,
|
||||||
heatmap: Heatmap,
|
heatmap: Heatmap,
|
||||||
pie: PieChart
|
pie: PieChart
|
||||||
|
|||||||
@ -2,14 +2,10 @@ import BaseChart from './BaseChart';
|
|||||||
import { dataPrep, zeroDataPrep } from './axis-chart-utils';
|
import { dataPrep, zeroDataPrep } from './axis-chart-utils';
|
||||||
import { Y_AXIS_MARGIN } from '../utils/constants';
|
import { Y_AXIS_MARGIN } from '../utils/constants';
|
||||||
import { getComponent } from '../objects/ChartComponents';
|
import { getComponent } from '../objects/ChartComponents';
|
||||||
import { AxisChartRenderer } from '../utils/draw';
|
|
||||||
import { getOffset, fire } from '../utils/dom';
|
import { getOffset, fire } from '../utils/dom';
|
||||||
import { equilizeNoOfElements } from '../utils/draw-utils';
|
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale } from '../utils/intervals';
|
||||||
import { Animator, translateHoriLine } from '../utils/animate';
|
import { floatTwo } from '../utils/helpers';
|
||||||
import { runSMILAnimation } from '../utils/animation';
|
import { MIN_BAR_PERCENT_HEIGHT, DEFAULT_AXIS_CHART_TYPE, BAR_CHART_SPACE_RATIO, LINE_CHART_DOT_SIZE } from '../utils/constants';
|
||||||
import { getRealIntervals, calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale } from '../utils/intervals';
|
|
||||||
import { floatTwo, fillArray, bindChange } from '../utils/helpers';
|
|
||||||
import { MIN_BAR_PERCENT_HEIGHT, DEFAULT_AXIS_CHART_TYPE, BAR_CHART_SPACE_RATIO } from '../utils/constants';
|
|
||||||
|
|
||||||
export default class AxisChart extends BaseChart {
|
export default class AxisChart extends BaseChart {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
@ -66,6 +62,10 @@ export default class AxisChart extends BaseChart {
|
|||||||
// Default, as per bar, and mixed. Only line will be a special case
|
// Default, as per bar, and mixed. Only line will be a special case
|
||||||
s.xOffset = s.unitWidth/2;
|
s.xOffset = s.unitWidth/2;
|
||||||
|
|
||||||
|
// // For a pure Line Chart
|
||||||
|
// s.unitWidth = this.width/(s.datasetLength - 1);
|
||||||
|
// s.xOffset = 0;
|
||||||
|
|
||||||
s.xAxis = {
|
s.xAxis = {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
positions: labels.map((d, i) =>
|
positions: labels.map((d, i) =>
|
||||||
@ -208,8 +208,6 @@ export default class AxisChart extends BaseChart {
|
|||||||
|
|
||||||
// console.log('barDatasets', barDatasets, this.state.datasets);
|
// console.log('barDatasets', barDatasets, this.state.datasets);
|
||||||
|
|
||||||
// Bars
|
|
||||||
|
|
||||||
let barsConfigs = barDatasets.map(d => {
|
let barsConfigs = barDatasets.map(d => {
|
||||||
let index = d.index;
|
let index = d.index;
|
||||||
return [
|
return [
|
||||||
@ -248,6 +246,38 @@ export default class AxisChart extends BaseChart {
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let lineConfigs = lineDatasets.map(d => {
|
||||||
|
let index = d.index;
|
||||||
|
return [
|
||||||
|
'lineGraph' + '-' + d.index,
|
||||||
|
{
|
||||||
|
index: index,
|
||||||
|
color: this.colors[index],
|
||||||
|
svgDefs: this.svgDefs,
|
||||||
|
heatline: this.lineOptions.heatline,
|
||||||
|
regionFill: this.lineOptions.regionFill,
|
||||||
|
hideDots: this.lineOptions.hideDots,
|
||||||
|
|
||||||
|
// same for all datasets
|
||||||
|
valuesOverPoints: this.valuesOverPoints,
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
let s = this.state;
|
||||||
|
let d = s.datasets[index];
|
||||||
|
|
||||||
|
return {
|
||||||
|
xPositions: s.xAxis.positions,
|
||||||
|
yPositions: d.yPositions,
|
||||||
|
|
||||||
|
values: d.values,
|
||||||
|
|
||||||
|
zeroLine: s.yAxis.zeroLine,
|
||||||
|
radius: this.lineOptions.dotSize || LINE_CHART_DOT_SIZE,
|
||||||
|
};
|
||||||
|
}.bind(this)
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
let markerConfigs = [
|
let markerConfigs = [
|
||||||
[
|
[
|
||||||
'yMarkers',
|
'yMarkers',
|
||||||
@ -266,7 +296,7 @@ export default class AxisChart extends BaseChart {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.componentConfigs = this.componentConfigs.concat(barsConfigs, markerConfigs);
|
this.componentConfigs = this.componentConfigs.concat(barsConfigs, lineConfigs, markerConfigs);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupComponents() {
|
setupComponents() {
|
||||||
@ -278,130 +308,6 @@ export default class AxisChart extends BaseChart {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
getChartComponents() {
|
|
||||||
let dataUnitsComponents = []
|
|
||||||
// this.state is not defined at this stage
|
|
||||||
this.data.datasets.forEach((d, index) => {
|
|
||||||
if(d.chartType === 'line') {
|
|
||||||
dataUnitsComponents.push(this.getPathComponent(d, index));
|
|
||||||
}
|
|
||||||
|
|
||||||
let renderer = this.unitRenderers[d.chartType];
|
|
||||||
dataUnitsComponents.push(this.getDataUnitComponent(
|
|
||||||
index, renderer
|
|
||||||
));
|
|
||||||
});
|
|
||||||
return dataUnitsComponents;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDataUnitComponent(index, unitRenderer) {
|
|
||||||
return new ChartComponent({
|
|
||||||
layerClass: 'dataset-units dataset-' + index,
|
|
||||||
makeElements: () => {
|
|
||||||
// yPositions, xPostions, color, valuesOverPoints,
|
|
||||||
let d = this.data.datasets[index];
|
|
||||||
|
|
||||||
return d.positions.map((y, j) => {
|
|
||||||
return unitRenderer.draw(
|
|
||||||
this.state.xAxis.positions[j],
|
|
||||||
y,
|
|
||||||
this.colors[index],
|
|
||||||
(this.valuesOverPoints ? (this.barOptions &&
|
|
||||||
this.barOptions.stacked ? d.cumulativeYs[j] : d.values[j]) : ''),
|
|
||||||
j,
|
|
||||||
y - (d.cumulativePositions ? d.cumulativePositions[j] : y)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
postMake: function() {
|
|
||||||
let translate_layer = () => {
|
|
||||||
this.layer.setAttribute('transform', `translate(${unitRenderer.consts.width * index}, 0)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// let d = this.data.datasets[index];
|
|
||||||
|
|
||||||
if(this.meta.type === 'bar' && (!this.meta.barOptions
|
|
||||||
|| !this.meta.barOptions.stacked)) {
|
|
||||||
|
|
||||||
translate_layer();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
animate: (svgUnits) => {
|
|
||||||
// have been updated in axis render;
|
|
||||||
let newX = this.state.xAxis.positions;
|
|
||||||
let newY = this.data.datasets[index].positions;
|
|
||||||
|
|
||||||
let lastUnit = svgUnits[svgUnits.length - 1];
|
|
||||||
let parentNode = lastUnit.parentNode;
|
|
||||||
|
|
||||||
if(this.oldState.xExtra > 0) {
|
|
||||||
for(var i = 0; i<this.oldState.xExtra; i++) {
|
|
||||||
let unit = lastUnit.cloneNode(true);
|
|
||||||
parentNode.appendChild(unit);
|
|
||||||
svgUnits.push(unit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svgUnits.map((unit, i) => {
|
|
||||||
if(newX[i] === undefined || newY[i] === undefined) return;
|
|
||||||
this.elementsToAnimate.push(unitRenderer.animate(
|
|
||||||
unit, // unit, with info to replace where it came from in the data
|
|
||||||
newX[i],
|
|
||||||
newY[i],
|
|
||||||
index,
|
|
||||||
this.data.datasets.length
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getPathComponent(d, index) {
|
|
||||||
return new ChartComponent({
|
|
||||||
layerClass: 'path dataset-path',
|
|
||||||
setData: () => {},
|
|
||||||
makeElements: () => {
|
|
||||||
let d = this.data.datasets[index];
|
|
||||||
let color = this.colors[index];
|
|
||||||
|
|
||||||
return getPaths(
|
|
||||||
d.positions,
|
|
||||||
this.state.xAxis.positions,
|
|
||||||
color,
|
|
||||||
this.config.heatline,
|
|
||||||
this.config.regionFill
|
|
||||||
);
|
|
||||||
},
|
|
||||||
animate: (paths) => {
|
|
||||||
let newX = this.state.xAxis.positions;
|
|
||||||
let newY = this.data.datasets[index].positions;
|
|
||||||
|
|
||||||
let oldX = this.oldState.xAxis.positions;
|
|
||||||
let oldY = this.oldState.datasets[index].positions;
|
|
||||||
|
|
||||||
|
|
||||||
let parentNode = paths[0].parentNode;
|
|
||||||
|
|
||||||
[oldX, newX] = equilizeNoOfElements(oldX, newX);
|
|
||||||
[oldY, newY] = equilizeNoOfElements(oldY, newY);
|
|
||||||
|
|
||||||
if(this.oldState.xExtra > 0) {
|
|
||||||
paths = getPaths(
|
|
||||||
oldY, oldX, this.colors[index],
|
|
||||||
this.config.heatline,
|
|
||||||
this.config.regionFill
|
|
||||||
);
|
|
||||||
parentNode.textContent = '';
|
|
||||||
paths.map(path => parentNode.appendChild(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
const newPointsList = newY.map((y, i) => (newX[i] + ',' + y));
|
|
||||||
this.elementsToAnimate = this.elementsToAnimate
|
|
||||||
.concat(this.renderer.animatepath(paths, newPointsList.join("L")));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bindTooltip() {
|
bindTooltip() {
|
||||||
// TODO: could be in tooltip itself, as it is a given functionality for its parent
|
// TODO: could be in tooltip itself, as it is a given functionality for its parent
|
||||||
this.chartWrapper.addEventListener('mousemove', (e) => {
|
this.chartWrapper.addEventListener('mousemove', (e) => {
|
||||||
|
|||||||
@ -148,17 +148,16 @@ export default class BaseChart {
|
|||||||
|
|
||||||
draw(init=false) {
|
draw(init=false) {
|
||||||
this.calcWidth();
|
this.calcWidth();
|
||||||
this.makeChartArea();
|
|
||||||
|
|
||||||
this.calc();
|
this.calc();
|
||||||
this.initComponents(); // Only depend on the drawArea made in makeChartArea
|
this.makeChartArea();
|
||||||
|
this.initComponents();
|
||||||
|
|
||||||
this.setupComponents();
|
this.setupComponents();
|
||||||
|
|
||||||
this.components.forEach(c => c.setup(this.drawArea)); // or c.build()
|
this.components.forEach(c => c.setup(this.drawArea)); // or c.build()
|
||||||
this.components.forEach(c => c.make()); // or c.build()
|
this.components.forEach(c => c.make()); // or c.build()
|
||||||
this.renderLegend();
|
|
||||||
|
|
||||||
|
this.renderLegend();
|
||||||
this.setupNavigation(init);
|
this.setupNavigation(init);
|
||||||
|
|
||||||
// TODO: remove timeout and decrease post animate time in chart component
|
// TODO: remove timeout and decrease post animate time in chart component
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
import AxisChart from './AxisChart';
|
|
||||||
// import { ChartComponent } from '../objects/ChartComponents';
|
|
||||||
import { makeSVGGroup, makePath, makeGradient } from '../utils/draw';
|
|
||||||
import { equilizeNoOfElements } from '../utils/draw-utils';
|
|
||||||
|
|
||||||
export default class LineChart extends AxisChart {
|
|
||||||
constructor(args) {
|
|
||||||
super(args);
|
|
||||||
this.type = 'line';
|
|
||||||
|
|
||||||
if(Object.getPrototypeOf(this) !== LineChart.prototype) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
configure(args) {
|
|
||||||
super.configure(args);
|
|
||||||
this.config.xAxisMode = args.xAxisMode || 'span';
|
|
||||||
this.config.yAxisMode = args.yAxisMode || 'span';
|
|
||||||
|
|
||||||
this.config.dotRadius = args.dotRadius || 4;
|
|
||||||
|
|
||||||
this.config.heatline = args.heatline || 0;
|
|
||||||
this.config.regionFill = args.regionFill || 0;
|
|
||||||
this.config.showDots = args.showDots || 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
configUnits() {
|
|
||||||
this.unitArgs = {
|
|
||||||
type: 'dot',
|
|
||||||
args: { radius: this.config.dotRadius }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// temp commented
|
|
||||||
setUnitWidthAndXOffset() {
|
|
||||||
this.state.unitWidth = this.width/(this.state.datasetLength - 1);
|
|
||||||
this.state.xOffset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import LineChart from './LineChart';
|
|
||||||
|
|
||||||
export default class ScatterChart extends LineChart {
|
|
||||||
constructor(args) {
|
|
||||||
super(args);
|
|
||||||
|
|
||||||
this.type = 'scatter';
|
|
||||||
|
|
||||||
if(!args.dotRadius) {
|
|
||||||
this.dotRadius = 8;
|
|
||||||
} else {
|
|
||||||
this.dotRadius = args.dotRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_values() {
|
|
||||||
super.setup_values();
|
|
||||||
this.unit_args = {
|
|
||||||
type: 'dot',
|
|
||||||
args: { radius: this.dotRadius }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
make_paths() {}
|
|
||||||
make_path() {}
|
|
||||||
}
|
|
||||||
@ -55,63 +55,9 @@ export class LineChartController extends AxisChartController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(x, y, color, label='', index=0) {
|
|
||||||
let dot = createSVG('circle', {
|
|
||||||
style: `fill: ${color}`,
|
|
||||||
'data-point-index': index,
|
|
||||||
cx: x,
|
|
||||||
cy: y,
|
|
||||||
r: this.consts.radius
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!label && !label.length) {
|
|
||||||
return dot;
|
|
||||||
} else {
|
|
||||||
let text = createSVG('text', {
|
|
||||||
className: 'data-point-value',
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
dy: (FONT_SIZE / 2 * -1 - this.consts.radius) + 'px',
|
|
||||||
'font-size': FONT_SIZE + 'px',
|
|
||||||
'text-anchor': 'middle',
|
|
||||||
innerHTML: label
|
|
||||||
});
|
|
||||||
|
|
||||||
return wrapInSVGGroup([dot, text]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
animate(dot, x, yTop) {
|
|
||||||
return [dot, {cx: x, cy: yTop}, UNIT_ANIM_DUR, STD_EASING];
|
|
||||||
// dot.animate({cy: yTop}, UNIT_ANIM_DUR, mina.easein);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPaths(yList, xList, color, heatline=false, regionFill=false) {
|
|
||||||
let pointsList = yList.map((y, i) => (xList[i] + ',' + y));
|
|
||||||
let pointsStr = pointsList.join("L");
|
|
||||||
let path = makePath("M"+pointsStr, 'line-graph-path', color);
|
|
||||||
|
|
||||||
// HeatLine
|
|
||||||
if(heatline) {
|
|
||||||
let gradient_id = makeGradient(this.svgDefs, color);
|
|
||||||
path.style.stroke = `url(#${gradient_id})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let components = [path];
|
|
||||||
|
|
||||||
// Region
|
|
||||||
if(regionFill) {
|
|
||||||
let gradient_id_region = makeGradient(this.svgDefs, color, true);
|
|
||||||
|
|
||||||
let zeroLine = this.state.yAxis.zeroLine;
|
|
||||||
// TODO: use zeroLine OR minimum
|
|
||||||
let pathStr = "M" + `0,${zeroLine}L` + pointsStr + `L${this.width},${zeroLine}`;
|
|
||||||
components.push(makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id_region})`));
|
|
||||||
}
|
|
||||||
|
|
||||||
return components;
|
|
||||||
}
|
|
||||||
|
|
||||||
// class BarChart extends AxisChart {
|
// class BarChart extends AxisChart {
|
||||||
// constructor(args) {
|
// constructor(args) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { makeSVGGroup } from '../utils/draw';
|
import { makeSVGGroup } from '../utils/draw';
|
||||||
import { xLine, yLine, yMarker, yRegion, datasetBar } from '../utils/draw';
|
import { xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, getPaths } from '../utils/draw';
|
||||||
import { equilizeNoOfElements } from '../utils/draw-utils';
|
import { equilizeNoOfElements } from '../utils/draw-utils';
|
||||||
import { Animator, translateHoriLine, translateVertLine, animateRegion, animateBar } from '../utils/animate';
|
import { translateHoriLine, translateVertLine, animateRegion, animateBar, animateDot, animatePath } from '../utils/animate';
|
||||||
|
|
||||||
class ChartComponent {
|
class ChartComponent {
|
||||||
constructor({
|
constructor({
|
||||||
@ -230,7 +230,6 @@ let componentConfigs = {
|
|||||||
let newValues = newData.values;
|
let newValues = newData.values;
|
||||||
let newCYs = newData.cumulativeYs;
|
let newCYs = newData.cumulativeYs;
|
||||||
|
|
||||||
|
|
||||||
let oldXPos = this.oldData.xPositions;
|
let oldXPos = this.oldData.xPositions;
|
||||||
let oldYPos = this.oldData.yPositions;
|
let oldYPos = this.oldData.yPositions;
|
||||||
let oldCYPos = this.oldData.cumulativeYPos;
|
let oldCYPos = this.oldData.cumulativeYPos;
|
||||||
@ -270,7 +269,79 @@ let componentConfigs = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
lineGraph: {
|
lineGraph: {
|
||||||
|
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; },
|
||||||
|
makeElements(data) {
|
||||||
|
let c = this.constants;
|
||||||
|
this.paths = getPaths(
|
||||||
|
data.xPositions,
|
||||||
|
data.yPositions,
|
||||||
|
c.color,
|
||||||
|
{
|
||||||
|
heatline: c.heatline,
|
||||||
|
regionFill: c.regionFill
|
||||||
|
},
|
||||||
|
{
|
||||||
|
svgDefs: c.svgDefs,
|
||||||
|
zeroLine: data.zeroLine
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
this.dots = []
|
||||||
|
|
||||||
|
if(!c.hideDots) {
|
||||||
|
this.dots = data.yPositions.map((y, j) => {
|
||||||
|
return datasetDot(
|
||||||
|
data.xPositions[j],
|
||||||
|
y,
|
||||||
|
data.radius,
|
||||||
|
c.color,
|
||||||
|
(c.valuesOverPoints ? data.values[j] : ''),
|
||||||
|
j
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.values(this.paths).concat(this.dots);
|
||||||
|
},
|
||||||
|
animateElements(newData) {
|
||||||
|
let c = this.constants;
|
||||||
|
|
||||||
|
let newXPos = newData.xPositions;
|
||||||
|
let newYPos = newData.yPositions;
|
||||||
|
let newValues = newData.values;
|
||||||
|
|
||||||
|
|
||||||
|
let oldXPos = this.oldData.xPositions;
|
||||||
|
let oldYPos = this.oldData.yPositions;
|
||||||
|
let oldValues = this.oldData.values;
|
||||||
|
|
||||||
|
[oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
|
||||||
|
[oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
|
||||||
|
[oldValues, newValues] = equilizeNoOfElements(oldValues, newValues);
|
||||||
|
|
||||||
|
this.render({
|
||||||
|
xPositions: oldXPos,
|
||||||
|
yPositions: oldYPos,
|
||||||
|
values: newValues,
|
||||||
|
|
||||||
|
zeroLine: this.oldData.zeroLine,
|
||||||
|
radius: this.oldData.radius,
|
||||||
|
});
|
||||||
|
|
||||||
|
let animateElements = [];
|
||||||
|
|
||||||
|
animateElements = animateElements.concat(animatePath(
|
||||||
|
this.paths, newXPos, newYPos, newData.zeroLine));
|
||||||
|
|
||||||
|
if(this.dots.length) {
|
||||||
|
this.dots.map((dot, i) => {
|
||||||
|
animateElements = animateElements.concat(animateDot(
|
||||||
|
dot, newXPos[i], newYPos[i]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return animateElements;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -53,8 +53,8 @@ export function animateBar(bar, x, yTop, width, index=0, meta={}) {
|
|||||||
STD_EASING
|
STD_EASING
|
||||||
]
|
]
|
||||||
|
|
||||||
let old = bar.getAttribute("transform").split("(")[1].slice(0, -1);
|
let oldCoordStr = bar.getAttribute("transform").split("(")[1].slice(0, -1);
|
||||||
let groupAnim = translate(bar, old, [x, y], MARKER_LINE_ANIM_DUR);
|
let groupAnim = translate(bar, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR);
|
||||||
return [rectAnim, groupAnim];
|
return [rectAnim, groupAnim];
|
||||||
} else {
|
} else {
|
||||||
return [[bar, {width: width, height: height, x: x, y: y}, UNIT_ANIM_DUR, STD_EASING]];
|
return [[bar, {width: width, height: height, x: x, y: y}, UNIT_ANIM_DUR, STD_EASING]];
|
||||||
@ -62,57 +62,39 @@ export function animateBar(bar, x, yTop, width, index=0, meta={}) {
|
|||||||
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
|
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
|
||||||
}
|
}
|
||||||
|
|
||||||
export var Animator = (function() {
|
export function animateDot(dot, x, y) {
|
||||||
var Animator = function(totalHeight, totalWidth, zeroLine, avgUnitWidth) {
|
if(dot.nodeName !== 'circle') {
|
||||||
// constants
|
let oldCoordStr = dot.getAttribute("transform").split("(")[1].slice(0, -1);
|
||||||
this.totalHeight = totalHeight;
|
let groupAnim = translate(dot, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR);
|
||||||
this.totalWidth = totalWidth;
|
return [groupAnim];
|
||||||
|
} else {
|
||||||
|
return [[dot, {cx: x, cy: y}, UNIT_ANIM_DUR, STD_EASING]];
|
||||||
|
}
|
||||||
|
// dot.animate({cy: yTop}, UNIT_ANIM_DUR, mina.easein);
|
||||||
|
}
|
||||||
|
|
||||||
// changeables
|
export function animatePath(paths, newXList, newYList, zeroLine) {
|
||||||
this.avgUnitWidth = avgUnitWidth;
|
let pathComponents = [];
|
||||||
this.zeroLine = zeroLine;
|
|
||||||
};
|
|
||||||
|
|
||||||
Animator.prototype = {
|
let pointsStr = newYList.map((y, i) => (newXList[i] + ',' + y));
|
||||||
bar: function(barObj, x, yTop, index, noOfDatasets) {
|
let pathStr = pointsStr.join("L");
|
||||||
let start = x - this.avgUnitWidth/4;
|
|
||||||
let width = (this.avgUnitWidth/2)/noOfDatasets;
|
|
||||||
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);
|
|
||||||
|
|
||||||
x = start + (width * index);
|
const animPath = [paths.path, {d:"M"+pathStr}, PATH_ANIM_DUR, STD_EASING];
|
||||||
|
pathComponents.push(animPath);
|
||||||
|
|
||||||
return [barObj, {width: width, height: height, x: x, y: y}, UNIT_ANIM_DUR, STD_EASING];
|
if(paths.region) {
|
||||||
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
|
let regStartPt = `${newXList[0]},${zeroLine}L`;
|
||||||
},
|
let regEndPt = `L${newXList.slice(-1)[0]}, ${zeroLine}`;
|
||||||
|
|
||||||
dot: function(dotObj, x, yTop) {
|
const animRegion = [
|
||||||
return [dotObj, {cx: x, cy: yTop}, UNIT_ANIM_DUR, STD_EASING];
|
paths.region,
|
||||||
// dot.animate({cy: yTop}, UNIT_ANIM_DUR, mina.easein);
|
{d:"M" + regStartPt + pathStr + regEndPt},
|
||||||
},
|
PATH_ANIM_DUR,
|
||||||
|
STD_EASING
|
||||||
path: function(d, pathStr) {
|
];
|
||||||
let pathComponents = [];
|
pathComponents.push(animRegion);
|
||||||
const animPath = [{unit: d.path, object: d, key: 'path'}, {d:"M"+pathStr}, PATH_ANIM_DUR, STD_EASING];
|
}
|
||||||
pathComponents.push(animPath);
|
|
||||||
|
|
||||||
if(d.regionPath) {
|
|
||||||
let regStartPt = `0,${this.zeroLine}L`;
|
|
||||||
let regEndPt = `L${this.totalWidth}, ${this.zeroLine}`;
|
|
||||||
|
|
||||||
const animRegion = [
|
|
||||||
{unit: d.regionPath, object: d, key: 'regionPath'},
|
|
||||||
{d:"M" + regStartPt + pathStr + regEndPt},
|
|
||||||
PATH_ANIM_DUR,
|
|
||||||
STD_EASING
|
|
||||||
];
|
|
||||||
pathComponents.push(animRegion);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathComponents;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Animator;
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
return pathComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
export const Y_AXIS_MARGIN = 60;
|
export const Y_AXIS_MARGIN = 60;
|
||||||
|
|
||||||
export const MIN_BAR_PERCENT_HEIGHT = 0.01;
|
|
||||||
export const DEFAULT_AXIS_CHART_TYPE = 'line';
|
export const DEFAULT_AXIS_CHART_TYPE = 'line';
|
||||||
export const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
|
export const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
|
||||||
export const BAR_CHART_SPACE_RATIO = 0.5;
|
|
||||||
|
export const BAR_CHART_SPACE_RATIO = 0.5;
|
||||||
|
export const MIN_BAR_PERCENT_HEIGHT = 0.01;
|
||||||
|
|
||||||
|
export const LINE_CHART_DOT_SIZE = 4;
|
||||||
@ -124,7 +124,7 @@ export function makePath(pathStr, className='', stroke='none', fill='none') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function makeGradient(svgDefElem, color, lighter = false) {
|
export function makeGradient(svgDefElem, color, lighter = false) {
|
||||||
let gradientId ='path-fill-gradient' + '-' + color;
|
let gradientId ='path-fill-gradient' + '-' + color + '-' +(lighter ? 'lighter' : 'default');
|
||||||
let gradientDef = renderVerticalGradient(svgDefElem, gradientId);
|
let gradientDef = renderVerticalGradient(svgDefElem, gradientId);
|
||||||
let opacities = [1, 0.6, 0.2];
|
let opacities = [1, 0.6, 0.2];
|
||||||
if(lighter) {
|
if(lighter) {
|
||||||
@ -400,75 +400,64 @@ export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AxisChartRenderer {
|
export function datasetDot(x, y, radius, color, label='', index=0, meta={}) {
|
||||||
constructor(state) {
|
let dot = createSVG('circle', {
|
||||||
this.refreshState(state);
|
style: `fill: ${color}`,
|
||||||
}
|
'data-point-index': index,
|
||||||
|
cx: x,
|
||||||
|
cy: y,
|
||||||
|
r: radius
|
||||||
|
});
|
||||||
|
|
||||||
refreshState(state) {
|
if(!label && !label.length) {
|
||||||
this.totalHeight = state.totalHeight;
|
return dot;
|
||||||
this.totalWidth = state.totalWidth;
|
} else {
|
||||||
this.zeroLine = state.zeroLine;
|
dot.setAttribute('cy', 0);
|
||||||
this.unitWidth = state.unitWidth;
|
dot.setAttribute('cx', 0);
|
||||||
this.xAxisMode = state.xAxisMode;
|
|
||||||
this.yAxisMode = state.yAxisMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
setZeroline(zeroLine) {
|
let text = createSVG('text', {
|
||||||
this.zeroLine = zeroLine;
|
className: 'data-point-value',
|
||||||
}
|
|
||||||
|
|
||||||
xMarker() {}
|
|
||||||
|
|
||||||
|
|
||||||
xRegion() {
|
|
||||||
return createSVG('rect', {
|
|
||||||
className: `bar mini`, // remove class
|
|
||||||
style: `fill: rgba(228, 234, 239, 0.49)`,
|
|
||||||
// 'data-point-index': index,
|
|
||||||
x: 0,
|
x: 0,
|
||||||
y: y2,
|
y: 0,
|
||||||
width: this.totalWidth,
|
dy: (FONT_SIZE / 2 * -1 - radius) + 'px',
|
||||||
height: y1 - y2
|
'font-size': FONT_SIZE + 'px',
|
||||||
|
'text-anchor': 'middle',
|
||||||
|
innerHTML: label
|
||||||
});
|
});
|
||||||
|
|
||||||
return region;
|
let group = createSVG('g', {
|
||||||
}
|
transform: `translate(${x}, ${y})`
|
||||||
|
});
|
||||||
|
group.appendChild(dot);
|
||||||
|
group.appendChild(text);
|
||||||
|
|
||||||
animatebar(bar, x, yTop, index, noOfDatasets) {
|
return group;
|
||||||
let start = x - this.avgUnitWidth/4;
|
|
||||||
let width = (this.avgUnitWidth/2)/noOfDatasets;
|
|
||||||
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);
|
|
||||||
|
|
||||||
x = start + (width * index);
|
|
||||||
|
|
||||||
return [bar, {width: width, height: height, x: x, y: y}, UNIT_ANIM_DUR, STD_EASING];
|
|
||||||
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
|
|
||||||
}
|
|
||||||
|
|
||||||
animatedot(dot, x, yTop) {
|
|
||||||
return [dot, {cx: x, cy: yTop}, UNIT_ANIM_DUR, STD_EASING];
|
|
||||||
// dot.animate({cy: yTop}, UNIT_ANIM_DUR, mina.easein);
|
|
||||||
}
|
|
||||||
|
|
||||||
animatepath(paths, pathStr) {
|
|
||||||
let pathComponents = [];
|
|
||||||
const animPath = [paths[0], {d:"M"+pathStr}, PATH_ANIM_DUR, STD_EASING];
|
|
||||||
pathComponents.push(animPath);
|
|
||||||
|
|
||||||
if(paths[1]) {
|
|
||||||
let regStartPt = `0,${this.zeroLine}L`;
|
|
||||||
let regEndPt = `L${this.totalWidth}, ${this.zeroLine}`;
|
|
||||||
|
|
||||||
const animRegion = [
|
|
||||||
paths[1],
|
|
||||||
{d:"M" + regStartPt + pathStr + regEndPt},
|
|
||||||
PATH_ANIM_DUR,
|
|
||||||
STD_EASING
|
|
||||||
];
|
|
||||||
pathComponents.push(animRegion);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathComponents;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPaths(xList, yList, color, options={}, meta={}) {
|
||||||
|
let pointsList = yList.map((y, i) => (xList[i] + ',' + y));
|
||||||
|
let pointsStr = pointsList.join("L");
|
||||||
|
let path = makePath("M"+pointsStr, 'line-graph-path', color);
|
||||||
|
|
||||||
|
// HeatLine
|
||||||
|
if(options.heatline) {
|
||||||
|
let gradient_id = makeGradient(meta.svgDefs, color);
|
||||||
|
path.style.stroke = `url(#${gradient_id})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let paths = {
|
||||||
|
path: path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region
|
||||||
|
if(options.regionFill) {
|
||||||
|
let gradient_id_region = makeGradient(meta.svgDefs, color, true);
|
||||||
|
|
||||||
|
// TODO: use zeroLine OR minimum
|
||||||
|
let pathStr = "M" + `${xList[0]},${meta.zeroLine}L` + pointsStr + `L${xList.slice(-1)[0]},${meta.zeroLine}`;
|
||||||
|
paths.region = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id_region})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|||||||
0
src/js/utils/keyboard.js
Normal file
0
src/js/utils/keyboard.js
Normal file
Loading…
x
Reference in New Issue
Block a user