[data] begin with zero data, bars rendered

This commit is contained in:
Prateeksha Singh 2018-02-27 22:18:57 +05:30
parent 8d2246e6c3
commit 8452e909bb
17 changed files with 479 additions and 459 deletions

View File

@ -232,6 +232,19 @@ function getStringWidth(string, charWidth) {
return (string+"").length * charWidth;
}
function getBarHeightAndYAttr(yTop, zeroLine) {
let height, y;
if (yTop < zeroLine) {
height = zeroLine - yTop;
y = yTop;
} else {
height = yTop - zeroLine;
y = zeroLine;
}
return [height, y];
}
function equilizeNoOfElements(array1, array2,
extra_count=array2.length - array1.length) {
@ -347,26 +360,6 @@ function createSVG(tag, o) {
return element;
}
function renderVerticalGradient(svgDefElem, gradientId) {
return createSVG('linearGradient', {
inside: svgDefElem,
id: gradientId,
x1: 0,
x2: 0,
y1: 0,
y2: 1
});
}
function setGradientStop(gradElem, offset, color, opacity) {
return createSVG('stop', {
'inside': gradElem,
'style': `stop-color: ${color}`,
'offset': offset,
'stop-opacity': opacity
});
}
function makeSVGContainer(parent, className, width, height) {
return createSVG('svg', {
className: className,
@ -390,7 +383,13 @@ function makeSVGGroup(parent, className, transform='') {
});
}
function wrapInSVGGroup(elements, className='') {
let g = createSVG('g', {
className: className
});
elements.forEach(e => g.appendChild(e));
return g;
}
function makePath(pathStr, className='', stroke='none', fill='none') {
return createSVG('path', {
@ -403,20 +402,7 @@ function makePath(pathStr, className='', stroke='none', fill='none') {
});
}
function makeGradient(svgDefElem, color, lighter = false) {
let gradientId ='path-fill-gradient' + '-' + color;
let gradientDef = renderVerticalGradient(svgDefElem, gradientId);
let opacities = [1, 0.6, 0.2];
if(lighter) {
opacities = [0.4, 0.2, 0];
}
setGradientStop(gradientDef, "0%", color, opacities[0]);
setGradientStop(gradientDef, "50%", color, opacities[1]);
setGradientStop(gradientDef, "100%", color, opacities[2]);
return gradientId;
}
function makeHeatSquare(className, x, y, size, fill='none', data={}) {
let args = {
@ -638,6 +624,37 @@ function yRegion(y1, y2, width, label) {
return region;
}
function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) {
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine);
// console.log(yTop, meta.zeroLine, y, offset);
let rect = createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: x - meta.barsWidth/2,
y: y - offset,
width: width,
height: height || meta.minHeight
});
if(!label && !label.length) {
return rect;
} else {
let text = createSVG('text', {
className: 'data-point-value',
x: x,
y: y - offset,
dy: (FONT_SIZE / 2 * -1) + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': 'middle',
innerHTML: label
});
return wrapInSVGGroup([rect, text]);
}
}
const PRESET_COLOR_MAP = {
'light-blue': '#7cd6fd',
'blue': '#5e64ff',
@ -872,13 +889,15 @@ class BaseChart {
this.title = title;
this.subtitle = subtitle;
this.argHeight = height;
this.type = type;
this.isNavigable = isNavigable;
if(this.isNavigable) {
this.currentIndex = 0;
}
this.data = this.prepareData(data);
this.realData = this.prepareData(data);
this.data = this.prepareFirstData(this.realData);
this.colors = [];
this.config = {};
this.state = {};
@ -990,6 +1009,7 @@ class BaseChart {
this.calcWidth();
this.makeChartArea();
this.calc();
this.initComponents(); // Only depend on the drawArea made in makeChartArea
this.setupComponents();
@ -1002,6 +1022,7 @@ class BaseChart {
// TODO: remove timeout and decrease post animate time in chart component
if(init) {
this.data = this.realData;
setTimeout(() => {this.update();}, 1000);
}
}
@ -1029,10 +1050,10 @@ class BaseChart {
calc() {} // builds state
render(animate=true) {
render(components=this.components, animate=true) {
// Can decouple to this.refreshComponents() first to save animation timeout
let elementsToAnimate = [];
this.components.forEach(c => {
components.forEach(c => {
elementsToAnimate = elementsToAnimate.concat(c.update(animate));
});
if(elementsToAnimate.length > 0) {
@ -1138,8 +1159,10 @@ class BaseChart {
const Y_AXIS_MARGIN = 60;
const MIN_BAR_PERCENT_HEIGHT = 0.01;
const DEFAULT_AXIS_CHART_TYPE = 'line';
const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
const BAR_CHART_SPACE_RATIO = 0.5;
function dataPrep(data, type) {
data.labels = data.labels || [];
@ -1176,23 +1199,60 @@ function dataPrep(data, type) {
// Set labels
//
// Set index
d.index = i;
// Set type
if(!d.chartType ) {
d.chartType = type || DEFAULT_AXIS_CHART_TYPE;
if(!AXIS_DATASET_CHART_TYPES.includes(type)) type === DEFAULT_AXIS_CHART_TYPE;
d.chartType = type;
}
});
// Markers
// Regions
// Set start and end
// data.yRegions = data.yRegions || [];
if(data.yRegions) {
data.yRegions.map(d => {
if(d.end < d.start) {
[d.start, d.end] = [d.end, start];
}
});
}
return data;
}
function zeroDataPrep(realData) {
let datasetLength = realData.labels.length;
let zeroArray = new Array(datasetLength).fill(0);
let zeroData = {
labels: realData.labels,
datasets: realData.datasets.map(d => {
return {
name: '',
values: zeroArray,
chartType: d.chartType
}
}),
yRegions: [
{
start: 0,
end: 0,
label: ''
}
],
yMarkers: [
{
value: 0,
label: ''
}
]
};
return zeroData;
}
class ChartComponent$1 {
constructor({
layerClass = '',
@ -1215,8 +1275,9 @@ class ChartComponent$1 {
this.store = [];
this.layerClass = typeof(layerClass) === 'function'
? layerClass() : layerClass;
this.layerClass = layerClass;
this.layerClass = typeof(this.layerClass) === 'function'
? this.layerClass() : this.layerClass;
this.refresh();
}
@ -1394,81 +1455,61 @@ let componentConfigs = {
},
barGraph: {
// opt:[
// 'barGraph',
// this.drawArea,
// {
// controller: barController,
// index: index,
// color: this.colors[index],
// valuesOverPoints: this.valuesOverPoints,
// stacked: this.barOptions && this.barOptions.stacked,
// spaceRatio: 0.5,
// minHeight: this.height * MIN_BAR_PERCENT_HEIGHT
// },
// {
// barsWidth: this.state.unitWidth * (1 - spaceRatio),
// barWidth: barsWidth/(stacked ? 1 : this.state.noOfDatasets),
// },
// function() {
// let s = this.state;
// return {
// barsWidth: this.state.unitWidth * (1 - spaceRatio),
// barWidth: barsWidth/(stacked ? 1 : this.state.noOfDatasets),
// positions: s.xAxisPositions,
// labels: s.xAxisLabels,
// }
// }.bind(this)
// ],
layerClass() { return 'y-regions' + this.constants.index; },
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; },
makeElements(data) {
let c = this.constants;
return data.yPositions.map((y, j) =>
barController.draw(
return data.yPositions.map((y, j) => {
// console.log(data.cumulativeYPos, data.cumulativeYPos[j]);
return datasetBar(
data.xPositions[j],
y,
color,
c.barWidth,
c.color,
(c.valuesOverPoints ? (c.stacked ? data.cumulativeYs[j] : data.values[j]) : ''),
j,
y - (data.cumulativePositions ? data.cumulativePositions[j] : y)
y - (c.stacked ? data.cumulativeYPos[j] : y),
{
zeroLine: c.zeroLine,
barsWidth: c.barsWidth,
minHeight: c.minHeight
}
)
);
});
},
postMake() {
if((!this.constants.stacked)) {
this.layer.setAttribute('transform',
`translate(${unitRenderer.consts.width * index}, 0)`);
`translate(${this.constants.width * this.constants.index}, 0)`);
}
},
animateElements(newData) {
[this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
// [this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
let newPos = newData.map(d => d.end);
let newLabels = newData.map(d => d.label);
let newStarts = newData.map(d => d.start);
// let newPos = newData.map(d => d.end);
// let newLabels = newData.map(d => d.label);
// let newStarts = newData.map(d => d.start);
let oldPos = this.oldData.map(d => d.end);
let oldLabels = this.oldData.map(d => d.label);
let oldStarts = this.oldData.map(d => d.start);
// let oldPos = this.oldData.map(d => d.end);
// let oldLabels = this.oldData.map(d => d.label);
// let oldStarts = this.oldData.map(d => d.start);
this.render(oldPos.map((pos, i) => {
return {
start: oldStarts[i],
end: oldPos[i],
label: newLabels[i]
}
}));
// this.render(oldPos.map((pos, i) => {
// return {
// start: oldStarts[i],
// end: oldPos[i],
// label: newLabels[i]
// }
// }));
let animateElements = [];
// let animateElements = [];
this.store.map((rectGroup, i) => {
animateElements = animateElements.concat(animateRegion(
rectGroup, newStarts[i], newPos[i], oldPos[i]
));
});
// this.store.map((rectGroup, i) => {
// animateElements = animateElements.concat(animateRegion(
// rectGroup, newStarts[i], newPos[i], oldPos[i]
// ));
// });
return animateElements;
// return animateElements;
}
},
@ -1486,101 +1527,6 @@ function getComponent(name, constants, getData) {
return new ChartComponent$1(config);
}
function getPaths(yList, xList, color, heatline=false, regionFill=false) {
let pointsList = yList.map((y, i) => (xList[i] + ',' + y));
let pointsStr = pointsList.join("L");
let path = makePath("M"+pointsStr, 'line-graph-path', color);
// HeatLine
if(heatline) {
let gradient_id = makeGradient(this.svgDefs, color);
path.style.stroke = `url(#${gradient_id})`;
}
let components = [path];
// Region
if(regionFill) {
let gradient_id_region = makeGradient(this.svgDefs, color, true);
let zeroLine = this.state.yAxis.zeroLine;
// TODO: use zeroLine OR minimum
let pathStr = "M" + `0,${zeroLine}L` + pointsStr + `L${this.width},${zeroLine}`;
components.push(makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id_region})`));
}
return components;
}
// class BarChart extends AxisChart {
// constructor(args) {
// super(args);
// this.type = 'bar';
// this.setup();
// }
// configure(args) {
// super.configure(args);
// this.config.xAxisMode = args.xAxisMode || 'tick';
// this.config.yAxisMode = args.yAxisMode || 'span';
// }
// // =================================
// makeOverlay() {
// // Just make one out of the first element
// let index = this.xAxisLabels.length - 1;
// let unit = this.y[0].svg_units[index];
// this.setCurrentDataPoint(index);
// if(this.overlay) {
// this.overlay.parentNode.removeChild(this.overlay);
// }
// this.overlay = unit.cloneNode();
// this.overlay.style.fill = '#000000';
// this.overlay.style.opacity = '0.4';
// this.drawArea.appendChild(this.overlay);
// }
// bindOverlay() {
// // on event, update overlay
// this.parent.addEventListener('data-select', (e) => {
// this.update_overlay(e.svg_unit);
// });
// }
// bind_units(units_array) {
// units_array.map(unit => {
// unit.addEventListener('click', () => {
// let index = unit.getAttribute('data-point-index');
// this.setCurrentDataPoint(index);
// });
// });
// }
// update_overlay(unit) {
// let attributes = [];
// Object.keys(unit.attributes).map(index => {
// attributes.push(unit.attributes[index]);
// });
// attributes.filter(attr => attr.specified).map(attr => {
// this.overlay.setAttribute(attr.name, attr.nodeValue);
// });
// this.overlay.style.fill = '#000000';
// this.overlay.style.opacity = '0.4';
// }
// onLeftArrow() {
// this.setCurrentDataPoint(this.currentIndex - 1);
// }
// onRightArrow() {
// this.setCurrentDataPoint(this.currentIndex + 1);
// }
// }
function normalize(x) {
// Calculates mantissa and exponent of a number
// Returns normalized number and exponent
@ -1757,17 +1703,7 @@ 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];
@ -1777,6 +1713,10 @@ function getValueRange(orderedArray) {
return orderedArray[orderedArray.length-1] - orderedArray[0];
}
function scale(val, yAxis) {
return floatTwo(yAxis.zeroLine - val * yAxis.scaleMultiplier)
}
function calcDistribution(values, distributionSize) {
// Assume non-negative values,
// implying distribution minimum at zero
@ -1805,19 +1745,19 @@ class AxisChart extends BaseChart {
this.valuesOverPoints = args.valuesOverPoints;
this.formatTooltipY = args.formatTooltipY;
this.formatTooltipX = args.formatTooltipX;
this.barOptions = args.barOptions;
this.lineOptions = args.lineOptions;
this.barOptions = args.barOptions || {};
this.lineOptions = args.lineOptions || {};
this.type = args.type || 'line';
this.xAxisMode = args.xAxisMode || 'span';
this.yAxisMode = args.yAxisMode || 'span';
this.zeroLine = this.height;
this.setTrivialState();
// this.setTrivialState();
this.setup();
}
configure(args) {
configure(args) {3;
super.configure();
// TODO: set in options and use
@ -1826,38 +1766,6 @@ class AxisChart extends BaseChart {
this.config.yAxisMode = args.yAxisMode;
}
setTrivialState() {
// Define data and stuff
let yTempPos = getRealIntervals(this.height, 4, 0, 0);
this.state = {
xAxis: {
positions: [],
labels: [],
},
yAxis: {
positions: yTempPos,
labels: yTempPos.map(d => ""),
},
yRegions: [
{
start: this.height,
end: this.height,
label: ''
}
],
yMarkers: [
{
position: this.height,
label: ''
}
]
};
this.calcWidth();
this.calcXPositions(this.state);
}
setMargins() {
super.setMargins();
this.translateXLeft = Y_AXIS_MARGIN;
@ -1868,10 +1776,12 @@ class AxisChart extends BaseChart {
return dataPrep(data, this.type);
}
prepareFirstData(data=this.data) {
return zeroDataPrep(data);
}
calc() {
this.calcXPositions();
this.calcYAxisParameters(this.getAllYValues(), this.type === 'line');
}
@ -1905,61 +1815,60 @@ class AxisChart extends BaseChart {
};
this.calcYUnits();
this.calcYMaximums();
this.calcYExtremes();
this.calcYRegions();
}
calcYUnits() {
let s = this.state;
this.data.datasets.map(d => {
d.positions = d.values.map(val =>
floatTwo(s.yAxis.zeroLine - val * s.yAxis.scaleMultiplier));
});
let scaleAll = values => values.map(val => scale(val, s.yAxis));
if(this.barOptions && this.barOptions.stacked) {
this.data.datasets.map((d, i) => {
d.cumulativePositions = d.cumulativeYs.map(val =>
floatTwo(s.yAxis.zeroLine - val * s.yAxis.scaleMultiplier));
});
}
s.datasets = this.data.datasets.map((d, i) => {
let values = d.values;
let cumulativeYs = d.cumulativeYs || [];
return {
name: d.name,
index: i,
chartType: d.chartType,
values: values,
yPositions: scaleAll(values),
cumulativeYs: cumulativeYs,
cumulativeYPos: scaleAll(cumulativeYs),
};
});
}
calcYMaximums() {
calcYExtremes() {
let s = this.state;
if(this.barOptions && this.barOptions.stacked) {
s.yExtremes = this.data.datasets[this.data.datasets.length - 1].cumulativePositions;
s.yExtremes = s.datasets[s.datasets.length - 1].cumulativeYPos;
return;
}
s.yExtremes = new Array(s.datasetLength).fill(9999);
this.data.datasets.map((d, i) => {
d.positions.map((pos, j) => {
s.datasets.map((d, i) => {
d.yPositions.map((pos, j) => {
if(pos < s.yExtremes[j]) {
s.yExtremes[j] = pos;
}
});
});
// Tooltip refresh should not be needed?
// this.chartWrapper.removeChild(this.tip.container);
// this.make_tooltip();
}
calcYRegions() {
let s = this.state;
if(this.data.yMarkers) {
this.state.yMarkers = this.data.yMarkers.map(d => {
d.position = floatTwo(s.yAxis.zeroLine - d.value * s.yAxis.scaleMultiplier);
d.position = scale(d.value, s.yAxis);
d.label += ': ' + d.value;
return d;
});
}
if(this.data.yRegions) {
this.state.yRegions = this.data.yRegions.map(d => {
if(d.end < d.start) {
[d.start, d.end] = [d.end, start];
}
d.start = floatTwo(s.yAxis.zeroLine - d.start * s.yAxis.scaleMultiplier);
d.end = floatTwo(s.yAxis.zeroLine - d.end * s.yAxis.scaleMultiplier);
d.start = scale(d.start, s.yAxis);
d.end = scale(d.end, s.yAxis);
return d;
});
}
@ -1982,6 +1891,9 @@ class AxisChart extends BaseChart {
}
initComponents() {
let s = this.state;
// console.log('this.state', Object.assign({}, this.state));
// console.log('this.state', this.state);
this.componentConfigs = [
[
'yAxis',
@ -2008,7 +1920,57 @@ class AxisChart extends BaseChart {
pos: 'right'
}
],
];
this.componentConfigs.map(args => {
args.push(
function() {
return this.state[args[0]];
}.bind(this)
);
});
let barDatasets = this.state.datasets.filter(d => d.chartType === 'bar');
let lineDatasets = this.state.datasets.filter(d => d.chartType === 'line');
// console.log('barDatasets', barDatasets, this.state.datasets);
// Bars
let spaceRatio = this.barOptions.spaceRatio || BAR_CHART_SPACE_RATIO;
let barsWidth = s.unitWidth * (1 - spaceRatio);
let barWidth = barsWidth/(this.barOptions.stacked ? 1 : barDatasets.length);
let barsConfigs = barDatasets.map(d => {
let index = d.index;
return [
'barGraph',
{
index: index,
color: this.colors[index],
// same for all datasets
valuesOverPoints: this.valuesOverPoints,
minHeight: this.height * MIN_BAR_PERCENT_HEIGHT,
barsWidth: barsWidth,
barWidth: barWidth,
zeroLine: s.yAxis.zeroLine
},
function() {
let s = this.state;
let d = s.datasets[index];
return {
xPositions: s.xAxis.positions,
yPositions: d.yPositions,
cumulativeYPos: d.cumulativeYPos,
values: d.values,
cumulativeYs: d.cumulativeYs
};
}.bind(this)
];
});
let markerConfigs = [
[
'yMarkers',
{
@ -2017,17 +1979,23 @@ class AxisChart extends BaseChart {
}
]
];
markerConfigs.map(args => {
args.push(
function() {
return this.state[args[0]];
}.bind(this)
);
});
this.componentConfigs = this.componentConfigs.concat(barsConfigs, markerConfigs);
}
setupComponents() {
let optionals = ['yMarkers', 'yRegions'];
this.components = new Map(this.componentConfigs
.filter(args => !optionals.includes(args[0]) || this.data[args[0]])
.filter(args => !optionals.includes(args[0]) || this.state[args[0]] || args[0] === 'barGraph')
.map(args => {
args.push(
function() {
return this.state[args[0]];
}.bind(this)
);
return [args[0], getComponent(...args)];
}));
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -177,7 +177,7 @@ let type_data = {
{
name: "Yet Another",
values: [15, 20, -3, -15, 58, 12, -17, 37],
chartType: 'line'
chartType: 'bar'
}
// temp : Stacked

View File

@ -1,16 +1,15 @@
import BaseChart from './BaseChart';
import { dataPrep } from './axis-chart-utils';
import { dataPrep, zeroDataPrep } from './axis-chart-utils';
import { Y_AXIS_MARGIN } from '../utils/constants';
import { getComponent } from '../objects/ChartComponents';
import { BarChartController, LineChartController, getPaths } from '../objects/AxisChartControllers';
import { AxisChartRenderer } from '../utils/draw';
import { getOffset, fire } from '../utils/dom';
import { equilizeNoOfElements } from '../utils/draw-utils';
import { Animator, translateHoriLine } from '../utils/animate';
import { runSMILAnimation } from '../utils/animation';
import { getRealIntervals, calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex } from '../utils/intervals';
import { getRealIntervals, calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale } from '../utils/intervals';
import { floatTwo, fillArray, bindChange } from '../utils/helpers';
import { MIN_BAR_PERCENT_HEIGHT, DEFAULT_AXIS_CHART_TYPE } from '../utils/constants';
import { MIN_BAR_PERCENT_HEIGHT, DEFAULT_AXIS_CHART_TYPE, BAR_CHART_SPACE_RATIO } from '../utils/constants';
export default class AxisChart extends BaseChart {
constructor(args) {
@ -19,19 +18,19 @@ export default class AxisChart extends BaseChart {
this.valuesOverPoints = args.valuesOverPoints;
this.formatTooltipY = args.formatTooltipY;
this.formatTooltipX = args.formatTooltipX;
this.barOptions = args.barOptions;
this.lineOptions = args.lineOptions;
this.barOptions = args.barOptions || {};
this.lineOptions = args.lineOptions || {};
this.type = args.type || 'line';
this.xAxisMode = args.xAxisMode || 'span';
this.yAxisMode = args.yAxisMode || 'span';
this.zeroLine = this.height;
this.setTrivialState();
// this.setTrivialState();
this.setup();
}
configure(args) {
configure(args) {3
super.configure();
// TODO: set in options and use
@ -40,38 +39,6 @@ export default class AxisChart extends BaseChart {
this.config.yAxisMode = args.yAxisMode;
}
setTrivialState() {
// Define data and stuff
let yTempPos = getRealIntervals(this.height, 4, 0, 0);
this.state = {
xAxis: {
positions: [],
labels: [],
},
yAxis: {
positions: yTempPos,
labels: yTempPos.map(d => ""),
},
yRegions: [
{
start: this.height,
end: this.height,
label: ''
}
],
yMarkers: [
{
position: this.height,
label: ''
}
]
}
this.calcWidth();
this.calcXPositions(this.state);
}
setMargins() {
super.setMargins();
this.translateXLeft = Y_AXIS_MARGIN;
@ -82,10 +49,12 @@ export default class AxisChart extends BaseChart {
return dataPrep(data, this.type);
}
prepareFirstData(data=this.data) {
return zeroDataPrep(data);
}
calc() {
this.calcXPositions();
this.calcYAxisParameters(this.getAllYValues(), this.type === 'line');
}
@ -119,61 +88,60 @@ export default class AxisChart extends BaseChart {
}
this.calcYUnits();
this.calcYMaximums();
this.calcYExtremes();
this.calcYRegions();
}
calcYUnits() {
let s = this.state;
this.data.datasets.map(d => {
d.positions = d.values.map(val =>
floatTwo(s.yAxis.zeroLine - val * s.yAxis.scaleMultiplier));
});
let scaleAll = values => values.map(val => scale(val, s.yAxis));
if(this.barOptions && this.barOptions.stacked) {
this.data.datasets.map((d, i) => {
d.cumulativePositions = d.cumulativeYs.map(val =>
floatTwo(s.yAxis.zeroLine - val * s.yAxis.scaleMultiplier));
});
}
s.datasets = this.data.datasets.map((d, i) => {
let values = d.values;
let cumulativeYs = d.cumulativeYs || [];
return {
name: d.name,
index: i,
chartType: d.chartType,
values: values,
yPositions: scaleAll(values),
cumulativeYs: cumulativeYs,
cumulativeYPos: scaleAll(cumulativeYs),
};
});
}
calcYMaximums() {
calcYExtremes() {
let s = this.state;
if(this.barOptions && this.barOptions.stacked) {
s.yExtremes = this.data.datasets[this.data.datasets.length - 1].cumulativePositions;
s.yExtremes = s.datasets[s.datasets.length - 1].cumulativeYPos;
return;
}
s.yExtremes = new Array(s.datasetLength).fill(9999);
this.data.datasets.map((d, i) => {
d.positions.map((pos, j) => {
s.datasets.map((d, i) => {
d.yPositions.map((pos, j) => {
if(pos < s.yExtremes[j]) {
s.yExtremes[j] = pos;
}
});
});
// Tooltip refresh should not be needed?
// this.chartWrapper.removeChild(this.tip.container);
// this.make_tooltip();
}
calcYRegions() {
let s = this.state;
if(this.data.yMarkers) {
this.state.yMarkers = this.data.yMarkers.map(d => {
d.position = floatTwo(s.yAxis.zeroLine - d.value * s.yAxis.scaleMultiplier);
d.position = scale(d.value, s.yAxis);
d.label += ': ' + d.value;
return d;
});
}
if(this.data.yRegions) {
this.state.yRegions = this.data.yRegions.map(d => {
if(d.end < d.start) {
[d.start, d.end] = [d.end, start];
}
d.start = floatTwo(s.yAxis.zeroLine - d.start * s.yAxis.scaleMultiplier);
d.end = floatTwo(s.yAxis.zeroLine - d.end * s.yAxis.scaleMultiplier);
d.start = scale(d.start, s.yAxis);
d.end = scale(d.end, s.yAxis);
return d;
});
}
@ -196,6 +164,9 @@ export default class AxisChart extends BaseChart {
}
initComponents() {
let s = this.state;
// console.log('this.state', Object.assign({}, this.state));
// console.log('this.state', this.state);
this.componentConfigs = [
[
'yAxis',
@ -222,7 +193,57 @@ export default class AxisChart extends BaseChart {
pos: 'right'
}
],
];
this.componentConfigs.map(args => {
args.push(
function() {
return this.state[args[0]];
}.bind(this)
);
});
let barDatasets = this.state.datasets.filter(d => d.chartType === 'bar');
let lineDatasets = this.state.datasets.filter(d => d.chartType === 'line');
// console.log('barDatasets', barDatasets, this.state.datasets);
// Bars
let spaceRatio = this.barOptions.spaceRatio || BAR_CHART_SPACE_RATIO;
let barsWidth = s.unitWidth * (1 - spaceRatio);
let barWidth = barsWidth/(this.barOptions.stacked ? 1 : barDatasets.length);
let barsConfigs = barDatasets.map(d => {
let index = d.index;
return [
'barGraph',
{
index: index,
color: this.colors[index],
// same for all datasets
valuesOverPoints: this.valuesOverPoints,
minHeight: this.height * MIN_BAR_PERCENT_HEIGHT,
barsWidth: barsWidth,
barWidth: barWidth,
zeroLine: s.yAxis.zeroLine
},
function() {
let s = this.state;
let d = s.datasets[index];
return {
xPositions: s.xAxis.positions,
yPositions: d.yPositions,
cumulativeYPos: d.cumulativeYPos,
values: d.values,
cumulativeYs: d.cumulativeYs
};
}.bind(this)
];
});
let markerConfigs = [
[
'yMarkers',
{
@ -231,17 +252,23 @@ export default class AxisChart extends BaseChart {
}
]
];
markerConfigs.map(args => {
args.push(
function() {
return this.state[args[0]];
}.bind(this)
);
});
this.componentConfigs = this.componentConfigs.concat(barsConfigs, markerConfigs);
}
setupComponents() {
let optionals = ['yMarkers', 'yRegions'];
this.components = new Map(this.componentConfigs
.filter(args => !optionals.includes(args[0]) || this.data[args[0]])
.filter(args => !optionals.includes(args[0]) || this.state[args[0]] || args[0] === 'barGraph')
.map(args => {
args.push(
function() {
return this.state[args[0]];
}.bind(this)
);
return [args[0], getComponent(...args)];
}));
}

View File

@ -28,13 +28,15 @@ export default class BaseChart {
this.title = title;
this.subtitle = subtitle;
this.argHeight = height;
this.type = type;
this.isNavigable = isNavigable;
if(this.isNavigable) {
this.currentIndex = 0;
}
this.data = this.prepareData(data);;
this.realData = this.prepareData(data);
this.data = this.prepareFirstData(this.realData);
this.colors = [];
this.config = {};
this.state = {};
@ -148,6 +150,7 @@ export default class BaseChart {
this.calcWidth();
this.makeChartArea();
this.calc();
this.initComponents(); // Only depend on the drawArea made in makeChartArea
this.setupComponents();
@ -160,6 +163,7 @@ export default class BaseChart {
// TODO: remove timeout and decrease post animate time in chart component
if(init) {
this.data = this.realData;
setTimeout(() => {this.update();}, 1000);
}
}
@ -187,10 +191,10 @@ export default class BaseChart {
calc() {} // builds state
render(animate=true) {
render(components=this.components, animate=true) {
// Can decouple to this.refreshComponents() first to save animation timeout
let elementsToAnimate = [];
this.components.forEach(c => {
components.forEach(c => {
elementsToAnimate = elementsToAnimate.concat(c.update(animate));
});
if(elementsToAnimate.length > 0) {

View File

@ -1,5 +1,5 @@
import { floatTwo, fillArray } from '../utils/helpers';
import { DEFAULT_AXIS_CHART_TYPE } from '../utils/constants';
import { DEFAULT_AXIS_CHART_TYPE, AXIS_DATASET_CHART_TYPES } from '../utils/constants';
export function dataPrep(data, type) {
data.labels = data.labels || [];
@ -36,19 +36,56 @@ export function dataPrep(data, type) {
// Set labels
//
// Set index
d.index = i;
// Set type
if(!d.chartType ) {
d.chartType = type || DEFAULT_AXIS_CHART_TYPE;
if(!AXIS_DATASET_CHART_TYPES.includes(type)) type === DEFAULT_AXIS_CHART_TYPE;
d.chartType = type;
}
});
// Markers
// Regions
// Set start and end
// data.yRegions = data.yRegions || [];
if(data.yRegions) {
data.yRegions.map(d => {
if(d.end < d.start) {
[d.start, d.end] = [d.end, start];
}
});
}
return data;
}
export function zeroDataPrep(realData) {
let datasetLength = realData.labels.length;
let zeroArray = new Array(datasetLength).fill(0);
let zeroData = {
labels: realData.labels,
datasets: realData.datasets.map(d => {
return {
name: '',
values: zeroArray,
chartType: d.chartType
}
}),
yRegions: [
{
start: 0,
end: 0,
label: ''
}
],
yMarkers: [
{
value: 0,
label: ''
}
]
};
return zeroData;
}

View File

@ -67,36 +67,6 @@ export class BarChartController extends AxisChartController {
? m.options.stacked : m.noOfDatasets);
}
draw(x, yTop, color, label='', index=0, offset=0) {
let [height, y] = getBarHeightAndYAttr(yTop, this.meta.zeroLine);
let rect = createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: x - this.consts.barsWidth/2,
y: y - offset,
width: this.consts.width,
height: height || this.consts.minHeight
});
if(!label && !label.length) {
return rect;
} else {
let text = createSVG('text', {
className: 'data-point-value',
x: x,
y: y - offset,
dy: (FONT_SIZE / 2 * -1) + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': 'middle',
innerHTML: label
});
return wrapInSVGGroup([rect, text]);
}
}
animate(bar, x, yTop, index, noOfDatasets) {
let start = x - this.meta.unitWidth/4;
let width = (this.meta.unitWidth/2)/noOfDatasets;

View File

@ -1,5 +1,5 @@
import { makeSVGGroup } from '../utils/draw';
import { xLine, yLine, yMarker, yRegion } from '../utils/draw';
import { xLine, yLine, yMarker, yRegion, datasetBar } from '../utils/draw';
import { equilizeNoOfElements } from '../utils/draw-utils';
import { Animator, translateHoriLine, translateVertLine, animateRegion } from '../utils/animate';
@ -25,8 +25,9 @@ class ChartComponent {
this.store = [];
this.layerClass = typeof(layerClass) === 'function'
? layerClass() : layerClass;
this.layerClass = layerClass;
this.layerClass = typeof(this.layerClass) === 'function'
? this.layerClass() : this.layerClass;
this.refresh();
}
@ -204,81 +205,61 @@ let componentConfigs = {
},
barGraph: {
// opt:[
// 'barGraph',
// this.drawArea,
// {
// controller: barController,
// index: index,
// color: this.colors[index],
// valuesOverPoints: this.valuesOverPoints,
// stacked: this.barOptions && this.barOptions.stacked,
// spaceRatio: 0.5,
// minHeight: this.height * MIN_BAR_PERCENT_HEIGHT
// },
// {
// barsWidth: this.state.unitWidth * (1 - spaceRatio),
// barWidth: barsWidth/(stacked ? 1 : this.state.noOfDatasets),
// },
// function() {
// let s = this.state;
// return {
// barsWidth: this.state.unitWidth * (1 - spaceRatio),
// barWidth: barsWidth/(stacked ? 1 : this.state.noOfDatasets),
// positions: s.xAxisPositions,
// labels: s.xAxisLabels,
// }
// }.bind(this)
// ],
layerClass() { return 'y-regions' + this.constants.index; },
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; },
makeElements(data) {
let c = this.constants;
return data.yPositions.map((y, j) =>
barController.draw(
return data.yPositions.map((y, j) => {
// console.log(data.cumulativeYPos, data.cumulativeYPos[j]);
return datasetBar(
data.xPositions[j],
y,
color,
c.barWidth,
c.color,
(c.valuesOverPoints ? (c.stacked ? data.cumulativeYs[j] : data.values[j]) : ''),
j,
y - (data.cumulativePositions ? data.cumulativePositions[j] : y)
y - (c.stacked ? data.cumulativeYPos[j] : y),
{
zeroLine: c.zeroLine,
barsWidth: c.barsWidth,
minHeight: c.minHeight
}
)
);
});
},
postMake() {
if((!this.constants.stacked)) {
this.layer.setAttribute('transform',
`translate(${unitRenderer.consts.width * index}, 0)`);
`translate(${this.constants.width * this.constants.index}, 0)`);
}
},
animateElements(newData) {
[this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
// [this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
let newPos = newData.map(d => d.end);
let newLabels = newData.map(d => d.label);
let newStarts = newData.map(d => d.start);
// let newPos = newData.map(d => d.end);
// let newLabels = newData.map(d => d.label);
// let newStarts = newData.map(d => d.start);
let oldPos = this.oldData.map(d => d.end);
let oldLabels = this.oldData.map(d => d.label);
let oldStarts = this.oldData.map(d => d.start);
// let oldPos = this.oldData.map(d => d.end);
// let oldLabels = this.oldData.map(d => d.label);
// let oldStarts = this.oldData.map(d => d.start);
this.render(oldPos.map((pos, i) => {
return {
start: oldStarts[i],
end: oldPos[i],
label: newLabels[i]
}
}));
// this.render(oldPos.map((pos, i) => {
// return {
// start: oldStarts[i],
// end: oldPos[i],
// label: newLabels[i]
// }
// }));
let animateElements = [];
// let animateElements = [];
this.store.map((rectGroup, i) => {
animateElements = animateElements.concat(animateRegion(
rectGroup, newStarts[i], newPos[i], oldPos[i]
));
});
// this.store.map((rectGroup, i) => {
// animateElements = animateElements.concat(animateRegion(
// rectGroup, newStarts[i], newPos[i], oldPos[i]
// ));
// });
return animateElements;
// return animateElements;
}
},

View File

@ -1,4 +1,6 @@
export const Y_AXIS_MARGIN = 60;
export const MIN_BAR_PERCENT_HEIGHT = 0.01;
export const DEFAULT_AXIS_CHART_TYPE = 'line';
export const DEFAULT_AXIS_CHART_TYPE = 'line';
export const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
export const BAR_CHART_SPACE_RATIO = 0.5;

View File

@ -2,15 +2,9 @@ import { fillArray } from './helpers';
export function getBarHeightAndYAttr(yTop, zeroLine) {
let height, y;
if (yTop <= zeroLine) {
if (yTop < zeroLine) {
height = zeroLine - yTop;
y = yTop;
// In case of invisible bars
if(height === 0) {
height = totalHeight * MIN_BAR_PERCENT_HEIGHT;
y -= height;
}
} else {
height = yTop - zeroLine;
y = zeroLine;

View File

@ -360,6 +360,37 @@ export function yRegion(y1, y2, width, label) {
return region;
}
export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) {
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine);
// console.log(yTop, meta.zeroLine, y, offset);
let rect = createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: x - meta.barsWidth/2,
y: y - offset,
width: width,
height: height || meta.minHeight
});
if(!label && !label.length) {
return rect;
} else {
let text = createSVG('text', {
className: 'data-point-value',
x: x,
y: y - offset,
dy: (FONT_SIZE / 2 * -1) + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': 'middle',
innerHTML: label
});
return wrapInSVGGroup([rect, text]);
}
}
export class AxisChartRenderer {
constructor(state) {
this.refreshState(state);

View File

@ -1,3 +1,5 @@
import { floatTwo } from './helpers';
function normalize(x) {
// Calculates mantissa and exponent of a number
// Returns normalized number and exponent
@ -194,6 +196,10 @@ export function getValueRange(orderedArray) {
return orderedArray[orderedArray.length-1] - orderedArray[0];
}
export function scale(val, yAxis) {
return floatTwo(yAxis.zeroLine - val * yAxis.scaleMultiplier)
}
export function calcDistribution(values, distributionSize) {
// Assume non-negative values,
// implying distribution minimum at zero