瀏覽代碼

[data] begin with zero data, bars rendered

tags/1.2.0
Prateeksha Singh 7 年之前
父節點
當前提交
8452e909bb
共有 17 個文件被更改,包括 479 次插入459 次删除
  1. +242
    -274
      dist/frappe-charts.esm.js
  2. +1
    -1
      dist/frappe-charts.min.cjs.js
  3. +1
    -1
      dist/frappe-charts.min.esm.js
  4. +1
    -1
      dist/frappe-charts.min.iife.js
  5. +1
    -1
      dist/frappe-charts.min.iife.js.map
  6. +1
    -1
      docs/assets/js/frappe-charts.min.js
  7. +1
    -1
      docs/assets/js/frappe-charts.min.js.map
  8. +1
    -1
      docs/assets/js/index.js
  9. +100
    -73
      src/js/charts/AxisChart.js
  10. +7
    -3
      src/js/charts/BaseChart.js
  11. +43
    -6
      src/js/charts/axis-chart-utils.js
  12. +0
    -30
      src/js/objects/AxisChartControllers.js
  13. +39
    -58
      src/js/objects/ChartComponents.js
  14. +3
    -1
      src/js/utils/constants.js
  15. +1
    -7
      src/js/utils/draw-utils.js
  16. +31
    -0
      src/js/utils/draw.js
  17. +6
    -0
      src/js/utils/intervals.js

+ 242
- 274
dist/frappe-charts.esm.js 查看文件

@@ -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);
}

calc() {
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)];
}));
}


+ 1
- 1
dist/frappe-charts.min.cjs.js
文件差異過大導致無法顯示
查看文件


+ 1
- 1
dist/frappe-charts.min.esm.js
文件差異過大導致無法顯示
查看文件


+ 1
- 1
dist/frappe-charts.min.iife.js
文件差異過大導致無法顯示
查看文件


+ 1
- 1
dist/frappe-charts.min.iife.js.map
文件差異過大導致無法顯示
查看文件


+ 1
- 1
docs/assets/js/frappe-charts.min.js
文件差異過大導致無法顯示
查看文件


+ 1
- 1
docs/assets/js/frappe-charts.min.js.map
文件差異過大導致無法顯示
查看文件


+ 1
- 1
docs/assets/js/index.js 查看文件

@@ -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


+ 100
- 73
src/js/charts/AxisChart.js 查看文件

@@ -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);
}

calc() {
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));

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),
};
});

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));
});
}
}

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)];
}));
}


+ 7
- 3
src/js/charts/BaseChart.js 查看文件

@@ -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) {


+ 43
- 6
src/js/charts/axis-chart-utils.js 查看文件

@@ -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;
}

+ 0
- 30
src/js/objects/AxisChartControllers.js 查看文件

@@ -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;


+ 39
- 58
src/js/objects/ChartComponents.js 查看文件

@@ -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;
}
},



+ 3
- 1
src/js/utils/constants.js 查看文件

@@ -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;

+ 1
- 7
src/js/utils/draw-utils.js 查看文件

@@ -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;


+ 31
- 0
src/js/utils/draw.js 查看文件

@@ -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);


+ 6
- 0
src/js/utils/intervals.js 查看文件

@@ -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


Loading…
取消
儲存