Working stacked bars

This commit is contained in:
Prateeksha Singh 2018-02-20 00:27:05 +05:30
parent 82f3446a43
commit df15885135
14 changed files with 252 additions and 121 deletions

View File

@ -232,9 +232,7 @@ function getStringWidth(string, charWidth) {
return (string+"").length * charWidth;
}
const MIN_BAR_PERCENT_HEIGHT = 0.01;
function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
function getBarHeightAndYAttr(yTop, zeroLine) {
let height, y;
if (yTop <= zeroLine) {
height = zeroLine - yTop;
@ -248,11 +246,6 @@ function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
} else {
height = yTop - zeroLine;
y = zeroLine;
// In case of invisible bars
if(height === 0) {
height = totalHeight * MIN_BAR_PERCENT_HEIGHT;
}
}
return [height, y];
@ -529,7 +522,6 @@ class AxisChartRenderer {
if(!options.pos) options.pos = 'bottom';
if(!options.offset) options.offset = 0;
if(!options.mode) options.mode = this.xAxisMode;
console.log(this.xAxisMode);
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
if(!options.className) options.className = '';
@ -1174,6 +1166,13 @@ class BaseChart {
);
this.svgDefs = makeSVGDefs(this.svg);
// I wish !!!
// this.svg = makeSVGGroup(
// svgContainer,
// 'flipped-coord-system',
// `translate(0, ${this.baseHeight}) scale(1, -1)`
// );
this.drawArea = makeSVGGroup(
this.svg,
this.type + '-chart',
@ -1266,12 +1265,18 @@ class ChartComponent {
constructor({
layerClass = '',
layerTransform = '',
preMake,
make,
postMake,
animate
}) {
this.layerClass = layerClass;
this.layerTransform = layerTransform;
this.preMake = preMake;
this.make = make;
this.postMake = postMake;
this.animate = animate;
this.layer = undefined;
@ -1281,12 +1286,15 @@ class ChartComponent {
refresh(args) {}
render() {
this.preMake && this.preMake();
this.store = this.make();
this.layer.textContent = '';
this.store.forEach(element => {
this.layer.appendChild(element);
});
this.postMake && this.postMake(this.store, this.layer);
}
setupParent(parent) {
@ -1302,17 +1310,23 @@ class ChartComponent {
}
}
const MIN_BAR_PERCENT_HEIGHT$1 = 0.01;
class AxisChartController {
constructor(meta) {
// TODO: make configurable passing args
this.refreshMeta(meta);
this.meta = meta || {};
this.setupArgs();
}
setupArgs() {}
setupArgs() {
this.consts = {};
}
setup() {}
refreshMeta(meta) {
this.meta = meta || {};
this.meta = Object.assign((this.meta || {}), meta);
}
draw() {}
@ -1327,39 +1341,40 @@ class BarChartController extends AxisChartController {
}
setupArgs() {
this.args = {
this.consts = {
spaceRatio: 0.5,
minHeight: this.meta.totalHeight * MIN_BAR_PERCENT_HEIGHT$1
};
}
draw(x, yTop, color, index, datasetIndex, noOfDatasets) {
let totalWidth = this.meta.unitWidth - this.meta.unitWidth * this.args.spaceRatio;
let startX = x - totalWidth/2;
refreshMeta(meta) {
if(meta) {
super.refreshMeta(meta);
}
let m = this.meta;
this.consts.barsWidth = m.unitWidth - m.unitWidth * this.consts.spaceRatio;
// temp commented
// let width = totalWidth / noOfDatasets;
// let currentX = startX + width * datasetIndex;
this.consts.width = this.consts.barsWidth / (m.options && m.options.stacked
? m.options.stacked : m.noOfDatasets);
}
// temp
let width = totalWidth;
let currentX = startX;
let [height, y] = getBarHeightAndYAttr(yTop, this.meta.zeroLine, this.meta.totalHeight);
draw(x, yTop, color, index, offset=0) {
let [height, y] = getBarHeightAndYAttr(yTop, this.meta.zeroLine);
return createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: currentX,
y: y,
width: width,
height: height
x: x - this.consts.barsWidth/2,
y: y - offset,
width: this.consts.width,
height: height || this.consts.minHeight
});
}
animate(bar, x, yTop, index, noOfDatasets) {
let start = x - this.meta.avgUnitWidth/4;
let width = (this.meta.avgUnitWidth/2)/noOfDatasets;
let start = x - this.meta.unitWidth/4;
let width = (this.meta.unitWidth/2)/noOfDatasets;
let [height, y] = getBarHeightAndYAttr(yTop, this.meta.zeroLine, this.meta.totalHeight);
x = start + (width * index);
@ -1375,8 +1390,7 @@ class LineChartController extends AxisChartController {
}
setupArgs() {
console.log(this);
this.args = {
this.consts = {
radius: this.meta.dotSize || 4
};
}
@ -1387,7 +1401,7 @@ class LineChartController extends AxisChartController {
'data-point-index': index,
cx: x,
cy: y,
r: this.args.radius
r: this.consts.radius
});
}
@ -1703,7 +1717,9 @@ class AxisChart extends BaseChart {
this.isSeries = args.isSeries;
this.formatTooltipY = args.formatTooltipY;
this.formatTooltipX = args.formatTooltipX;
this.unitType = args.unitType || 'line';
this.barOptions = args.barOptions;
this.lineOptions = args.lineOptions;
this.type = args.type || 'line';
this.setupUnitRenderer();
@ -1722,6 +1738,7 @@ class AxisChart extends BaseChart {
preSetup() {}
setupUnitRenderer() {
// TODO: this is empty
let options = this.rawChartArgs.options;
this.unitRenderers = {
bar: new BarChartController(options),
@ -1752,7 +1769,7 @@ class AxisChart extends BaseChart {
this.data.datasets.map(d => {
if(!d.chartType ) {
d.chartType = this.unitType;
d.chartType = this.type;
}
});
@ -1852,17 +1869,29 @@ class AxisChart extends BaseChart {
calcYUnits() {
let s = this.state;
s.datasets.map(d => {
d.positions = d.values.map(val => floatTwo(s.yAxis.zeroLine - val * s.yAxis.scaleMultiplier));
d.positions = d.values.map(val =>
floatTwo(s.yAxis.zeroLine - val * s.yAxis.scaleMultiplier));
});
if(this.barOptions && this.barOptions.stacked) {
s.datasets.map((d, i) => {
d.cumulativePositions = d.cumulativeYs.map(val =>
floatTwo(s.yAxis.zeroLine - val * s.yAxis.scaleMultiplier));
});
}
}
calcYMaximums() {
let s = this.state;
s.yUnitMinimums = new Array(s.datasetLength).fill(9999);
if(this.barOptions && this.barOptions.stacked) {
s.yExtremes = s.datasets[s.datasets.length - 1].cumulativePositions;
return;
}
s.yExtremes = new Array(s.datasetLength).fill(9999);
s.datasets.map((d, i) => {
d.positions.map((pos, j) => {
if(pos < s.yUnitMinimums[j]) {
s.yUnitMinimums[j] = pos;
if(pos < s.yExtremes[j]) {
s.yExtremes[j] = pos;
}
});
});
@ -1899,7 +1928,19 @@ class AxisChart extends BaseChart {
getAllYValues() {
// TODO: yMarkers, regions, sums, every Y value ever
return [].concat(...this.state.datasets.map(d => d.values));
let key = 'values';
if(this.barOptions && this.barOptions.stacked) {
key = 'cumulativeYs';
let cumulative = new Array(this.state.datasetLength).fill(0);
this.state.datasets.map((d, i) => {
let values = this.state.datasets[i].values;
d[key] = cumulative = cumulative.map((c, i) => c + values[i]);
});
}
return [].concat(...this.state.datasets.map(d => d[key]));
}
calcIntermedState() {
@ -2011,31 +2052,50 @@ class AxisChart extends BaseChart {
if(d.chartType === 'line') {
dataUnitsComponents.push(this.getPathComponent(d, index));
}
console.log(this.unitRenderers[d.chartType], d.chartType);
let renderer = this.unitRenderers[d.chartType];
dataUnitsComponents.push(this.getDataUnitComponent(
d, index, this.unitRenderers[d.chartType]
index, renderer
));
});
return dataUnitsComponents;
}
getDataUnitComponent(d, index, unitRenderer) {
getDataUnitComponent(index, unitRenderer) {
return new ChartComponent({
layerClass: 'dataset-units dataset-' + index,
preMake: () => { },
make: () => {
let d = this.state.datasets[index];
console.log('d.positions', d.positions);
console.log('d.cumulativePositions', d.cumulativePositions);
console.log('d.cumulativeYs', d.cumulativeYs);
return d.positions.map((y, j) => {
return unitRenderer.draw(
this.state.xAxisPositions[j],
y,
this.colors[index],
j,
index,
this.state.noOfDatasets
j
,
y - (d.cumulativePositions ? d.cumulativePositions[j] : y)
);
});
},
postMake: (store, layer) => {
let translate_layer = () => {
layer.setAttribute('transform', `translate(${unitRenderer.consts.width * index}, 0)`);
};
// let d = this.state.datasets[index];
if(this.type === 'bar' && (!this.barOptions
|| !this.barOptions.stacked)) {
translate_layer();
}
},
animate: (svgUnits) => {
// have been updated in axis render;
let newX = this.state.xAxisPositions;
@ -2181,9 +2241,13 @@ class AxisChart extends BaseChart {
totalWidth: this.width,
zeroLine: this.state.zeroLine,
unitWidth: this.state.unitWidth,
noOfDatasets: this.state.noOfDatasets,
};
meta = Object.assign(meta, this.rawChartArgs.options);
Object.keys(this.unitRenderers).map(key => {
meta.options = this[key + 'Options'];
this.unitRenderers[key].refreshMeta(meta);
});
}
@ -2205,7 +2269,7 @@ class AxisChart extends BaseChart {
mapTooltipXPosition(relX) {
let s = this.state;
if(!s.yUnitMinimums) return;
if(!s.yExtremes) return;
let titles = s.xAxisLabels;
if(this.formatTooltipX && this.formatTooltipX(titles[0])) {
@ -2219,7 +2283,7 @@ class AxisChart extends BaseChart {
// let delta = i === 0 ? s.unitWidth : xVal - s.xAxisPositions[i-1];
if(relX > xVal - s.unitWidth/2) {
let x = xVal + this.translateXLeft;
let y = s.yUnitMinimums[i] + this.translateY;
let y = s.yExtremes[i] + this.translateY;
let values = s.datasets.map((set, j) => {
return {
@ -3138,11 +3202,12 @@ const chartTypes = {
};
function getChartByType(chartType = 'line', options) {
debugger;
if(chartType === 'line') {
options.unitType = 'line';
options.type = 'line';
return new AxisChart(options);
} else if (chartType === 'bar') {
options.unitType = 'bar';
options.type = 'bar';
return new AxisChart(options);
}

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

@ -119,12 +119,13 @@ let type_data = {
name: "Another Set",
values: [30, 50, -10, 15, 18, 32, 27, 14],
axisPosition: 'right',
chartType: 'line'
chartType: 'bar'
},
// {
// name: "Yet Another",
// values: [15, 20, -3, -15, 58, 12, -17, 37]
// }
{
name: "Yet Another",
values: [15, 20, -3, -15, 58, 12, -17, 37],
// chartType: 'line'
}
// temp : Stacked
// {
@ -148,10 +149,13 @@ let type_chart = new Chart({
data: type_data,
type: 'line',
height: 250,
colors: ['purple', 'magenta'],
colors: ['purple', 'magenta', 'light-blue'],
isSeries: 1,
xAxisMode: 'tick',
yAxisMode: 'span',
barOptions: {
// stacked: 1
}
// formatTooltipX: d => (d + '').toUpperCase(),
// formatTooltipY: d => d + ' pts'
});
@ -202,8 +206,10 @@ let plot_chart_args = {
height: 250,
colors: ['blue'],
isSeries: 1,
showDots: 0,
heatline: 1,
lineOptions: {
showDots: 0,
heatline: 1,
},
xAxisMode: 'tick',
yAxisMode: 'span'
};
@ -377,10 +383,6 @@ let aggr_data = {
},
{
"values": [25, 50, -10, 15, 18, 32, 27],
"unitArgs": {
type: 'dot',
args: { radius: 4 }
},
}
]
};
@ -391,6 +393,9 @@ let aggr_chart = new Chart({
type: 'bar',
height: 250,
colors: ['light-green', 'blue'],
barOptions: {
stacked: 1
}
});
document.querySelector('[data-aggregation="sums"]').addEventListener("click", (e) => {

View File

@ -30,11 +30,12 @@ const chartTypes = {
};
function getChartByType(chartType = 'line', options) {
debugger;
if(chartType === 'line') {
options.unitType = 'line';
options.type = 'line';
return new AxisChart(options);
} else if (chartType === 'bar') {
options.unitType = 'bar';
options.type = 'bar';
return new AxisChart(options);
}

View File

@ -16,7 +16,9 @@ export default class AxisChart extends BaseChart {
this.isSeries = args.isSeries;
this.formatTooltipY = args.formatTooltipY;
this.formatTooltipX = args.formatTooltipX;
this.unitType = args.unitType || 'line';
this.barOptions = args.barOptions;
this.lineOptions = args.lineOptions;
this.type = args.type || 'line';
this.setupUnitRenderer();
@ -35,6 +37,7 @@ export default class AxisChart extends BaseChart {
preSetup() {}
setupUnitRenderer() {
// TODO: this is empty
let options = this.rawChartArgs.options;
this.unitRenderers = {
bar: new BarChartController(options),
@ -65,7 +68,7 @@ export default class AxisChart extends BaseChart {
this.data.datasets.map(d => {
if(!d.chartType ) {
d.chartType = this.unitType;
d.chartType = this.type;
}
});
@ -165,17 +168,29 @@ export default class AxisChart extends BaseChart {
calcYUnits() {
let s = this.state;
s.datasets.map(d => {
d.positions = d.values.map(val => floatTwo(s.yAxis.zeroLine - val * s.yAxis.scaleMultiplier));
d.positions = d.values.map(val =>
floatTwo(s.yAxis.zeroLine - val * s.yAxis.scaleMultiplier));
});
if(this.barOptions && this.barOptions.stacked) {
s.datasets.map((d, i) => {
d.cumulativePositions = d.cumulativeYs.map(val =>
floatTwo(s.yAxis.zeroLine - val * s.yAxis.scaleMultiplier));
});
}
}
calcYMaximums() {
let s = this.state;
s.yUnitMinimums = new Array(s.datasetLength).fill(9999);
if(this.barOptions && this.barOptions.stacked) {
s.yExtremes = s.datasets[s.datasets.length - 1].cumulativePositions;
return;
}
s.yExtremes = new Array(s.datasetLength).fill(9999);
s.datasets.map((d, i) => {
d.positions.map((pos, j) => {
if(pos < s.yUnitMinimums[j]) {
s.yUnitMinimums[j] = pos;
if(pos < s.yExtremes[j]) {
s.yExtremes[j] = pos;
}
});
});
@ -212,7 +227,19 @@ export default class AxisChart extends BaseChart {
getAllYValues() {
// TODO: yMarkers, regions, sums, every Y value ever
return [].concat(...this.state.datasets.map(d => d.values));
let key = 'values';
if(this.barOptions && this.barOptions.stacked) {
key = 'cumulativeYs';
let cumulative = new Array(this.state.datasetLength).fill(0);
this.state.datasets.map((d, i) => {
let values = this.state.datasets[i].values;
d[key] = cumulative = cumulative.map((c, i) => c + values[i]);
});
}
return [].concat(...this.state.datasets.map(d => d[key]));
}
calcIntermedState() {
@ -324,31 +351,50 @@ export default class AxisChart extends BaseChart {
if(d.chartType === 'line') {
dataUnitsComponents.push(this.getPathComponent(d, index));
}
console.log(this.unitRenderers[d.chartType], d.chartType);
let renderer = this.unitRenderers[d.chartType];
dataUnitsComponents.push(this.getDataUnitComponent(
d, index, this.unitRenderers[d.chartType]
index, renderer
));
});
return dataUnitsComponents;
}
getDataUnitComponent(d, index, unitRenderer) {
getDataUnitComponent(index, unitRenderer) {
return new ChartComponent({
layerClass: 'dataset-units dataset-' + index,
preMake: () => { },
make: () => {
let d = this.state.datasets[index];
console.log('d.positions', d.positions);
console.log('d.cumulativePositions', d.cumulativePositions);
console.log('d.cumulativeYs', d.cumulativeYs);
return d.positions.map((y, j) => {
return unitRenderer.draw(
this.state.xAxisPositions[j],
y,
this.colors[index],
j,
index,
this.state.noOfDatasets
j
,
y - (d.cumulativePositions ? d.cumulativePositions[j] : y)
);
});
},
postMake: (store, layer) => {
let translate_layer = () => {
layer.setAttribute('transform', `translate(${unitRenderer.consts.width * index}, 0)`);
}
// let d = this.state.datasets[index];
if(this.type === 'bar' && (!this.barOptions
|| !this.barOptions.stacked)) {
translate_layer();
}
},
animate: (svgUnits) => {
// have been updated in axis render;
let newX = this.state.xAxisPositions;
@ -494,9 +540,13 @@ export default class AxisChart extends BaseChart {
totalWidth: this.width,
zeroLine: this.state.zeroLine,
unitWidth: this.state.unitWidth,
noOfDatasets: this.state.noOfDatasets,
};
meta = Object.assign(meta, this.rawChartArgs.options);
Object.keys(this.unitRenderers).map(key => {
meta.options = this[key + 'Options'];
this.unitRenderers[key].refreshMeta(meta);
});
}
@ -518,7 +568,7 @@ export default class AxisChart extends BaseChart {
mapTooltipXPosition(relX) {
let s = this.state;
if(!s.yUnitMinimums) return;
if(!s.yExtremes) return;
let titles = s.xAxisLabels;
if(this.formatTooltipX && this.formatTooltipX(titles[0])) {
@ -532,7 +582,7 @@ export default class AxisChart extends BaseChart {
// let delta = i === 0 ? s.unitWidth : xVal - s.xAxisPositions[i-1];
if(relX > xVal - s.unitWidth/2) {
let x = xVal + this.translateXLeft;
let y = s.yUnitMinimums[i] + this.translateY;
let y = s.yExtremes[i] + this.translateY;
let values = s.datasets.map((set, j) => {
return {

View File

@ -236,6 +236,13 @@ export default class BaseChart {
);
this.svgDefs = makeSVGDefs(this.svg);
// I wish !!!
// this.svg = makeSVGGroup(
// svgContainer,
// 'flipped-coord-system',
// `translate(0, ${this.baseHeight}) scale(1, -1)`
// );
this.drawArea = makeSVGGroup(
this.svg,
this.type + '-chart',

View File

@ -2,17 +2,23 @@ import { getBarHeightAndYAttr } from '../utils/draw-utils';
import { createSVG, makePath, makeGradient } from '../utils/draw';
import { STD_EASING, UNIT_ANIM_DUR, MARKER_LINE_ANIM_DUR, PATH_ANIM_DUR } from '../utils/animate';
const MIN_BAR_PERCENT_HEIGHT = 0.01;
class AxisChartController {
constructor(meta) {
// TODO: make configurable passing args
this.refreshMeta(meta);
this.meta = meta || {};
this.setupArgs();
}
setupArgs() {}
setupArgs() {
this.consts = {};
}
setup() {}
refreshMeta(meta) {
this.meta = meta || {};
this.meta = Object.assign((this.meta || {}), meta);
}
draw() {}
@ -24,15 +30,13 @@ export class AxisController extends AxisChartController {
super(meta);
}
setupArgs() {}
draw(x, y, color, index) {
return createSVG('circle', {
style: `fill: ${color}`,
'data-point-index': index,
cx: x,
cy: y,
r: this.args.radius
r: this.consts.radius
});
}
@ -48,39 +52,40 @@ export class BarChartController extends AxisChartController {
}
setupArgs() {
this.args = {
this.consts = {
spaceRatio: 0.5,
minHeight: this.meta.totalHeight * MIN_BAR_PERCENT_HEIGHT
};
}
draw(x, yTop, color, index, datasetIndex, noOfDatasets) {
let totalWidth = this.meta.unitWidth - this.meta.unitWidth * this.args.spaceRatio;
let startX = x - totalWidth/2;
refreshMeta(meta) {
if(meta) {
super.refreshMeta(meta);
}
let m = this.meta;
this.consts.barsWidth = m.unitWidth - m.unitWidth * this.consts.spaceRatio;
// temp commented
// let width = totalWidth / noOfDatasets;
// let currentX = startX + width * datasetIndex;
this.consts.width = this.consts.barsWidth / (m.options && m.options.stacked
? m.options.stacked : m.noOfDatasets);
}
// temp
let width = totalWidth;
let currentX = startX;
let [height, y] = getBarHeightAndYAttr(yTop, this.meta.zeroLine, this.meta.totalHeight);
draw(x, yTop, color, index, offset=0) {
let [height, y] = getBarHeightAndYAttr(yTop, this.meta.zeroLine);
return createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: currentX,
y: y,
width: width,
height: height
x: x - this.consts.barsWidth/2,
y: y - offset,
width: this.consts.width,
height: height || this.consts.minHeight
});
}
animate(bar, x, yTop, index, noOfDatasets) {
let start = x - this.meta.avgUnitWidth/4;
let width = (this.meta.avgUnitWidth/2)/noOfDatasets;
let start = x - this.meta.unitWidth/4;
let width = (this.meta.unitWidth/2)/noOfDatasets;
let [height, y] = getBarHeightAndYAttr(yTop, this.meta.zeroLine, this.meta.totalHeight);
x = start + (width * index);
@ -96,8 +101,7 @@ export class LineChartController extends AxisChartController {
}
setupArgs() {
console.log(this);
this.args = {
this.consts = {
radius: this.meta.dotSize || 4
};
}
@ -108,7 +112,7 @@ export class LineChartController extends AxisChartController {
'data-point-index': index,
cx: x,
cy: y,
r: this.args.radius
r: this.consts.radius
});
}

View File

@ -4,12 +4,18 @@ export class ChartComponent {
constructor({
layerClass = '',
layerTransform = '',
preMake,
make,
postMake,
animate
}) {
this.layerClass = layerClass;
this.layerTransform = layerTransform;
this.preMake = preMake;
this.make = make;
this.postMake = postMake;
this.animate = animate;
this.layer = undefined;
@ -19,12 +25,15 @@ export class ChartComponent {
refresh(args) {}
render() {
this.preMake && this.preMake();
this.store = this.make();
this.layer.textContent = '';
this.store.forEach(element => {
this.layer.appendChild(element);
});
this.postMake && this.postMake(this.store, this.layer);
}
setupParent(parent) {

View File

@ -1,2 +0,0 @@
import { getBarHeightAndYAttr } from '../utils/draw-utils';

View File

@ -1,8 +1,6 @@
import { fillArray } from './helpers';
const MIN_BAR_PERCENT_HEIGHT = 0.01;
export function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
export function getBarHeightAndYAttr(yTop, zeroLine) {
let height, y;
if (yTop <= zeroLine) {
height = zeroLine - yTop;
@ -16,11 +14,6 @@ export function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
} else {
height = yTop - zeroLine;
y = zeroLine;
// In case of invisible bars
if(height === 0) {
height = totalHeight * MIN_BAR_PERCENT_HEIGHT;
}
}
return [height, y];

View File

@ -236,7 +236,6 @@ export class AxisChartRenderer {
if(!options.pos) options.pos = 'bottom';
if(!options.offset) options.offset = 0;
if(!options.mode) options.mode = this.xAxisMode;
console.log(this.xAxisMode);
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
if(!options.className) options.className = '';