render multiple y axis

This commit is contained in:
Prateeksha Singh 2018-01-01 20:29:13 +05:30
parent 405f8fb18b
commit b3ca8f0819
15 changed files with 713 additions and 417 deletions

View File

@ -281,6 +281,7 @@ function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
const AXIS_TICK_LENGTH = 6; const AXIS_TICK_LENGTH = 6;
const LABEL_MARGIN = 4; const LABEL_MARGIN = 4;
const FONT_SIZE = 10; const FONT_SIZE = 10;
const BASE_LINE_COLOR = '#dadada';
function $$1(expr, con) { function $$1(expr, con) {
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
@ -416,20 +417,22 @@ function makeText(className, x, y, content) {
}); });
} }
function makeVertXLine(x, label, totalHeight, mode, stroke='#dadada') { function makeVertLine(x, label, y1, y2, options={}) {
let height = mode === 'span' ? -1 * AXIS_TICK_LENGTH : totalHeight; if(!options.stroke) options.stroke = BASE_LINE_COLOR;
let l = createSVG('line', { let l = createSVG('line', {
className: 'line-vertical ' + options.className,
x1: 0, x1: 0,
x2: 0, x2: 0,
y1: totalHeight + AXIS_TICK_LENGTH, y1: y1,
y2: height, y2: y2,
stroke: stroke styles: {
stroke: options.stroke
}
}); });
let text = createSVG('text', { let text = createSVG('text', {
x: 0, x: 0,
y: totalHeight + AXIS_TICK_LENGTH + LABEL_MARGIN, y: y1 > y2 ? y1 + LABEL_MARGIN : y1 - LABEL_MARGIN - FONT_SIZE,
dy: FONT_SIZE + 'px', dy: FONT_SIZE + 'px',
'font-size': FONT_SIZE + 'px', 'font-size': FONT_SIZE + 'px',
'text-anchor': 'middle', 'text-anchor': 'middle',
@ -446,50 +449,34 @@ function makeVertXLine(x, label, totalHeight, mode, stroke='#dadada') {
return line; return line;
} }
function makeHoriYLine(y, label, totalWidth, mode, pos='left') { function makeHoriLine(y, label, x1, x2, options={}) {
let lineType = ''; if(!options.stroke) options.stroke = BASE_LINE_COLOR;
let w2 = mode === 'span' ? totalWidth + AXIS_TICK_LENGTH : 0; if(!options.lineType) options.lineType = '';
let className = 'line-horizontal ' + options.className +
// temp : works correctly (options.lineType === "dashed" ? "dashed": "");
let x1, x2, textX, anchor;
if(mode === 'tick') {
if(pos === 'right') {
x1 = totalWidth;
x2 = totalWidth + AXIS_TICK_LENGTH;
textX = totalWidth + AXIS_TICK_LENGTH + LABEL_MARGIN;
anchor = 'start';
} else {
x1 = -1 * AXIS_TICK_LENGTH;
x2 = w2;
textX = -1 * (LABEL_MARGIN + AXIS_TICK_LENGTH);
anchor = 'end';
}
} else {
x1 = -1 * AXIS_TICK_LENGTH;
x2 = w2;
textX = -1 * (LABEL_MARGIN + AXIS_TICK_LENGTH);
anchor = 'end';
}
let l = createSVG('line', { let l = createSVG('line', {
className: lineType === "dashed" ? "dashed": "", className: className,
x1: x1, x1: x1,
x2: x2, x2: x2,
y1: 0, y1: 0,
y2: 0 y2: 0,
styles: {
stroke: options.stroke
}
}); });
let text = createSVG('text', { let text = createSVG('text', {
x: textX, x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN,
y: 0, y: 0,
dy: (FONT_SIZE / 2 - 2) + 'px', dy: (FONT_SIZE / 2 - 2) + 'px',
'font-size': FONT_SIZE + 'px', 'font-size': FONT_SIZE + 'px',
'text-anchor': anchor, 'text-anchor': x1 < x2 ? 'end' : 'start',
innerHTML: label+"" innerHTML: label+""
}); });
let line = createSVG('g', { let line = createSVG('g', {
transform: `translate(0, ${y})`, transform: `translate(0, ${ y })`,
'stroke-opacity': 1 'stroke-opacity': 1
}); });
@ -517,6 +504,10 @@ class AxisChartRenderer {
this.yAxisMode = state.yAxisMode; this.yAxisMode = state.yAxisMode;
} }
setZeroline(zeroLine) {
this.zeroLine = zeroLine;
}
bar(x, yTop, args, color, index, datasetIndex, noOfDatasets, prevX, prevY) { bar(x, yTop, args, color, index, datasetIndex, noOfDatasets, prevX, prevY) {
let totalWidth = this.unitWidth - args.spaceWidth; let totalWidth = this.unitWidth - args.spaceWidth;
@ -553,14 +544,61 @@ class AxisChartRenderer {
}); });
} }
// temp: stroke xLine(x, label, options={}) {
xLine(x, label, pos='bottom', stroke='', mode=this.xAxisMode) { if(!options.pos) options.pos = 'bottom';
if(!options.offset) options.offset = 0;
if(!options.mode) options.mode = this.xAxisMode;
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
if(!options.className) options.className = '';
// Draw X axis line in span/tick mode with optional label // Draw X axis line in span/tick mode with optional label
return makeVertXLine(x, label, this.totalHeight, mode); // y2(span)
// |
// |
// x line |
// |
// |
// ---------------------+-- y2(tick)
// |
// y1
let y1 = this.totalHeight + AXIS_TICK_LENGTH;
let y2 = options.mode === 'span' ? -1 * AXIS_TICK_LENGTH : this.totalHeight;
if(options.mode === 'tick' && options.pos === 'top') {
// top axis ticks
y1 = -1 * AXIS_TICK_LENGTH;
y2 = 0;
}
return makeVertLine(x, label, y1, y2, {
stroke: options.stroke,
className: options.className
});
} }
yLine(y, label, pos='left', mode=this.yAxisMode) { yLine(y, label, options={}) {
return makeHoriYLine(y, label, this.totalWidth, mode, pos); 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
});
} }
xMarker() {} xMarker() {}
@ -688,6 +726,7 @@ class BaseChart {
this.parent = typeof parent === 'string' ? document.querySelector(parent) : parent; this.parent = typeof parent === 'string' ? document.querySelector(parent) : parent;
this.title = title; this.title = title;
this.subtitle = subtitle; this.subtitle = subtitle;
this.argHeight = height;
this.isNavigable = isNavigable; this.isNavigable = isNavigable;
if(this.isNavigable) { if(this.isNavigable) {
@ -702,7 +741,6 @@ class BaseChart {
// showLegend, which then all functions will check // showLegend, which then all functions will check
this.setColors(); this.setColors();
this.setMargins(args);
// constants // constants
this.config = { this.config = {
@ -735,12 +773,19 @@ class BaseChart {
this.colors = this.colors.map(color => getColor(color)); this.colors = this.colors.map(color => getColor(color));
} }
setMargins(args) { setMargins() {
let height = args.height; // TODO: think for all
let height = this.argHeight;
this.baseHeight = height; this.baseHeight = height;
this.height = height - 40; this.height = height - 40; // change
this.translateX = 60; this.translateY = 20;
this.translateY = 10;
this.setHorizontalMargin();
}
setHorizontalMargin() {
this.translateXLeft = 60;
this.translateXRight = 40;
} }
validate(){ validate(){
@ -780,7 +825,10 @@ class BaseChart {
_setup() { _setup() {
this.bindWindowEvents(); this.bindWindowEvents();
this.setupConstants(); this.setupConstants();
this.prepareData();
this.setupComponents(); this.setupComponents();
this.setMargins();
this.makeContainer(); this.makeContainer();
this.makeTooltip(); // without binding this.makeTooltip(); // without binding
this.draw(true); this.draw(true);
@ -864,15 +912,16 @@ class BaseChart {
// } // }
// }); // });
this.baseWidth = getElementContentWidth(this.parent) - outerAnnotationsWidth; this.baseWidth = getElementContentWidth(this.parent) - outerAnnotationsWidth;
this.width = this.baseWidth - this.translateX * 2; this.width = this.baseWidth - (this.translateXLeft + this.translateXRight);
} }
refresh() { //?? refresh? refresh() { //?? refresh?
this.oldState = this.state ? Object.assign({}, this.state) : {}; this.oldState = this.state ? Object.assign({}, this.state) : {};
this.intermedState = {};
this.prepareData(); this.prepareData();
this.reCalc(); this.reCalc();
this.refreshRenderer(); this.refreshRenderer();
this.refreshComponents();
} }
makeChartArea() { makeChartArea() {
@ -887,7 +936,7 @@ class BaseChart {
this.drawArea = makeSVGGroup( this.drawArea = makeSVGGroup(
this.svg, this.svg,
this.type + '-chart', this.type + '-chart',
`translate(${this.translateX}, ${this.translateY})` `translate(${this.translateXLeft}, ${this.translateY})`
); );
} }
@ -905,7 +954,6 @@ class BaseChart {
return; return;
} }
this.intermedState = this.calcIntermedState(); this.intermedState = this.calcIntermedState();
this.refreshComponents();
this.animateComponents(); this.animateComponents();
setTimeout(() => { setTimeout(() => {
this.renderComponents(); this.renderComponents();
@ -914,22 +962,15 @@ class BaseChart {
// (opt, should not redraw if still in animate?) // (opt, should not redraw if still in animate?)
} }
calcIntermedState() {} calcIntermedState() {
this.intermedState = {};
}
// convenient component array abstractions // convenient component array abstractions
setComponentParent() { this.components.forEach(c => c.setupParent(this.drawArea)); }; setComponentParent() { this.components.forEach(c => c.setupParent(this.drawArea)); };
makeComponentLayers() { this.components.forEach(c => c.makeLayer()); } makeComponentLayers() { this.components.forEach(c => c.makeLayer()); }
renderComponents() { this.components.forEach(c => c.render()); } renderComponents() { this.components.forEach(c => c.render()); }
animateComponents() { this.components.forEach(c => c.animate()); } animateComponents() { this.components.forEach(c => c.animate()); }
refreshComponents() {
let args = {
chartState: this.state,
oldChartState: this.oldState,
intermedState: this.intermedState,
chartRenderer: this.renderer
};
this.components.forEach(c => c.refresh(args));
}
renderLegend() {} renderLegend() {}
@ -979,36 +1020,28 @@ class BaseChart {
} }
} }
const Y_AXIS_MARGIN = 60;
class ChartComponent { class ChartComponent {
constructor({ constructor({
layerClass = '', layerClass = '',
layerTransform = '', layerTransform = '',
make, make,
argsKeys,
animate animate
}) { }) {
this.layerClass = layerClass; // 'y axis' this.layerClass = layerClass; // 'y axis'
this.layerTransform = layerTransform; this.layerTransform = layerTransform;
this.make = make; this.make = make;
this.argsKeys = argsKeys;//['yAxisPositions', 'yAxisLabels'];
this.animate = animate; this.animate = animate;
this.layer = undefined; this.layer = undefined;
this.store = []; //[[]] depends on indexed this.store = []; //[[]] depends on indexed
} }
refresh(args) { refresh(args) {}
this.chartState = args.chartState;
this.oldChartState = args.oldChartState;
this.intermedState = args.intermedState;
this.chartRenderer = args.chartRenderer;
}
render() { render() {
let args = this.argsKeys.map(key => this.chartState[key]); this.store = this.make();
args.unshift(this.chartRenderer);
this.store = this.make(...args);
this.layer.textContent = ''; this.layer.textContent = '';
this.store.forEach(element => { this.store.forEach(element => {
@ -1026,53 +1059,6 @@ class ChartComponent {
} }
// Indexed according to dataset // Indexed according to dataset
class IndexedChartComponent extends ChartComponent {
constructor(args) {
super(args);
this.stores = [];
}
refresh(args) {
super.refresh(args);
this.totalIndices = this.chartState[this.argsKeys[0]].length;
}
makeLayer() {
super.makeLayer();
this.layers = [];
for(var i = 0; i < this.totalIndices; i++) {
this.layers[i] = makeSVGGroup(this.layer, this.layerClass + '-' + i);
}
}
addLayer() {}
render() {
let datasetArrays = this.argsKeys.map(key => this.chartState[key]);
// datasetArrays will have something like an array of X positions sets
// datasetArrays = [
// xUnitPositions, yUnitPositions, colors, unitTypes, yUnitValues
// ]
// where xUnitPositions = [[0,0,0], [1,1,1]]
// i.e.: [ [[0,0,0], [1,1,1]], ... ]
for(var i = 0; i < this.totalIndices; i++) {
let args = datasetArrays.map(datasetArray => datasetArray[i]);
args.unshift(this.chartRenderer);
args.push(i);
args.push(this.totalIndices);
this.stores.push(this.make(...args));
let layer = this.layers[i];
layer.textContent = '';
this.stores[i].forEach(element => {
layer.appendChild(element);
});
}
}
}
const REPLACE_ALL_NEW_DUR = 250; const REPLACE_ALL_NEW_DUR = 250;
@ -1428,9 +1414,15 @@ class AxisChart extends BaseChart {
this.is_series = args.is_series; this.is_series = args.is_series;
this.format_tooltip_y = args.format_tooltip_y; this.format_tooltip_y = args.format_tooltip_y;
this.format_tooltip_x = args.format_tooltip_x; this.format_tooltip_x = args.format_tooltip_x;
this.zeroLine = this.height; this.zeroLine = this.height;
} }
setHorizontalMargin() {
this.translateXLeft = Y_AXIS_MARGIN;
this.translateXRight = Y_AXIS_MARGIN;
}
checkData(data) { checkData(data) {
return true; return true;
} }
@ -1441,7 +1433,10 @@ class AxisChart extends BaseChart {
prepareData() { prepareData() {
let s = this.state; let s = this.state;
s.xAxisLabels = this.data.labels || []; s.xAxisLabels = this.data.labels || [];
s.xAxisPositions = [];
s.datasetLength = s.xAxisLabels.length; s.datasetLength = s.xAxisLabels.length;
let zeroArray = new Array(s.datasetLength).fill(0); let zeroArray = new Array(s.datasetLength).fill(0);
@ -1474,6 +1469,16 @@ class AxisChart extends BaseChart {
}); });
s.noOfDatasets = s.datasets.length; s.noOfDatasets = s.datasets.length;
// s.yAxis = [];
this.prepareYAxis();
}
prepareYAxis() {
this.state.yAxis = {
labels: [],
positions: []
};
} }
reCalc() { reCalc() {
@ -1484,15 +1489,15 @@ class AxisChart extends BaseChart {
this.calcXPositions(); this.calcXPositions();
// Y // Y
s.datasetsLabels = this.data.datasets.map(d => d.label); s.datasetsLabels = this.data.datasets.map(d => d.name);
// s.yUnitValues = [[]]; indexed component // s.yUnitValues = [[]]; indexed component
// s.yUnitValues = [[[12, 34, 68], [10, 5, 46]], [[20, 20, 20]]]; // array of indexed components // s.yUnitValues = [[[12, 34, 68], [10, 5, 46]], [[20, 20, 20]]]; // array of indexed components
s.yUnitValues = s.datasets.map(d => d.values); // indexed component s.yUnitValues = s.datasets.map(d => d.values); // indexed component
s.yAxisLabels = calcIntervals(this.getAllYValues(), this.type === 'line');
this.calcYAxisPositions();
this.calcYUnitPositions(); this.setYAxis();
this.calcYUnits();
// should be state // should be state
this.configUnits(); this.configUnits();
@ -1501,6 +1506,11 @@ class AxisChart extends BaseChart {
s.unitTypes = s.datasets.map(d => d.unitArgs ? d.unitArgs : this.state.unitArgs); s.unitTypes = s.datasets.map(d => d.unitArgs ? d.unitArgs : this.state.unitArgs);
} }
setYAxis() {
this.calcYAxisParameters(this.state.yAxis, this.getAllYValues(), this.type === 'line');
this.state.zeroLine = this.state.yAxis.zeroLine;
}
calcXPositions() { calcXPositions() {
let s = this.state; let s = this.state;
this.setUnitWidthAndXOffset(); this.setUnitWidthAndXOffset();
@ -1510,18 +1520,18 @@ class AxisChart extends BaseChart {
s.xUnitPositions = new Array(s.noOfDatasets).fill(s.xAxisPositions); s.xUnitPositions = new Array(s.noOfDatasets).fill(s.xAxisPositions);
} }
calcYAxisPositions() { calcYAxisParameters(yAxis, dataValues, withMinimum = 'false') {
let s = this.state; yAxis.labels = calcIntervals(dataValues, withMinimum);
const yPts = s.yAxisLabels; const yPts = yAxis.labels;
s.scaleMultiplier = this.height / getValueRange(yPts); yAxis.scaleMultiplier = this.height / getValueRange(yPts);
const intervalHeight = getIntervalSize(yPts) * s.scaleMultiplier; const intervalHeight = getIntervalSize(yPts) * yAxis.scaleMultiplier;
s.zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight); yAxis.zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight);
s.yAxisPositions = yPts.map(d => s.zeroLine - d * s.scaleMultiplier); yAxis.positions = yPts.map(d => yAxis.zeroLine - d * yAxis.scaleMultiplier);
} }
calcYUnitPositions() { calcYUnits() {
let s = this.state; let s = this.state;
s.yUnitPositions = s.yUnitValues.map(values => s.yUnitPositions = s.yUnitValues.map(values =>
values.map(val => floatTwo(s.zeroLine - val * s.scaleMultiplier)) values.map(val => floatTwo(s.zeroLine - val * s.scaleMultiplier))
@ -1557,6 +1567,105 @@ class AxisChart extends BaseChart {
// //
} }
setupValues() {}
setupComponents() {
// temp : will be an indexedchartcomponent
// this.yAxisAux = new ChartComponent({
// layerClass: 'y axis aux',
// make: (renderer, positions, values) => {
// positions = [0, 70, 140, 270];
// values = [300, 200, 100, 0];
// return positions.map((position, i) => renderer.yLine(position, values[i], 'right'));
// },
// animate: () => {}
// });
this.setupYAxesComponents();
this.xAxis = new ChartComponent({
layerClass: 'x axis',
make: () => {
let s = this.state;
return s.xAxisPositions.map((position, i) =>
this.renderer.xLine(position, s.xAxisLabels[i], {pos:'top'})
);
},
// animate: (animator, lines, oldX, newX) => {
// lines.map((xLine, i) => {
// elements_to_animate.push(animator.verticalLine(
// xLine, newX[i], oldX[i]
// ));
// });
// }
});
// this.dataUnits = new IndexedChartComponent({
// layerClass: 'dataset-units',
// make: (renderer, xPosSet, yPosSet, color, unitType,
// yValueSet, datasetIndex, noOfDatasets) => {;
// let unitSet = yPosSet.map((y, i) => {
// return renderer[unitType.type](
// xPosSet[i],
// y,
// unitType.args,
// color,
// i,
// datasetIndex,
// noOfDatasets
// );
// });
// if(this.type === 'line') {
// let pointsList = yPosSet.map((y, i) => (xPosSet[i] + ',' + y));
// let pointsStr = pointsList.join("L");
// unitSet.unshift(makePath("M"+pointsStr, 'line-graph-path', color));
// }
// return unitSet;
// },
// argsKeys: ['xUnitPositions', 'yUnitPositions',
// 'colors', 'unitTypes', 'yUnitValues'],
// animate: () => {}
// });
// TODO: rebind new units
// if(this.isNavigable) {
// this.bind_units(units_array);
// }
this.yMarkerLines = {};
this.xMarkerLines = {};
// Marker Regions
this.components = [
// temp
// this.yAxesAux,
...this.yAxesComponents,
this.xAxis,
// this.yMarkerLines,
// this.xMarkerLines,
// this.dataUnits,
];
}
setupYAxesComponents() {
this.yAxesComponents = [ new ChartComponent({
layerClass: 'y axis',
make: () => {
let s = this.state;
return s.yAxis.positions.map((position, i) =>
this.renderer.yLine(position, s.yAxis.labels[i], {pos:'right'})
);
},
animate: () => {}
})];
}
refreshRenderer() { refreshRenderer() {
// These args are basically the current state of the chart, // These args are basically the current state of the chart,
// with constant and alive params mixed // with constant and alive params mixed
@ -1577,95 +1686,6 @@ class AxisChart extends BaseChart {
} }
} }
setupComponents() {
// temp : will be an indexedchartcomponent
// this.yAxisAux = new ChartComponent({
// layerClass: 'y axis aux',
// make: (renderer, positions, values) => {
// positions = [0, 70, 140, 270];
// values = [300, 200, 100, 0];
// return positions.map((position, i) => renderer.yLine(position, values[i], 'right'));
// },
// argsKeys: ['yAxisPositions', 'yAxisLabels'],
// animate: () => {}
// });
this.yAxis = new ChartComponent({
layerClass: 'y axis',
make: (renderer, positions, values) => {
return positions.map((position, i) => renderer.yLine(position, values[i]));
},
argsKeys: ['yAxisPositions', 'yAxisLabels'],
animate: () => {}
});
this.xAxis = new ChartComponent({
layerClass: 'x axis',
make: (renderer, positions, values) => {
return positions.map((position, i) => renderer.xLine(position, values[i]));
},
argsKeys: ['xAxisPositions', 'xAxisLabels'],
animate: (animator, lines, oldX, newX) => {
lines.map((xLine, i) => {
elements_to_animate.push(animator.verticalLine(
xLine, newX[i], oldX[i]
));
});
}
});
this.dataUnits = new IndexedChartComponent({
layerClass: 'dataset-units',
make: (renderer, xPosSet, yPosSet, color, unitType,
yValueSet, datasetIndex, noOfDatasets) => {
let unitSet = yPosSet.map((y, i) => {
return renderer[unitType.type](
xPosSet[i],
y,
unitType.args,
color,
i,
datasetIndex,
noOfDatasets
);
});
if(this.type === 'line') {
let pointsList = yPosSet.map((y, i) => (xPosSet[i] + ',' + y));
let pointsStr = pointsList.join("L");
unitSet.unshift(makePath("M"+pointsStr, 'line-graph-path', color));
}
return unitSet;
},
argsKeys: ['xUnitPositions', 'yUnitPositions',
'colors', 'unitTypes', 'yUnitValues'],
animate: () => {}
});
// TODO: rebind new units
// if(this.isNavigable) {
// this.bind_units(units_array);
// }
this.yMarkerLines = {};
this.xMarkerLines = {};
// Marker Regions
this.components = [
// temp
// this.yAxisAux,
this.yAxis,
this.xAxis,
// this.yMarkerLines,
// this.xMarkerLines,
this.dataUnits,
];
}
} }
class BarChart extends AxisChart { class BarChart extends AxisChart {
@ -1891,6 +1911,103 @@ class ScatterChart extends LineChart {
make_path() {} make_path() {}
} }
class MultiAxisChart extends AxisChart {
constructor(args) {
super(args);
this.type = 'multiaxis';
this.unitType = args.unitType || 'line';
this.setup();
}
setHorizontalMargin() {
let noOfLeftAxes = this.data.datasets.filter(d => d.axisPosition === 'left').length;
this.translateXLeft = (noOfLeftAxes) * Y_AXIS_MARGIN;
this.translateXRight = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
}
prepareYAxis() {
this.state.yAxes = [];
let sets = this.state.datasets;
// let axesLeft = sets.filter(d => d.axisPosition === 'left');
// let axesRight = sets.filter(d => d.axisPosition === 'right');
// let axesNone = sets.filter(d => !d.axisPosition ||
// !['left', 'right'].includes(d.axisPosition));
let leftCount = 0, rightCount = 0;
sets.forEach((d, i) => {
this.state.yAxes.push({
position: d.axisPosition,
color: d.color,
dataValues: d.values,
index: d.axisPosition === 'left' ? leftCount++ : rightCount++
});
});
}
configure(args) {
super.configure(args);
this.config.xAxisMode = args.xAxisMode || 'tick';
this.config.yAxisMode = args.yAxisMode || 'span';
}
// setUnitWidthAndXOffset() {
// this.state.unitWidth = this.width/(this.state.datasetLength);
// this.state.xOffset = this.state.unitWidth/2;
// }
configUnits() {
this.state.unitArgs = {
type: 'bar',
args: {
spaceWidth: this.state.unitWidth/2,
}
};
}
setYAxis() {
this.state.yAxes.map(yAxis => {
// console.log(yAxis);
this.calcYAxisParameters(yAxis, yAxis.dataValues, this.unitType === 'line');
// console.log(yAxis);
});
}
setupYAxesComponents() {
this.yAxesComponents = this.state.yAxes.map((e, i) => {
return new ChartComponent({
layerClass: 'y axis y-axis-' + i,
make: () => {
let d = this.state.yAxes[i];
this.renderer.setZeroline(d.zeroline);
let axis = d.positions.map((position, j) =>
this.renderer.yLine(position, d.labels[j], {
pos: d.position,
mode: 'tick',
offset: d.index * Y_AXIS_MARGIN,
stroke: this.colors[i]
})
);
let guidePos = d.position === 'left'
? -1 * d.index * Y_AXIS_MARGIN
: this.width + d.index * Y_AXIS_MARGIN;
axis.push(this.renderer.xLine(guidePos, '', {
pos:'top',
mode: 'span',
stroke: this.colors[i],
className: 'y-axis-guide'
}));
return axis;
},
animate: () => {}
});
});
}
}
class PercentageChart extends BaseChart { class PercentageChart extends BaseChart {
constructor(args) { constructor(args) {
super(args); super(args);
@ -2499,6 +2616,7 @@ class Heatmap extends BaseChart {
const chartTypes = { const chartTypes = {
line: LineChart, line: LineChart,
bar: BarChart, bar: BarChart,
multiaxis: MultiAxisChart,
scatter: ScatterChart, scatter: ScatterChart,
percentage: PercentageChart, percentage: PercentageChart,
heatmap: Heatmap, heatmap: Heatmap,

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
.chart-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .graph-focus-margin{margin:0 5%}.chart-container>.title{margin-top:25px;margin-left:25px;text-align:left;font-weight:400;font-size:12px;color:#6c7680}.chart-container .graphics{margin-top:10px;padding-top:10px;padding-bottom:10px;position:relative}.chart-container .graph-stats-group{-ms-flex-pack:distribute;-webkit-box-flex:1;-ms-flex:1;flex:1}.chart-container .graph-stats-container,.chart-container .graph-stats-group{display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around}.chart-container .graph-stats-container{-ms-flex-pack:distribute;padding-top:10px}.chart-container .graph-stats-container .stats{padding-bottom:15px}.chart-container .graph-stats-container .stats-title{color:#8d99a6}.chart-container .graph-stats-container .stats-value{font-size:20px;font-weight:300}.chart-container .graph-stats-container .stats-description{font-size:12px;color:#8d99a6}.chart-container .graph-stats-container .graph-data .stats-value{color:#98d85b}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .percentage-graph .progress{margin-bottom:0}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path,.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.chart-container .progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#36414c;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.chart-container .graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.chart-container .graph-svg-tip ol,.chart-container .graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.chart-container .graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.chart-container .graph-svg-tip strong{color:#dfe2e5;font-weight:600}.chart-container .graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.chart-container .graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.chart-container .graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.chart-container .graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.chart-container .graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}.chart-container .indicator,.chart-container .indicator-right{background:none;font-size:12px;vertical-align:middle;font-weight:700;color:#6c7680}.chart-container .indicator i{content:"";display:inline-block;height:8px;width:8px;border-radius:8px}.chart-container .indicator:before,.chart-container .indicator i{margin:0 4px 0 0}.chart-container .indicator-right:after{margin:0 0 0 4px} .chart-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .multiaxis-chart .line-horizontal,.chart-container .multiaxis-chart .y-axis-guide{stroke-width:2px}.chart-container .graph-focus-margin{margin:0 5%}.chart-container>.title{margin-top:25px;margin-left:25px;text-align:left;font-weight:400;font-size:12px;color:#6c7680}.chart-container .graphics{margin-top:10px;padding-top:10px;padding-bottom:10px;position:relative}.chart-container .graph-stats-group{-ms-flex-pack:distribute;-webkit-box-flex:1;-ms-flex:1;flex:1}.chart-container .graph-stats-container,.chart-container .graph-stats-group{display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around}.chart-container .graph-stats-container{-ms-flex-pack:distribute;padding-top:10px}.chart-container .graph-stats-container .stats{padding-bottom:15px}.chart-container .graph-stats-container .stats-title{color:#8d99a6}.chart-container .graph-stats-container .stats-value{font-size:20px;font-weight:300}.chart-container .graph-stats-container .stats-description{font-size:12px;color:#8d99a6}.chart-container .graph-stats-container .graph-data .stats-value{color:#98d85b}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .percentage-graph .progress{margin-bottom:0}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path,.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.chart-container .progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#36414c;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.chart-container .graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.chart-container .graph-svg-tip ol,.chart-container .graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.chart-container .graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.chart-container .graph-svg-tip strong{color:#dfe2e5;font-weight:600}.chart-container .graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.chart-container .graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.chart-container .graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.chart-container .graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.chart-container .graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}.chart-container .indicator,.chart-container .indicator-right{background:none;font-size:12px;vertical-align:middle;font-weight:700;color:#6c7680}.chart-container .indicator i{content:"";display:inline-block;height:8px;width:8px;border-radius:8px}.chart-container .indicator:before,.chart-container .indicator i{margin:0 4px 0 0}.chart-container .indicator-right:after{margin:0 0 0 4px}

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

@ -8,7 +8,7 @@ let bar_composite_data = {
"2013", "2014", "2015", "2016", "2017"], "2013", "2014", "2015", "2016", "2017"],
datasets: [{ datasets: [{
"label": "Events", "name": "Events",
"values": report_count_list, "values": report_count_list,
// "formatted": report_count_list.map(d => d + " reports") // "formatted": report_count_list.map(d => d + " reports")
}] }]
@ -77,29 +77,31 @@ let type_data = {
datasets: [ datasets: [
{ {
label: "Some Data", name: "Some Data",
values: [18, 40, 30, 35, 8, 52, 17, -4] values: [18, 40, 30, 35, 8, 52, 17, -4],
axisPosition: 'right'
}, },
{ {
label: "Another Set", name: "Another Set",
values: [30, 50, -10, 15, 18, 32, 27, 14] values: [30, 50, -10, 15, 18, 32, 27, 14],
axisPosition: 'right'
}, },
// { // {
// label: "Yet Another", // name: "Yet Another",
// values: [15, 20, -3, -15, 58, 12, -17, 37] // values: [15, 20, -3, -15, 58, 12, -17, 37]
// } // }
// temp : Stacked // temp : Stacked
// { // {
// label: "Some Data", // name: "Some Data",
// values:[25, 30, 50, 45, 18, 12, 27, 14] // values:[25, 30, 50, 45, 18, 12, 27, 14]
// }, // },
// { // {
// label: "Another Set", // name: "Another Set",
// values: [18, 20, 30, 35, 8, 7, 17, 4] // values: [18, 20, 30, 35, 8, 7, 17, 4]
// }, // },
// { // {
// label: "Another Set", // name: "Another Set",
// values: [11, 8, 19, 15, 3, 4, 10, 2] // values: [11, 8, 19, 15, 3, 4, 10, 2]
// }, // },
] ]
@ -109,7 +111,7 @@ let type_chart = new Chart({
parent: "#chart-types", parent: "#chart-types",
// title: "My Awesome Chart", // title: "My Awesome Chart",
data: type_data, data: type_data,
type: 'bar', type: 'multiaxis',
height: 250, height: 250,
colors: ['purple', 'magenta'], colors: ['purple', 'magenta'],
is_series: 1, is_series: 1,
@ -225,8 +227,8 @@ let update_data = {
}], }],
"specific_values": [ "specific_values": [
{ {
label: "Altitude", name: "Altitude",
// label: "A very long text", // name: "A very long text",
line_type: "dashed", line_type: "dashed",
value: 38 value: 38
}, },

View File

@ -3,6 +3,7 @@ import '../scss/charts.scss';
import BarChart from './charts/BarChart'; import BarChart from './charts/BarChart';
import LineChart from './charts/LineChart'; import LineChart from './charts/LineChart';
import ScatterChart from './charts/ScatterChart'; import ScatterChart from './charts/ScatterChart';
import MultiAxisChart from './charts/MultiAxisChart';
import PercentageChart from './charts/PercentageChart'; import PercentageChart from './charts/PercentageChart';
import PieChart from './charts/PieChart'; import PieChart from './charts/PieChart';
import Heatmap from './charts/Heatmap'; import Heatmap from './charts/Heatmap';
@ -18,6 +19,7 @@ import Heatmap from './charts/Heatmap';
const chartTypes = { const chartTypes = {
line: LineChart, line: LineChart,
bar: BarChart, bar: BarChart,
multiaxis: MultiAxisChart,
scatter: ScatterChart, scatter: ScatterChart,
percentage: PercentageChart, percentage: PercentageChart,
heatmap: Heatmap, heatmap: Heatmap,

View File

@ -1,5 +1,6 @@
import BaseChart from './BaseChart'; import BaseChart from './BaseChart';
import { ChartComponent, IndexedChartComponent } from '../objects/ChartComponent'; import { Y_AXIS_MARGIN } from '../utils/margins';
import { ChartComponent } from '../objects/ChartComponent';
import { getOffset, fire } from '../utils/dom'; import { getOffset, fire } from '../utils/dom';
import { AxisChartRenderer, makePath, makeGradient } from '../utils/draw'; import { AxisChartRenderer, makePath, makeGradient } from '../utils/draw';
import { equilizeNoOfElements } from '../utils/draw-utils'; import { equilizeNoOfElements } from '../utils/draw-utils';
@ -14,9 +15,15 @@ export default class AxisChart extends BaseChart {
this.is_series = args.is_series; this.is_series = args.is_series;
this.format_tooltip_y = args.format_tooltip_y; this.format_tooltip_y = args.format_tooltip_y;
this.format_tooltip_x = args.format_tooltip_x; this.format_tooltip_x = args.format_tooltip_x;
this.zeroLine = this.height; this.zeroLine = this.height;
} }
setHorizontalMargin() {
this.translateXLeft = Y_AXIS_MARGIN;
this.translateXRight = Y_AXIS_MARGIN;
}
checkData(data) { checkData(data) {
return true; return true;
} }
@ -27,7 +34,10 @@ export default class AxisChart extends BaseChart {
prepareData() { prepareData() {
let s = this.state; let s = this.state;
s.xAxisLabels = this.data.labels || []; s.xAxisLabels = this.data.labels || [];
s.xAxisPositions = [];
s.datasetLength = s.xAxisLabels.length; s.datasetLength = s.xAxisLabels.length;
let zeroArray = new Array(s.datasetLength).fill(0); let zeroArray = new Array(s.datasetLength).fill(0);
@ -60,6 +70,16 @@ export default class AxisChart extends BaseChart {
}); });
s.noOfDatasets = s.datasets.length; s.noOfDatasets = s.datasets.length;
// s.yAxis = [];
this.prepareYAxis();
}
prepareYAxis() {
this.state.yAxis = {
labels: [],
positions: []
};
} }
reCalc() { reCalc() {
@ -70,15 +90,15 @@ export default class AxisChart extends BaseChart {
this.calcXPositions(); this.calcXPositions();
// Y // Y
s.datasetsLabels = this.data.datasets.map(d => d.label); s.datasetsLabels = this.data.datasets.map(d => d.name);
// s.yUnitValues = [[]]; indexed component // s.yUnitValues = [[]]; indexed component
// s.yUnitValues = [[[12, 34, 68], [10, 5, 46]], [[20, 20, 20]]]; // array of indexed components // s.yUnitValues = [[[12, 34, 68], [10, 5, 46]], [[20, 20, 20]]]; // array of indexed components
s.yUnitValues = s.datasets.map(d => d.values); // indexed component s.yUnitValues = s.datasets.map(d => d.values); // indexed component
s.yAxisLabels = calcIntervals(this.getAllYValues(), this.type === 'line');
this.calcYAxisPositions();
this.calcYUnitPositions(); this.setYAxis();
this.calcYUnits();
// should be state // should be state
this.configUnits(); this.configUnits();
@ -87,6 +107,11 @@ export default class AxisChart extends BaseChart {
s.unitTypes = s.datasets.map(d => d.unitArgs ? d.unitArgs : this.state.unitArgs); s.unitTypes = s.datasets.map(d => d.unitArgs ? d.unitArgs : this.state.unitArgs);
} }
setYAxis() {
this.calcYAxisParameters(this.state.yAxis, this.getAllYValues(), this.type === 'line');
this.state.zeroLine = this.state.yAxis.zeroLine;
}
calcXPositions() { calcXPositions() {
let s = this.state; let s = this.state;
this.setUnitWidthAndXOffset(); this.setUnitWidthAndXOffset();
@ -96,18 +121,18 @@ export default class AxisChart extends BaseChart {
s.xUnitPositions = new Array(s.noOfDatasets).fill(s.xAxisPositions); s.xUnitPositions = new Array(s.noOfDatasets).fill(s.xAxisPositions);
} }
calcYAxisPositions() { calcYAxisParameters(yAxis, dataValues, withMinimum = 'false') {
let s = this.state; yAxis.labels = calcIntervals(dataValues, withMinimum);
const yPts = s.yAxisLabels; const yPts = yAxis.labels;
s.scaleMultiplier = this.height / getValueRange(yPts); yAxis.scaleMultiplier = this.height / getValueRange(yPts);
const intervalHeight = getIntervalSize(yPts) * s.scaleMultiplier; const intervalHeight = getIntervalSize(yPts) * yAxis.scaleMultiplier;
s.zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight); yAxis.zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight);
s.yAxisPositions = yPts.map(d => s.zeroLine - d * s.scaleMultiplier); yAxis.positions = yPts.map(d => yAxis.zeroLine - d * yAxis.scaleMultiplier);
} }
calcYUnitPositions() { calcYUnits() {
let s = this.state; let s = this.state;
s.yUnitPositions = s.yUnitValues.map(values => s.yUnitPositions = s.yUnitValues.map(values =>
values.map(val => floatTwo(s.zeroLine - val * s.scaleMultiplier)) values.map(val => floatTwo(s.zeroLine - val * s.scaleMultiplier))
@ -143,6 +168,105 @@ export default class AxisChart extends BaseChart {
// //
} }
setupValues() {}
setupComponents() {
// temp : will be an indexedchartcomponent
// this.yAxisAux = new ChartComponent({
// layerClass: 'y axis aux',
// make: (renderer, positions, values) => {
// positions = [0, 70, 140, 270];
// values = [300, 200, 100, 0];
// return positions.map((position, i) => renderer.yLine(position, values[i], 'right'));
// },
// animate: () => {}
// });
this.setupYAxesComponents();
this.xAxis = new ChartComponent({
layerClass: 'x axis',
make: () => {
let s = this.state;
return s.xAxisPositions.map((position, i) =>
this.renderer.xLine(position, s.xAxisLabels[i], {pos:'top'})
);
},
// animate: (animator, lines, oldX, newX) => {
// lines.map((xLine, i) => {
// elements_to_animate.push(animator.verticalLine(
// xLine, newX[i], oldX[i]
// ));
// });
// }
});
// this.dataUnits = new IndexedChartComponent({
// layerClass: 'dataset-units',
// make: (renderer, xPosSet, yPosSet, color, unitType,
// yValueSet, datasetIndex, noOfDatasets) => {;
// let unitSet = yPosSet.map((y, i) => {
// return renderer[unitType.type](
// xPosSet[i],
// y,
// unitType.args,
// color,
// i,
// datasetIndex,
// noOfDatasets
// );
// });
// if(this.type === 'line') {
// let pointsList = yPosSet.map((y, i) => (xPosSet[i] + ',' + y));
// let pointsStr = pointsList.join("L");
// unitSet.unshift(makePath("M"+pointsStr, 'line-graph-path', color));
// }
// return unitSet;
// },
// argsKeys: ['xUnitPositions', 'yUnitPositions',
// 'colors', 'unitTypes', 'yUnitValues'],
// animate: () => {}
// });
// TODO: rebind new units
// if(this.isNavigable) {
// this.bind_units(units_array);
// }
this.yMarkerLines = {};
this.xMarkerLines = {};
// Marker Regions
this.components = [
// temp
// this.yAxesAux,
...this.yAxesComponents,
this.xAxis,
// this.yMarkerLines,
// this.xMarkerLines,
// this.dataUnits,
];
}
setupYAxesComponents() {
this.yAxesComponents = [ new ChartComponent({
layerClass: 'y axis',
make: () => {
let s = this.state;
return s.yAxis.positions.map((position, i) =>
this.renderer.yLine(position, s.yAxis.labels[i], {pos:'right'})
);
},
animate: () => {}
})];
}
refreshRenderer() { refreshRenderer() {
// These args are basically the current state of the chart, // These args are basically the current state of the chart,
// with constant and alive params mixed // with constant and alive params mixed
@ -163,93 +287,4 @@ export default class AxisChart extends BaseChart {
} }
} }
setupComponents() {
// temp : will be an indexedchartcomponent
// this.yAxisAux = new ChartComponent({
// layerClass: 'y axis aux',
// make: (renderer, positions, values) => {
// positions = [0, 70, 140, 270];
// values = [300, 200, 100, 0];
// return positions.map((position, i) => renderer.yLine(position, values[i], 'right'));
// },
// argsKeys: ['yAxisPositions', 'yAxisLabels'],
// animate: () => {}
// });
this.yAxis = new ChartComponent({
layerClass: 'y axis',
make: (renderer, positions, values) => {
return positions.map((position, i) => renderer.yLine(position, values[i]));
},
argsKeys: ['yAxisPositions', 'yAxisLabels'],
animate: () => {}
});
this.xAxis = new ChartComponent({
layerClass: 'x axis',
make: (renderer, positions, values) => {
return positions.map((position, i) => renderer.xLine(position, values[i]));
},
argsKeys: ['xAxisPositions', 'xAxisLabels'],
animate: (animator, lines, oldX, newX) => {
lines.map((xLine, i) => {
elements_to_animate.push(animator.verticalLine(
xLine, newX[i], oldX[i]
));
});
}
});
this.dataUnits = new IndexedChartComponent({
layerClass: 'dataset-units',
make: (renderer, xPosSet, yPosSet, color, unitType,
yValueSet, datasetIndex, noOfDatasets) => {;
let unitSet = yPosSet.map((y, i) => {
return renderer[unitType.type](
xPosSet[i],
y,
unitType.args,
color,
i,
datasetIndex,
noOfDatasets
);
});
if(this.type === 'line') {
let pointsList = yPosSet.map((y, i) => (xPosSet[i] + ',' + y));
let pointsStr = pointsList.join("L");
unitSet.unshift(makePath("M"+pointsStr, 'line-graph-path', color));
}
return unitSet;
},
argsKeys: ['xUnitPositions', 'yUnitPositions',
'colors', 'unitTypes', 'yUnitValues'],
animate: () => {}
});
// TODO: rebind new units
// if(this.isNavigable) {
// this.bind_units(units_array);
// }
this.yMarkerLines = {};
this.xMarkerLines = {};
// Marker Regions
this.components = [
// temp
// this.yAxisAux,
this.yAxis,
this.xAxis,
// this.yMarkerLines,
// this.xMarkerLines,
this.dataUnits,
];
}
} }

View File

@ -26,6 +26,7 @@ export default class BaseChart {
this.parent = typeof parent === 'string' ? document.querySelector(parent) : parent; this.parent = typeof parent === 'string' ? document.querySelector(parent) : parent;
this.title = title; this.title = title;
this.subtitle = subtitle; this.subtitle = subtitle;
this.argHeight = height;
this.isNavigable = isNavigable; this.isNavigable = isNavigable;
if(this.isNavigable) { if(this.isNavigable) {
@ -40,7 +41,6 @@ export default class BaseChart {
// showLegend, which then all functions will check // showLegend, which then all functions will check
this.setColors(); this.setColors();
this.setMargins(args);
// constants // constants
this.config = { this.config = {
@ -73,12 +73,19 @@ export default class BaseChart {
this.colors = this.colors.map(color => getColor(color)); this.colors = this.colors.map(color => getColor(color));
} }
setMargins(args) { setMargins() {
let height = args.height; // TODO: think for all
let height = this.argHeight;
this.baseHeight = height; this.baseHeight = height;
this.height = height - 40; this.height = height - 40; // change
this.translateX = 60; this.translateY = 20;
this.translateY = 10;
this.setHorizontalMargin();
}
setHorizontalMargin() {
this.translateXLeft = 60;
this.translateXRight = 40;
} }
validate(){ validate(){
@ -120,7 +127,10 @@ export default class BaseChart {
_setup() { _setup() {
this.bindWindowEvents(); this.bindWindowEvents();
this.setupConstants(); this.setupConstants();
this.prepareData();
this.setupComponents(); this.setupComponents();
this.setMargins();
this.makeContainer(); this.makeContainer();
this.makeTooltip(); // without binding this.makeTooltip(); // without binding
this.draw(true); this.draw(true);
@ -204,15 +214,16 @@ export default class BaseChart {
// } // }
// }); // });
this.baseWidth = getElementContentWidth(this.parent) - outerAnnotationsWidth; this.baseWidth = getElementContentWidth(this.parent) - outerAnnotationsWidth;
this.width = this.baseWidth - this.translateX * 2; this.width = this.baseWidth - (this.translateXLeft + this.translateXRight);
} }
refresh() { //?? refresh? refresh() { //?? refresh?
this.oldState = this.state ? Object.assign({}, this.state) : {}; this.oldState = this.state ? Object.assign({}, this.state) : {};
this.intermedState = {};
this.prepareData(); this.prepareData();
this.reCalc(); this.reCalc();
this.refreshRenderer(); this.refreshRenderer();
this.refreshComponents();
} }
makeChartArea() { makeChartArea() {
@ -227,7 +238,7 @@ export default class BaseChart {
this.drawArea = makeSVGGroup( this.drawArea = makeSVGGroup(
this.svg, this.svg,
this.type + '-chart', this.type + '-chart',
`translate(${this.translateX}, ${this.translateY})` `translate(${this.translateXLeft}, ${this.translateY})`
); );
} }
@ -245,7 +256,6 @@ export default class BaseChart {
return; return;
} }
this.intermedState = this.calcIntermedState(); this.intermedState = this.calcIntermedState();
this.refreshComponents();
this.animateComponents(); this.animateComponents();
setTimeout(() => { setTimeout(() => {
this.renderComponents(); this.renderComponents();
@ -254,22 +264,15 @@ export default class BaseChart {
// (opt, should not redraw if still in animate?) // (opt, should not redraw if still in animate?)
} }
calcIntermedState() {} calcIntermedState() {
this.intermedState = {};
}
// convenient component array abstractions // convenient component array abstractions
setComponentParent() { this.components.forEach(c => c.setupParent(this.drawArea)); }; setComponentParent() { this.components.forEach(c => c.setupParent(this.drawArea)); };
makeComponentLayers() { this.components.forEach(c => c.makeLayer()); } makeComponentLayers() { this.components.forEach(c => c.makeLayer()); }
renderComponents() { this.components.forEach(c => c.render()); } renderComponents() { this.components.forEach(c => c.render()); }
animateComponents() { this.components.forEach(c => c.animate()); } animateComponents() { this.components.forEach(c => c.animate()); }
refreshComponents() {
let args = {
chartState: this.state,
oldChartState: this.oldState,
intermedState: this.intermedState,
chartRenderer: this.renderer
};
this.components.forEach(c => c.refresh(args));
}
renderLegend() {} renderLegend() {}

View File

@ -0,0 +1,100 @@
import AxisChart from './AxisChart';
import { Y_AXIS_MARGIN } from '../utils/margins';
import { ChartComponent } from '../objects/ChartComponent';
export default class MultiAxisChart extends AxisChart {
constructor(args) {
super(args);
this.type = 'multiaxis';
this.unitType = args.unitType || 'line';
this.setup();
}
setHorizontalMargin() {
let noOfLeftAxes = this.data.datasets.filter(d => d.axisPosition === 'left').length;
this.translateXLeft = (noOfLeftAxes) * Y_AXIS_MARGIN;
this.translateXRight = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
}
prepareYAxis() {
this.state.yAxes = [];
let sets = this.state.datasets;
// let axesLeft = sets.filter(d => d.axisPosition === 'left');
// let axesRight = sets.filter(d => d.axisPosition === 'right');
// let axesNone = sets.filter(d => !d.axisPosition ||
// !['left', 'right'].includes(d.axisPosition));
let leftCount = 0, rightCount = 0;
sets.forEach((d, i) => {
this.state.yAxes.push({
position: d.axisPosition,
color: d.color,
dataValues: d.values,
index: d.axisPosition === 'left' ? leftCount++ : rightCount++
});
});
}
configure(args) {
super.configure(args);
this.config.xAxisMode = args.xAxisMode || 'tick';
this.config.yAxisMode = args.yAxisMode || 'span';
}
// setUnitWidthAndXOffset() {
// this.state.unitWidth = this.width/(this.state.datasetLength);
// this.state.xOffset = this.state.unitWidth/2;
// }
configUnits() {
this.state.unitArgs = {
type: 'bar',
args: {
spaceWidth: this.state.unitWidth/2,
}
};
}
setYAxis() {
this.state.yAxes.map(yAxis => {
// console.log(yAxis);
this.calcYAxisParameters(yAxis, yAxis.dataValues, this.unitType === 'line');
// console.log(yAxis);
});
}
setupYAxesComponents() {
this.yAxesComponents = this.state.yAxes.map((e, i) => {
return new ChartComponent({
layerClass: 'y axis y-axis-' + i,
make: () => {
let d = this.state.yAxes[i];
this.renderer.setZeroline(d.zeroline);
let axis = d.positions.map((position, j) =>
this.renderer.yLine(position, d.labels[j], {
pos: d.position,
mode: 'tick',
offset: d.index * Y_AXIS_MARGIN,
stroke: this.colors[i]
})
);
let guidePos = d.position === 'left'
? -1 * d.index * Y_AXIS_MARGIN
: this.width + d.index * Y_AXIS_MARGIN;
axis.push(this.renderer.xLine(guidePos, '', {
pos:'top',
mode: 'span',
stroke: this.colors[i],
className: 'y-axis-guide'
}));
return axis;
},
animate: () => {}
});
});
}
}

View File

@ -5,31 +5,21 @@ export class ChartComponent {
layerClass = '', layerClass = '',
layerTransform = '', layerTransform = '',
make, make,
argsKeys,
animate animate
}) { }) {
this.layerClass = layerClass; // 'y axis' this.layerClass = layerClass; // 'y axis'
this.layerTransform = layerTransform; this.layerTransform = layerTransform;
this.make = make; this.make = make;
this.argsKeys = argsKeys;//['yAxisPositions', 'yAxisLabels'];
this.animate = animate; this.animate = animate;
this.layer = undefined; this.layer = undefined;
this.store = []; //[[]] depends on indexed this.store = []; //[[]] depends on indexed
} }
refresh(args) { refresh(args) {}
this.chartState = args.chartState;
this.oldChartState = args.oldChartState;
this.intermedState = args.intermedState;
this.chartRenderer = args.chartRenderer;
}
render() { render() {
let args = this.argsKeys.map(key => this.chartState[key]); this.store = this.make();
args.unshift(this.chartRenderer);
this.store = this.make(...args);
this.layer.textContent = ''; this.layer.textContent = '';
this.store.forEach(element => { this.store.forEach(element => {
@ -79,7 +69,6 @@ export class IndexedChartComponent extends ChartComponent {
// i.e.: [ [[0,0,0], [1,1,1]], ... ] // i.e.: [ [[0,0,0], [1,1,1]], ... ]
for(var i = 0; i < this.totalIndices; i++) { for(var i = 0; i < this.totalIndices; i++) {
let args = datasetArrays.map(datasetArray => datasetArray[i]); let args = datasetArrays.map(datasetArray => datasetArray[i]);
args.unshift(this.chartRenderer);
args.push(i); args.push(i);
args.push(this.totalIndices); args.push(this.totalIndices);

View File

@ -3,6 +3,7 @@ import { getBarHeightAndYAttr } from './draw-utils';
const AXIS_TICK_LENGTH = 6; const AXIS_TICK_LENGTH = 6;
const LABEL_MARGIN = 4; const LABEL_MARGIN = 4;
const FONT_SIZE = 10; const FONT_SIZE = 10;
const BASE_LINE_COLOR = '#dadada';
function $(expr, con) { function $(expr, con) {
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
@ -138,20 +139,22 @@ export function makeText(className, x, y, content) {
}); });
} }
export function makeVertXLine(x, label, totalHeight, mode, stroke='#dadada') { export function makeVertLine(x, label, y1, y2, options={}) {
let height = mode === 'span' ? -1 * AXIS_TICK_LENGTH : totalHeight; if(!options.stroke) options.stroke = BASE_LINE_COLOR;
let l = createSVG('line', { let l = createSVG('line', {
className: 'line-vertical ' + options.className,
x1: 0, x1: 0,
x2: 0, x2: 0,
y1: totalHeight + AXIS_TICK_LENGTH, y1: y1,
y2: height, y2: y2,
stroke: stroke styles: {
stroke: options.stroke
}
}); });
let text = createSVG('text', { let text = createSVG('text', {
x: 0, x: 0,
y: totalHeight + AXIS_TICK_LENGTH + LABEL_MARGIN, y: y1 > y2 ? y1 + LABEL_MARGIN : y1 - LABEL_MARGIN - FONT_SIZE,
dy: FONT_SIZE + 'px', dy: FONT_SIZE + 'px',
'font-size': FONT_SIZE + 'px', 'font-size': FONT_SIZE + 'px',
'text-anchor': 'middle', 'text-anchor': 'middle',
@ -168,50 +171,34 @@ export function makeVertXLine(x, label, totalHeight, mode, stroke='#dadada') {
return line; return line;
} }
export function makeHoriYLine(y, label, totalWidth, mode, pos='left') { export function makeHoriLine(y, label, x1, x2, options={}) {
let lineType = ''; if(!options.stroke) options.stroke = BASE_LINE_COLOR;
let w2 = mode === 'span' ? totalWidth + AXIS_TICK_LENGTH : 0; if(!options.lineType) options.lineType = '';
let className = 'line-horizontal ' + options.className +
// temp : works correctly (options.lineType === "dashed" ? "dashed": "");
let x1, x2, textX, anchor;
if(mode === 'tick') {
if(pos === 'right') {
x1 = totalWidth;
x2 = totalWidth + AXIS_TICK_LENGTH;
textX = totalWidth + AXIS_TICK_LENGTH + LABEL_MARGIN;
anchor = 'start';
} else {
x1 = -1 * AXIS_TICK_LENGTH;
x2 = w2;
textX = -1 * (LABEL_MARGIN + AXIS_TICK_LENGTH);
anchor = 'end';
}
} else {
x1 = -1 * AXIS_TICK_LENGTH;
x2 = w2;
textX = -1 * (LABEL_MARGIN + AXIS_TICK_LENGTH);
anchor = 'end';
}
let l = createSVG('line', { let l = createSVG('line', {
className: lineType === "dashed" ? "dashed": "", className: className,
x1: x1, x1: x1,
x2: x2, x2: x2,
y1: 0, y1: 0,
y2: 0 y2: 0,
styles: {
stroke: options.stroke
}
}); });
let text = createSVG('text', { let text = createSVG('text', {
x: textX, x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN,
y: 0, y: 0,
dy: (FONT_SIZE / 2 - 2) + 'px', dy: (FONT_SIZE / 2 - 2) + 'px',
'font-size': FONT_SIZE + 'px', 'font-size': FONT_SIZE + 'px',
'text-anchor': anchor, 'text-anchor': x1 < x2 ? 'end' : 'start',
innerHTML: label+"" innerHTML: label+""
}); });
let line = createSVG('g', { let line = createSVG('g', {
transform: `translate(0, ${y})`, transform: `translate(0, ${ y })`,
'stroke-opacity': 1 'stroke-opacity': 1
}); });
@ -239,6 +226,10 @@ export class AxisChartRenderer {
this.yAxisMode = state.yAxisMode; this.yAxisMode = state.yAxisMode;
} }
setZeroline(zeroLine) {
this.zeroLine = zeroLine;
}
bar(x, yTop, args, color, index, datasetIndex, noOfDatasets, prevX, prevY) { bar(x, yTop, args, color, index, datasetIndex, noOfDatasets, prevX, prevY) {
let totalWidth = this.unitWidth - args.spaceWidth; let totalWidth = this.unitWidth - args.spaceWidth;
@ -275,14 +266,63 @@ export class AxisChartRenderer {
}); });
} }
// temp: stroke xLine(x, label, options={}) {
xLine(x, label, pos='bottom', stroke='', mode=this.xAxisMode) { if(!options.pos) options.pos = 'bottom';
if(!options.offset) options.offset = 0;
if(!options.mode) options.mode = this.xAxisMode;
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
if(!options.className) options.className = '';
// Draw X axis line in span/tick mode with optional label // Draw X axis line in span/tick mode with optional label
return makeVertXLine(x, label, this.totalHeight, mode); // y2(span)
// |
// |
// x line |
// |
// |
// ---------------------+-- y2(tick)
// |
// y1
let y1 = this.totalHeight + AXIS_TICK_LENGTH;
let y2 = options.mode === 'span' ? -1 * AXIS_TICK_LENGTH : this.totalHeight;
if(options.mode === 'tick' && options.pos === 'top') {
// top axis ticks
y1 = -1 * AXIS_TICK_LENGTH;
y2 = 0;
}
return makeVertLine(x, label, y1, y2, {
stroke: options.stroke,
className: options.className
});
} }
yLine(y, label, pos='left', mode=this.yAxisMode) { yLine(y, label, options={}) {
return makeHoriYLine(y, label, this.totalWidth, mode, pos); 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
});
} }
xMarker() {} xMarker() {}

1
src/js/utils/margins.js Normal file
View File

@ -0,0 +1 @@
export const Y_AXIS_MARGIN = 60;

View File

@ -4,6 +4,12 @@
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
.multiaxis-chart {
.line-horizontal, .y-axis-guide {
stroke-width: 2px;
}
}
.graph-focus-margin { .graph-focus-margin {
margin: 0px 5%; margin: 0px 5%;
} }
@ -53,9 +59,9 @@
.axis, .chart-label { .axis, .chart-label {
fill: #555b51; fill: #555b51;
// temp commented // temp commented
// line { line {
// stroke: #dadada; stroke: #dadada;
// } }
} }
.percentage-graph { .percentage-graph {
.progress { .progress {