@@ -290,6 +290,23 @@ const REPLACE_ALL_NEW_DUR = 250; | |||||
const STD_EASING = 'easein'; | const STD_EASING = 'easein'; | ||||
function translate(unit, oldCoord, newCoord, duration) { | |||||
return [ | |||||
unit, | |||||
{transform: newCoord.join(', ')}, | |||||
duration, | |||||
STD_EASING, | |||||
"translate", | |||||
{transform: oldCoord.join(', ')} | |||||
]; | |||||
} | |||||
function translateHoriLine(yLine, newY, oldY) { | |||||
return translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR); | |||||
} | |||||
const AXIS_TICK_LENGTH = 6; | const AXIS_TICK_LENGTH = 6; | ||||
const LABEL_MARGIN = 4; | const LABEL_MARGIN = 4; | ||||
const FONT_SIZE = 10; | const FONT_SIZE = 10; | ||||
@@ -478,8 +495,8 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
className: className, | className: className, | ||||
x1: x1, | x1: x1, | ||||
x2: x2, | x2: x2, | ||||
y1: y, | |||||
y2: y, | |||||
y1: 0, | |||||
y2: 0, | |||||
styles: { | styles: { | ||||
stroke: options.stroke | stroke: options.stroke | ||||
} | } | ||||
@@ -487,7 +504,7 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
let text = createSVG('text', { | let text = createSVG('text', { | ||||
x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN, | x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN, | ||||
y: y, | |||||
y: 0, | |||||
dy: (FONT_SIZE / 2 - 2) + 'px', | dy: (FONT_SIZE / 2 - 2) + 'px', | ||||
'font-size': FONT_SIZE + 'px', | 'font-size': FONT_SIZE + 'px', | ||||
'text-anchor': x1 < x2 ? 'end' : 'start', | 'text-anchor': x1 < x2 ? 'end' : 'start', | ||||
@@ -495,6 +512,7 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
}); | }); | ||||
let line = createSVG('g', { | let line = createSVG('g', { | ||||
transform: `translate(0, ${y})`, | |||||
'stroke-opacity': 1 | 'stroke-opacity': 1 | ||||
}); | }); | ||||
@@ -508,6 +526,31 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
return line; | return line; | ||||
} | } | ||||
function yLine(y, label, width, options={}) { | |||||
if(!options.pos) options.pos = 'left'; | |||||
if(!options.offset) options.offset = 0; | |||||
if(!options.mode) options.mode = 'span'; | |||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR; | |||||
if(!options.className) options.className = ''; | |||||
let x1 = -1 * AXIS_TICK_LENGTH; | |||||
let x2 = options.mode === 'span' ? width + AXIS_TICK_LENGTH : 0; | |||||
if(options.mode === 'tick' && options.pos === 'right') { | |||||
x1 = width + AXIS_TICK_LENGTH; | |||||
x2 = width; | |||||
} | |||||
x1 += options.offset; | |||||
x2 += options.offset; | |||||
return makeHoriLine(y, label, x1, x2, { | |||||
stroke: options.stroke, | |||||
className: options.className, | |||||
lineType: options.lineType | |||||
}); | |||||
} | |||||
class AxisChartRenderer { | class AxisChartRenderer { | ||||
constructor(state) { | constructor(state) { | ||||
this.refreshState(state); | this.refreshState(state); | ||||
@@ -529,7 +572,7 @@ class AxisChartRenderer { | |||||
xLine(x, label, options={}) { | xLine(x, label, options={}) { | ||||
if(!options.pos) options.pos = 'bottom'; | if(!options.pos) options.pos = 'bottom'; | ||||
if(!options.offset) options.offset = 0; | if(!options.offset) options.offset = 0; | ||||
if(!options.mode) options.mode = this.xAxisMode; | |||||
if(!options.mode) options.mode = 'span'; | |||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR; | if(!options.stroke) options.stroke = BASE_LINE_COLOR; | ||||
if(!options.className) options.className = ''; | if(!options.className) options.className = ''; | ||||
@@ -560,30 +603,7 @@ class AxisChartRenderer { | |||||
}); | }); | ||||
} | } | ||||
yLine(y, label, options={}) { | |||||
if(!options.pos) options.pos = 'left'; | |||||
if(!options.offset) options.offset = 0; | |||||
if(!options.mode) options.mode = this.yAxisMode; | |||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR; | |||||
if(!options.className) options.className = ''; | |||||
let x1 = -1 * AXIS_TICK_LENGTH; | |||||
let x2 = options.mode === 'span' ? this.totalWidth + AXIS_TICK_LENGTH : 0; | |||||
if(options.mode === 'tick' && options.pos === 'right') { | |||||
x1 = this.totalWidth + AXIS_TICK_LENGTH; | |||||
x2 = this.totalWidth; | |||||
} | |||||
x1 += options.offset; | |||||
x2 += options.offset; | |||||
return makeHoriLine(y, label, x1, x2, { | |||||
stroke: options.stroke, | |||||
className: options.className, | |||||
lineType: options.lineType | |||||
}); | |||||
} | |||||
xMarker() {} | xMarker() {} | ||||
@@ -713,25 +733,6 @@ class AxisChartRenderer { | |||||
return pathComponents; | 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 = { | const PRESET_COLOR_MAP = { | ||||
@@ -1066,11 +1067,15 @@ class BaseChart { | |||||
_setup() { | _setup() { | ||||
this.bindWindowEvents(); | this.bindWindowEvents(); | ||||
this.setupConstants(); | this.setupConstants(); | ||||
this.setupComponents(); | |||||
this.setMargins(); | this.setMargins(); | ||||
this.makeContainer(); | this.makeContainer(); | ||||
this.makeTooltip(); // without binding | this.makeTooltip(); // without binding | ||||
this.calcWidth(); | |||||
this.makeChartArea(); | |||||
this.setupComponents(); | |||||
this.draw(true); | this.draw(true); | ||||
} | } | ||||
@@ -1113,31 +1118,13 @@ class BaseChart { | |||||
bindTooltip() {} | bindTooltip() {} | ||||
draw(init=false) { | draw(init=false) { | ||||
// difference from update(): draw the whole object due to groudbreaking event (init, resize, etc.) | |||||
// (draw everything, layers, groups, units) | |||||
this.calcWidth(); | |||||
// refresh conponent with chart | |||||
this.refresh(this.data); | |||||
this.makeChartArea(); | |||||
this.setComponentParent(); | |||||
this.makeComponentLayers(); | |||||
this.components.forEach(c => c.make()); // or c.build() | |||||
this.renderLegend(); | this.renderLegend(); | ||||
this.setupNavigation(init); | |||||
// first time plain render, so no rerender | |||||
this.renderComponents(); | |||||
this.renderConstants(); | |||||
if(this.config.animate) this.update(this.firstUpdateData); | |||||
} | |||||
this.setupNavigation(init); | |||||
update(data) { | |||||
this.refresh(data); | |||||
this.reRender(); | |||||
// TODO: remove timeout and decrease post animate time in chart component | |||||
setTimeout(() => {this.update();}, 1000); | |||||
} | } | ||||
calcWidth() { | calcWidth() { | ||||
@@ -1153,15 +1140,34 @@ class BaseChart { | |||||
this.width = this.baseWidth - (this.translateXLeft + this.translateXRight); | this.width = this.baseWidth - (this.translateXLeft + this.translateXRight); | ||||
} | } | ||||
refresh(data) { //?? refresh? | |||||
this.oldState = this.state ? JSON.parse(JSON.stringify(this.state)) : {}; | |||||
this.intermedState = {}; // use this for the extra position problems? | |||||
update(data=this.data) { | |||||
this.prepareData(data); | this.prepareData(data); | ||||
this.reCalc(); | |||||
this.calc(); // builds state | |||||
this.refreshRenderer(); | this.refreshRenderer(); | ||||
this.render(); | |||||
} | } | ||||
prepareData() {} | |||||
renderConstants() {} | |||||
calc() {} // builds state | |||||
refreshRenderer() { | |||||
this.renderer = {}; | |||||
} | |||||
render(animate=true) { | |||||
this.refreshComponents(); | |||||
this.elementsToAnimate = [].concat.apply([], this.components.map(c => c.update(animate))); | |||||
console.log(this.elementsToAnimate); | |||||
if(this.elementsToAnimate) { | |||||
runSMILAnimation(this.chartWrapper, this.svg, this.elementsToAnimate); | |||||
} | |||||
} | |||||
refreshComponents() {} | |||||
makeChartArea() { | makeChartArea() { | ||||
this.svg = makeSVGContainer( | this.svg = makeSVGContainer( | ||||
this.chartWrapper, | this.chartWrapper, | ||||
@@ -1185,41 +1191,6 @@ class BaseChart { | |||||
); | ); | ||||
} | } | ||||
prepareData() {} | |||||
renderConstants() {} | |||||
reCalc() {} | |||||
// Will update values(state) | |||||
// Will recalc specific parts depending on the update | |||||
refreshRenderer() { | |||||
this.renderer = {}; | |||||
} | |||||
reRender(animate=true) { | |||||
if(!animate) { | |||||
this.renderComponents(); | |||||
return; | |||||
} | |||||
this.elementsToAnimate = []; | |||||
this.loadAnimatedComponents(); | |||||
runSMILAnimation(this.chartWrapper, this.svg, this.elementsToAnimate); | |||||
setTimeout(() => { | |||||
this.renderComponents(); | |||||
}, 400); | |||||
// TODO: should be max anim duration required | |||||
// (opt, should not redraw if still in animate?) | |||||
} | |||||
// 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()); } | |||||
loadAnimatedComponents() { this.components.forEach(c => c.loadAnimatedComponents()); } | |||||
refreshComponents() { this.components.forEach(c => c.refresh(this.state, this.rawChartArgs)); } | |||||
renderLegend() {} | renderLegend() {} | ||||
setupNavigation(init=false) { | setupNavigation(init=false) { | ||||
@@ -1299,66 +1270,107 @@ class BaseChart { | |||||
const Y_AXIS_MARGIN = 60; | const Y_AXIS_MARGIN = 60; | ||||
class ChartComponent { | |||||
class ChartComponent$1 { | |||||
constructor({ | constructor({ | ||||
layerClass = '', | layerClass = '', | ||||
layerTransform = '', | layerTransform = '', | ||||
initData, | |||||
parent, | |||||
constants, | |||||
data, | |||||
// called on update | // called on update | ||||
setData, | |||||
preMake, | preMake, | ||||
make, | |||||
makeElements, | |||||
postMake, | postMake, | ||||
animate | |||||
animateElements | |||||
}) { | }) { | ||||
this.parent = parent; | |||||
this.layerClass = layerClass; | this.layerClass = layerClass; | ||||
this.layerTransform = layerTransform; | this.layerTransform = layerTransform; | ||||
this.initData = initData; | |||||
this.setData = setData; | |||||
this.constants = constants; | |||||
this.preMake = preMake; | this.preMake = preMake; | ||||
this.make = make; | |||||
this.makeElements = makeElements; | |||||
this.postMake = postMake; | this.postMake = postMake; | ||||
this.animate = animate; | |||||
this.animateElements = animateElements; | |||||
this.layer = undefined; | |||||
this.store = []; | this.store = []; | ||||
} | |||||
this.layer = makeSVGGroup(this.parent, this.layerClass, this.layerTransform); | |||||
refresh(state, args) { | |||||
this.meta = Object.assign((this.meta || {}), args); | |||||
this.state = state; | |||||
} | |||||
this.data = data; | |||||
this.make(); | |||||
} | |||||
render() { | |||||
this.data = this.setData(); // The only without this function? | |||||
refresh(data) { | |||||
this.data = data; | |||||
} | |||||
make() { | |||||
this.preMake && this.preMake(); | this.preMake && this.preMake(); | ||||
this.store = this.make(); | |||||
this.render(this.data); | |||||
this.postMake && this.postMake(); | |||||
this.oldData = this.data; | |||||
} | |||||
render(data) { | |||||
this.store = this.makeElements(data); | |||||
this.layer.textContent = ''; | this.layer.textContent = ''; | ||||
this.store.forEach(element => { | this.store.forEach(element => { | ||||
this.layer.appendChild(element); | this.layer.appendChild(element); | ||||
}); | }); | ||||
this.postMake && this.postMake(); | |||||
} | } | ||||
setupParent(parent) { | |||||
this.parent = parent; | |||||
update(animate = true) { | |||||
let animateElements = []; | |||||
if(animate) { | |||||
animateElements = this.animateElements(this.data); | |||||
} | |||||
// TODO: Can we remove this? | |||||
setTimeout(() => { | |||||
this.make(); | |||||
}, 1400); | |||||
return animateElements; | |||||
} | } | ||||
} | |||||
loadAnimatedComponents() { | |||||
this.animate(this.store); | |||||
} | |||||
function getYAxisComponent(parent, constants, initData) { | |||||
return new ChartComponent$1({ | |||||
parent: parent, | |||||
layerClass: 'y axis', | |||||
constants: constants, | |||||
data: initData, | |||||
makeElements: function(data) { | |||||
return data.positions.map((position, i) => | |||||
yLine(position, data.labels[i], this.constants.width, | |||||
{mode: this.constants.mode, pos: this.constants.pos}) | |||||
); | |||||
}, | |||||
animateElements: function(newData) { | |||||
let newPos = newData.positions; | |||||
let newLabels = newData.labels; | |||||
let oldPos = this.oldData.positions; | |||||
let oldLabels = this.oldData.labels; | |||||
[oldPos, newPos] = equilizeNoOfElements(oldPos, newPos); | |||||
[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); | |||||
this.render({ | |||||
positions: oldPos, | |||||
labels: newLabels | |||||
}); | |||||
makeLayer() { | |||||
this.layer = makeSVGGroup(this.parent, this.layerClass, this.layerTransform); | |||||
} | |||||
return this.store.map((line, i) => { | |||||
return translateHoriLine( | |||||
line, newPos[i], oldPos[i] | |||||
); | |||||
}); | |||||
} | |||||
}) | |||||
} | } | ||||
const MIN_BAR_PERCENT_HEIGHT$1 = 0.01; | const MIN_BAR_PERCENT_HEIGHT$1 = 0.01; | ||||
@@ -1612,7 +1624,7 @@ function normalize(x) { | |||||
return [sig * man, exp]; | return [sig * man, exp]; | ||||
} | } | ||||
function getRangeIntervals(max, min=0) { | |||||
function getChartRangeIntervals(max, min=0) { | |||||
let upperBound = Math.ceil(max); | let upperBound = Math.ceil(max); | ||||
let lowerBound = Math.floor(min); | let lowerBound = Math.floor(min); | ||||
let range = upperBound - lowerBound; | let range = upperBound - lowerBound; | ||||
@@ -1650,19 +1662,19 @@ function getRangeIntervals(max, min=0) { | |||||
return intervals; | return intervals; | ||||
} | } | ||||
function getIntervals(maxValue, minValue=0) { | |||||
function getChartIntervals(maxValue, minValue=0) { | |||||
let [normalMaxValue, exponent] = normalize(maxValue); | let [normalMaxValue, exponent] = normalize(maxValue); | ||||
let normalMinValue = minValue ? minValue/Math.pow(10, exponent): 0; | let normalMinValue = minValue ? minValue/Math.pow(10, exponent): 0; | ||||
// Allow only 7 significant digits | // Allow only 7 significant digits | ||||
normalMaxValue = normalMaxValue.toFixed(6); | normalMaxValue = normalMaxValue.toFixed(6); | ||||
let intervals = getRangeIntervals(normalMaxValue, normalMinValue); | |||||
let intervals = getChartRangeIntervals(normalMaxValue, normalMinValue); | |||||
intervals = intervals.map(value => value * Math.pow(10, exponent)); | intervals = intervals.map(value => value * Math.pow(10, exponent)); | ||||
return intervals; | return intervals; | ||||
} | } | ||||
function calcIntervals(values, withMinimum=false) { | |||||
function calcChartIntervals(values, withMinimum=false) { | |||||
//*** Where the magic happens *** | //*** Where the magic happens *** | ||||
// Calculates best-fit y intervals from given values | // Calculates best-fit y intervals from given values | ||||
@@ -1675,7 +1687,7 @@ function calcIntervals(values, withMinimum=false) { | |||||
let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars | let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars | ||||
function getPositiveFirstIntervals(maxValue, absMinValue) { | function getPositiveFirstIntervals(maxValue, absMinValue) { | ||||
let intervals = getIntervals(maxValue); | |||||
let intervals = getChartIntervals(maxValue); | |||||
let intervalSize = intervals[1] - intervals[0]; | let intervalSize = intervals[1] - intervals[0]; | ||||
@@ -1693,9 +1705,9 @@ function calcIntervals(values, withMinimum=false) { | |||||
if(maxValue >= 0 && minValue >= 0) { | if(maxValue >= 0 && minValue >= 0) { | ||||
exponent = normalize(maxValue)[1]; | exponent = normalize(maxValue)[1]; | ||||
if(!withMinimum) { | if(!withMinimum) { | ||||
intervals = getIntervals(maxValue); | |||||
intervals = getChartIntervals(maxValue); | |||||
} else { | } else { | ||||
intervals = getIntervals(maxValue, minValue); | |||||
intervals = getChartIntervals(maxValue, minValue); | |||||
} | } | ||||
} | } | ||||
@@ -1733,9 +1745,9 @@ function calcIntervals(values, withMinimum=false) { | |||||
exponent = normalize(pseudoMaxValue)[1]; | exponent = normalize(pseudoMaxValue)[1]; | ||||
if(!withMinimum) { | if(!withMinimum) { | ||||
intervals = getIntervals(pseudoMaxValue); | |||||
intervals = getChartIntervals(pseudoMaxValue); | |||||
} else { | } else { | ||||
intervals = getIntervals(pseudoMaxValue, pseudoMinValue); | |||||
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue); | |||||
} | } | ||||
intervals = intervals.reverse().map(d => d * (-1)); | intervals = intervals.reverse().map(d => d * (-1)); | ||||
@@ -1765,6 +1777,18 @@ function getZeroIndex(yPts) { | |||||
return zeroIndex; | return zeroIndex; | ||||
} | } | ||||
function getRealIntervals(max, noOfIntervals, min = 0, asc = 1) { | |||||
let range = max - min; | |||||
let part = range * 1.0 / noOfIntervals; | |||||
let intervals = []; | |||||
for(var i = 0; i <= noOfIntervals; i++) { | |||||
intervals.push(min + part * i); | |||||
} | |||||
return asc ? intervals : intervals.reverse(); | |||||
} | |||||
function getIntervalSize(orderedArray) { | function getIntervalSize(orderedArray) { | ||||
return orderedArray[1] - orderedArray[0]; | return orderedArray[1] - orderedArray[0]; | ||||
} | } | ||||
@@ -1805,6 +1829,9 @@ class AxisChart extends BaseChart { | |||||
this.lineOptions = args.lineOptions; | this.lineOptions = args.lineOptions; | ||||
this.type = args.type || 'line'; | this.type = args.type || 'line'; | ||||
this.xAxisMode = args.xAxisMode || 'span'; | |||||
this.yAxisMode = args.yAxisMode || 'span'; | |||||
this.setupUnitRenderer(); | this.setupUnitRenderer(); | ||||
this.zeroLine = this.height; | this.zeroLine = this.height; | ||||
@@ -1908,7 +1935,7 @@ class AxisChart extends BaseChart { | |||||
}; | }; | ||||
} | } | ||||
reCalc() { | |||||
calc() { | |||||
let s = this.state; | let s = this.state; | ||||
s.xAxisLabels = this.data.labels; | s.xAxisLabels = this.data.labels; | ||||
@@ -1940,7 +1967,7 @@ class AxisChart extends BaseChart { | |||||
} | } | ||||
calcYAxisParameters(yAxis, dataValues, withMinimum = 'false') { | calcYAxisParameters(yAxis, dataValues, withMinimum = 'false') { | ||||
yAxis.labels = calcIntervals(dataValues, withMinimum); | |||||
yAxis.labels = calcChartIntervals(dataValues, withMinimum); | |||||
const yPts = yAxis.labels; | const yPts = yAxis.labels; | ||||
yAxis.scaleMultiplier = this.height / getValueRange(yPts); | yAxis.scaleMultiplier = this.height / getValueRange(yPts); | ||||
@@ -2040,78 +2067,63 @@ class AxisChart extends BaseChart { | |||||
// this.bind_units(units_array); | // this.bind_units(units_array); | ||||
// } | // } | ||||
this.yAxis = getYAxisComponent( | |||||
this.drawArea, | |||||
{ | |||||
mode: this.yAxisMode, | |||||
width: this.width, | |||||
// pos: 'right' | |||||
}, | |||||
{ | |||||
positions: getRealIntervals(this.height, 4, 0, 0), | |||||
labels: getRealIntervals(this.height, 4, 0, 0).map(d => d + ""), | |||||
} | |||||
); | |||||
this.components = [ | this.components = [ | ||||
...this.getYAxesComponents(), | |||||
this.getXAxisComponents(), | |||||
...this.getYRegions(), | |||||
...this.getXRegions(), | |||||
...this.getYMarkerLines(), | |||||
// ...this.getXMarkerLines(), | |||||
...this.getChartComponents(), | |||||
...this.getChartLabels(), | |||||
this.yAxis | |||||
// this.getXAxisComponents(), | |||||
// ...this.getYRegions(), | |||||
// ...this.getXRegions(), | |||||
// ...this.getYMarkerLines(), | |||||
// // ...this.getXMarkerLines(), | |||||
// ...this.getChartComponents(), | |||||
// ...this.getChartLabels(), | |||||
]; | ]; | ||||
} | } | ||||
getYAxesComponents() { | |||||
return [new ChartComponent({ | |||||
layerClass: 'y axis', | |||||
setData: () => { | |||||
// let s = this.state; | |||||
// data = {}; | |||||
// return data; | |||||
}, | |||||
initializeData: function() { | |||||
this.axesPositions = this.state; | |||||
}, | |||||
make: () => { | |||||
// positions, labels, renderer | |||||
let s = this.state; | |||||
return s.yAxis.positions.map((position, i) => | |||||
this.renderer.yLine(position, s.yAxis.labels[i], {pos:'right'}) | |||||
); | |||||
}, | |||||
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); | |||||
} | |||||
} | |||||
refreshComponents() { | |||||
this.refreshYAxis(); | |||||
} | |||||
yLines.map((line, i) => { | |||||
// console.log(line, newY[i], oldY[i]); | |||||
this.elementsToAnimate.push(this.renderer.translateHoriLine( | |||||
line, newY[i], oldY[i] | |||||
)); | |||||
}); | |||||
} | |||||
})]; | |||||
refreshYAxis() { | |||||
let s = this.state; | |||||
this.yAxis.refresh({ | |||||
positions: s.yAxis.positions, | |||||
labels: s.yAxis.labels, | |||||
}); | |||||
} | } | ||||
getXAxisComponents() { | getXAxisComponents() { | ||||
return new ChartComponent({ | return new ChartComponent({ | ||||
layerClass: 'x axis', | layerClass: 'x axis', | ||||
setData: () => {}, | |||||
make: () => { | |||||
setData: () => { | |||||
let s = this.state; | |||||
let data = { | |||||
positions: s.xAxisPositions, | |||||
labels: s.xAxisLabels, | |||||
}; | |||||
let constants = { | |||||
mode: this.xAxisMode, | |||||
height: this.height | |||||
}; | |||||
return [data, constants]; | |||||
}, | |||||
makeElements: () => { | |||||
let s = this.state; | let s = this.state; | ||||
// positions | // positions | ||||
// TODO: xAxis Label spacing | // TODO: xAxis Label spacing | ||||
return s.xAxisPositions.map((position, i) => | return s.xAxisPositions.map((position, i) => | ||||
this.renderer.xLine(position, s.xAxisLabels[i] | |||||
xLine(position, s.xAxisLabels[i], this.constants.height | |||||
// , {pos:'top'} | // , {pos:'top'} | ||||
) | ) | ||||
); | ); | ||||
@@ -2168,7 +2180,7 @@ class AxisChart extends BaseChart { | |||||
layerClass: 'dataset-units dataset-' + index, | layerClass: 'dataset-units dataset-' + index, | ||||
setData: () => {}, | setData: () => {}, | ||||
preMake: () => { }, | preMake: () => { }, | ||||
make: () => { | |||||
makeElements: () => { | |||||
let d = this.state.datasets[index]; | let d = this.state.datasets[index]; | ||||
return d.positions.map((y, j) => { | return d.positions.map((y, j) => { | ||||
@@ -2230,7 +2242,7 @@ class AxisChart extends BaseChart { | |||||
return new ChartComponent({ | return new ChartComponent({ | ||||
layerClass: 'path dataset-path', | layerClass: 'path dataset-path', | ||||
setData: () => {}, | setData: () => {}, | ||||
make: () => { | |||||
makeElements: () => { | |||||
let d = this.state.datasets[index]; | let d = this.state.datasets[index]; | ||||
let color = this.colors[index]; | let color = this.colors[index]; | ||||
@@ -2280,7 +2292,7 @@ class AxisChart extends BaseChart { | |||||
return new ChartComponent({ | return new ChartComponent({ | ||||
layerClass: 'y-markers', | layerClass: 'y-markers', | ||||
setData: () => {}, | setData: () => {}, | ||||
make: () => { | |||||
makeElements: () => { | |||||
let s = this.state; | let s = this.state; | ||||
return s.yMarkers.map(marker => | return s.yMarkers.map(marker => | ||||
this.renderer.yMarker(marker.value, marker.name, | this.renderer.yMarker(marker.value, marker.name, | ||||
@@ -2301,7 +2313,7 @@ class AxisChart extends BaseChart { | |||||
return new ChartComponent({ | return new ChartComponent({ | ||||
layerClass: 'y-regions', | layerClass: 'y-regions', | ||||
setData: () => {}, | setData: () => {}, | ||||
make: () => { | |||||
makeElements: () => { | |||||
let s = this.state; | let s = this.state; | ||||
return s.yRegions.map(region => | return s.yRegions.map(region => | ||||
this.renderer.yRegion(region.start, region.end, region.name) | this.renderer.yRegion(region.start, region.end, region.name) | ||||
@@ -2312,10 +2324,6 @@ class AxisChart extends BaseChart { | |||||
}); | }); | ||||
} | } | ||||
getXRegions() { | |||||
return []; | |||||
} | |||||
refreshRenderer() { | refreshRenderer() { | ||||
// These args are basically the current state of the chart, | // These args are basically the current state of the chart, | ||||
// with constant and alive params mixed | // with constant and alive params mixed | ||||
@@ -2335,8 +2343,6 @@ class AxisChart extends BaseChart { | |||||
this.renderer.refreshState(state); | this.renderer.refreshState(state); | ||||
} | } | ||||
this.refreshComponents(); | |||||
let meta = { | let meta = { | ||||
totalHeight: this.height, | totalHeight: this.height, | ||||
totalWidth: this.width, | totalWidth: this.width, | ||||
@@ -2778,40 +2784,42 @@ class PercentageChart extends BaseChart { | |||||
}); | }); | ||||
} | } | ||||
bindTooltip() { | |||||
this.slices.map((slice, i) => { | |||||
slice.addEventListener('mouseenter', () => { | |||||
let g_off = getOffset(this.chartWrapper), p_off = getOffset(slice); | |||||
calc() {} | |||||
let x = p_off.left - g_off.left + slice.offsetWidth/2; | |||||
let y = p_off.top - g_off.top - 6; | |||||
let title = (this.formatted_labels && this.formatted_labels.length>0 | |||||
? this.formatted_labels[i] : this.labels[i]) + ': '; | |||||
let percent = (this.slice_totals[i]*100/this.grand_total).toFixed(1); | |||||
// bindTooltip() { | |||||
// this.slices.map((slice, i) => { | |||||
// slice.addEventListener('mouseenter', () => { | |||||
// let g_off = getOffset(this.chartWrapper), p_off = getOffset(slice); | |||||
this.tip.set_values(x, y, title, percent + "%"); | |||||
this.tip.show_tip(); | |||||
}); | |||||
}); | |||||
} | |||||
// let x = p_off.left - g_off.left + slice.offsetWidth/2; | |||||
// let y = p_off.top - g_off.top - 6; | |||||
// let title = (this.formatted_labels && this.formatted_labels.length>0 | |||||
// ? this.formatted_labels[i] : this.labels[i]) + ': '; | |||||
// let percent = (this.slice_totals[i]*100/this.grand_total).toFixed(1); | |||||
renderLegend() { | |||||
let x_values = this.formatted_labels && this.formatted_labels.length > 0 | |||||
? this.formatted_labels : this.labels; | |||||
this.legend_totals.map((d, i) => { | |||||
if(d) { | |||||
let stats = $$1.create('div', { | |||||
className: 'stats', | |||||
inside: this.statsWrapper | |||||
}); | |||||
stats.innerHTML = `<span class="indicator"> | |||||
<i style="background: ${this.colors[i]}"></i> | |||||
<span class="text-muted">${x_values[i]}:</span> | |||||
${d} | |||||
</span>`; | |||||
} | |||||
}); | |||||
} | |||||
// this.tip.set_values(x, y, title, percent + "%"); | |||||
// this.tip.show_tip(); | |||||
// }); | |||||
// }); | |||||
// } | |||||
// renderLegend() { | |||||
// let x_values = this.formatted_labels && this.formatted_labels.length > 0 | |||||
// ? this.formatted_labels : this.labels; | |||||
// this.legend_totals.map((d, i) => { | |||||
// if(d) { | |||||
// let stats = $.create('div', { | |||||
// className: 'stats', | |||||
// inside: this.statsWrapper | |||||
// }); | |||||
// stats.innerHTML = `<span class="indicator"> | |||||
// <i style="background: ${this.colors[i]}"></i> | |||||
// <span class="text-muted">${x_values[i]}:</span> | |||||
// ${d} | |||||
// </span>`; | |||||
// } | |||||
// }); | |||||
// } | |||||
} | } | ||||
const ANGLE_RATIO = Math.PI / 180; | const ANGLE_RATIO = Math.PI / 180; | ||||
@@ -53,6 +53,7 @@ let bar_composite_chart = new Chart ({ | |||||
isNavigable: 1, | isNavigable: 1, | ||||
isSeries: 1, | isSeries: 1, | ||||
valuesOverPoints: 1, | valuesOverPoints: 1, | ||||
yAxisMode: 'tick' | |||||
// regionFill: 1 | // regionFill: 1 | ||||
}); | }); | ||||
@@ -16,6 +16,7 @@ import pkg from './package.json'; | |||||
export default [ | export default [ | ||||
{ | { | ||||
input: 'src/js/chart.js', | input: 'src/js/chart.js', | ||||
sourcemap: true, | |||||
output: [ | output: [ | ||||
{ | { | ||||
file: 'docs/assets/js/frappe-charts.min.js', | file: 'docs/assets/js/frappe-charts.min.js', | ||||
@@ -1,13 +1,13 @@ | |||||
import BaseChart from './BaseChart'; | import BaseChart from './BaseChart'; | ||||
import { Y_AXIS_MARGIN } from '../utils/margins'; | import { Y_AXIS_MARGIN } from '../utils/margins'; | ||||
import { ChartComponent } from '../objects/ChartComponent'; | |||||
import { getYAxisComponent } from '../objects/ChartComponents'; | |||||
import { BarChartController, LineChartController, getPaths } from '../objects/AxisChartControllers'; | import { BarChartController, LineChartController, getPaths } from '../objects/AxisChartControllers'; | ||||
import { getOffset, fire } from '../utils/dom'; | |||||
import { AxisChartRenderer } from '../utils/draw'; | import { AxisChartRenderer } from '../utils/draw'; | ||||
import { getOffset, fire } from '../utils/dom'; | |||||
import { equilizeNoOfElements } from '../utils/draw-utils'; | import { equilizeNoOfElements } from '../utils/draw-utils'; | ||||
import { Animator } from '../utils/animate'; | |||||
import { Animator, translateHoriLine } from '../utils/animate'; | |||||
import { runSMILAnimation } from '../utils/animation'; | import { runSMILAnimation } from '../utils/animation'; | ||||
import { calcIntervals, getIntervalSize, getValueRange, getZeroIndex } from '../utils/intervals'; | |||||
import { getRealIntervals, calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex } from '../utils/intervals'; | |||||
import { floatTwo, fillArray } from '../utils/helpers'; | import { floatTwo, fillArray } from '../utils/helpers'; | ||||
export default class AxisChart extends BaseChart { | export default class AxisChart extends BaseChart { | ||||
@@ -21,6 +21,9 @@ export default class AxisChart extends BaseChart { | |||||
this.lineOptions = args.lineOptions; | this.lineOptions = args.lineOptions; | ||||
this.type = args.type || 'line'; | this.type = args.type || 'line'; | ||||
this.xAxisMode = args.xAxisMode || 'span'; | |||||
this.yAxisMode = args.yAxisMode || 'span'; | |||||
this.setupUnitRenderer(); | this.setupUnitRenderer(); | ||||
this.zeroLine = this.height; | this.zeroLine = this.height; | ||||
@@ -124,7 +127,7 @@ export default class AxisChart extends BaseChart { | |||||
}; | }; | ||||
} | } | ||||
reCalc() { | |||||
calc() { | |||||
let s = this.state; | let s = this.state; | ||||
s.xAxisLabels = this.data.labels; | s.xAxisLabels = this.data.labels; | ||||
@@ -156,7 +159,7 @@ export default class AxisChart extends BaseChart { | |||||
} | } | ||||
calcYAxisParameters(yAxis, dataValues, withMinimum = 'false') { | calcYAxisParameters(yAxis, dataValues, withMinimum = 'false') { | ||||
yAxis.labels = calcIntervals(dataValues, withMinimum); | |||||
yAxis.labels = calcChartIntervals(dataValues, withMinimum); | |||||
const yPts = yAxis.labels; | const yPts = yAxis.labels; | ||||
yAxis.scaleMultiplier = this.height / getValueRange(yPts); | yAxis.scaleMultiplier = this.height / getValueRange(yPts); | ||||
@@ -256,78 +259,63 @@ export default class AxisChart extends BaseChart { | |||||
// this.bind_units(units_array); | // this.bind_units(units_array); | ||||
// } | // } | ||||
this.yAxis = getYAxisComponent( | |||||
this.drawArea, | |||||
{ | |||||
mode: this.yAxisMode, | |||||
width: this.width, | |||||
// pos: 'right' | |||||
}, | |||||
{ | |||||
positions: getRealIntervals(this.height, 4, 0, 0), | |||||
labels: getRealIntervals(this.height, 4, 0, 0).map(d => d + ""), | |||||
} | |||||
) | |||||
this.components = [ | this.components = [ | ||||
...this.getYAxesComponents(), | |||||
this.getXAxisComponents(), | |||||
...this.getYRegions(), | |||||
...this.getXRegions(), | |||||
...this.getYMarkerLines(), | |||||
// ...this.getXMarkerLines(), | |||||
...this.getChartComponents(), | |||||
...this.getChartLabels(), | |||||
this.yAxis | |||||
// this.getXAxisComponents(), | |||||
// ...this.getYRegions(), | |||||
// ...this.getXRegions(), | |||||
// ...this.getYMarkerLines(), | |||||
// // ...this.getXMarkerLines(), | |||||
// ...this.getChartComponents(), | |||||
// ...this.getChartLabels(), | |||||
]; | ]; | ||||
} | } | ||||
getYAxesComponents() { | |||||
return [new ChartComponent({ | |||||
layerClass: 'y axis', | |||||
setData: () => { | |||||
// let s = this.state; | |||||
// data = {}; | |||||
// return data; | |||||
}, | |||||
initializeData: function() { | |||||
this.axesPositions = this.state | |||||
}, | |||||
make: () => { | |||||
// positions, labels, renderer | |||||
let s = this.state; | |||||
return s.yAxis.positions.map((position, i) => | |||||
this.renderer.yLine(position, s.yAxis.labels[i], {pos:'right'}) | |||||
); | |||||
}, | |||||
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); | |||||
} | |||||
} | |||||
refreshComponents() { | |||||
this.refreshYAxis(); | |||||
} | |||||
yLines.map((line, i) => { | |||||
// console.log(line, newY[i], oldY[i]); | |||||
this.elementsToAnimate.push(this.renderer.translateHoriLine( | |||||
line, newY[i], oldY[i] | |||||
)); | |||||
}); | |||||
} | |||||
})]; | |||||
refreshYAxis() { | |||||
let s = this.state; | |||||
this.yAxis.refresh({ | |||||
positions: s.yAxis.positions, | |||||
labels: s.yAxis.labels, | |||||
}); | |||||
} | } | ||||
getXAxisComponents() { | getXAxisComponents() { | ||||
return new ChartComponent({ | return new ChartComponent({ | ||||
layerClass: 'x axis', | layerClass: 'x axis', | ||||
setData: () => {}, | |||||
make: () => { | |||||
setData: () => { | |||||
let s = this.state; | |||||
let data = { | |||||
positions: s.xAxisPositions, | |||||
labels: s.xAxisLabels, | |||||
}; | |||||
let constants = { | |||||
mode: this.xAxisMode, | |||||
height: this.height | |||||
} | |||||
return [data, constants]; | |||||
}, | |||||
makeElements: () => { | |||||
let s = this.state; | let s = this.state; | ||||
// positions | // positions | ||||
// TODO: xAxis Label spacing | // TODO: xAxis Label spacing | ||||
return s.xAxisPositions.map((position, i) => | return s.xAxisPositions.map((position, i) => | ||||
this.renderer.xLine(position, s.xAxisLabels[i] | |||||
xLine(position, s.xAxisLabels[i], this.constants.height | |||||
// , {pos:'top'} | // , {pos:'top'} | ||||
) | ) | ||||
); | ); | ||||
@@ -384,7 +372,7 @@ export default class AxisChart extends BaseChart { | |||||
layerClass: 'dataset-units dataset-' + index, | layerClass: 'dataset-units dataset-' + index, | ||||
setData: () => {}, | setData: () => {}, | ||||
preMake: () => { }, | preMake: () => { }, | ||||
make: () => { | |||||
makeElements: () => { | |||||
let d = this.state.datasets[index]; | let d = this.state.datasets[index]; | ||||
return d.positions.map((y, j) => { | return d.positions.map((y, j) => { | ||||
@@ -446,7 +434,7 @@ export default class AxisChart extends BaseChart { | |||||
return new ChartComponent({ | return new ChartComponent({ | ||||
layerClass: 'path dataset-path', | layerClass: 'path dataset-path', | ||||
setData: () => {}, | setData: () => {}, | ||||
make: () => { | |||||
makeElements: () => { | |||||
let d = this.state.datasets[index]; | let d = this.state.datasets[index]; | ||||
let color = this.colors[index]; | let color = this.colors[index]; | ||||
@@ -496,7 +484,7 @@ export default class AxisChart extends BaseChart { | |||||
return new ChartComponent({ | return new ChartComponent({ | ||||
layerClass: 'y-markers', | layerClass: 'y-markers', | ||||
setData: () => {}, | setData: () => {}, | ||||
make: () => { | |||||
makeElements: () => { | |||||
let s = this.state; | let s = this.state; | ||||
return s.yMarkers.map(marker => | return s.yMarkers.map(marker => | ||||
this.renderer.yMarker(marker.value, marker.name, | this.renderer.yMarker(marker.value, marker.name, | ||||
@@ -517,7 +505,7 @@ export default class AxisChart extends BaseChart { | |||||
return new ChartComponent({ | return new ChartComponent({ | ||||
layerClass: 'y-regions', | layerClass: 'y-regions', | ||||
setData: () => {}, | setData: () => {}, | ||||
make: () => { | |||||
makeElements: () => { | |||||
let s = this.state; | let s = this.state; | ||||
return s.yRegions.map(region => | return s.yRegions.map(region => | ||||
this.renderer.yRegion(region.start, region.end, region.name) | this.renderer.yRegion(region.start, region.end, region.name) | ||||
@@ -528,10 +516,6 @@ export default class AxisChart extends BaseChart { | |||||
}); | }); | ||||
} | } | ||||
getXRegions() { | |||||
return []; | |||||
} | |||||
refreshRenderer() { | refreshRenderer() { | ||||
// These args are basically the current state of the chart, | // These args are basically the current state of the chart, | ||||
// with constant and alive params mixed | // with constant and alive params mixed | ||||
@@ -551,8 +535,6 @@ export default class AxisChart extends BaseChart { | |||||
this.renderer.refreshState(state); | this.renderer.refreshState(state); | ||||
} | } | ||||
this.refreshComponents(); | |||||
let meta = { | let meta = { | ||||
totalHeight: this.height, | totalHeight: this.height, | ||||
totalWidth: this.width, | totalWidth: this.width, | ||||
@@ -128,11 +128,15 @@ export default class BaseChart { | |||||
_setup() { | _setup() { | ||||
this.bindWindowEvents(); | this.bindWindowEvents(); | ||||
this.setupConstants(); | this.setupConstants(); | ||||
this.setupComponents(); | |||||
this.setMargins(); | this.setMargins(); | ||||
this.makeContainer(); | this.makeContainer(); | ||||
this.makeTooltip(); // without binding | this.makeTooltip(); // without binding | ||||
this.calcWidth(); | |||||
this.makeChartArea(); | |||||
this.setupComponents(); | |||||
this.draw(true); | this.draw(true); | ||||
} | } | ||||
@@ -175,31 +179,13 @@ export default class BaseChart { | |||||
bindTooltip() {} | bindTooltip() {} | ||||
draw(init=false) { | draw(init=false) { | ||||
// difference from update(): draw the whole object due to groudbreaking event (init, resize, etc.) | |||||
// (draw everything, layers, groups, units) | |||||
this.calcWidth(); | |||||
// refresh conponent with chart | |||||
this.refresh(this.data); | |||||
this.makeChartArea(); | |||||
this.setComponentParent(); | |||||
this.makeComponentLayers(); | |||||
this.components.forEach(c => c.make()); // or c.build() | |||||
this.renderLegend(); | this.renderLegend(); | ||||
this.setupNavigation(init); | |||||
// first time plain render, so no rerender | |||||
this.renderComponents(); | |||||
this.renderConstants(); | |||||
if(this.config.animate) this.update(this.firstUpdateData); | |||||
} | |||||
this.setupNavigation(init); | |||||
update(data) { | |||||
this.refresh(data); | |||||
this.reRender(); | |||||
// TODO: remove timeout and decrease post animate time in chart component | |||||
setTimeout(() => {this.update();}, 1000); | |||||
} | } | ||||
calcWidth() { | calcWidth() { | ||||
@@ -215,15 +201,34 @@ export default class BaseChart { | |||||
this.width = this.baseWidth - (this.translateXLeft + this.translateXRight); | this.width = this.baseWidth - (this.translateXLeft + this.translateXRight); | ||||
} | } | ||||
refresh(data) { //?? refresh? | |||||
this.oldState = this.state ? JSON.parse(JSON.stringify(this.state)) : {}; | |||||
this.intermedState = {}; // use this for the extra position problems? | |||||
update(data=this.data) { | |||||
this.prepareData(data); | this.prepareData(data); | ||||
this.reCalc(); | |||||
this.calc(); // builds state | |||||
this.refreshRenderer(); | this.refreshRenderer(); | ||||
this.render(); | |||||
} | |||||
prepareData() {} | |||||
renderConstants() {} | |||||
calc() {} // builds state | |||||
refreshRenderer() { | |||||
this.renderer = {}; | |||||
} | } | ||||
render(animate=true) { | |||||
this.refreshComponents(); | |||||
this.elementsToAnimate = [].concat.apply([], this.components.map(c => c.update(animate))); | |||||
console.log(this.elementsToAnimate); | |||||
if(this.elementsToAnimate) { | |||||
runSMILAnimation(this.chartWrapper, this.svg, this.elementsToAnimate); | |||||
} | |||||
} | |||||
refreshComponents() {} | |||||
makeChartArea() { | makeChartArea() { | ||||
this.svg = makeSVGContainer( | this.svg = makeSVGContainer( | ||||
this.chartWrapper, | this.chartWrapper, | ||||
@@ -247,41 +252,6 @@ export default class BaseChart { | |||||
); | ); | ||||
} | } | ||||
prepareData() {} | |||||
renderConstants() {} | |||||
reCalc() {} | |||||
// Will update values(state) | |||||
// Will recalc specific parts depending on the update | |||||
refreshRenderer() { | |||||
this.renderer = {}; | |||||
} | |||||
reRender(animate=true) { | |||||
if(!animate) { | |||||
this.renderComponents(); | |||||
return; | |||||
} | |||||
this.elementsToAnimate = []; | |||||
this.loadAnimatedComponents(); | |||||
runSMILAnimation(this.chartWrapper, this.svg, this.elementsToAnimate); | |||||
setTimeout(() => { | |||||
this.renderComponents(); | |||||
}, 400); | |||||
// TODO: should be max anim duration required | |||||
// (opt, should not redraw if still in animate?) | |||||
} | |||||
// 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()); } | |||||
loadAnimatedComponents() { this.components.forEach(c => c.loadAnimatedComponents()); } | |||||
refreshComponents() { this.components.forEach(c => c.refresh(this.state, this.rawChartArgs)); } | |||||
renderLegend() {} | renderLegend() {} | ||||
setupNavigation(init=false) { | setupNavigation(init=false) { | ||||
@@ -1,5 +1,5 @@ | |||||
import AxisChart from './AxisChart'; | import AxisChart from './AxisChart'; | ||||
import { ChartComponent } from '../objects/ChartComponent'; | |||||
// import { ChartComponent } from '../objects/ChartComponents'; | |||||
import { makeSVGGroup, makePath, makeGradient } from '../utils/draw'; | import { makeSVGGroup, makePath, makeGradient } from '../utils/draw'; | ||||
import { equilizeNoOfElements } from '../utils/draw-utils'; | import { equilizeNoOfElements } from '../utils/draw-utils'; | ||||
@@ -1,6 +1,6 @@ | |||||
import AxisChart from './AxisChart'; | import AxisChart from './AxisChart'; | ||||
import { Y_AXIS_MARGIN } from '../utils/margins'; | import { Y_AXIS_MARGIN } from '../utils/margins'; | ||||
import { ChartComponent } from '../objects/ChartComponent'; | |||||
// import { ChartComponent } from '../objects/ChartComponents'; | |||||
import { floatTwo } from '../utils/helpers'; | import { floatTwo } from '../utils/helpers'; | ||||
export default class MultiAxisChart extends AxisChart { | export default class MultiAxisChart extends AxisChart { | ||||
@@ -89,38 +89,40 @@ export default class PercentageChart extends BaseChart { | |||||
}); | }); | ||||
} | } | ||||
bindTooltip() { | |||||
this.slices.map((slice, i) => { | |||||
slice.addEventListener('mouseenter', () => { | |||||
let g_off = getOffset(this.chartWrapper), p_off = getOffset(slice); | |||||
let x = p_off.left - g_off.left + slice.offsetWidth/2; | |||||
let y = p_off.top - g_off.top - 6; | |||||
let title = (this.formatted_labels && this.formatted_labels.length>0 | |||||
? this.formatted_labels[i] : this.labels[i]) + ': '; | |||||
let percent = (this.slice_totals[i]*100/this.grand_total).toFixed(1); | |||||
this.tip.set_values(x, y, title, percent + "%"); | |||||
this.tip.show_tip(); | |||||
}); | |||||
}); | |||||
} | |||||
renderLegend() { | |||||
let x_values = this.formatted_labels && this.formatted_labels.length > 0 | |||||
? this.formatted_labels : this.labels; | |||||
this.legend_totals.map((d, i) => { | |||||
if(d) { | |||||
let stats = $.create('div', { | |||||
className: 'stats', | |||||
inside: this.statsWrapper | |||||
}); | |||||
stats.innerHTML = `<span class="indicator"> | |||||
<i style="background: ${this.colors[i]}"></i> | |||||
<span class="text-muted">${x_values[i]}:</span> | |||||
${d} | |||||
</span>`; | |||||
} | |||||
}); | |||||
} | |||||
calc() {} | |||||
// bindTooltip() { | |||||
// this.slices.map((slice, i) => { | |||||
// slice.addEventListener('mouseenter', () => { | |||||
// let g_off = getOffset(this.chartWrapper), p_off = getOffset(slice); | |||||
// let x = p_off.left - g_off.left + slice.offsetWidth/2; | |||||
// let y = p_off.top - g_off.top - 6; | |||||
// let title = (this.formatted_labels && this.formatted_labels.length>0 | |||||
// ? this.formatted_labels[i] : this.labels[i]) + ': '; | |||||
// let percent = (this.slice_totals[i]*100/this.grand_total).toFixed(1); | |||||
// this.tip.set_values(x, y, title, percent + "%"); | |||||
// this.tip.show_tip(); | |||||
// }); | |||||
// }); | |||||
// } | |||||
// renderLegend() { | |||||
// let x_values = this.formatted_labels && this.formatted_labels.length > 0 | |||||
// ? this.formatted_labels : this.labels; | |||||
// this.legend_totals.map((d, i) => { | |||||
// if(d) { | |||||
// let stats = $.create('div', { | |||||
// className: 'stats', | |||||
// inside: this.statsWrapper | |||||
// }); | |||||
// stats.innerHTML = `<span class="indicator"> | |||||
// <i style="background: ${this.colors[i]}"></i> | |||||
// <span class="text-muted">${x_values[i]}:</span> | |||||
// ${d} | |||||
// </span>`; | |||||
// } | |||||
// }); | |||||
// } | |||||
} | } |
@@ -1,63 +0,0 @@ | |||||
import { makeSVGGroup } from '../utils/draw'; | |||||
export class ChartComponent { | |||||
constructor({ | |||||
layerClass = '', | |||||
layerTransform = '', | |||||
initData, | |||||
// called on update | |||||
setData, | |||||
preMake, | |||||
make, | |||||
postMake, | |||||
animate | |||||
}) { | |||||
this.layerClass = layerClass; | |||||
this.layerTransform = layerTransform; | |||||
this.initData = initData; | |||||
this.setData = setData; | |||||
this.preMake = preMake; | |||||
this.make = make; | |||||
this.postMake = postMake; | |||||
this.animate = animate; | |||||
this.layer = undefined; | |||||
this.store = []; | |||||
} | |||||
refresh(state, args) { | |||||
this.meta = Object.assign((this.meta || {}), args); | |||||
this.state = state; | |||||
} | |||||
render() { | |||||
this.data = this.setData(); // The only without this function? | |||||
this.preMake && this.preMake(); | |||||
this.store = this.make(); | |||||
this.layer.textContent = ''; | |||||
this.store.forEach(element => { | |||||
this.layer.appendChild(element); | |||||
}); | |||||
this.postMake && this.postMake(); | |||||
} | |||||
setupParent(parent) { | |||||
this.parent = parent; | |||||
} | |||||
loadAnimatedComponents() { | |||||
this.animate(this.store); | |||||
} | |||||
makeLayer() { | |||||
this.layer = makeSVGGroup(this.parent, this.layerClass, this.layerTransform); | |||||
} | |||||
} |
@@ -0,0 +1,109 @@ | |||||
import { makeSVGGroup } from '../utils/draw'; | |||||
import { yLine } from '../utils/draw'; | |||||
import { equilizeNoOfElements } from '../utils/draw-utils'; | |||||
import { Animator, translateHoriLine } from '../utils/animate'; | |||||
class ChartComponent { | |||||
constructor({ | |||||
layerClass = '', | |||||
layerTransform = '', | |||||
parent, | |||||
constants, | |||||
data, | |||||
// called on update | |||||
preMake, | |||||
makeElements, | |||||
postMake, | |||||
animateElements | |||||
}) { | |||||
this.parent = parent; | |||||
this.layerClass = layerClass; | |||||
this.layerTransform = layerTransform; | |||||
this.constants = constants; | |||||
this.preMake = preMake; | |||||
this.makeElements = makeElements; | |||||
this.postMake = postMake; | |||||
this.animateElements = animateElements; | |||||
this.store = []; | |||||
this.layer = makeSVGGroup(this.parent, this.layerClass, this.layerTransform); | |||||
this.data = data; | |||||
this.make(); | |||||
} | |||||
refresh(data) { | |||||
this.data = data; | |||||
} | |||||
make() { | |||||
this.preMake && this.preMake(); | |||||
this.render(this.data); | |||||
this.postMake && this.postMake(); | |||||
this.oldData = this.data; | |||||
} | |||||
render(data) { | |||||
this.store = this.makeElements(data); | |||||
this.layer.textContent = ''; | |||||
this.store.forEach(element => { | |||||
this.layer.appendChild(element); | |||||
}); | |||||
} | |||||
update(animate = true) { | |||||
let animateElements = [] | |||||
if(animate) { | |||||
animateElements = this.animateElements(this.data); | |||||
} | |||||
// TODO: Can we remove this? | |||||
setTimeout(() => { | |||||
this.make(); | |||||
}, 1400); | |||||
return animateElements; | |||||
} | |||||
} | |||||
export function getYAxisComponent(parent, constants, initData) { | |||||
return new ChartComponent({ | |||||
parent: parent, | |||||
layerClass: 'y axis', | |||||
constants: constants, | |||||
data: initData, | |||||
makeElements: function(data) { | |||||
return data.positions.map((position, i) => | |||||
yLine(position, data.labels[i], this.constants.width, | |||||
{mode: this.constants.mode, pos: this.constants.pos}) | |||||
); | |||||
}, | |||||
animateElements: function(newData) { | |||||
let newPos = newData.positions; | |||||
let newLabels = newData.labels; | |||||
let oldPos = this.oldData.positions; | |||||
let oldLabels = this.oldData.labels; | |||||
let extra = newPos.length - oldPos.length; | |||||
[oldPos, newPos] = equilizeNoOfElements(oldPos, newPos); | |||||
[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); | |||||
this.render({ | |||||
positions: oldPos, | |||||
labels: newLabels | |||||
}); | |||||
return this.store.map((line, i) => { | |||||
return translateHoriLine( | |||||
line, newPos[i], oldPos[i] | |||||
); | |||||
}); | |||||
} | |||||
}) | |||||
} |
@@ -7,6 +7,25 @@ export const REPLACE_ALL_NEW_DUR = 250; | |||||
export const STD_EASING = 'easein'; | export const STD_EASING = 'easein'; | ||||
export function translate(unit, oldCoord, newCoord, duration) { | |||||
return [ | |||||
unit, | |||||
{transform: newCoord.join(', ')}, | |||||
duration, | |||||
STD_EASING, | |||||
"translate", | |||||
{transform: oldCoord.join(', ')} | |||||
]; | |||||
} | |||||
export function translateVertLine(xLine, newX, oldX) { | |||||
return translate(xLine, [oldX, 0], [newX, 0], MARKER_LINE_ANIM_DUR); | |||||
} | |||||
export function translateHoriLine(yLine, newY, oldY) { | |||||
return translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR); | |||||
} | |||||
export var Animator = (function() { | export var Animator = (function() { | ||||
var Animator = function(totalHeight, totalWidth, zeroLine, avgUnitWidth) { | var Animator = function(totalHeight, totalWidth, zeroLine, avgUnitWidth) { | ||||
// constants | // constants | ||||
@@ -54,25 +73,6 @@ export var Animator = (function() { | |||||
} | } | ||||
return pathComponents; | return pathComponents; | ||||
}, | |||||
translate: function(obj, oldCoord, newCoord, duration) { | |||||
return [ | |||||
{unit: obj, array: [0], index: 0}, | |||||
{transform: newCoord.join(', ')}, | |||||
duration, | |||||
STD_EASING, | |||||
"translate", | |||||
{transform: oldCoord.join(', ')} | |||||
]; | |||||
}, | |||||
translateVertLine: function(xLine, newX, oldX) { | |||||
return this.translate(xLine, [oldX, 0], [newX, 0], MARKER_LINE_ANIM_DUR); | |||||
}, | |||||
translateHoriLine: function(yLine, newY, oldY) { | |||||
return this.translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR); | |||||
} | } | ||||
}; | }; | ||||
@@ -208,8 +208,8 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
className: className, | className: className, | ||||
x1: x1, | x1: x1, | ||||
x2: x2, | x2: x2, | ||||
y1: y, | |||||
y2: y, | |||||
y1: 0, | |||||
y2: 0, | |||||
styles: { | styles: { | ||||
stroke: options.stroke | stroke: options.stroke | ||||
} | } | ||||
@@ -217,7 +217,7 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
let text = createSVG('text', { | let text = createSVG('text', { | ||||
x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN, | x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN, | ||||
y: y, | |||||
y: 0, | |||||
dy: (FONT_SIZE / 2 - 2) + 'px', | dy: (FONT_SIZE / 2 - 2) + 'px', | ||||
'font-size': FONT_SIZE + 'px', | 'font-size': FONT_SIZE + 'px', | ||||
'text-anchor': x1 < x2 ? 'end' : 'start', | 'text-anchor': x1 < x2 ? 'end' : 'start', | ||||
@@ -225,6 +225,7 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
}); | }); | ||||
let line = createSVG('g', { | let line = createSVG('g', { | ||||
transform: `translate(0, ${y})`, | |||||
'stroke-opacity': 1 | 'stroke-opacity': 1 | ||||
}); | }); | ||||
@@ -238,6 +239,33 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
return line; | return line; | ||||
} | } | ||||
export function yLine(y, label, width, options={}) { | |||||
if(!options.pos) options.pos = 'left'; | |||||
if(!options.offset) options.offset = 0; | |||||
if(!options.mode) options.mode = 'span'; | |||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR; | |||||
if(!options.className) options.className = ''; | |||||
let x1 = -1 * AXIS_TICK_LENGTH; | |||||
let x2 = options.mode === 'span' ? width + AXIS_TICK_LENGTH : 0; | |||||
if(options.mode === 'tick' && options.pos === 'right') { | |||||
x1 = width + AXIS_TICK_LENGTH | |||||
x2 = width; | |||||
} | |||||
let offset = options.pos === 'left' ? -1 * options.offset : options.offset; | |||||
x1 += options.offset; | |||||
x2 += options.offset; | |||||
return makeHoriLine(y, label, x1, x2, { | |||||
stroke: options.stroke, | |||||
className: options.className, | |||||
lineType: options.lineType | |||||
}); | |||||
} | |||||
export class AxisChartRenderer { | export class AxisChartRenderer { | ||||
constructor(state) { | constructor(state) { | ||||
this.refreshState(state); | this.refreshState(state); | ||||
@@ -259,7 +287,7 @@ export class AxisChartRenderer { | |||||
xLine(x, label, options={}) { | xLine(x, label, options={}) { | ||||
if(!options.pos) options.pos = 'bottom'; | if(!options.pos) options.pos = 'bottom'; | ||||
if(!options.offset) options.offset = 0; | if(!options.offset) options.offset = 0; | ||||
if(!options.mode) options.mode = this.xAxisMode; | |||||
if(!options.mode) options.mode = 'span'; | |||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR; | if(!options.stroke) options.stroke = BASE_LINE_COLOR; | ||||
if(!options.className) options.className = ''; | if(!options.className) options.className = ''; | ||||
@@ -290,32 +318,7 @@ export class AxisChartRenderer { | |||||
}); | }); | ||||
} | } | ||||
yLine(y, label, options={}) { | |||||
if(!options.pos) options.pos = 'left'; | |||||
if(!options.offset) options.offset = 0; | |||||
if(!options.mode) options.mode = this.yAxisMode; | |||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR; | |||||
if(!options.className) options.className = ''; | |||||
let x1 = -1 * AXIS_TICK_LENGTH; | |||||
let x2 = options.mode === 'span' ? this.totalWidth + AXIS_TICK_LENGTH : 0; | |||||
if(options.mode === 'tick' && options.pos === 'right') { | |||||
x1 = this.totalWidth + AXIS_TICK_LENGTH | |||||
x2 = this.totalWidth; | |||||
} | |||||
let offset = options.pos === 'left' ? -1 * options.offset : options.offset; | |||||
x1 += options.offset; | |||||
x2 += options.offset; | |||||
return makeHoriLine(y, label, x1, x2, { | |||||
stroke: options.stroke, | |||||
className: options.className, | |||||
lineType: options.lineType | |||||
}); | |||||
} | |||||
xMarker() {} | xMarker() {} | ||||
@@ -445,23 +448,4 @@ export class AxisChartRenderer { | |||||
return pathComponents; | 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); | |||||
} | |||||
} | } |
@@ -21,7 +21,7 @@ function normalize(x) { | |||||
return [sig * man, exp]; | return [sig * man, exp]; | ||||
} | } | ||||
function getRangeIntervals(max, min=0) { | |||||
function getChartRangeIntervals(max, min=0) { | |||||
let upperBound = Math.ceil(max); | let upperBound = Math.ceil(max); | ||||
let lowerBound = Math.floor(min); | let lowerBound = Math.floor(min); | ||||
let range = upperBound - lowerBound; | let range = upperBound - lowerBound; | ||||
@@ -59,19 +59,19 @@ function getRangeIntervals(max, min=0) { | |||||
return intervals; | return intervals; | ||||
} | } | ||||
function getIntervals(maxValue, minValue=0) { | |||||
function getChartIntervals(maxValue, minValue=0) { | |||||
let [normalMaxValue, exponent] = normalize(maxValue); | let [normalMaxValue, exponent] = normalize(maxValue); | ||||
let normalMinValue = minValue ? minValue/Math.pow(10, exponent): 0; | let normalMinValue = minValue ? minValue/Math.pow(10, exponent): 0; | ||||
// Allow only 7 significant digits | // Allow only 7 significant digits | ||||
normalMaxValue = normalMaxValue.toFixed(6); | normalMaxValue = normalMaxValue.toFixed(6); | ||||
let intervals = getRangeIntervals(normalMaxValue, normalMinValue); | |||||
let intervals = getChartRangeIntervals(normalMaxValue, normalMinValue); | |||||
intervals = intervals.map(value => value * Math.pow(10, exponent)); | intervals = intervals.map(value => value * Math.pow(10, exponent)); | ||||
return intervals; | return intervals; | ||||
} | } | ||||
export function calcIntervals(values, withMinimum=false) { | |||||
export function calcChartIntervals(values, withMinimum=false) { | |||||
//*** Where the magic happens *** | //*** Where the magic happens *** | ||||
// Calculates best-fit y intervals from given values | // Calculates best-fit y intervals from given values | ||||
@@ -84,7 +84,7 @@ export function calcIntervals(values, withMinimum=false) { | |||||
let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars | let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars | ||||
function getPositiveFirstIntervals(maxValue, absMinValue) { | function getPositiveFirstIntervals(maxValue, absMinValue) { | ||||
let intervals = getIntervals(maxValue); | |||||
let intervals = getChartIntervals(maxValue); | |||||
let intervalSize = intervals[1] - intervals[0]; | let intervalSize = intervals[1] - intervals[0]; | ||||
@@ -102,9 +102,9 @@ export function calcIntervals(values, withMinimum=false) { | |||||
if(maxValue >= 0 && minValue >= 0) { | if(maxValue >= 0 && minValue >= 0) { | ||||
exponent = normalize(maxValue)[1]; | exponent = normalize(maxValue)[1]; | ||||
if(!withMinimum) { | if(!withMinimum) { | ||||
intervals = getIntervals(maxValue); | |||||
intervals = getChartIntervals(maxValue); | |||||
} else { | } else { | ||||
intervals = getIntervals(maxValue, minValue); | |||||
intervals = getChartIntervals(maxValue, minValue); | |||||
} | } | ||||
} | } | ||||
@@ -142,9 +142,9 @@ export function calcIntervals(values, withMinimum=false) { | |||||
exponent = normalize(pseudoMaxValue)[1]; | exponent = normalize(pseudoMaxValue)[1]; | ||||
if(!withMinimum) { | if(!withMinimum) { | ||||
intervals = getIntervals(pseudoMaxValue); | |||||
intervals = getChartIntervals(pseudoMaxValue); | |||||
} else { | } else { | ||||
intervals = getIntervals(pseudoMaxValue, pseudoMinValue); | |||||
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue); | |||||
} | } | ||||
intervals = intervals.reverse().map(d => d * (-1)); | intervals = intervals.reverse().map(d => d * (-1)); | ||||
@@ -174,6 +174,18 @@ export function getZeroIndex(yPts) { | |||||
return zeroIndex; | return zeroIndex; | ||||
} | } | ||||
export function getRealIntervals(max, noOfIntervals, min = 0, asc = 1) { | |||||
let range = max - min; | |||||
let part = range * 1.0 / noOfIntervals; | |||||
let intervals = []; | |||||
for(var i = 0; i <= noOfIntervals; i++) { | |||||
intervals.push(min + part * i); | |||||
} | |||||
return asc ? intervals : intervals.reverse(); | |||||
} | |||||
export function getIntervalSize(orderedArray) { | export function getIntervalSize(orderedArray) { | ||||
return orderedArray[1] - orderedArray[0]; | return orderedArray[1] - orderedArray[0]; | ||||
} | } | ||||