Ver a proveniência

[component] axes components working

tags/1.2.0
pratu16x7 há 7 anos
ascendente
cometimento
cbb1242fe7
15 ficheiros alterados com 575 adições e 1331 eliminações
  1. +274
    -692
      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
      docs/assets/js/frappe-charts.min.js
  6. +6
    -6
      docs/assets/js/index.js
  7. +4
    -4
      docs/index.html
  8. +127
    -520
      src/js/charts/AxisChart.js
  9. +12
    -7
      src/js/charts/BarChart.js
  10. +52
    -56
      src/js/charts/BaseChart.js
  11. +13
    -13
      src/js/charts/LineChart.js
  12. +51
    -26
      src/js/objects/ChartComponent.js
  13. +1
    -1
      src/js/utils/draw-utils.js
  14. +2
    -2
      src/js/utils/draw.js
  15. +29
    -0
      src/js/utils/intervals.js

+ 274
- 692
dist/frappe-charts.esm.js
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande
Ver ficheiro


+ 1
- 1
dist/frappe-charts.min.cjs.js
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande
Ver ficheiro


+ 1
- 1
dist/frappe-charts.min.esm.js
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande
Ver ficheiro


+ 1
- 1
dist/frappe-charts.min.iife.js
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande
Ver ficheiro


+ 1
- 1
docs/assets/js/frappe-charts.min.js
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande
Ver ficheiro


+ 6
- 6
docs/assets/js/index.js Ver ficheiro

@@ -8,7 +8,7 @@ let bar_composite_data = {
"2013", "2014", "2015", "2016", "2017"],

