@@ -290,6 +290,23 @@ const REPLACE_ALL_NEW_DUR = 250; | |||
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 LABEL_MARGIN = 4; | |||
const FONT_SIZE = 10; | |||
@@ -478,8 +495,8 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||
className: className, | |||
x1: x1, | |||
x2: x2, | |||
y1: y, | |||
y2: y, | |||
y1: 0, | |||
y2: 0, | |||
styles: { | |||
stroke: options.stroke | |||
} | |||
@@ -487,7 +504,7 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||
let text = createSVG('text', { | |||
x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN, | |||
y: y, | |||
y: 0, | |||
dy: (FONT_SIZE / 2 - 2) + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
'text-anchor': x1 < x2 ? 'end' : 'start', | |||
@@ -495,6 +512,7 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||
}); | |||
let line = createSVG('g', { | |||
transform: `translate(0, ${y})`, | |||
'stroke-opacity': 1 | |||
}); | |||
@@ -508,6 +526,31 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||
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 { | |||
constructor(state) { | |||
this.refreshState(state); | |||
@@ -529,7 +572,7 @@ class AxisChartRenderer { | |||
xLine(x, label, options={}) { | |||
if(!options.pos) options.pos = 'bottom'; | |||
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.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() {} | |||
@@ -713,25 +733,6 @@ class AxisChartRenderer { | |||
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 = { | |||
@@ -1066,11 +1067,15 @@ class BaseChart { | |||
_setup() { | |||
this.bindWindowEvents(); | |||
this.setupConstants(); | |||
this.setupComponents(); | |||
this.setMargins(); | |||
this.makeContainer(); | |||
this.makeTooltip(); // without binding | |||
this.calcWidth(); | |||
this.makeChartArea(); | |||
this.setupComponents(); | |||
this.draw(true); | |||
} | |||
@@ -1113,31 +1118,13 @@ class BaseChart { | |||
bindTooltip() {} | |||
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.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() { | |||
@@ -1153,15 +1140,34 @@ class BaseChart { | |||
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.reCalc(); | |||
this.calc(); // builds state | |||
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() { | |||
this.svg = makeSVGContainer( | |||
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() {} | |||
setupNavigation(init=false) { | |||
@@ -1299,66 +1270,107 @@ class BaseChart { | |||
const Y_AXIS_MARGIN = 60; | |||
class ChartComponent { | |||
class ChartComponent$1 { | |||
constructor({ | |||
layerClass = '', | |||
layerTransform = '', | |||
initData, | |||
parent, | |||
constants, | |||
data, | |||
// called on update | |||
setData, | |||
preMake, | |||
make, | |||
makeElements, | |||
postMake, | |||
animate | |||
animateElements | |||
}) { | |||
this.parent = parent; | |||
this.layerClass = layerClass; | |||
this.layerTransform = layerTransform; | |||
this.initData = initData; | |||
this.setData = setData; | |||
this.constants = constants; | |||
this.preMake = preMake; | |||
this.make = make; | |||
this.makeElements = makeElements; | |||
this.postMake = postMake; | |||
this.animate = animate; | |||
this.animateElements = animateElements; | |||
this.layer = undefined; | |||
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.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.store.forEach(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; | |||
@@ -1612,7 +1624,7 @@ function normalize(x) { | |||
return [sig * man, exp]; | |||
} | |||
function getRangeIntervals(max, min=0) { | |||
function getChartRangeIntervals(max, min=0) { | |||
let upperBound = Math.ceil(max); | |||
let lowerBound = Math.floor(min); | |||
let range = upperBound - lowerBound; | |||
@@ -1650,19 +1662,19 @@ function getRangeIntervals(max, min=0) { | |||
return intervals; | |||
} | |||
function getIntervals(maxValue, minValue=0) { | |||
function getChartIntervals(maxValue, minValue=0) { | |||
let [normalMaxValue, exponent] = normalize(maxValue); | |||
let normalMinValue = minValue ? minValue/Math.pow(10, exponent): 0; | |||
// Allow only 7 significant digits | |||
normalMaxValue = normalMaxValue.toFixed(6); | |||
let intervals = getRangeIntervals(normalMaxValue, normalMinValue); | |||
let intervals = getChartRangeIntervals(normalMaxValue, normalMinValue); | |||
intervals = intervals.map(value => value * Math.pow(10, exponent)); | |||
return intervals; | |||
} | |||
function calcIntervals(values, withMinimum=false) { | |||
function calcChartIntervals(values, withMinimum=false) { | |||
//*** Where the magic happens *** | |||
// 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 | |||
function getPositiveFirstIntervals(maxValue, absMinValue) { | |||
let intervals = getIntervals(maxValue); | |||
let intervals = getChartIntervals(maxValue); | |||
let intervalSize = intervals[1] - intervals[0]; | |||
@@ -1693,9 +1705,9 @@ function calcIntervals(values, withMinimum=false) { | |||
if(maxValue >= 0 && minValue >= 0) { | |||
exponent = normalize(maxValue)[1]; | |||
if(!withMinimum) { | |||
intervals = getIntervals(maxValue); | |||
intervals = getChartIntervals(maxValue); | |||
} else { | |||
intervals = getIntervals(maxValue, minValue); | |||
intervals = getChartIntervals(maxValue, minValue); | |||
} | |||
} | |||
@@ -1733,9 +1745,9 @@ function calcIntervals(values, withMinimum=false) { | |||
exponent = normalize(pseudoMaxValue)[1]; | |||
if(!withMinimum) { | |||
intervals = getIntervals(pseudoMaxValue); | |||
intervals = getChartIntervals(pseudoMaxValue); | |||
} else { | |||
intervals = getIntervals(pseudoMaxValue, pseudoMinValue); | |||
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue); | |||
} | |||
intervals = intervals.reverse().map(d => d * (-1)); | |||
@@ -1765,6 +1777,18 @@ function getZeroIndex(yPts) { | |||
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) { | |||
return orderedArray[1] - orderedArray[0]; | |||
} | |||
@@ -1805,6 +1829,9 @@ class AxisChart extends BaseChart { | |||
this.lineOptions = args.lineOptions; | |||
this.type = args.type || 'line'; | |||
this.xAxisMode = args.xAxisMode || 'span'; | |||
this.yAxisMode = args.yAxisMode || 'span'; | |||
this.setupUnitRenderer(); | |||
this.zeroLine = this.height; | |||
@@ -1908,7 +1935,7 @@ class AxisChart extends BaseChart { | |||
}; | |||
} | |||
reCalc() { | |||
calc() { | |||
let s = this.state; | |||
s.xAxisLabels = this.data.labels; | |||
@@ -1940,7 +1967,7 @@ class AxisChart extends BaseChart { | |||
} | |||
calcYAxisParameters(yAxis, dataValues, withMinimum = 'false') { | |||
yAxis.labels = calcIntervals(dataValues, withMinimum); | |||
yAxis.labels = calcChartIntervals(dataValues, withMinimum); | |||
const yPts = yAxis.labels; | |||
yAxis.scaleMultiplier = this.height / getValueRange(yPts); | |||
@@ -2040,78 +2067,63 @@ class AxisChart extends BaseChart { | |||
// 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.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() { | |||
return new ChartComponent({ | |||
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; | |||
// positions | |||
// TODO: xAxis Label spacing | |||
return s.xAxisPositions.map((position, i) => | |||
this.renderer.xLine(position, s.xAxisLabels[i] | |||
xLine(position, s.xAxisLabels[i], this.constants.height | |||
// , {pos:'top'} | |||
) | |||
); | |||
@@ -2168,7 +2180,7 @@ class AxisChart extends BaseChart { | |||
layerClass: 'dataset-units dataset-' + index, | |||
setData: () => {}, | |||
preMake: () => { }, | |||
make: () => { | |||
makeElements: () => { | |||
let d = this.state.datasets[index]; | |||
return d.positions.map((y, j) => { | |||
@@ -2230,7 +2242,7 @@ class AxisChart extends BaseChart { | |||
return new ChartComponent({ | |||
layerClass: 'path dataset-path', | |||
setData: () => {}, | |||
make: () => { | |||
makeElements: () => { | |||
let d = this.state.datasets[index]; | |||
let color = this.colors[index]; | |||
@@ -2280,7 +2292,7 @@ class AxisChart extends BaseChart { | |||
return new ChartComponent({ | |||
layerClass: 'y-markers', | |||
setData: () => {}, | |||
make: () => { | |||
makeElements: () => { | |||
let s = this.state; | |||
return s.yMarkers.map(marker => | |||
this.renderer.yMarker(marker.value, marker.name, | |||
@@ -2301,7 +2313,7 @@ class AxisChart extends BaseChart { | |||
return new ChartComponent({ | |||
layerClass: 'y-regions', | |||
setData: () => {}, | |||
make: () => { | |||
makeElements: () => { | |||
let s = this.state; | |||
return s.yRegions.map(region => | |||
this.renderer.yRegion(region.start, region.end, region.name) | |||
@@ -2312,10 +2324,6 @@ class AxisChart extends BaseChart { | |||
}); | |||
} | |||
getXRegions() { | |||
return []; | |||
} | |||
refreshRenderer() { | |||
// These args are basically the current state of the chart, | |||
// with constant and alive params mixed | |||
@@ -2335,8 +2343,6 @@ class AxisChart extends BaseChart { | |||
this.renderer.refreshState(state); | |||
} | |||
this.refreshComponents(); | |||
let meta = { | |||
totalHeight: this.height, | |||
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; | |||
@@ -53,6 +53,7 @@ let bar_composite_chart = new Chart ({ | |||
isNavigable: 1, | |||
isSeries: 1, | |||
valuesOverPoints: 1, | |||
yAxisMode: 'tick' | |||
// regionFill: 1 | |||
}); | |||
@@ -16,6 +16,7 @@ import pkg from './package.json'; | |||
export default [ | |||
{ | |||
input: 'src/js/chart.js', | |||
sourcemap: true, | |||
output: [ | |||
{ | |||
file: 'docs/assets/js/frappe-charts.min.js', | |||
@@ -1,13 +1,13 @@ | |||
import BaseChart from './BaseChart'; | |||
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 { getOffset, fire } from '../utils/dom'; | |||
import { AxisChartRenderer } from '../utils/draw'; | |||
import { getOffset, fire } from '../utils/dom'; | |||
import { equilizeNoOfElements } from '../utils/draw-utils'; | |||
import { Animator } from '../utils/animate'; | |||
import { Animator, translateHoriLine } from '../utils/animate'; | |||
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'; | |||
export default class AxisChart extends BaseChart { | |||
@@ -21,6 +21,9 @@ export default class AxisChart extends BaseChart { | |||
this.lineOptions = args.lineOptions; | |||
this.type = args.type || 'line'; | |||
this.xAxisMode = args.xAxisMode || 'span'; | |||
this.yAxisMode = args.yAxisMode || 'span'; | |||
this.setupUnitRenderer(); | |||
this.zeroLine = this.height; | |||
@@ -124,7 +127,7 @@ export default class AxisChart extends BaseChart { | |||
}; | |||
} | |||
reCalc() { | |||
calc() { | |||
let s = this.state; | |||
s.xAxisLabels = this.data.labels; | |||
@@ -156,7 +159,7 @@ export default class AxisChart extends BaseChart { | |||
} | |||
calcYAxisParameters(yAxis, dataValues, withMinimum = 'false') { | |||
yAxis.labels = calcIntervals(dataValues, withMinimum); | |||
yAxis.labels = calcChartIntervals(dataValues, withMinimum); | |||
const yPts = yAxis.labels; | |||
yAxis.scaleMultiplier = this.height / getValueRange(yPts); | |||
@@ -256,78 +259,63 @@ export default class AxisChart extends BaseChart { | |||
// 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.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() { | |||
return new ChartComponent({ | |||
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; | |||
// positions | |||
// TODO: xAxis Label spacing | |||
return s.xAxisPositions.map((position, i) => | |||
this.renderer.xLine(position, s.xAxisLabels[i] | |||
xLine(position, s.xAxisLabels[i], this.constants.height | |||
// , {pos:'top'} | |||
) | |||
); | |||
@@ -384,7 +372,7 @@ export default class AxisChart extends BaseChart { | |||
layerClass: 'dataset-units dataset-' + index, | |||
setData: () => {}, | |||
preMake: () => { }, | |||
make: () => { | |||
makeElements: () => { | |||
let d = this.state.datasets[index]; | |||
return d.positions.map((y, j) => { | |||
@@ -446,7 +434,7 @@ export default class AxisChart extends BaseChart { | |||
return new ChartComponent({ | |||
layerClass: 'path dataset-path', | |||
setData: () => {}, | |||
make: () => { | |||
makeElements: () => { | |||
let d = this.state.datasets[index]; | |||
let color = this.colors[index]; | |||
@@ -496,7 +484,7 @@ export default class AxisChart extends BaseChart { | |||
return new ChartComponent({ | |||
layerClass: 'y-markers', | |||
setData: () => {}, | |||
make: () => { | |||
makeElements: () => { | |||
let s = this.state; | |||
return s.yMarkers.map(marker => | |||
this.renderer.yMarker(marker.value, marker.name, | |||
@@ -517,7 +505,7 @@ export default class AxisChart extends BaseChart { | |||
return new ChartComponent({ | |||
layerClass: 'y-regions', | |||
setData: () => {}, | |||
make: () => { | |||
makeElements: () => { | |||
let s = this.state; | |||
return s.yRegions.map(region => | |||
this.renderer.yRegion(region.start, region.end, region.name) | |||
@@ -528,10 +516,6 @@ export default class AxisChart extends BaseChart { | |||
}); | |||
} | |||
getXRegions() { | |||
return []; | |||
} | |||
refreshRenderer() { | |||
// These args are basically the current state of the chart, | |||
// with constant and alive params mixed | |||
@@ -551,8 +535,6 @@ export default class AxisChart extends BaseChart { | |||
this.renderer.refreshState(state); | |||
} | |||
this.refreshComponents(); | |||
let meta = { | |||
totalHeight: this.height, | |||
totalWidth: this.width, | |||
@@ -128,11 +128,15 @@ export default class BaseChart { | |||
_setup() { | |||
this.bindWindowEvents(); | |||
this.setupConstants(); | |||
this.setupComponents(); | |||
this.setMargins(); | |||
this.makeContainer(); | |||
this.makeTooltip(); // without binding | |||
this.calcWidth(); | |||
this.makeChartArea(); | |||
this.setupComponents(); | |||
this.draw(true); | |||
} | |||
@@ -175,31 +179,13 @@ export default class BaseChart { | |||
bindTooltip() {} | |||
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.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() { | |||
@@ -215,15 +201,34 @@ export default class BaseChart { | |||
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.reCalc(); | |||
this.calc(); // builds state | |||
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() { | |||
this.svg = makeSVGContainer( | |||
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() {} | |||
setupNavigation(init=false) { | |||
@@ -1,5 +1,5 @@ | |||
import AxisChart from './AxisChart'; | |||
import { ChartComponent } from '../objects/ChartComponent'; | |||
// import { ChartComponent } from '../objects/ChartComponents'; | |||
import { makeSVGGroup, makePath, makeGradient } from '../utils/draw'; | |||
import { equilizeNoOfElements } from '../utils/draw-utils'; | |||
@@ -1,6 +1,6 @@ | |||
import AxisChart from './AxisChart'; | |||
import { Y_AXIS_MARGIN } from '../utils/margins'; | |||
import { ChartComponent } from '../objects/ChartComponent'; | |||
// import { ChartComponent } from '../objects/ChartComponents'; | |||
import { floatTwo } from '../utils/helpers'; | |||
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 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() { | |||
var Animator = function(totalHeight, totalWidth, zeroLine, avgUnitWidth) { | |||
// constants | |||
@@ -54,25 +73,6 @@ export var Animator = (function() { | |||
} | |||
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, | |||
x1: x1, | |||
x2: x2, | |||
y1: y, | |||
y2: y, | |||
y1: 0, | |||
y2: 0, | |||
styles: { | |||
stroke: options.stroke | |||
} | |||
@@ -217,7 +217,7 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||
let text = createSVG('text', { | |||
x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN, | |||
y: y, | |||
y: 0, | |||
dy: (FONT_SIZE / 2 - 2) + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
'text-anchor': x1 < x2 ? 'end' : 'start', | |||
@@ -225,6 +225,7 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||
}); | |||
let line = createSVG('g', { | |||
transform: `translate(0, ${y})`, | |||
'stroke-opacity': 1 | |||
}); | |||
@@ -238,6 +239,33 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||
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 { | |||
constructor(state) { | |||
this.refreshState(state); | |||
@@ -259,7 +287,7 @@ export class AxisChartRenderer { | |||
xLine(x, label, options={}) { | |||
if(!options.pos) options.pos = 'bottom'; | |||
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.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() {} | |||
@@ -445,23 +448,4 @@ export class AxisChartRenderer { | |||
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]; | |||
} | |||
function getRangeIntervals(max, min=0) { | |||
function getChartRangeIntervals(max, min=0) { | |||
let upperBound = Math.ceil(max); | |||
let lowerBound = Math.floor(min); | |||
let range = upperBound - lowerBound; | |||
@@ -59,19 +59,19 @@ function getRangeIntervals(max, min=0) { | |||
return intervals; | |||
} | |||
function getIntervals(maxValue, minValue=0) { | |||
function getChartIntervals(maxValue, minValue=0) { | |||
let [normalMaxValue, exponent] = normalize(maxValue); | |||
let normalMinValue = minValue ? minValue/Math.pow(10, exponent): 0; | |||
// Allow only 7 significant digits | |||
normalMaxValue = normalMaxValue.toFixed(6); | |||
let intervals = getRangeIntervals(normalMaxValue, normalMinValue); | |||
let intervals = getChartRangeIntervals(normalMaxValue, normalMinValue); | |||
intervals = intervals.map(value => value * Math.pow(10, exponent)); | |||
return intervals; | |||
} | |||
export function calcIntervals(values, withMinimum=false) { | |||
export function calcChartIntervals(values, withMinimum=false) { | |||
//*** Where the magic happens *** | |||
// 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 | |||
function getPositiveFirstIntervals(maxValue, absMinValue) { | |||
let intervals = getIntervals(maxValue); | |||
let intervals = getChartIntervals(maxValue); | |||
let intervalSize = intervals[1] - intervals[0]; | |||
@@ -102,9 +102,9 @@ export function calcIntervals(values, withMinimum=false) { | |||
if(maxValue >= 0 && minValue >= 0) { | |||
exponent = normalize(maxValue)[1]; | |||
if(!withMinimum) { | |||
intervals = getIntervals(maxValue); | |||
intervals = getChartIntervals(maxValue); | |||
} else { | |||
intervals = getIntervals(maxValue, minValue); | |||
intervals = getChartIntervals(maxValue, minValue); | |||
} | |||
} | |||
@@ -142,9 +142,9 @@ export function calcIntervals(values, withMinimum=false) { | |||
exponent = normalize(pseudoMaxValue)[1]; | |||
if(!withMinimum) { | |||
intervals = getIntervals(pseudoMaxValue); | |||
intervals = getChartIntervals(pseudoMaxValue); | |||
} else { | |||
intervals = getIntervals(pseudoMaxValue, pseudoMinValue); | |||
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue); | |||
} | |||
intervals = intervals.reverse().map(d => d * (-1)); | |||
@@ -174,6 +174,18 @@ export function getZeroIndex(yPts) { | |||
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) { | |||
return orderedArray[1] - orderedArray[0]; | |||
} | |||