animate using components
This commit is contained in:
parent
f60fd25c00
commit
5e705db263
690
dist/frappe-charts.esm.js
vendored
690
dist/frappe-charts.esm.js
vendored
@ -255,7 +255,16 @@ function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
|
||||
return [height, y];
|
||||
}
|
||||
|
||||
function equilizeNoOfElements(array1, array2,
|
||||
extra_count=array2.length - array1.length) {
|
||||
|
||||
if(extra_count > 0) {
|
||||
array1 = fillArray(array1, extra_count);
|
||||
} else {
|
||||
array2 = fillArray(array2, extra_count);
|
||||
}
|
||||
return [array1, array2];
|
||||
}
|
||||
|
||||
// let char_width = 8;
|
||||
// let allowed_space = avgUnitWidth * 1.5;
|
||||
@ -278,6 +287,13 @@ function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
|
||||
// }
|
||||
// }
|
||||
|
||||
const UNIT_ANIM_DUR = 350;
|
||||
const PATH_ANIM_DUR = 350;
|
||||
const MARKER_LINE_ANIM_DUR = UNIT_ANIM_DUR;
|
||||
const REPLACE_ALL_NEW_DUR = 250;
|
||||
|
||||
const STD_EASING = 'easein';
|
||||
|
||||
const AXIS_TICK_LENGTH = 6;
|
||||
const LABEL_MARGIN = 4;
|
||||
const FONT_SIZE = 10;
|
||||
@ -508,7 +524,7 @@ class AxisChartRenderer {
|
||||
this.zeroLine = zeroLine;
|
||||
}
|
||||
|
||||
bar(x, yTop, args, color, index, datasetIndex, noOfDatasets, prevX, prevY) {
|
||||
bar(x, yTop, args, color, index, datasetIndex, noOfDatasets) {
|
||||
|
||||
let totalWidth = this.unitWidth - args.spaceWidth;
|
||||
let startX = x - totalWidth/2;
|
||||
@ -601,11 +617,68 @@ class AxisChartRenderer {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
xMarker() {}
|
||||
yMarker() {}
|
||||
|
||||
xRegion() {}
|
||||
yRegion() {}
|
||||
|
||||
animatebar(bar, x, yTop, index, noOfDatasets) {
|
||||
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;
|
||||
}
|
||||
|
||||
translate(unit, oldCoord, newCoord, duration) {
|
||||
return [
|
||||
unit,
|
||||
{transform: newCoord.join(', ')},
|
||||
duration,
|
||||
STD_EASING,
|
||||
"translate",
|
||||
{transform: oldCoord.join(', ')}
|
||||
];
|
||||
}
|
||||
|
||||
translateVertLine(xLine, newX, oldX) {
|
||||
return this.translate(xLine, [oldX, 0], [newX, 0], MARKER_LINE_ANIM_DUR);
|
||||
}
|
||||
|
||||
translateHoriLine(yLine, newY, oldY) {
|
||||
return this.translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR);
|
||||
}
|
||||
}
|
||||
|
||||
const PRESET_COLOR_MAP = {
|
||||
@ -705,6 +778,121 @@ function getDifferentChart(type, current_type, args) {
|
||||
});
|
||||
}
|
||||
|
||||
// Leveraging SMIL Animations
|
||||
|
||||
const EASING = {
|
||||
ease: "0.25 0.1 0.25 1",
|
||||
linear: "0 0 1 1",
|
||||
// easein: "0.42 0 1 1",
|
||||
easein: "0.1 0.8 0.2 1",
|
||||
easeout: "0 0 0.58 1",
|
||||
easeinout: "0.42 0 0.58 1"
|
||||
};
|
||||
|
||||
function animateSVGElement(element, props, dur, easingType="linear", type=undefined, oldValues={}) {
|
||||
|
||||
let animElement = element.cloneNode(true);
|
||||
let newElement = element.cloneNode(true);
|
||||
|
||||
for(var attributeName in props) {
|
||||
let animateElement;
|
||||
if(attributeName === 'transform') {
|
||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
|
||||
} else {
|
||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animate");
|
||||
}
|
||||
let currentValue = oldValues[attributeName] || element.getAttribute(attributeName);
|
||||
let value = props[attributeName];
|
||||
|
||||
let animAttr = {
|
||||
attributeName: attributeName,
|
||||
from: currentValue,
|
||||
to: value,
|
||||
begin: "0s",
|
||||
dur: dur/1000 + "s",
|
||||
values: currentValue + ";" + value,
|
||||
keySplines: EASING[easingType],
|
||||
keyTimes: "0;1",
|
||||
calcMode: "spline",
|
||||
fill: 'freeze'
|
||||
};
|
||||
|
||||
if(type) {
|
||||
animAttr["type"] = type;
|
||||
}
|
||||
|
||||
for (var i in animAttr) {
|
||||
animateElement.setAttribute(i, animAttr[i]);
|
||||
}
|
||||
|
||||
animElement.appendChild(animateElement);
|
||||
|
||||
if(type) {
|
||||
newElement.setAttribute(attributeName, `translate(${value})`);
|
||||
} else {
|
||||
newElement.setAttribute(attributeName, value);
|
||||
}
|
||||
}
|
||||
|
||||
return [animElement, newElement];
|
||||
}
|
||||
|
||||
function transform(element, style) { // eslint-disable-line no-unused-vars
|
||||
element.style.transform = style;
|
||||
element.style.webkitTransform = style;
|
||||
element.style.msTransform = style;
|
||||
element.style.mozTransform = style;
|
||||
element.style.oTransform = style;
|
||||
}
|
||||
|
||||
function animateSVG(svgContainer, elements) {
|
||||
let newElements = [];
|
||||
let animElements = [];
|
||||
|
||||
elements.map(element => {
|
||||
let unit = element[0];
|
||||
let parent = unit.parentNode;
|
||||
|
||||
let animElement, newElement;
|
||||
|
||||
element[0] = unit;
|
||||
[animElement, newElement] = animateSVGElement(...element);
|
||||
|
||||
newElements.push(newElement);
|
||||
animElements.push([animElement, parent]);
|
||||
|
||||
parent.replaceChild(animElement, unit);
|
||||
});
|
||||
|
||||
let animSvg = svgContainer.cloneNode(true);
|
||||
|
||||
animElements.map((animElement, i) => {
|
||||
animElement[1].replaceChild(newElements[i], animElement[0]);
|
||||
elements[i][0] = newElements[i];
|
||||
});
|
||||
|
||||
return animSvg;
|
||||
}
|
||||
|
||||
function runSMILAnimation(parent, svgElement, elementsToAnimate) {
|
||||
if(elementsToAnimate.length === 0) return;
|
||||
|
||||
let animSvgElement = animateSVG(svgElement, elementsToAnimate);
|
||||
if(svgElement.parentNode == parent) {
|
||||
parent.removeChild(svgElement);
|
||||
parent.appendChild(animSvgElement);
|
||||
|
||||
}
|
||||
|
||||
// Replace the new svgElement (data has already been replaced)
|
||||
setTimeout(() => {
|
||||
if(animSvgElement.parentNode == parent) {
|
||||
parent.removeChild(animSvgElement);
|
||||
parent.appendChild(svgElement);
|
||||
}
|
||||
}, REPLACE_ALL_NEW_DUR);
|
||||
}
|
||||
|
||||
class BaseChart {
|
||||
constructor({
|
||||
height = 240,
|
||||
@ -825,7 +1013,6 @@ class BaseChart {
|
||||
_setup() {
|
||||
this.bindWindowEvents();
|
||||
this.setupConstants();
|
||||
this.prepareData();
|
||||
this.setupComponents();
|
||||
|
||||
this.setMargins();
|
||||
@ -879,7 +1066,7 @@ class BaseChart {
|
||||
this.calcWidth();
|
||||
|
||||
// refresh conponent with chart
|
||||
this.refresh();
|
||||
this.refresh(this.data);
|
||||
|
||||
this.makeChartArea();
|
||||
this.setComponentParent();
|
||||
@ -890,15 +1077,16 @@ class BaseChart {
|
||||
|
||||
// first time plain render, so no rerender
|
||||
this.renderComponents();
|
||||
this.renderConstants();
|
||||
|
||||
if(this.config.animate) this.update(this.firstUpdateData);
|
||||
}
|
||||
|
||||
update() {
|
||||
update(data) {
|
||||
// difference from draw(): yes you do rerender everything here as well,
|
||||
// but not things like the chart itself or layers, mosty only at component level
|
||||
// HERE IS WHERE THE ACTUAL STATE CHANGES, and old one matters, not in draw
|
||||
this.refresh();
|
||||
this.refresh(data);
|
||||
this.reRender();
|
||||
}
|
||||
|
||||
@ -915,11 +1103,11 @@ class BaseChart {
|
||||
this.width = this.baseWidth - (this.translateXLeft + this.translateXRight);
|
||||
}
|
||||
|
||||
refresh() { //?? refresh?
|
||||
this.oldState = this.state ? Object.assign({}, this.state) : {};
|
||||
this.intermedState = {};
|
||||
refresh(data) { //?? refresh?
|
||||
this.oldState = this.state ? JSON.parse(JSON.stringify(this.state)) : {};
|
||||
this.intermedState = {}; // use this for the extra position problems?
|
||||
|
||||
this.prepareData();
|
||||
this.prepareData(data);
|
||||
this.reCalc();
|
||||
this.refreshRenderer();
|
||||
}
|
||||
@ -931,7 +1119,7 @@ class BaseChart {
|
||||
this.baseWidth,
|
||||
this.baseHeight
|
||||
);
|
||||
this.svg_defs = makeSVGDefs(this.svg);
|
||||
this.svgDefs = makeSVGDefs(this.svg);
|
||||
|
||||
this.drawArea = makeSVGGroup(
|
||||
this.svg,
|
||||
@ -942,6 +1130,8 @@ class BaseChart {
|
||||
|
||||
prepareData() {}
|
||||
|
||||
renderConstants() {}
|
||||
|
||||
reCalc() {}
|
||||
// Will update values(state)
|
||||
// Will recalc specific parts depending on the update
|
||||
@ -953,8 +1143,9 @@ class BaseChart {
|
||||
this.renderComponents();
|
||||
return;
|
||||
}
|
||||
this.intermedState = this.calcIntermedState();
|
||||
this.animateComponents();
|
||||
this.elementsToAnimate = [];
|
||||
this.loadAnimatedComponents();
|
||||
runSMILAnimation(this.chartWrapper, this.svg, this.elementsToAnimate);
|
||||
setTimeout(() => {
|
||||
this.renderComponents();
|
||||
}, 400);
|
||||
@ -962,15 +1153,11 @@ class BaseChart {
|
||||
// (opt, should not redraw if still in animate?)
|
||||
}
|
||||
|
||||
calcIntermedState() {
|
||||
this.intermedState = {};
|
||||
}
|
||||
|
||||
// convenient component array abstractions
|
||||
setComponentParent() { this.components.forEach(c => c.setupParent(this.drawArea)); };
|
||||
makeComponentLayers() { this.components.forEach(c => c.makeLayer()); }
|
||||
renderComponents() { this.components.forEach(c => c.render()); }
|
||||
animateComponents() { this.components.forEach(c => c.animate()); }
|
||||
loadAnimatedComponents() { this.components.forEach(c => c.loadAnimatedComponents()); }
|
||||
|
||||
renderLegend() {}
|
||||
|
||||
@ -1053,154 +1240,15 @@ class ChartComponent {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
loadAnimatedComponents() {
|
||||
this.animate(this.store);
|
||||
}
|
||||
|
||||
makeLayer() {
|
||||
this.layer = makeSVGGroup(this.parent, this.layerClass, this.layerTransform);
|
||||
}
|
||||
}
|
||||
|
||||
const REPLACE_ALL_NEW_DUR = 250;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// export function animateXLines(animator, lines, oldX, newX) {
|
||||
// // this.xAxisLines.map((xLine, i) => {
|
||||
// return lines.map((xLine, i) => {
|
||||
// return animator.verticalLine(xLine, newX[i], oldX[i]);
|
||||
// });
|
||||
// }
|
||||
|
||||
// export function animateYLines(animator, lines, oldY, newY) {
|
||||
// // this.yAxisLines.map((yLine, i) => {
|
||||
// lines.map((yLine, i) => {
|
||||
// return animator.horizontalLine(yLine, newY[i], oldY[i]);
|
||||
// });
|
||||
// }
|
||||
|
||||
// Leveraging SMIL Animations
|
||||
|
||||
const EASING = {
|
||||
ease: "0.25 0.1 0.25 1",
|
||||
linear: "0 0 1 1",
|
||||
// easein: "0.42 0 1 1",
|
||||
easein: "0.1 0.8 0.2 1",
|
||||
easeout: "0 0 0.58 1",
|
||||
easeinout: "0.42 0 0.58 1"
|
||||
};
|
||||
|
||||
function animateSVGElement(element, props, dur, easingType="linear", type=undefined, oldValues={}) {
|
||||
|
||||
let animElement = element.cloneNode(true);
|
||||
let newElement = element.cloneNode(true);
|
||||
|
||||
for(var attributeName in props) {
|
||||
let animateElement;
|
||||
if(attributeName === 'transform') {
|
||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
|
||||
} else {
|
||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animate");
|
||||
}
|
||||
let currentValue = oldValues[attributeName] || element.getAttribute(attributeName);
|
||||
let value = props[attributeName];
|
||||
|
||||
let animAttr = {
|
||||
attributeName: attributeName,
|
||||
from: currentValue,
|
||||
to: value,
|
||||
begin: "0s",
|
||||
dur: dur/1000 + "s",
|
||||
values: currentValue + ";" + value,
|
||||
keySplines: EASING[easingType],
|
||||
keyTimes: "0;1",
|
||||
calcMode: "spline",
|
||||
fill: 'freeze'
|
||||
};
|
||||
|
||||
if(type) {
|
||||
animAttr["type"] = type;
|
||||
}
|
||||
|
||||
for (var i in animAttr) {
|
||||
animateElement.setAttribute(i, animAttr[i]);
|
||||
}
|
||||
|
||||
animElement.appendChild(animateElement);
|
||||
|
||||
if(type) {
|
||||
newElement.setAttribute(attributeName, `translate(${value})`);
|
||||
} else {
|
||||
newElement.setAttribute(attributeName, value);
|
||||
}
|
||||
}
|
||||
|
||||
return [animElement, newElement];
|
||||
}
|
||||
|
||||
function transform(element, style) { // eslint-disable-line no-unused-vars
|
||||
element.style.transform = style;
|
||||
element.style.webkitTransform = style;
|
||||
element.style.msTransform = style;
|
||||
element.style.mozTransform = style;
|
||||
element.style.oTransform = style;
|
||||
}
|
||||
|
||||
function animateSVG(svgContainer, elements) {
|
||||
let newElements = [];
|
||||
let animElements = [];
|
||||
|
||||
elements.map(element => {
|
||||
let obj = element[0];
|
||||
let parent = obj.unit.parentNode;
|
||||
|
||||
let animElement, newElement;
|
||||
|
||||
element[0] = obj.unit;
|
||||
[animElement, newElement] = animateSVGElement(...element);
|
||||
|
||||
newElements.push(newElement);
|
||||
animElements.push([animElement, parent]);
|
||||
|
||||
parent.replaceChild(animElement, obj.unit);
|
||||
|
||||
if(obj.array) {
|
||||
obj.array[obj.index] = newElement;
|
||||
} else {
|
||||
obj.object[obj.key] = newElement;
|
||||
}
|
||||
});
|
||||
|
||||
let animSvg = svgContainer.cloneNode(true);
|
||||
|
||||
animElements.map((animElement, i) => {
|
||||
animElement[1].replaceChild(newElements[i], animElement[0]);
|
||||
elements[i][0] = newElements[i];
|
||||
});
|
||||
|
||||
return animSvg;
|
||||
}
|
||||
|
||||
function runSMILAnimation(parent, svgElement, elementsToAnimate) {
|
||||
if(elementsToAnimate.length === 0) return;
|
||||
|
||||
let animSvgElement = animateSVG(svgElement, elementsToAnimate);
|
||||
if(svgElement.parentNode == parent) {
|
||||
parent.removeChild(svgElement);
|
||||
parent.appendChild(animSvgElement);
|
||||
|
||||
}
|
||||
|
||||
// Replace the new svgElement (data has already been replaced)
|
||||
setTimeout(() => {
|
||||
if(animSvgElement.parentNode == parent) {
|
||||
parent.removeChild(animSvgElement);
|
||||
parent.appendChild(svgElement);
|
||||
}
|
||||
}, REPLACE_ALL_NEW_DUR);
|
||||
}
|
||||
|
||||
function normalize(x) {
|
||||
// Calculates mantissa and exponent of a number
|
||||
// Returns normalized number and exponent
|
||||
@ -1429,18 +1477,26 @@ class AxisChart extends BaseChart {
|
||||
//
|
||||
}
|
||||
|
||||
prepareData() {
|
||||
setupConstants() {
|
||||
this.state = {
|
||||
xAxisLabels: [],
|
||||
xAxisPositions: [],
|
||||
};
|
||||
|
||||
this.prepareYAxis();
|
||||
}
|
||||
|
||||
prepareData(data) {
|
||||
let s = this.state;
|
||||
|
||||
s.xAxisLabels = this.data.labels || [];
|
||||
s.xAxisPositions = [];
|
||||
s.xAxisLabels = data.labels || [];
|
||||
|
||||
s.datasetLength = s.xAxisLabels.length;
|
||||
|
||||
let zeroArray = new Array(s.datasetLength).fill(0);
|
||||
|
||||
s.datasets = this.data.datasets; // whole dataset info too
|
||||
if(!this.data.datasets) {
|
||||
s.datasets = data.datasets; // whole dataset info too
|
||||
if(!data.datasets) {
|
||||
// default
|
||||
s.datasets = [{
|
||||
values: zeroArray // Proof that state version will be seen instead of this.data
|
||||
@ -1467,9 +1523,6 @@ class AxisChart extends BaseChart {
|
||||
});
|
||||
|
||||
s.noOfDatasets = s.datasets.length;
|
||||
|
||||
// s.yAxis = [];
|
||||
this.prepareYAxis();
|
||||
}
|
||||
|
||||
prepareYAxis() {
|
||||
@ -1482,17 +1535,12 @@ class AxisChart extends BaseChart {
|
||||
reCalc() {
|
||||
let s = this.state;
|
||||
|
||||
// X
|
||||
s.xAxisLabels = this.data.labels;
|
||||
this.calcXPositions();
|
||||
|
||||
// Y
|
||||
s.datasetsLabels = this.data.datasets.map(d => d.name);
|
||||
|
||||
this.setYAxis();
|
||||
|
||||
this.calcYUnits();
|
||||
|
||||
this.calcYMaximums();
|
||||
|
||||
// should be state
|
||||
@ -1572,18 +1620,14 @@ class AxisChart extends BaseChart {
|
||||
// this.bind_units(units_array);
|
||||
// }
|
||||
|
||||
this.yMarkerLines = {};
|
||||
this.xMarkerLines = {};
|
||||
|
||||
// Marker Regions
|
||||
|
||||
this.components = [
|
||||
// temp
|
||||
// this.yAxesAux,
|
||||
...this.getYAxesComponents(),
|
||||
this.getXAxisComponents(),
|
||||
// this.yMarkerLines,
|
||||
// this.xMarkerLines,
|
||||
// this.getYMarkerLines(),
|
||||
// this.getXMarkerLines(),
|
||||
// TODO: regions too?
|
||||
...this.getPathComponents(),
|
||||
...this.getDataUnitsComponents(this.config),
|
||||
];
|
||||
@ -1598,7 +1642,32 @@ class AxisChart extends BaseChart {
|
||||
this.renderer.yLine(position, s.yAxis.labels[i], {pos:'right'})
|
||||
);
|
||||
},
|
||||
animate: () => {}
|
||||
animate: (yLines) => {
|
||||
// Equilize
|
||||
let newY = this.state.yAxis.positions;
|
||||
let oldY = this.oldState.yAxis.positions;
|
||||
|
||||
let extra = newY.length - oldY.length;
|
||||
let lastLine = yLines[yLines.length - 1];
|
||||
let parentNode = lastLine.parentNode;
|
||||
|
||||
[oldY, newY] = equilizeNoOfElements(oldY, newY);
|
||||
// console.log(newY.slice(), oldY.slice());
|
||||
if(extra > 0) {
|
||||
for(var i = 0; i<extra; i++) {
|
||||
let line = lastLine.cloneNode(true);
|
||||
parentNode.appendChild(line);
|
||||
yLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
yLines.map((line, i) => {
|
||||
// console.log(line, newY[i], oldY[i]);
|
||||
this.elementsToAnimate.push(this.renderer.translateHoriLine(
|
||||
line, newY[i], oldY[i]
|
||||
));
|
||||
});
|
||||
}
|
||||
})];
|
||||
}
|
||||
|
||||
@ -1607,22 +1676,39 @@ class AxisChart extends BaseChart {
|
||||
layerClass: 'x axis',
|
||||
make: () => {
|
||||
let s = this.state;
|
||||
// TODO: xAxis Label spacing
|
||||
return s.xAxisPositions.map((position, i) =>
|
||||
this.renderer.xLine(position, s.xAxisLabels[i], {pos:'top'})
|
||||
);
|
||||
},
|
||||
// animate: (animator, lines, oldX, newX) => {
|
||||
// lines.map((xLine, i) => {
|
||||
// elements_to_animate.push(animator.verticalLine(
|
||||
// xLine, newX[i], oldX[i]
|
||||
// ));
|
||||
// });
|
||||
// }
|
||||
animate: (xLines) => {
|
||||
// Equilize
|
||||
let newX = this.state.xAxisPositions;
|
||||
let oldX = this.oldState.xAxisPositions;
|
||||
|
||||
this.oldState.xExtra = newX.length - oldX.length;
|
||||
let lastLine = xLines[xLines.length - 1];
|
||||
let parentNode = lastLine.parentNode;
|
||||
|
||||
[oldX, newX] = equilizeNoOfElements(oldX, newX);
|
||||
if(this.oldState.xExtra > 0) {
|
||||
for(var i = 0; i<this.oldState.xExtra; i++) {
|
||||
let line = lastLine.cloneNode(true);
|
||||
parentNode.appendChild(line);
|
||||
xLines.push(line);
|
||||
}
|
||||
}
|
||||
xLines.map((line, i) => {
|
||||
this.elementsToAnimate.push(this.renderer.translateVertLine(
|
||||
line, newX[i], oldX[i]
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDataUnitsComponents() {
|
||||
return this.state.datasets.map((d, index) => {
|
||||
return this.data.datasets.map((d, index) => {
|
||||
return new ChartComponent({
|
||||
layerClass: 'dataset-units dataset-' + index,
|
||||
make: () => {
|
||||
@ -1637,11 +1723,39 @@ class AxisChart extends BaseChart {
|
||||
this.colors[index],
|
||||
j,
|
||||
index,
|
||||
this.state.datasetLength
|
||||
this.state.noOfDatasets
|
||||
);
|
||||
});
|
||||
},
|
||||
animate: () => {}
|
||||
animate: (svgUnits) => {
|
||||
let unitType = this.unitArgs.type;
|
||||
|
||||
// have been updated in axis render;
|
||||
let newX = this.state.xAxisPositions;
|
||||
let newY = this.state.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(this.renderer['animate' + unitType](
|
||||
unit, // unit, with info to replace where it came from in the data
|
||||
newX[i],
|
||||
newY[i],
|
||||
index,
|
||||
this.state.noOfDatasets
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1650,6 +1764,14 @@ class AxisChart extends BaseChart {
|
||||
return [];
|
||||
}
|
||||
|
||||
getYMarkerLines() {
|
||||
return [];
|
||||
}
|
||||
|
||||
getXMarkerLines() {
|
||||
return [];
|
||||
}
|
||||
|
||||
refreshRenderer() {
|
||||
// These args are basically the current state of the chart,
|
||||
// with constant and alive params mixed
|
||||
@ -1670,6 +1792,32 @@ class AxisChart extends BaseChart {
|
||||
}
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
addDataPoint(label, datasetValues, index=this.state.datasetLength) {
|
||||
// console.log(label, datasetValues, this.data.labels);
|
||||
this.data.labels.splice(index, 0, label);
|
||||
this.data.datasets.map((d, i) => {
|
||||
d.values.splice(index, 0, datasetValues[i]);
|
||||
});
|
||||
// console.log(this.data);
|
||||
this.update(this.data);
|
||||
}
|
||||
|
||||
removeDataPoint(index = this.state.datasetLength-1) {
|
||||
this.data.labels.splice(index, 1);
|
||||
this.data.datasets.map(d => {
|
||||
d.values.splice(index, 1);
|
||||
});
|
||||
this.update(this.data);
|
||||
}
|
||||
|
||||
updateData() {
|
||||
// animate if same no. of datasets,
|
||||
// else return new chart
|
||||
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
class BarChart extends AxisChart {
|
||||
@ -1794,46 +1942,84 @@ class LineChart extends AxisChart {
|
||||
getDataUnitsComponents(config) {
|
||||
if(!config.showDots) {
|
||||
return [];
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return super.getDataUnitsComponents();
|
||||
}
|
||||
}
|
||||
|
||||
getPathComponents() {
|
||||
return this.state.datasets.map((d, index) => {
|
||||
return this.data.datasets.map((d, index) => {
|
||||
return new ChartComponent({
|
||||
layerClass: 'path dataset-path',
|
||||
make: () => {
|
||||
let d = this.state.datasets[index];
|
||||
let color = this.colors[index];
|
||||
|
||||
let pointsList = d.positions.map((y, i) => (this.state.xAxisPositions[i] + ',' + y));
|
||||
let pointsStr = pointsList.join("L");
|
||||
let path = makePath("M"+pointsStr, 'line-graph-path', color);
|
||||
|
||||
// HeatLine
|
||||
if(this.config.heatline) {
|
||||
let gradient_id = makeGradient(this.svg_defs, color);
|
||||
path.style.stroke = `url(#${gradient_id})`;
|
||||
}
|
||||
|
||||
let components = [path];
|
||||
|
||||
// Region
|
||||
if(this.config.regionFill) {
|
||||
let gradient_id_region = makeGradient(this.svg_defs, color, true);
|
||||
|
||||
let zeroLine = this.state.yAxis.zeroLine;
|
||||
let pathStr = "M" + `0,${zeroLine}L` + pointsStr + `L${this.width},${zeroLine}`;
|
||||
components.push(makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id_region})`));
|
||||
}
|
||||
|
||||
return components;
|
||||
return this.getPaths(
|
||||
d.positions,
|
||||
this.state.xAxisPositions,
|
||||
color,
|
||||
this.config.heatline,
|
||||
this.config.regionFill
|
||||
);
|
||||
},
|
||||
animate: () => {}
|
||||
animate: (paths) => {
|
||||
let newX = this.state.xAxisPositions;
|
||||
let newY = this.state.datasets[index].positions;
|
||||
|
||||
let oldX = this.oldState.xAxisPositions;
|
||||
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 = this.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")));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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 ScatterChart extends LineChart {
|
||||
@ -1877,7 +2063,10 @@ class MultiAxisChart extends AxisChart {
|
||||
this.translateXRight = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
|
||||
}
|
||||
|
||||
prepareYAxis() {
|
||||
prepareYAxis() { }
|
||||
|
||||
prepareData(data) {
|
||||
super.prepareData(data);
|
||||
let sets = this.state.datasets;
|
||||
// let axesLeft = sets.filter(d => d.axisPosition === 'left');
|
||||
// let axesRight = sets.filter(d => d.axisPosition === 'right');
|
||||
@ -1926,8 +2115,22 @@ class MultiAxisChart extends AxisChart {
|
||||
});
|
||||
}
|
||||
|
||||
renderConstants() {
|
||||
this.state.datasets.map(d => {
|
||||
let guidePos = d.yAxis.position === 'left'
|
||||
? -1 * d.yAxis.index * Y_AXIS_MARGIN
|
||||
: this.width + d.yAxis.index * Y_AXIS_MARGIN;
|
||||
this.renderer.xLine(guidePos, '', {
|
||||
pos:'top',
|
||||
mode: 'span',
|
||||
stroke: this.colors[i],
|
||||
className: 'y-axis-guide'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getYAxesComponents() {
|
||||
return this.state.datasets.map((e, i) => {
|
||||
return this.data.datasets.map((e, i) => {
|
||||
return new ChartComponent({
|
||||
layerClass: 'y axis y-axis-' + i,
|
||||
make: () => {
|
||||
@ -1940,30 +2143,18 @@ class MultiAxisChart extends AxisChart {
|
||||
stroke: this.colors[i]
|
||||
};
|
||||
|
||||
let yAxisLines = yAxis.positions.map((position, j) =>
|
||||
return yAxis.positions.map((position, j) =>
|
||||
this.renderer.yLine(position, yAxis.labels[j], options)
|
||||
);
|
||||
|
||||
let guidePos = yAxis.position === 'left'
|
||||
? -1 * yAxis.index * Y_AXIS_MARGIN
|
||||
: this.width + yAxis.index * Y_AXIS_MARGIN;
|
||||
|
||||
yAxisLines.push(this.renderer.xLine(guidePos, '', {
|
||||
pos:'top',
|
||||
mode: 'span',
|
||||
stroke: this.colors[i],
|
||||
className: 'y-axis-guide'
|
||||
}));
|
||||
|
||||
return yAxisLines;
|
||||
},
|
||||
animate: () => {}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// TODO remove renderer zeroline from above and below
|
||||
getDataUnitsComponents() {
|
||||
return this.state.datasets.map((d, index) => {
|
||||
return this.data.datasets.map((d, index) => {
|
||||
return new ChartComponent({
|
||||
layerClass: 'dataset-units dataset-' + index,
|
||||
make: () => {
|
||||
@ -1985,7 +2176,38 @@ class MultiAxisChart extends AxisChart {
|
||||
);
|
||||
});
|
||||
},
|
||||
animate: () => {}
|
||||
animate: (svgUnits) => {
|
||||
let d = this.state.datasets[index];
|
||||
let unitType = this.unitArgs.type;
|
||||
|
||||
// have been updated in axis render;
|
||||
let newX = this.state.xAxisPositions;
|
||||
let newY = this.state.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);
|
||||
}
|
||||
}
|
||||
|
||||
this.renderer.setZeroline(d.yAxis.zeroLine);
|
||||
|
||||
svgUnits.map((unit, i) => {
|
||||
if(newX[i] === undefined || newY[i] === undefined) return;
|
||||
this.elementsToAnimate.push(this.renderer['animate' + unitType](
|
||||
unit, // unit, with info to replace where it came from in the data
|
||||
newX[i],
|
||||
newY[i],
|
||||
index,
|
||||
this.state.noOfDatasets
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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
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
@ -249,23 +249,23 @@ let chart_update_buttons = document.querySelector('.chart-update-buttons');
|
||||
|
||||
chart_update_buttons.querySelector('[data-update="random"]').addEventListener("click", (e) => {
|
||||
shuffle(update_data_all_indices);
|
||||
update_chart.updateData(
|
||||
[{values: get_update_data(update_data_all_values)}],
|
||||
update_data_all_labels.slice(0, 10)
|
||||
);
|
||||
let data = {
|
||||
labels: update_data_all_labels.slice(0, 10),
|
||||
datasets: [{values: get_update_data(update_data_all_values)}],
|
||||
}
|
||||
update_chart.update(data);
|
||||
});
|
||||
|
||||
chart_update_buttons.querySelector('[data-update="add"]').addEventListener("click", (e) => {
|
||||
// NOTE: this ought to be problem, labels stay the same after update
|
||||
let index = update_chart.xAxisLabels.length; // last index to add
|
||||
let index = update_chart.state.datasetLength; // last index to add
|
||||
if(index >= update_data_all_indices.length) return;
|
||||
update_chart.add_data_point(
|
||||
[update_data_all_values[index]], update_data_all_labels[index]
|
||||
update_chart.addDataPoint(
|
||||
update_data_all_labels[index], [update_data_all_values[index]]
|
||||
);
|
||||
});
|
||||
|
||||
chart_update_buttons.querySelector('[data-update="remove"]').addEventListener("click", (e) => {
|
||||
update_chart.remove_data_point();
|
||||
update_chart.removeDataPoint();
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -32,18 +32,26 @@ export default class AxisChart extends BaseChart {
|
||||
//
|
||||
}
|
||||
|
||||
prepareData() {
|
||||
setupConstants() {
|
||||
this.state = {
|
||||
xAxisLabels: [],
|
||||
xAxisPositions: [],
|
||||
}
|
||||
|
||||
this.prepareYAxis();
|
||||
}
|
||||
|
||||
prepareData(data) {
|
||||
let s = this.state;
|
||||
|
||||
s.xAxisLabels = this.data.labels || [];
|
||||
s.xAxisPositions = [];
|
||||
s.xAxisLabels = data.labels || [];
|
||||
|
||||
s.datasetLength = s.xAxisLabels.length;
|
||||
|
||||
let zeroArray = new Array(s.datasetLength).fill(0);
|
||||
|
||||
s.datasets = this.data.datasets; // whole dataset info too
|
||||
if(!this.data.datasets) {
|
||||
s.datasets = data.datasets; // whole dataset info too
|
||||
if(!data.datasets) {
|
||||
// default
|
||||
s.datasets = [{
|
||||
values: zeroArray // Proof that state version will be seen instead of this.data
|
||||
@ -70,9 +78,6 @@ export default class AxisChart extends BaseChart {
|
||||
});
|
||||
|
||||
s.noOfDatasets = s.datasets.length;
|
||||
|
||||
// s.yAxis = [];
|
||||
this.prepareYAxis();
|
||||
}
|
||||
|
||||
prepareYAxis() {
|
||||
@ -85,17 +90,12 @@ export default class AxisChart extends BaseChart {
|
||||
reCalc() {
|
||||
let s = this.state;
|
||||
|
||||
// X
|
||||
s.xAxisLabels = this.data.labels;
|
||||
this.calcXPositions();
|
||||
|
||||
// Y
|
||||
s.datasetsLabels = this.data.datasets.map(d => d.name);
|
||||
|
||||
this.setYAxis();
|
||||
|
||||
this.calcYUnits();
|
||||
|
||||
this.calcYMaximums();
|
||||
|
||||
// should be state
|
||||
@ -175,18 +175,14 @@ export default class AxisChart extends BaseChart {
|
||||
// this.bind_units(units_array);
|
||||
// }
|
||||
|
||||
this.yMarkerLines = {};
|
||||
this.xMarkerLines = {};
|
||||
|
||||
// Marker Regions
|
||||
|
||||
this.components = [
|
||||
// temp
|
||||
// this.yAxesAux,
|
||||
...this.getYAxesComponents(),
|
||||
this.getXAxisComponents(),
|
||||
// this.yMarkerLines,
|
||||
// this.xMarkerLines,
|
||||
// this.getYMarkerLines(),
|
||||
// this.getXMarkerLines(),
|
||||
// TODO: regions too?
|
||||
...this.getPathComponents(),
|
||||
...this.getDataUnitsComponents(this.config),
|
||||
];
|
||||
@ -201,7 +197,32 @@ export default class AxisChart extends BaseChart {
|
||||
this.renderer.yLine(position, s.yAxis.labels[i], {pos:'right'})
|
||||
);
|
||||
},
|
||||
animate: () => {}
|
||||
animate: (yLines) => {
|
||||
// Equilize
|
||||
let newY = this.state.yAxis.positions;
|
||||
let oldY = this.oldState.yAxis.positions;
|
||||
|
||||
let extra = newY.length - oldY.length;
|
||||
let lastLine = yLines[yLines.length - 1];
|
||||
let parentNode = lastLine.parentNode;
|
||||
|
||||
[oldY, newY] = equilizeNoOfElements(oldY, newY);
|
||||
// console.log(newY.slice(), oldY.slice());
|
||||
if(extra > 0) {
|
||||
for(var i = 0; i<extra; i++) {
|
||||
let line = lastLine.cloneNode(true);
|
||||
parentNode.appendChild(line);
|
||||
yLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
yLines.map((line, i) => {
|
||||
// console.log(line, newY[i], oldY[i]);
|
||||
this.elementsToAnimate.push(this.renderer.translateHoriLine(
|
||||
line, newY[i], oldY[i]
|
||||
));
|
||||
});
|
||||
}
|
||||
})];
|
||||
}
|
||||
|
||||
@ -210,22 +231,39 @@ export default class AxisChart extends BaseChart {
|
||||
layerClass: 'x axis',
|
||||
make: () => {
|
||||
let s = this.state;
|
||||
// TODO: xAxis Label spacing
|
||||
return s.xAxisPositions.map((position, i) =>
|
||||
this.renderer.xLine(position, s.xAxisLabels[i], {pos:'top'})
|
||||
);
|
||||
},
|
||||
// animate: (animator, lines, oldX, newX) => {
|
||||
// lines.map((xLine, i) => {
|
||||
// elements_to_animate.push(animator.verticalLine(
|
||||
// xLine, newX[i], oldX[i]
|
||||
// ));
|
||||
// });
|
||||
// }
|
||||
animate: (xLines) => {
|
||||
// Equilize
|
||||
let newX = this.state.xAxisPositions;
|
||||
let oldX = this.oldState.xAxisPositions;
|
||||
|
||||
this.oldState.xExtra = newX.length - oldX.length;
|
||||
let lastLine = xLines[xLines.length - 1];
|
||||
let parentNode = lastLine.parentNode;
|
||||
|
||||
[oldX, newX] = equilizeNoOfElements(oldX, newX);
|
||||
if(this.oldState.xExtra > 0) {
|
||||
for(var i = 0; i<this.oldState.xExtra; i++) {
|
||||
let line = lastLine.cloneNode(true);
|
||||
parentNode.appendChild(line);
|
||||
xLines.push(line);
|
||||
}
|
||||
}
|
||||
xLines.map((line, i) => {
|
||||
this.elementsToAnimate.push(this.renderer.translateVertLine(
|
||||
line, newX[i], oldX[i]
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDataUnitsComponents() {
|
||||
return this.state.datasets.map((d, index) => {
|
||||
return this.data.datasets.map((d, index) => {
|
||||
return new ChartComponent({
|
||||
layerClass: 'dataset-units dataset-' + index,
|
||||
make: () => {
|
||||
@ -240,11 +278,39 @@ export default class AxisChart extends BaseChart {
|
||||
this.colors[index],
|
||||
j,
|
||||
index,
|
||||
this.state.datasetLength
|
||||
this.state.noOfDatasets
|
||||
);
|
||||
});
|
||||
},
|
||||
animate: () => {}
|
||||
animate: (svgUnits) => {
|
||||
let unitType = this.unitArgs.type;
|
||||
|
||||
// have been updated in axis render;
|
||||
let newX = this.state.xAxisPositions;
|
||||
let newY = this.state.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(this.renderer['animate' + unitType](
|
||||
unit, // unit, with info to replace where it came from in the data
|
||||
newX[i],
|
||||
newY[i],
|
||||
index,
|
||||
this.state.noOfDatasets
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -253,6 +319,14 @@ export default class AxisChart extends BaseChart {
|
||||
return [];
|
||||
}
|
||||
|
||||
getYMarkerLines() {
|
||||
return [];
|
||||
}
|
||||
|
||||
getXMarkerLines() {
|
||||
return [];
|
||||
}
|
||||
|
||||
refreshRenderer() {
|
||||
// These args are basically the current state of the chart,
|
||||
// with constant and alive params mixed
|
||||
@ -273,4 +347,30 @@ export default class AxisChart extends BaseChart {
|
||||
}
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
addDataPoint(label, datasetValues, index=this.state.datasetLength) {
|
||||
// console.log(label, datasetValues, this.data.labels);
|
||||
this.data.labels.splice(index, 0, label);
|
||||
this.data.datasets.map((d, i) => {
|
||||
d.values.splice(index, 0, datasetValues[i]);
|
||||
});
|
||||
// console.log(this.data);
|
||||
this.update(this.data);
|
||||
}
|
||||
|
||||
removeDataPoint(index = this.state.datasetLength-1) {
|
||||
this.data.labels.splice(index, 1);
|
||||
this.data.datasets.map(d => {
|
||||
d.values.splice(index, 1);
|
||||
});
|
||||
this.update(this.data);
|
||||
}
|
||||
|
||||
updateData() {
|
||||
// animate if same no. of datasets,
|
||||
// else return new chart
|
||||
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { makeSVGContainer, makeSVGDefs, makeSVGGroup } from '../utils/draw';
|
||||
import { getStringWidth } from '../utils/helpers';
|
||||
import { getColor, DEFAULT_COLORS } from '../utils/colors';
|
||||
import { getDifferentChart } from '../config';
|
||||
import { runSMILAnimation } from '../utils/animation';
|
||||
|
||||
export default class BaseChart {
|
||||
constructor({
|
||||
@ -127,7 +128,6 @@ export default class BaseChart {
|
||||
_setup() {
|
||||
this.bindWindowEvents();
|
||||
this.setupConstants();
|
||||
this.prepareData();
|
||||
this.setupComponents();
|
||||
|
||||
this.setMargins();
|
||||
@ -181,7 +181,7 @@ export default class BaseChart {
|
||||
this.calcWidth();
|
||||
|
||||
// refresh conponent with chart
|
||||
this.refresh();
|
||||
this.refresh(this.data);
|
||||
|
||||
this.makeChartArea();
|
||||
this.setComponentParent();
|
||||
@ -192,15 +192,16 @@ export default class BaseChart {
|
||||
|
||||
// first time plain render, so no rerender
|
||||
this.renderComponents();
|
||||
this.renderConstants();
|
||||
|
||||
if(this.config.animate) this.update(this.firstUpdateData);
|
||||
}
|
||||
|
||||
update() {
|
||||
update(data) {
|
||||
// difference from draw(): yes you do rerender everything here as well,
|
||||
// but not things like the chart itself or layers, mosty only at component level
|
||||
// HERE IS WHERE THE ACTUAL STATE CHANGES, and old one matters, not in draw
|
||||
this.refresh();
|
||||
this.refresh(data);
|
||||
this.reRender();
|
||||
}
|
||||
|
||||
@ -217,11 +218,11 @@ export default class BaseChart {
|
||||
this.width = this.baseWidth - (this.translateXLeft + this.translateXRight);
|
||||
}
|
||||
|
||||
refresh() { //?? refresh?
|
||||
this.oldState = this.state ? Object.assign({}, this.state) : {};
|
||||
this.intermedState = {};
|
||||
refresh(data) { //?? refresh?
|
||||
this.oldState = this.state ? JSON.parse(JSON.stringify(this.state)) : {};
|
||||
this.intermedState = {}; // use this for the extra position problems?
|
||||
|
||||
this.prepareData();
|
||||
this.prepareData(data);
|
||||
this.reCalc();
|
||||
this.refreshRenderer();
|
||||
}
|
||||
@ -233,7 +234,7 @@ export default class BaseChart {
|
||||
this.baseWidth,
|
||||
this.baseHeight
|
||||
);
|
||||
this.svg_defs = makeSVGDefs(this.svg);
|
||||
this.svgDefs = makeSVGDefs(this.svg);
|
||||
|
||||
this.drawArea = makeSVGGroup(
|
||||
this.svg,
|
||||
@ -244,6 +245,8 @@ export default class BaseChart {
|
||||
|
||||
prepareData() {}
|
||||
|
||||
renderConstants() {}
|
||||
|
||||
reCalc() {}
|
||||
// Will update values(state)
|
||||
// Will recalc specific parts depending on the update
|
||||
@ -255,8 +258,9 @@ export default class BaseChart {
|
||||
this.renderComponents();
|
||||
return;
|
||||
}
|
||||
this.intermedState = this.calcIntermedState();
|
||||
this.animateComponents();
|
||||
this.elementsToAnimate = [];
|
||||
this.loadAnimatedComponents();
|
||||
runSMILAnimation(this.chartWrapper, this.svg, this.elementsToAnimate);
|
||||
setTimeout(() => {
|
||||
this.renderComponents();
|
||||
}, 400);
|
||||
@ -264,15 +268,11 @@ export default class BaseChart {
|
||||
// (opt, should not redraw if still in animate?)
|
||||
}
|
||||
|
||||
calcIntermedState() {
|
||||
this.intermedState = {};
|
||||
}
|
||||
|
||||
// convenient component array abstractions
|
||||
setComponentParent() { this.components.forEach(c => c.setupParent(this.drawArea)); };
|
||||
makeComponentLayers() { this.components.forEach(c => c.makeLayer()); }
|
||||
renderComponents() { this.components.forEach(c => c.render()); }
|
||||
animateComponents() { this.components.forEach(c => c.animate()); }
|
||||
loadAnimatedComponents() { this.components.forEach(c => c.loadAnimatedComponents()); }
|
||||
|
||||
renderLegend() {}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import AxisChart from './AxisChart';
|
||||
import { ChartComponent } from '../objects/ChartComponent';
|
||||
import { makeSVGGroup, makePath, makeGradient } from '../utils/draw';
|
||||
import { equilizeNoOfElements } from '../utils/draw-utils';
|
||||
|
||||
export default class LineChart extends AxisChart {
|
||||
constructor(args) {
|
||||
@ -42,44 +43,82 @@ export default class LineChart extends AxisChart {
|
||||
getDataUnitsComponents(config) {
|
||||
if(!config.showDots) {
|
||||
return [];
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return super.getDataUnitsComponents();
|
||||
}
|
||||
}
|
||||
|
||||
getPathComponents() {
|
||||
return this.state.datasets.map((d, index) => {
|
||||
return this.data.datasets.map((d, index) => {
|
||||
return new ChartComponent({
|
||||
layerClass: 'path dataset-path',
|
||||
make: () => {
|
||||
let d = this.state.datasets[index];
|
||||
let color = this.colors[index];
|
||||
|
||||
let pointsList = d.positions.map((y, i) => (this.state.xAxisPositions[i] + ',' + y));
|
||||
let pointsStr = pointsList.join("L");
|
||||
let path = makePath("M"+pointsStr, 'line-graph-path', color);
|
||||
|
||||
// HeatLine
|
||||
if(this.config.heatline) {
|
||||
let gradient_id = makeGradient(this.svg_defs, color);
|
||||
path.style.stroke = `url(#${gradient_id})`;
|
||||
}
|
||||
|
||||
let components = [path];
|
||||
|
||||
// Region
|
||||
if(this.config.regionFill) {
|
||||
let gradient_id_region = makeGradient(this.svg_defs, color, true);
|
||||
|
||||
let zeroLine = this.state.yAxis.zeroLine;
|
||||
let pathStr = "M" + `0,${zeroLine}L` + pointsStr + `L${this.width},${zeroLine}`;
|
||||
components.push(makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id_region})`));
|
||||
}
|
||||
|
||||
return components;
|
||||
return this.getPaths(
|
||||
d.positions,
|
||||
this.state.xAxisPositions,
|
||||
color,
|
||||
this.config.heatline,
|
||||
this.config.regionFill
|
||||
);
|
||||
},
|
||||
animate: () => {}
|
||||
animate: (paths) => {
|
||||
let newX = this.state.xAxisPositions;
|
||||
let newY = this.state.datasets[index].positions;
|
||||
|
||||
let oldX = this.oldState.xAxisPositions;
|
||||
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 = this.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")));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,10 @@ export default class MultiAxisChart extends AxisChart {
|
||||
this.translateXRight = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
|
||||
}
|
||||
|
||||
prepareYAxis() {
|
||||
prepareYAxis() { }
|
||||
|
||||
prepareData(data) {
|
||||
super.prepareData(data);
|
||||
let sets = this.state.datasets;
|
||||
// let axesLeft = sets.filter(d => d.axisPosition === 'left');
|
||||
// let axesRight = sets.filter(d => d.axisPosition === 'right');
|
||||
@ -66,8 +69,22 @@ export default class MultiAxisChart extends AxisChart {
|
||||
});
|
||||
}
|
||||
|
||||
renderConstants() {
|
||||
this.state.datasets.map(d => {
|
||||
let guidePos = d.yAxis.position === 'left'
|
||||
? -1 * d.yAxis.index * Y_AXIS_MARGIN
|
||||
: this.width + d.yAxis.index * Y_AXIS_MARGIN;
|
||||
this.renderer.xLine(guidePos, '', {
|
||||
pos:'top',
|
||||
mode: 'span',
|
||||
stroke: this.colors[i],
|
||||
className: 'y-axis-guide'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
getYAxesComponents() {
|
||||
return this.state.datasets.map((e, i) => {
|
||||
return this.data.datasets.map((e, i) => {
|
||||
return new ChartComponent({
|
||||
layerClass: 'y axis y-axis-' + i,
|
||||
make: () => {
|
||||
@ -80,30 +97,18 @@ export default class MultiAxisChart extends AxisChart {
|
||||
stroke: this.colors[i]
|
||||
};
|
||||
|
||||
let yAxisLines = yAxis.positions.map((position, j) =>
|
||||
return yAxis.positions.map((position, j) =>
|
||||
this.renderer.yLine(position, yAxis.labels[j], options)
|
||||
);
|
||||
|
||||
let guidePos = yAxis.position === 'left'
|
||||
? -1 * yAxis.index * Y_AXIS_MARGIN
|
||||
: this.width + yAxis.index * Y_AXIS_MARGIN;
|
||||
|
||||
yAxisLines.push(this.renderer.xLine(guidePos, '', {
|
||||
pos:'top',
|
||||
mode: 'span',
|
||||
stroke: this.colors[i],
|
||||
className: 'y-axis-guide'
|
||||
}));
|
||||
|
||||
return yAxisLines;
|
||||
},
|
||||
animate: () => {}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// TODO remove renderer zeroline from above and below
|
||||
getDataUnitsComponents() {
|
||||
return this.state.datasets.map((d, index) => {
|
||||
return this.data.datasets.map((d, index) => {
|
||||
return new ChartComponent({
|
||||
layerClass: 'dataset-units dataset-' + index,
|
||||
make: () => {
|
||||
@ -125,7 +130,38 @@ export default class MultiAxisChart extends AxisChart {
|
||||
);
|
||||
});
|
||||
},
|
||||
animate: () => {}
|
||||
animate: (svgUnits) => {
|
||||
let d = this.state.datasets[index];
|
||||
let unitType = this.unitArgs.type;
|
||||
|
||||
// have been updated in axis render;
|
||||
let newX = this.state.xAxisPositions;
|
||||
let newY = this.state.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);
|
||||
}
|
||||
}
|
||||
|
||||
this.renderer.setZeroline(d.yAxis.zeroLine);
|
||||
|
||||
svgUnits.map((unit, i) => {
|
||||
if(newX[i] === undefined || newY[i] === undefined) return;
|
||||
this.elementsToAnimate.push(this.renderer['animate' + unitType](
|
||||
unit, // unit, with info to replace where it came from in the data
|
||||
newX[i],
|
||||
newY[i],
|
||||
index,
|
||||
this.state.noOfDatasets
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -31,6 +31,10 @@ export class ChartComponent {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
loadAnimatedComponents() {
|
||||
this.animate(this.store);
|
||||
}
|
||||
|
||||
makeLayer() {
|
||||
this.layer = makeSVGGroup(this.parent, this.layerClass, this.layerTransform);
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { getBarHeightAndYAttr } from './draw-utils';
|
||||
|
||||
const UNIT_ANIM_DUR = 350;
|
||||
const PATH_ANIM_DUR = 650;
|
||||
const MARKER_LINE_ANIM_DUR = UNIT_ANIM_DUR;
|
||||
export const UNIT_ANIM_DUR = 350;
|
||||
export const PATH_ANIM_DUR = 350;
|
||||
export const MARKER_LINE_ANIM_DUR = UNIT_ANIM_DUR;
|
||||
export const REPLACE_ALL_NEW_DUR = 250;
|
||||
|
||||
const STD_EASING = 'easein';
|
||||
export const STD_EASING = 'easein';
|
||||
|
||||
export var Animator = (function() {
|
||||
var Animator = function(totalHeight, totalWidth, zeroLine, avgUnitWidth) {
|
||||
@ -67,11 +67,11 @@ export var Animator = (function() {
|
||||
];
|
||||
},
|
||||
|
||||
verticalLine: function(xLine, newX, oldX) {
|
||||
translateVertLine: function(xLine, newX, oldX) {
|
||||
return this.translate(xLine, [oldX, 0], [newX, 0], MARKER_LINE_ANIM_DUR);
|
||||
},
|
||||
|
||||
horizontalLine: function(yLine, newY, oldY) {
|
||||
translateHoriLine: function(yLine, newY, oldY) {
|
||||
return this.translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR);
|
||||
}
|
||||
};
|
||||
@ -79,37 +79,4 @@ export var Animator = (function() {
|
||||
return Animator;
|
||||
})();
|
||||
|
||||
export function animate_path(animator, d, newX, newY) {
|
||||
const newPointsList = newY.map((y, i) => (newX[i] + ',' + y));
|
||||
return this.animator.path(d, newPointsList.join("L"));
|
||||
}
|
||||
|
||||
export function animate_units(animator, d, newX, newY, type, noOfDatasets) {
|
||||
// let type = this.unit_args.type;
|
||||
|
||||
return d.svg_units.map((unit, i) => {
|
||||
// if(newX[i] === undefined || newY[i] === undefined) return;
|
||||
return animator[type](
|
||||
{unit:unit, array:d.svg_units, index: i}, // unit, with info to replace where it came from in the data
|
||||
newX[i],
|
||||
newY[i],
|
||||
d.index,
|
||||
noOfDatasets
|
||||
// this.y.length
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// export function animateXLines(animator, lines, oldX, newX) {
|
||||
// // this.xAxisLines.map((xLine, i) => {
|
||||
// return lines.map((xLine, i) => {
|
||||
// return animator.verticalLine(xLine, newX[i], oldX[i]);
|
||||
// });
|
||||
// }
|
||||
|
||||
// export function animateYLines(animator, lines, oldY, newY) {
|
||||
// // this.yAxisLines.map((yLine, i) => {
|
||||
// lines.map((yLine, i) => {
|
||||
// return animator.horizontalLine(yLine, newY[i], oldY[i]);
|
||||
// });
|
||||
// }
|
||||
|
||||
@ -72,24 +72,18 @@ function animateSVG(svgContainer, elements) {
|
||||
let animElements = [];
|
||||
|
||||
elements.map(element => {
|
||||
let obj = element[0];
|
||||
let parent = obj.unit.parentNode;
|
||||
let unit = element[0];
|
||||
let parent = unit.parentNode;
|
||||
|
||||
let animElement, newElement;
|
||||
|
||||
element[0] = obj.unit;
|
||||
element[0] = unit;
|
||||
[animElement, newElement] = animateSVGElement(...element);
|
||||
|
||||
newElements.push(newElement);
|
||||
animElements.push([animElement, parent]);
|
||||
|
||||
parent.replaceChild(animElement, obj.unit);
|
||||
|
||||
if(obj.array) {
|
||||
obj.array[obj.index] = newElement;
|
||||
} else {
|
||||
obj.object[obj.key] = newElement;
|
||||
}
|
||||
parent.replaceChild(animElement, unit);
|
||||
});
|
||||
|
||||
let animSvg = svgContainer.cloneNode(true);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { getBarHeightAndYAttr } from './draw-utils';
|
||||
import { STD_EASING, UNIT_ANIM_DUR, MARKER_LINE_ANIM_DUR, PATH_ANIM_DUR } from './animate';
|
||||
|
||||
const AXIS_TICK_LENGTH = 6;
|
||||
const LABEL_MARGIN = 4;
|
||||
@ -230,7 +231,7 @@ export class AxisChartRenderer {
|
||||
this.zeroLine = zeroLine;
|
||||
}
|
||||
|
||||
bar(x, yTop, args, color, index, datasetIndex, noOfDatasets, prevX, prevY) {
|
||||
bar(x, yTop, args, color, index, datasetIndex, noOfDatasets) {
|
||||
|
||||
let totalWidth = this.unitWidth - args.spaceWidth;
|
||||
let startX = x - totalWidth/2;
|
||||
@ -325,9 +326,66 @@ export class AxisChartRenderer {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
xMarker() {}
|
||||
yMarker() {}
|
||||
|
||||
xRegion() {}
|
||||
yRegion() {}
|
||||
|
||||
animatebar(bar, x, yTop, index, noOfDatasets) {
|
||||
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;
|
||||
}
|
||||
|
||||
translate(unit, oldCoord, newCoord, duration) {
|
||||
return [
|
||||
unit,
|
||||
{transform: newCoord.join(', ')},
|
||||
duration,
|
||||
STD_EASING,
|
||||
"translate",
|
||||
{transform: oldCoord.join(', ')}
|
||||
];
|
||||
}
|
||||
|
||||
translateVertLine(xLine, newX, oldX) {
|
||||
return this.translate(xLine, [oldX, 0], [newX, 0], MARKER_LINE_ANIM_DUR);
|
||||
}
|
||||
|
||||
translateHoriLine(yLine, newY, oldY) {
|
||||
return this.translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user