datasets: [{
"title": "Events",
"label": "Events",
"values": report_count_list,
// "formatted": report_count_list.map(d => d + " reports")
}]
@@ -77,15 +77,15 @@ let type_data = {

datasets: [
{
title: "Some Data",
label: "Some Data",
values: [25, 40, 30, 35, 8, 52, 17, -4]
},
{
title: "Another Set",
label: "Another Set",
values: [25, 50, -10, 15, 18, 32, 27, 14]
},
{
title: "Yet Another",
label: "Yet Another",
values: [15, 20, -3, -15, 58, 12, -17, 37]
}
]
@@ -211,8 +211,8 @@ let update_data = {
}],
"specific_values": [
{
title: "Altitude",
// title: "A very long text",
label: "Altitude",
// label: "A very long text",
line_type: "dashed",
value: 38
},


+ 4
- 4
docs/index.html Ver ficheiro

@@ -64,15 +64,15 @@

datasets: [
{
title: "Some Data",
label: "Some Data",
values: [25, 40, 30, 35, 8, 52, 17, -4]
},
{
title: "Another Set",
label: "Another Set",
values: [25, 50, -10, 15, 18, 32, 27, 14]
},
{
title: "Yet Another",
label: "Yet Another",
values: [15, 20, -3, -15, 58, 12, -17, 37]
}
]
@@ -144,7 +144,7 @@

data.specific_values = [
{
title: "Altitude",
label: "Altitude",
line_type: "dashed", // or "solid"
value: 38
}


+ 127
- 520
src/js/charts/AxisChart.js Ver ficheiro

@@ -1,11 +1,12 @@
import BaseChart from './BaseChart';
import { ChartComponent, IndexedChartComponent } from '../objects/ChartComponent';
import { getOffset, fire } from '../utils/dom';
import { AxisChartRenderer } from '../utils/draw';
import { equilizeNoOfElements } from '../utils/draw-utils';
import { Animator } from '../utils/animate';
import { runSMILAnimation } from '../utils/animation';
import { calcIntervals } from '../utils/intervals';
import { floatTwo } from '../utils/helpers';
import { calcIntervals, getIntervalSize, getValueRange, getZeroIndex } from '../utils/intervals';
import { floatTwo, fillArray } from '../utils/helpers';

export default class AxisChart extends BaseChart {
constructor(args) {
@@ -16,571 +17,177 @@ export default class AxisChart extends BaseChart {
this.zeroLine = this.height;
}

parseData() {
let args = this.rawChartArgs;
this.xAxisLabels = args.data.labels || [];
this.y = args.data.datasets || [];

this.y.forEach(function(d, i) {
d.index = i;
}, this);
checkData(data) {
return true;
}

reCalc() {
// examples:

// [A] Dimension change:

// [B] Data change:
// 1. X values update
// 2. Y values update


// Aka all the values(state), these all will be documented in an old values object

// Backup first!
this.oldValues = ["everything"];

// extracted, raw args will remain in their own object
this.datasetsLabels = [];
this.datasetsValues = [[[12, 34, 68], [10, 5, 46]], [[20, 20, 20]]];

// CALCULATION: we'll need the first batch of calcs
// List of what will happen:
// this.xOffset = 0;
// this.unitWidth = 0;
// this.scaleMultipliers = [];
// this.datasetsPoints =

// Now, the function calls

// var merged = [].concat(...arrays)



// INIT
// axes
this.yAxisPositions = [this.height, this.height/2, 0];
this.yAxisLabels = ['0', '5', '10'];

this.xPositions = [0, this.width/2, this.width];
this.xAxisLabels = ['0', '5', '10'];


}

calcInitStage() {
// will borrow from the full recalc function
}

calcIntermediateValues() {
getFirstUpdateData(data) {
//
}

// this should be inherent in BaseChart
refreshRenderer() {
// These args are basically the current state of the chart,
// with constant and alive params mixed
this.renderer = new AxisChartRenderer({
totalHeight: this.height,
totalWidth: this.width,
zeroLine: this.zeroLine,
avgUnitWidth: this.avgUnitWidth,
xAxisMode: this.xAxisMode,
yAxisMode: this.yAxisMode
});
}

setupComponents() {
// Must have access to all current data things
let self = this;
this.yAxis = {
layerClass: 'y axis',
layer: undefined,
make: self.makeYLines.bind(self),
makeArgs: [self.yAxisPositions, self.yAxisLabels],
store: [],
// animate? or update? will come to while implementing
animate: self.animateYLines,
// indexed: 1 // ?? As per datasets?
};
this.xAxis = {
layerClass: 'x axis',
layer: undefined,
make: self.makeXLines.bind(self),
// TODO: will implement series skip with avgUnitWidth and isSeries later
makeArgs: [self.xPositions, self.xAxisLabels],
store: [],
animate: self.animateXLines
};
// Indexed according to dataset
// this.dataUnits = {
// layerClass: 'y marker axis',
// layer: undefined,
// make: makeXLines,
// makeArgs: [this.xPositions, this.xAxisLabels],
// store: [],
// animate: animateXLines,
// indexed: 1
// };
this.yMarkerLines = {
// layerClass: 'y marker axis',
// layer: undefined,
// make: makeYMarkerLines,
// makeArgs: [this.yMarkerPositions, this.yMarker],
// store: [],
// animate: animateYMarkerLines
};
this.xMarkerLines = {
// layerClass: 'x marker axis',
// layer: undefined,
// make: makeXMarkerLines,
// makeArgs: [this.xMarkerPositions, this.xMarker],
// store: [],
// animate: animateXMarkerLines
};

// Marker Regions

this.components = [
this.yAxis,
this.xAxis,
// this.yMarkerLines,
// this.xMarkerLines,
// this.dataUnits,
];
}

setup_values() {
this.data.datasets.map(d => {
d.values = d.values.map(val => (!isNaN(val) ? val : 0));
});
this.setup_x();
this.setup_y();
}

setup_x() {
this.set_avgUnitWidth_and_x_offset();
if(this.xPositions) {
this.x_old_axis_positions = this.xPositions.slice();
}
this.xPositions = this.xAxisLabels.map((d, i) =>
floatTwo(this.x_offset + i * this.avgUnitWidth));

if(!this.x_old_axis_positions) {
this.x_old_axis_positions = this.xPositions.slice();
}
}

setup_y() {
if(this.yAxisLabels) {
this.y_old_axis_values = this.yAxisLabels.slice();
}

let values = this.get_all_y_values();

if(this.y_sums && this.y_sums.length > 0) {
values = values.concat(this.y_sums);
}

this.yAxisLabels = calcIntervals(values, this.type === 'line');

if(!this.y_old_axis_values) {
this.y_old_axis_values = this.yAxisLabels.slice();
}

const y_pts = this.yAxisLabels;
const value_range = y_pts[y_pts.length-1] - y_pts[0];

if(this.multiplier) this.old_multiplier = this.multiplier;
this.multiplier = this.height / value_range;
if(!this.old_multiplier) this.old_multiplier = this.multiplier;

const interval = y_pts[1] - y_pts[0];
const interval_height = interval * this.multiplier;

let zero_index;

if(y_pts.indexOf(0) >= 0) {
// the range has a given zero
// zero-line on the chart
zero_index = y_pts.indexOf(0);
} else if(y_pts[0] > 0) {
// Minimum value is positive
// zero-line is off the chart: below
let min = y_pts[0];
zero_index = (-1) * min / interval;
} else {
// Maximum value is negative
// zero-line is off the chart: above
let max = y_pts[y_pts.length - 1];
zero_index = (-1) * max / interval + (y_pts.length - 1);
}

if(this.zeroLine) this.old_zeroLine = this.zeroLine;
this.zeroLine = this.height - (zero_index * interval_height);
if(!this.old_zeroLine) this.old_zeroLine = this.zeroLine;

// Make positions arrays for y elements
if(this.yAxisPositions) this.oldYAxisPositions = this.yAxisPositions;
this.yAxisPositions = this.yAxisLabels.map(d => this.zeroLine - d * this.multiplier);
if(!this.oldYAxisPositions) this.oldYAxisPositions = this.yAxisPositions;

// if(this.yAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions;
// this.yAnnotationPositions = this.specific_values.map(d => this.zeroLine - d.value * this.multiplier);
// if(!this.oldYAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions;
}

makeXLines(positions, values) {
// TODO: draw as per condition (with/without label etc.)
return positions.map((position, i) => this.renderer.xLine(position, values[i]));
}

makeYLines(positions, values) {
return positions.map((position, i) => this.renderer.yLine(position, values[i]));
}

draw_graph(init=false) {
// TODO: NO INIT!
if(this.raw_chart_args.hasOwnProperty("init") && !this.raw_chart_args.init) {
this.y.map((d, i) => {
d.svg_units = [];
this.make_path && this.make_path(d, this.xPositions, d.yUnitPositions, this.colors[i]);
this.makeUnits(d);
this.calcYDependencies();
calcYDependencies() {
this.y_min_tops = new Array(this.xAxisLabels.length).fill(9999);
this.y.map(d => {
d.yUnitPositions = d.values.map( val => floatTwo(this.zeroLine - val * this.multiplier));
d.yUnitPositions.map( (yUnitPosition, i) => {
if(yUnitPosition < this.y_min_tops[i]) {
this.y_min_tops[i] = yUnitPosition;
}
});
return;
}
if(init) {
this.draw_new_graph_and_animate();
return;
}
this.y.map((d, i) => {
d.svg_units = [];
this.make_path && this.make_path(d, this.xPositions, d.yUnitPositions, this.colors[i]);
this.makeUnits(d);
});
}

draw_new_graph_and_animate() {
let data = [];
this.y.map((d, i) => {
// Anim: Don't draw initial values, store them and update later
d.yUnitPositions = new Array(d.values.length).fill(this.zeroLine); // no value
data.push({values: d.values});
d.svg_units = [];

this.make_path && this.make_path(d, this.xPositions, d.yUnitPositions, this.colors[i]);
this.makeUnits(d);
});

setTimeout(() => {
this.updateData(data);
}, 350);
}

setupNavigation(init) {
if(init) {
// Hack: defer nav till initial updateData
setTimeout(() => {
super.setupNavigation(init);
}, 500);
} else {
super.setupNavigation(init);
}
// this.chartWrapper.removeChild(this.tip.container);
// this.make_tooltip();
}

makeUnits(d) {
this.makeDatasetUnits(
this.xPositions,
d.yUnitPositions,
this.colors[d.index],
d.index,
this.y.length
);
}
prepareData() {
let s = this.state;
s.xAxisLabels = this.data.labels || [];
s.datasetLength = s.xAxisLabels.length;

makeDatasetUnits(x_values, y_values, color, dataset_index,
no_of_datasets, units_group, units_array, unit) {

if(!units_group) units_group = this.svg_units_groups[dataset_index];
if(!units_array) units_array = this.y[dataset_index].svg_units;
if(!unit) unit = this.unit_args;

units_group.textContent = '';
units_array.length = 0;

let unit_AxisChartRenderer = new AxisChartRenderer(this.height, this.zeroLine, this.avgUnitWidth);

y_values.map((y, i) => {
let data_unit = unit_AxisChartRenderer[unit.type](
x_values[i],
y,
unit.args,
color,
i,
dataset_index,
no_of_datasets
);
units_group.appendChild(data_unit);
units_array.push(data_unit);
});
let zeroArray = new Array(s.datasetLength).fill(0);

if(this.isNavigable) {
this.bind_units(units_array);
s.datasets = this.data.datasets;
if(!this.data.datasets) {
// default
s.datasets = [{
values: zeroArray
}];
}
}

bindTooltip() {
// TODO: could be in tooltip itself, as it is a given functionality for its parent
this.chartWrapper.addEventListener('mousemove', (e) => {
let offset = getOffset(this.chartWrapper);
let relX = e.pageX - offset.left - this.translateX;
let relY = e.pageY - offset.top - this.translateY;

if(relY < this.height + this.translateY * 2) {
this.mapTooltipXPosition(relX);
s.datasets.map((d, i)=> {
let vals = d.values;
if(!vals) {
vals = zeroArray;
} else {
this.tip.hide_tip();
// Check for non values
vals = vals.map(val => (!isNaN(val) ? val : 0));

// Trim or extend
if(vals.length > s.datasetLength) {
vals = vals.slice(0, s.datasetLength);
} else {
vals = fillArray(vals, s.datasetLength - vals.length, 0);
}
}
});
}

mapTooltipXPosition(relX) {
if(!this.y_min_tops) return;

let titles = this.xAxisLabels;
if(this.format_tooltip_x && this.format_tooltip_x(this.xAxisLabels[0])) {
titles = this.xAxisLabels.map(d=>this.format_tooltip_x(d));
}

let y_format = this.format_tooltip_y && this.format_tooltip_y(this.y[0].values[0]);

for(var i=this.xPositions.length - 1; i >= 0 ; i--) {
let x_val = this.xPositions[i];
// let delta = i === 0 ? this.avgUnitWidth : x_val - this.xPositions[i-1];
if(relX > x_val - this.avgUnitWidth/2) {
let x = x_val + this.translateX;
let y = this.y_min_tops[i] + this.translateY;

let title = titles[i];
let values = this.y.map((set, j) => {
return {
title: set.title,
value: y_format ? this.format_tooltip_y(set.values[i]) : set.values[i],
color: this.colors[j],
};
});

this.tip.set_values(x, y, title, '', values);
this.tip.show_tip();
break;
}
}
d.index = i;
});
}

// API
updateData(newY, newX) {
if(!newX) {
newX = this.xAxisLabels;
}
this.updating = true;

this.old_x_values = this.xAxisLabels.slice();
this.old_y_axis_tops = this.y.map(d => d.yUnitPositions.slice());

this.old_y_values = this.y.map(d => d.values);

// Just update values prop, setup_x/y() will do the rest
if(newY) this.y.map(d => {d.values = newY[d.index].values;});
if(newX) this.xAxisLabels = newX;

this.setup_x();
this.setup_y();
reCalc() {
let s = this.state;

// Change in data, so calculate dependencies
this.calcYDependencies();
// X
s.xAxisLabels = this.data.labels;
this.calcXPositions();

// Got the values? Now begin drawing
this.animator = new Animator(this.height, this.width, this.zeroLine, this.avgUnitWidth);
// Y
s.datasetsLabels = this.data.datasets.map(d => d.label);

this.animate_graphs();
// s.datasetsValues = [[]]; indexed component
// s.datasetsValues = [[[12, 34, 68], [10, 5, 46]], [[20, 20, 20]]]; // array of indexed components
s.datasetsValues = s.datasets.map(d => d.values); // indexed component
s.yAxisLabels = calcIntervals(this.getAllYValues(), this.type === 'line');
this.calcYAxisPositions();

this.updating = false;
// *** this.state.datasetsPoints =
}

animate_graphs() {
this.elements_to_animate = [];
// Pre-prep, equilize no of positions between old and new
let [old_x, newX] = equilizeNoOfElements(
this.x_old_axis_positions.slice(),
this.xPositions.slice()
);

let [oldYAxis, newYAxis] = equilizeNoOfElements(
this.oldYAxisPositions.slice(),
this.yAxisPositions.slice()
);

let newXValues = this.xAxisLabels.slice();
let newYValues = this.yAxisLabels.slice();

let extra_points = this.xPositions.slice().length - this.x_old_axis_positions.slice().length;

if(extra_points > 0) {
this.makeXLines(old_x, newXValues);
}
// No Y extra check?
this.makeYLines(oldYAxis, newYValues);

// Animation
if(extra_points !== 0) {
this.animateXLines(old_x, newX);
}
this.animateYLines(oldYAxis, newYAxis);

this.y.map(d => {
let [old_y, newY] = equilizeNoOfElements(
this.old_y_axis_tops[d.index].slice(),
d.yUnitPositions.slice()
);
if(extra_points > 0) {
this.make_path && this.make_path(d, old_x, old_y, this.colors[d.index]);
this.makeDatasetUnits(old_x, old_y, this.colors[d.index], d.index, this.y.length);
}
// Animation
d.path && this.animate_path(d, newX, newY);
this.animate_units(d, newX, newY);
});

runSMILAnimation(this.chartWrapper, this.svg, this.elements_to_animate);

setTimeout(() => {
this.y.map(d => {
this.make_path && this.make_path(d, this.xPositions, d.yUnitPositions, this.colors[d.index]);
this.makeUnits(d);

this.makeYLines(this.yAxisPositions, this.yAxisLabels);
this.makeXLines(this.xPositions, this.xAxisLabels);
// this.make_y_specifics(this.yAnnotationPositions, this.specific_values);
});
}, 400);
calcXPositions() {
let s = this.state;
this.setUnitWidthAndXOffset();
s.xPositions = s.xAxisLabels.map((d, i) =>
floatTwo(s.xOffset + i * s.unitWidth));
}

animate_path(d, newX, newY) {
const newPointsList = newY.map((y, i) => (newX[i] + ',' + y));
this.elements_to_animate = this.elements_to_animate
.concat(this.animator.path(d, newPointsList.join("L")));
}
calcYAxisPositions() {
let s = this.state;
const yPts = s.yAxisLabels;

animate_units(d, newX, newY) {
let type = this.unit_args.type;

d.svg_units.map((unit, i) => {
if(newX[i] === undefined || newY[i] === undefined) return;
this.elements_to_animate.push(this.animator[type](
{unit:unit, array:d.svg_units, index: i}, // unit, with info to replace where it came from in the data
newX[i],
newY[i],
d.index,
this.y.length
));
});
}
s.scaleMultiplier = this.height / getValueRange(yPts);
const intervalHeight = getIntervalSize(yPts) * s.scaleMultiplier;
s.zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight);

animateXLines(oldX, newX) {
this.xAxisLines.map((xLine, i) => {
this.elements_to_animate.push(this.animator.verticalLine(
xLine, newX[i], oldX[i]
));
});
s.yAxisPositions = yPts.map(d => s.zeroLine - d * s.scaleMultiplier);
}

animateYLines(oldY, newY) {
this.yAxisLines.map((yLine, i) => {
this.elements_to_animate.push(this.animator.horizontalLine(
yLine, newY[i], oldY[i]
));
});
setUnitWidthAndXOffset() {
this.state.unitWidth = this.width/(this.state.datasetLength - 1);
this.state.xOffset = 0;
}

animateYAnnotations() {
//
getAllYValues() {
// TODO: yMarkers, regions, sums, every Y value ever
return [].concat(...this.state.datasetsValues);
}

add_data_point(y_point, x_point, index=this.xAxisLabels.length) {
let newY = this.y.map(data_set => { return {values:data_set.values}; });
newY.map((d, i) => { d.values.splice(index, 0, y_point[i]); });
let newX = this.xAxisLabels.slice();
newX.splice(index, 0, x_point);

this.updateData(newY, newX);
calcIntermedState() {
//
}

remove_data_point(index = this.xAxisLabels.length-1) {
if(this.xAxisLabels.length < 3) return;
// this should be inherent in BaseChart
refreshRenderer() {
// These args are basically the current state of the chart,
// with constant and alive params mixed
let state = {
totalHeight: this.height,
totalWidth: this.width,

let newY = this.y.map(data_set => { return {values:data_set.values}; });
newY.map((d) => { d.values.splice(index, 1); });
let newX = this.xAxisLabels.slice();
newX.splice(index, 1);
xAxisMode: this.config.xAxisMode,
yAxisMode: this.config.yAxisMode,

this.updateData(newY, newX);
zeroLine: this.state.zeroLine,
unitWidth: this.state.unitWidth,
};
if(!this.renderer) {
this.renderer = new AxisChartRenderer(state);
} else {
this.renderer.refreshState(state);
}
}

getDataPoint(index=this.currentIndex) {
// check for length
let data_point = {
index: index
};
let y = this.y[0];
['svg_units', 'yUnitPositions', 'values'].map(key => {
let data_key = key.slice(0, key.length-1);
data_point[data_key] = y[key][index];
setupComponents() {
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: () => {}
});
data_point.label = this.xAxisLabels[index];
return data_point;
}

updateCurrentDataPoint(index) {
index = parseInt(index);
if(index < 0) index = 0;
if(index >= this.xAxisLabels.length) index = this.xAxisLabels.length - 1;
if(index === this.currentIndex) return;
this.currentIndex = index;
fire(this.parent, "data-select", this.getDataPoint());
}
this.xAxis = new ChartComponent({
layerClass: 'x axis',
make: (renderer, positions, values) => {
return positions.map((position, i) => renderer.xLine(position, values[i]));
},
argsKeys: ['xPositions', 'xAxisLabels'],
animate: () => {}
});

set_avgUnitWidth_and_x_offset() {
// Set the ... you get it
this.avgUnitWidth = this.width/(this.xAxisLabels.length - 1);
this.x_offset = 0;
}
// Indexed according to dataset

get_all_y_values() {
let all_values = [];
// this.dataUnits = new IndexedChartComponent({
// layerClass: 'x axis',
// make: (renderer, positions, values) => {
// return positions.map((position, i) => renderer.xLine(position, values[i]));
// },
// argsKeys: ['xPositions', 'xAxisLabels'],
// animate: () => {}
// });

// Add in all the y values in the datasets
this.y.map(d => {
all_values = all_values.concat(d.values);
});
this.yMarkerLines = {};
this.xMarkerLines = {};

// Add in all the specific values
return all_values.concat(this.specific_values.map(d => d.value));
}
// Marker Regions

calcYDependencies() {
this.y_min_tops = new Array(this.xAxisLabels.length).fill(9999);
this.y.map(d => {
d.yUnitPositions = d.values.map( val => floatTwo(this.zeroLine - val * this.multiplier));
d.yUnitPositions.map( (yUnitPosition, i) => {
if(yUnitPosition < this.y_min_tops[i]) {
this.y_min_tops[i] = yUnitPosition;
}
});
});
// this.chartWrapper.removeChild(this.tip.container);
// this.make_tooltip();
this.components = [
this.yAxis,
this.xAxis,
// this.yMarkerLines,
// this.xMarkerLines,
// this.dataUnits,
];
}

}

+ 12
- 7
src/js/charts/BarChart.js Ver ficheiro

@@ -3,13 +3,21 @@ import AxisChart from './AxisChart';
export default class BarChart extends AxisChart {
constructor(args) {
super(args);

this.type = 'bar';
this.xAxisMode = args.xAxisMode || 'tick';
this.yAxisMode = args.yAxisMode || 'span';
this.setup();
}

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 + 1);
this.state.xOffset = this.state.unitWidth;
}

setup_values() {
super.setup_values();
this.x_offset = this.avgUnitWidth;
@@ -74,8 +82,5 @@ export default class BarChart extends AxisChart {
this.updateCurrentDataPoint(this.currentIndex + 1);
}

set_avgUnitWidth_and_x_offset() {
this.avgUnitWidth = this.width/(this.xAxisLabels.length + 1);
this.x_offset = this.avgUnitWidth;
}

}

+ 52
- 56
src/js/charts/BaseChart.js Ver ficheiro

@@ -14,6 +14,7 @@ export default class BaseChart {
colors = [],

isNavigable = 0,
showLegend = 1,

type = '',

@@ -31,23 +32,26 @@ export default class BaseChart {
this.currentIndex = 0;
}

this.setupConfiguration();
this.configure(arguments[0]);
}

setupConfiguration() {
configure(args) {
// Make a this.config, that has stuff like showTooltip,
// showLegend, which then all functions will check

this.setColors();
this.setMargins();
this.setMargins(args);

// constants
this.config = {
showTooltip: 1,
showTooltip: 1, // calculate
showLegend: 1,
isNavigable: 0,
animate: 1
// animate: 1
animate: 0
};

this.state = {};
}

setColors() {
@@ -67,8 +71,8 @@ export default class BaseChart {
this.colors = this.colors.map(color => getColor(color));
}

setMargins() {
let height = this.rawChartArgs.height;
setMargins(args) {
let height = args.height;
this.baseHeight = height;
this.height = height - 40;
this.translateX = 60;
@@ -103,7 +107,7 @@ export default class BaseChart {
}

checkData() {}
getFirstUpdateData(data) {}
getFirstUpdateData() {}

setup() {
if(this.validate()) {
@@ -114,9 +118,7 @@ export default class BaseChart {
_setup() {
this.bindWindowEvents();
this.setupConstants();

// this.setupComponents();

this.setupComponents();
this.makeContainer();
this.makeTooltip(); // without binding
this.draw(true);
@@ -158,32 +160,35 @@ export default class BaseChart {
this.bindTooltip();
}

bindTooltip() {}

draw(init=false) {
// difference from update(): draw the whole object due to groudbreaking event (init, resize, etc.)
// (draw everything, layers, groups, units)
this.calc();
this.refreshRenderer() // this chart's rendered with the config
this.setupComponents();
this.calcWidth();
this.refresh(); // refresh conponent with chart a

this.makeChartArea();
this.makeLayers();
this.setComponentParent();
this.makeComponentLayers();

this.renderComponents(); // with zero values
this.renderLegend();
this.setupNavigation(init);

this.renderComponents(); // first time plain render, so no rerender

if(this.config.animate) this.update(this.firstUpdateData);
}

update() {
// difference from draw(): yes you do rerender everything here as well,
// but not things like the chart itself, mosty only at component level
this.reCalc();
// but not things like the chart itself or layers, mosty only at component level
// HERE IS WHERE THE ACTUAL STATE CHANGES, and old one matters, not in draw
this.refresh();
this.reRender();
}

refreshRenderer() {}

calcWidth() {
let outerAnnotationsWidth = 0;
// let charWidth = 8;
@@ -197,9 +202,12 @@ export default class BaseChart {
this.width = this.baseWidth - this.translateX * 2;
}

calc() {
this.calcWidth();
refresh() { //?? refresh?
this.oldState = this.state ? Object.assign({}, this.state) : {};
this.prepareData();
this.reCalc();
this.refreshRenderer();
this.refreshComponents();
}

makeChartArea() {
@@ -218,33 +226,21 @@ export default class BaseChart {
);
}

prepareData() {}

makeLayers() {
this.components.forEach((component) => {
component.layer = this.makeLayer(component.layerClass);
});
}

calculateValues() {}

renderComponents() {
this.components.forEach(c => {
c.store = c.make(...c.makeArgs);
c.layer.textContent = '';
c.store.forEach(element => {c.layer.appendChild(element);});
});
}
reCalc() {}
// Will update values(state)
// Will recalc specific parts depending on the update

reCalc() {
// Will update values(state)
// Will recalc specific parts depending on the update
}
refreshRenderer() {}

reRender(animate=true) {
if(!animate) {
this.renderComponents();
return;
}
this.intermedState = this.calcIntermedState();
this.refreshComponents();
this.animateComponents();
setTimeout(() => {
this.renderComponents();
@@ -253,19 +249,23 @@ export default class BaseChart {
// (opt, should not redraw if still in animate?)
}

animateComponents() {
this.intermedValues = this.calcIntermediateValues();
this.components.forEach(c => {
// c.store = c.animate(...c.animateArgs);
// c.layer.textContent = '';
// c.store.forEach(element => {c.layer.appendChild(element);});
});
calcIntermedState() {}

// convenient component array abstractions
setComponentParent() { this.components.forEach(c => c.setupParent(this.drawArea)); };
makeComponentLayers() { this.components.forEach(c => c.makeLayer()); }
renderComponents() { this.components.forEach(c => c.render()); }
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));
}


calcInitStage() {}


renderLegend() {}

setupNavigation(init=false) {
@@ -309,10 +309,6 @@ export default class BaseChart {
getDataPoint() {}
updateCurrentDataPoint() {}

makeLayer(className, transform='') {
return makeSVGGroup(this.drawArea, className, transform);
}

getDifferentChart(type) {
return getDifferentChart(type, this.type, this.rawChartArgs);
}


+ 13
- 13
src/js/charts/LineChart.js Ver ficheiro

@@ -4,27 +4,27 @@ import { makeSVGGroup, makePath, makeGradient } from '../utils/draw';
export default class LineChart extends AxisChart {
constructor(args) {
super(args);

this.xAxisMode = args.xAxisMode || 'span';
this.yAxisMode = args.yAxisMode || 'span';

if(args.hasOwnProperty('show_dots')) {
this.show_dots = args.show_dots;
} else {
this.show_dots = 1;
}
this.region_fill = args.region_fill;
this.type = 'line';

if(Object.getPrototypeOf(this) !== LineChart.prototype) {
return;
}
this.dot_radius = args.dot_radius || 4;
this.heatline = args.heatline;
this.type = 'line';

this.setup();
}

configure(args) {
super.configure(args);
this.config.xAxisMode = args.xAxisMode || 'span';
this.config.yAxisMode = args.yAxisMode || 'span';

this.config.dot_radius = args.dot_radius || 4;

this.config.heatline = args.heatline || 0;
this.config.region_fill = args.region_fill || 0;
this.config.show_dots = args.show_dots || 1;
}

setupPreUnitLayers() {
// Path groups
this.paths_groups = [];


+ 51
- 26
src/js/objects/ChartComponent.js Ver ficheiro

@@ -1,26 +1,51 @@
// export default class ChartComponent {
// constructor({
// parent = null,
// colors = []
// }) {
// this.parent = parent;
// this.colors = colors;
// this.title_name = '';
// this.title_value = '';
// this.list_values = [];
// this.title_value_first = 0;

// this.x = 0;
// this.y = 0;

// this.top = 0;
// this.left = 0;

// this.setup();
// }

// setup() {
// this.make_tooltip();
// }

// }
import { makeSVGGroup } from '../utils/draw';

export class ChartComponent {
constructor({
layerClass = '',
layerTransform = '',
make,
argsKeys,
animate
}) {
this.layerClass = layerClass; // 'y axis'
this.layerTransform = layerTransform;
this.make = make;
this.argsKeys = argsKeys;//['yAxisPositions', 'yAxisLabels'];
this.animate = animate;

this.layer = undefined;
this.store = []; //[[]] depends on indexed
}

refresh(args) {
this.chartState = args.chartState;
this.oldChartState = args.oldChartState;
this.intermedState = args.intermedState;

this.chartRenderer = args.chartRenderer;
}

render() {
let args = this.argsKeys.map(key => this.chartState[key]);
args.unshift(this.chartRenderer);
this.store = this.make(...args);

this.layer.textContent = '';
this.store.forEach(element => {
this.layer.appendChild(element);
});
}

setupParent(parent) {
this.parent = parent;
}

makeLayer() {
this.layer = makeSVGGroup(this.parent, this.layerClass, this.layerTransform);
}
}

export class IndexedChartComponent extends ChartComponent {
//
}

+ 1
- 1
src/js/utils/draw-utils.js Ver ficheiro

@@ -1,4 +1,4 @@
import { fillArray } from '../utils/helpers';
import { fillArray } from './helpers';

const MIN_BAR_PERCENT_HEIGHT = 0.01;



+ 2
- 2
src/js/utils/draw.js Ver ficheiro

@@ -205,10 +205,10 @@ export function makeHoriYLine(y, label, totalWidth, mode) {

export class AxisChartRenderer {
constructor(state) {
this.updateState(state);
this.refreshState(state);
}

updateState(state) {
refreshState(state) {
this.totalHeight = state.totalHeight;
this.totalWidth = state.totalWidth;
this.zeroLine = state.zeroLine;


+ 29
- 0
src/js/utils/intervals.js Ver ficheiro

@@ -153,6 +153,35 @@ export function calcIntervals(values, withMinimum=false) {
return intervals;
}

export function getZeroIndex(yPts) {
let zeroIndex;
let interval = getIntervalSize(yPts);
if(yPts.indexOf(0) >= 0) {
// the range has a given zero
// zero-line on the chart
zeroIndex = yPts.indexOf(0);
} else if(yPts[0] > 0) {
// Minimum value is positive
// zero-line is off the chart: below
let min = yPts[0];
zeroIndex = (-1) * min / interval;
} else {
// Maximum value is negative
// zero-line is off the chart: above
let max = yPts[yPts.length - 1];
zeroIndex = (-1) * max / interval + (yPts.length - 1);
}
return zeroIndex;
}

export function getIntervalSize(orderedArray) {
return orderedArray[1] - orderedArray[0];
}

export function getValueRange(orderedArray) {
return orderedArray[orderedArray.length-1] - orderedArray[0];
}

export function calcDistribution(values, distributionSize) {
// Assume non-negative values,
// implying distribution minimum at zero


Carregando…
Cancelar
Guardar