Преглед изворни кода

[major] regions, markers, tooltips, unit control

tags/1.2.0
Prateeksha Singh пре 7 година
родитељ
комит
55c8b6861f
15 измењених фајлова са 1294 додато и 591 уклоњено
  1. +608
    -290
      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. +47
    -10
      docs/assets/js/index.js
  7. +2
    -2
      docs/index.html
  8. +17
    -5
      src/js/chart.js
  9. +292
    -63
      src/js/charts/AxisChart.js
  10. +0
    -83
      src/js/charts/BarChart.js
  11. +0
    -81
      src/js/charts/LineChart.js
  12. +6
    -3
      src/js/charts/MultiAxisChart.js
  13. +215
    -0
      src/js/objects/AxisChartControllers.js
  14. +1
    -1
      src/js/utils/draw-utils.js
  15. +102
    -49
      src/js/utils/draw.js

+ 608
- 290
dist/frappe-charts.esm.js
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 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
docs/assets/js/frappe-charts.min.js
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 47
- 10
docs/assets/js/index.js Прегледај датотеку

@@ -51,7 +51,7 @@ let bar_composite_chart = new Chart ({
height: 180,
colors: ['orange'],
isNavigable: 1,
is_series: 1
isSeries: 1
// regionFill: 1
});

@@ -59,9 +59,12 @@ let line_composite_chart = new Chart ({
parent: c2,
data: line_composite_data,
type: 'line',
options: {
dotSize: 10
},
height: 180,
colors: ['green'],
is_series: 1
isSeries: 1
});

bar_composite_chart.parent.addEventListener('data-select', (e) => {
@@ -75,16 +78,48 @@ let type_data = {
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm",
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"],

yMarkers: [
{
name: "Marker 1",
value: 42,
type: 'dashed'
},
{
name: "Marker 2",
value: 25,
type: 'dashed'
}
],

yRegions: [
{
name: "Region Y 1",
start: 10,
end: 50
},
],

// will depend on series code for calculating X values
// xRegions: [
// {
// name: "Region X 2",
// start: ,
// end: ,
// }
// ],

datasets: [
{
name: "Some Data",
values: [18, 40, 30, 35, 8, 52, 17, -4],
axisPosition: 'right'
axisPosition: 'right',
chartType: 'bar'
},
{
name: "Another Set",
values: [30, 50, -10, 15, 18, 32, 27, 14],
axisPosition: 'right'
axisPosition: 'right',
chartType: 'line'
},
// {
// name: "Yet Another",
@@ -111,12 +146,14 @@ let type_chart = new Chart({
parent: "#chart-types",
// title: "My Awesome Chart",
data: type_data,
type: 'multiaxis',
type: 'line',
height: 250,
colors: ['purple', 'magenta'],
is_series: 1,
format_tooltip_x: d => (d + '').toUpperCase(),
format_tooltip_y: d => d + ' pts'
isSeries: 1,
xAxisMode: 'tick',
yAxisMode: 'span',
// formatTooltipX: d => (d + '').toUpperCase(),
// formatTooltipY: d => d + ' pts'
});

Array.prototype.slice.call(
@@ -164,7 +201,7 @@ let plot_chart_args = {
type: 'line',
height: 250,
colors: ['blue'],
is_series: 1,
isSeries: 1,
showDots: 0,
heatline: 1,
xAxisMode: 'tick',
@@ -241,7 +278,7 @@ let update_chart = new Chart({
type: 'line',
height: 250,
colors: ['red'],
is_series: 1,
isSeries: 1,
regionFill: 1
});



+ 2
- 2
docs/index.html Прегледај датотеку

@@ -24,7 +24,7 @@
<div class="container">
<div class="row hero" style="padding-top: 30px; padding-bottom: 0px;">
<div class="jumbotron" style="background: transparent;">
<h1>Frappé Charts</h1>
<h1>Frappe Charts</h1>
<p class="mt-2">GitHub-inspired simple and modern charts for the web</p>
<p class="mt-2">with zero dependencies.</p>
<!--<p class="mt-2">Because dumb charts are hard to come by.</p>-->
@@ -162,7 +162,7 @@
xAxisMode: 'tick', // for short label ticks
// or 'span' for long spanning vertical axis lines
yAxisMode: 'span', // for long horizontal lines, or 'tick'
is_series: 1, // to allow for skipping of X values
isSeries: 1, // to allow for skipping of X values
...</code></pre>
<div id="chart-trends" class="border"></div>
<div class="btn-group chart-plot-buttons mt-1 mx-auto" role="group">


+ 17
- 5
src/js/chart.js Прегледај датотеку

@@ -1,12 +1,11 @@
import '../scss/charts.scss';

import BarChart from './charts/BarChart';
import LineChart from './charts/LineChart';
import ScatterChart from './charts/ScatterChart';
import MultiAxisChart from './charts/MultiAxisChart';
import PercentageChart from './charts/PercentageChart';
import PieChart from './charts/PieChart';
import Heatmap from './charts/Heatmap';
import AxisChart from './charts/AxisChart';

// if (ENV !== 'production') {
// // Enable LiveReload
@@ -16,9 +15,13 @@ import Heatmap from './charts/Heatmap';
// );
// }

// If type is bar




const chartTypes = {
line: LineChart,
bar: BarChart,
mixed: AxisChart,
multiaxis: MultiAxisChart,
scatter: ScatterChart,
percentage: PercentageChart,
@@ -27,8 +30,17 @@ const chartTypes = {
};

function getChartByType(chartType = 'line', options) {
if(chartType === 'line') {
options.unitType = 'line';
return new AxisChart(options);
} else if (chartType === 'bar') {
options.unitType = 'bar';
return new AxisChart(options);
}

if (!chartTypes[chartType]) {
return new LineChart(options);
console.error("Undefined chart type: " + chartType);
return;
}

return new chartTypes[chartType](options);


+ 292
- 63
src/js/charts/AxisChart.js Прегледај датотеку

@@ -1,8 +1,9 @@
import BaseChart from './BaseChart';
import { Y_AXIS_MARGIN } from '../utils/margins';
import { ChartComponent } from '../objects/ChartComponent';
import { BarChartController, LineChartController, getPaths } from '../objects/AxisChartControllers';
import { getOffset, fire } from '../utils/dom';
import { AxisChartRenderer, makePath, makeGradient } from '../utils/draw';
import { AxisChartRenderer } from '../utils/draw';
import { equilizeNoOfElements } from '../utils/draw-utils';
import { Animator } from '../utils/animate';
import { runSMILAnimation } from '../utils/animation';
@@ -12,11 +13,33 @@ import { floatTwo, fillArray } from '../utils/helpers';
export default class AxisChart extends BaseChart {
constructor(args) {
super(args);
this.is_series = args.is_series;
this.format_tooltip_y = args.format_tooltip_y;
this.format_tooltip_x = args.format_tooltip_x;
this.isSeries = args.isSeries;
this.formatTooltipY = args.formatTooltipY;
this.formatTooltipX = args.formatTooltipX;
this.unitType = args.unitType || 'line';

this.setupUnitRenderer();

this.zeroLine = this.height;
this.preSetup();
this.setup();
}

configure(args) {
super.configure();

this.config.xAxisMode = args.xAxisMode;
this.config.yAxisMode = args.yAxisMode;
}

preSetup() {}

setupUnitRenderer() {
let options = this.rawChartArgs.options;
this.unitRenderers = {
bar: new BarChartController(options),
line: new LineChartController(options)
};
}

setHorizontalMargin() {
@@ -36,8 +59,16 @@ export default class AxisChart extends BaseChart {
this.state = {
xAxisLabels: [],
xAxisPositions: [],
xAxisMode: this.config.xAxisMode,
yAxisMode: this.config.yAxisMode
}

this.data.datasets.map(d => {
if(!d.chartType ) {
d.chartType = this.unitType;
}
});

this.prepareYAxis();
}

@@ -78,6 +109,8 @@ export default class AxisChart extends BaseChart {
});

s.noOfDatasets = s.datasets.length;
s.yMarkers = data.yMarkers;
s.yRegions = data.yRegions;
}

prepareYAxis() {
@@ -97,6 +130,7 @@ export default class AxisChart extends BaseChart {
this.setYAxis();
this.calcYUnits();
this.calcYMaximums();
this.calcYRegions();

// should be state
this.configUnits();
@@ -111,7 +145,8 @@ export default class AxisChart extends BaseChart {
let s = this.state;
this.setUnitWidthAndXOffset();
s.xAxisPositions = s.xAxisLabels.map((d, i) =>
floatTwo(s.xOffset + i * s.unitWidth));
floatTwo(s.xOffset + i * s.unitWidth)
);

s.xUnitPositions = new Array(s.noOfDatasets).fill(s.xAxisPositions);
}
@@ -150,8 +185,26 @@ export default class AxisChart extends BaseChart {
// this.make_tooltip();
}

calcYRegions() {
let s = this.state;
if(s.yMarkers) {
s.yMarkers = s.yMarkers.map(d => {
d.value = floatTwo(s.yAxis.zeroLine - d.value * s.yAxis.scaleMultiplier);
return d;
});
}
if(s.yRegions) {
s.yRegions = s.yRegions.map(d => {
d.start = floatTwo(s.yAxis.zeroLine - d.start * s.yAxis.scaleMultiplier);
d.end = floatTwo(s.yAxis.zeroLine - d.end * s.yAxis.scaleMultiplier);
return d;
});
}
}

configUnits() {}

// Default, as per bar, and mixed. Only line will be a special case
setUnitWidthAndXOffset() {
this.state.unitWidth = this.width/(this.state.datasetLength);
this.state.xOffset = this.state.unitWidth/2;
@@ -180,11 +233,11 @@ export default class AxisChart extends BaseChart {
// this.yAxesAux,
...this.getYAxesComponents(),
this.getXAxisComponents(),
// this.getYMarkerLines(),
// this.getXMarkerLines(),
// TODO: regions too?
...this.getPathComponents(),
...this.getDataUnitsComponents(this.config),
...this.getYRegions(),
...this.getXRegions(),
...this.getYMarkerLines(),
// ...this.getXMarkerLines(),
...this.getChartComponents(),
];
}

@@ -233,7 +286,9 @@ export default class AxisChart extends BaseChart {
let s = this.state;
// TODO: xAxis Label spacing
return s.xAxisPositions.map((position, i) =>
this.renderer.xLine(position, s.xAxisLabels[i], {pos:'top'})
this.renderer.xLine(position, s.xAxisLabels[i]
// , {pos:'top'}
)
);
},
animate: (xLines) => {
@@ -262,68 +317,156 @@ export default class AxisChart extends BaseChart {
});
}

getDataUnitsComponents() {
return this.data.datasets.map((d, index) => {
return new ChartComponent({
layerClass: 'dataset-units dataset-' + index,
make: () => {
let d = this.state.datasets[index];
let unitType = this.unitArgs;

return d.positions.map((y, j) => {
return this.renderer[unitType.type](
this.state.xAxisPositions[j],
y,
unitType.args,
this.colors[index],
j,
index,
this.state.noOfDatasets
);
});
},
animate: (svgUnits) => {
let unitType = this.unitArgs.type;

// have been updated in axis render;
let newX = this.state.xAxisPositions;
let newY = this.state.datasets[index].positions;

let lastUnit = svgUnits[svgUnits.length - 1];
let parentNode = lastUnit.parentNode;

if(this.oldState.xExtra > 0) {
for(var i = 0; i<this.oldState.xExtra; i++) {
let unit = lastUnit.cloneNode(true);
parentNode.appendChild(unit);
svgUnits.push(unit);
}
}
getChartComponents() {
let dataUnitsComponents = []
// this.state is not defined at this stage
this.data.datasets.forEach((d, index) => {
if(d.chartType === 'line') {
dataUnitsComponents.push(this.getPathComponent(d, index));
}
console.log(this.unitRenderers[d.chartType], d.chartType);
dataUnitsComponents.push(this.getDataUnitComponent(
d, index, this.unitRenderers[d.chartType]
));
});
return dataUnitsComponents;
}

svgUnits.map((unit, i) => {
if(newX[i] === undefined || newY[i] === undefined) return;
this.elementsToAnimate.push(this.renderer['animate' + unitType](
unit, // unit, with info to replace where it came from in the data
newX[i],
newY[i],
index,
this.state.noOfDatasets
));
});
getDataUnitComponent(d, index, unitRenderer) {
return new ChartComponent({
layerClass: 'dataset-units dataset-' + index,
make: () => {
let d = this.state.datasets[index];

return d.positions.map((y, j) => {
return unitRenderer.draw(
this.state.xAxisPositions[j],
y,
this.colors[index],
j,
index,
this.state.noOfDatasets
);
});
},
animate: (svgUnits) => {
// have been updated in axis render;
let newX = this.state.xAxisPositions;
let newY = this.state.datasets[index].positions;

let lastUnit = svgUnits[svgUnits.length - 1];
let parentNode = lastUnit.parentNode;

if(this.oldState.xExtra > 0) {
for(var i = 0; i<this.oldState.xExtra; i++) {
let unit = lastUnit.cloneNode(true);
parentNode.appendChild(unit);
svgUnits.push(unit);
}
}
});

svgUnits.map((unit, i) => {
if(newX[i] === undefined || newY[i] === undefined) return;
this.elementsToAnimate.push(unitRenderer.animate(
unit, // unit, with info to replace where it came from in the data
newX[i],
newY[i],
index,
this.state.noOfDatasets
));
});
}
});
}

getPathComponents() {
return [];
getPathComponent(d, index) {
return new ChartComponent({
layerClass: 'path dataset-path',
make: () => {
let d = this.state.datasets[index];
let color = this.colors[index];

return getPaths(
d.positions,
this.state.xAxisPositions,
color,
this.config.heatline,
this.config.regionFill
);
},
animate: (paths) => {
let newX = this.state.xAxisPositions;
let newY = this.state.datasets[index].positions;

let oldX = this.oldState.xAxisPositions;
let oldY = this.oldState.datasets[index].positions;


let parentNode = paths[0].parentNode;

[oldX, newX] = equilizeNoOfElements(oldX, newX);
[oldY, newY] = equilizeNoOfElements(oldY, newY);

if(this.oldState.xExtra > 0) {
paths = getPaths(
oldY, oldX, this.colors[index],
this.config.heatline,
this.config.regionFill
);
parentNode.textContent = '';
paths.map(path => parentNode.appendChild(path));
}

const newPointsList = newY.map((y, i) => (newX[i] + ',' + y));
this.elementsToAnimate = this.elementsToAnimate
.concat(this.renderer.animatepath(paths, newPointsList.join("L")));
}
});
}

getYMarkerLines() {
return [];
if(!this.data.yMarkers) {
return [];
}
return this.data.yMarkers.map((d, index) => {
return new ChartComponent({
layerClass: 'y-markers',
make: () => {
let s = this.state;
return s.yMarkers.map(marker =>
this.renderer.yMarker(marker.value, marker.name,
{pos:'right', mode: 'span', lineType: marker.type})
);
},
animate: () => {}
});
});
}

// getXMarkerLines() {
// return [];
// }

getYRegions() {
if(!this.data.yRegions) {
return [];
}
// return [];
return this.data.yRegions.map((d, index) => {
return new ChartComponent({
layerClass: 'y-regions',
make: () => {
let s = this.state;
return s.yRegions.map(region =>
this.renderer.yRegion(region.start, region.end, region.name)
);
},
animate: () => {}
});
});
}

getXMarkerLines() {
getXRegions() {
return [];
}

@@ -345,6 +488,88 @@ export default class AxisChart extends BaseChart {
} else {
this.renderer.refreshState(state);
}

let meta = {
totalHeight: this.height,
totalWidth: this.width,
zeroLine: this.state.zeroLine,
unitWidth: this.state.unitWidth,
};

Object.keys(this.unitRenderers).map(key => {
this.unitRenderers[key].refreshMeta(meta);
});
}

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

if(relY < this.height + this.translateY * 2) {
this.mapTooltipXPosition(relX);
} else {
this.tip.hide_tip();
}
});
}

mapTooltipXPosition(relX) {
let s = this.state;
if(!s.yUnitMinimums) return;

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

let formatY = this.formatTooltipY && this.formatTooltipY(this.y[0].values[0]);

for(var i=s.datasetLength - 1; i >= 0 ; i--) {
let xVal = s.xAxisPositions[i];
// let delta = i === 0 ? s.unitWidth : xVal - s.xAxisPositions[i-1];
if(relX > xVal - s.unitWidth/2) {
let x = xVal + this.translateXLeft;
let y = s.yUnitMinimums[i] + this.translateY;

let values = s.datasets.map((set, j) => {
return {
title: set.title,
value: formatY ? this.formatTooltipY(set.values[i]) : set.values[i],
color: this.colors[j],
};
});

this.tip.set_values(x, y, titles[i], '', values);
this.tip.show_tip();
break;
}
}
}

getDataPoint(index=this.current_index) {
// 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];
});
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.current_index) return;
this.current_index = index;
$.fire(this.parent, "data-select", this.getDataPoint());
}

// API
@@ -374,3 +599,7 @@ export default class AxisChart extends BaseChart {
//
}
}


// keep a binding at the end of chart


+ 0
- 83
src/js/charts/BarChart.js Прегледај датотеку

@@ -1,83 +0,0 @@
import AxisChart from './AxisChart';

export default 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';
}

// setUnitWidthAndXOffset() {
// this.state.unitWidth = this.width/(this.state.datasetLength);
// this.state.xOffset = this.state.unitWidth/2;
// }

configUnits() {
this.unitArgs = {
type: 'bar',
args: {
spaceWidth: this.state.unitWidth/2,
}
};
}

// makeOverlay() {
// // Just make one out of the first element
// let index = this.xAxisLabels.length - 1;
// let unit = this.y[0].svg_units[index];
// this.updateCurrentDataPoint(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.updateCurrentDataPoint(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.updateCurrentDataPoint(this.currentIndex - 1);
// }

// onRightArrow() {
// this.updateCurrentDataPoint(this.currentIndex + 1);
// }

}

+ 0
- 81
src/js/charts/LineChart.js Прегледај датотеку

@@ -40,85 +40,4 @@ export default class LineChart extends AxisChart {
this.state.xOffset = 0;
}

getDataUnitsComponents(config) {
if(!config.showDots) {
return [];
}
else {
return super.getDataUnitsComponents();
}
}

getPathComponents() {
return this.data.datasets.map((d, index) => {
return new ChartComponent({
layerClass: 'path dataset-path',
make: () => {
let d = this.state.datasets[index];
let color = this.colors[index];

return this.getPaths(
d.positions,
this.state.xAxisPositions,
color,
this.config.heatline,
this.config.regionFill
);
},
animate: (paths) => {
let newX = this.state.xAxisPositions;
let newY = this.state.datasets[index].positions;

let oldX = this.oldState.xAxisPositions;
let oldY = this.oldState.datasets[index].positions;


let parentNode = paths[0].parentNode;

[oldX, newX] = equilizeNoOfElements(oldX, newX);
[oldY, newY] = equilizeNoOfElements(oldY, newY);

if(this.oldState.xExtra > 0) {
paths = this.getPaths(
oldY, oldX, this.colors[index],
this.config.heatline,
this.config.regionFill
);
parentNode.textContent = '';
paths.map(path => parentNode.appendChild(path));
}

const newPointsList = newY.map((y, i) => (newX[i] + ',' + y));
this.elementsToAnimate = this.elementsToAnimate
.concat(this.renderer.animatepath(paths, newPointsList.join("L")));
}
});
});
}

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

+ 6
- 3
src/js/charts/MultiAxisChart.js Прегледај датотеку

@@ -6,9 +6,12 @@ import { floatTwo } from '../utils/helpers';
export default class MultiAxisChart extends AxisChart {
constructor(args) {
super(args);
// this.unitType = args.unitType || 'line';
// this.setup();
}

preSetup() {
this.type = 'multiaxis';
this.unitType = args.unitType || 'line';
this.setup();
}

setHorizontalMargin() {
@@ -107,7 +110,7 @@ export default class MultiAxisChart extends AxisChart {
}

// TODO remove renderer zeroline from above and below
getDataUnitsComponents() {
getChartComponents() {
return this.data.datasets.map((d, index) => {
return new ChartComponent({
layerClass: 'dataset-units dataset-' + index,


+ 215
- 0
src/js/objects/AxisChartControllers.js Прегледај датотеку

@@ -0,0 +1,215 @@
import { getBarHeightAndYAttr } from '../utils/draw-utils';
import { createSVG, makePath, makeGradient } from '../utils/draw';
import { STD_EASING, UNIT_ANIM_DUR, MARKER_LINE_ANIM_DUR, PATH_ANIM_DUR } from '../utils/animate';

class AxisChartController {
constructor(meta) {
// TODO: make configurable passing args
this.refreshMeta(meta);
this.setupArgs();
}

setupArgs() {}

refreshMeta(meta) {
this.meta = meta || {};
}

draw() {}
animate() {}
}

export class AxisController extends AxisChartController {
constructor(meta) {
super(meta);
}

setupArgs() {}

draw(x, y, color, index) {
return createSVG('circle', {
style: `fill: ${color}`,
'data-point-index': index,
cx: x,
cy: y,
r: this.args.radius
});
}

animate(dot, x, yTop) {
return [dot, {cx: x, cy: yTop}, UNIT_ANIM_DUR, STD_EASING];
// dot.animate({cy: yTop}, UNIT_ANIM_DUR, mina.easein);
}
}

export class BarChartController extends AxisChartController {
constructor(meta) {
super(meta);
}

setupArgs() {
this.args = {
spaceRatio: 0.5,
};
}

draw(x, yTop, color, index, datasetIndex, noOfDatasets) {
let totalWidth = this.meta.unitWidth - this.meta.unitWidth * this.args.spaceRatio;
let startX = x - totalWidth/2;

// temp commented
// let width = totalWidth / noOfDatasets;
// let currentX = startX + width * datasetIndex;

// temp
let width = totalWidth;
let currentX = startX;

let [height, y] = getBarHeightAndYAttr(yTop, this.meta.zeroLine, this.meta.totalHeight);

return createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: currentX,
y: y,
width: width,
height: height
});
}

animate(bar, x, yTop, index, noOfDatasets) {
let start = x - this.meta.avgUnitWidth/4;
let width = (this.meta.avgUnitWidth/2)/noOfDatasets;
let [height, y] = getBarHeightAndYAttr(yTop, this.meta.zeroLine, this.meta.totalHeight);

x = start + (width * index);

return [bar, {width: width, height: height, x: x, y: y}, UNIT_ANIM_DUR, STD_EASING];
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
}
}

export class LineChartController extends AxisChartController {
constructor(meta) {
super(meta);
}

setupArgs() {
console.log(this);
this.args = {
radius: this.meta.dotSize || 4
};
}

draw(x, y, color, index) {
return createSVG('circle', {
style: `fill: ${color}`,
'data-point-index': index,
cx: x,
cy: y,
r: this.args.radius
});
}

animate(dot, x, yTop) {
return [dot, {cx: x, cy: yTop}, UNIT_ANIM_DUR, STD_EASING];
// dot.animate({cy: yTop}, UNIT_ANIM_DUR, mina.easein);
}
}

export 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.updateCurrentDataPoint(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.updateCurrentDataPoint(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.updateCurrentDataPoint(this.currentIndex - 1);
// }

// onRightArrow() {
// this.updateCurrentDataPoint(this.currentIndex + 1);
// }
// }


+ 1
- 1
src/js/utils/draw-utils.js Прегледај датотеку

@@ -44,7 +44,7 @@ export function equilizeNoOfElements(array1, array2,
// return values.map((value, i) => {
// let space_taken = getStringWidth(value, char_width) + 2;
// if(space_taken > allowed_space) {
// if(is_series) {
// if(isSeries) {
// // Skip some axis lines if X axis is a series
// let skips = 1;
// while((space_taken/skips)*2 > allowed_space) {


+ 102
- 49
src/js/utils/draw.js Прегледај датотеку

@@ -1,16 +1,18 @@
import { getBarHeightAndYAttr } from './draw-utils';
import { getStringWidth } from './helpers';
import { STD_EASING, UNIT_ANIM_DUR, MARKER_LINE_ANIM_DUR, PATH_ANIM_DUR } from './animate';

const AXIS_TICK_LENGTH = 6;
const LABEL_MARGIN = 4;
const FONT_SIZE = 10;
const BASE_LINE_COLOR = '#dadada';
const BASE_BG_COLOR = '#F7FAFC';

function $(expr, con) {
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
}

function createSVG(tag, o) {
export function createSVG(tag, o) {
var element = document.createElementNS("http://www.w3.org/2000/svg", tag);

for (var i in o) {
@@ -119,7 +121,7 @@ export function makeHeatSquare(className, x, y, size, fill='none', data={}) {
y: y,
width: size,
height: size,
fill: fill
fill: 1
};

Object.keys(data).map(key => {
@@ -140,7 +142,7 @@ export function makeText(className, x, y, content) {
});
}

export function makeVertLine(x, label, y1, y2, options={}) {
function makeVertLine(x, label, y1, y2, options={}) {
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
let l = createSVG('line', {
className: 'line-vertical ' + options.className,
@@ -172,7 +174,7 @@ export function makeVertLine(x, label, y1, y2, options={}) {
return line;
}

export function makeHoriLine(y, label, x1, x2, options={}) {
function makeHoriLine(y, label, x1, x2, options={}) {
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
if(!options.lineType) options.lineType = '';
let className = 'line-horizontal ' + options.className +
@@ -182,8 +184,8 @@ export function makeHoriLine(y, label, x1, x2, options={}) {
className: className,
x1: x1,
x2: x2,
y1: 0,
y2: 0,
y1: y,
y2: y,
styles: {
stroke: options.stroke
}
@@ -191,7 +193,7 @@ export function makeHoriLine(y, label, x1, x2, options={}) {

let text = createSVG('text', {
x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN,
y: 0,
y: y,
dy: (FONT_SIZE / 2 - 2) + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': x1 < x2 ? 'end' : 'start',
@@ -199,7 +201,6 @@ export function makeHoriLine(y, label, x1, x2, options={}) {
});

let line = createSVG('g', {
transform: `translate(0, ${ y })`,
'stroke-opacity': 1
});

@@ -231,46 +232,11 @@ export class AxisChartRenderer {
this.zeroLine = zeroLine;
}

bar(x, yTop, args, color, index, datasetIndex, noOfDatasets) {

let totalWidth = this.unitWidth - args.spaceWidth;
let startX = x - totalWidth/2;

// temp commented
// let width = totalWidth / noOfDatasets;
// let currentX = startX + width * datasetIndex;

// temp
let width = totalWidth;
let currentX = startX;

let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);

return createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: currentX,
y: y,
width: width,
height: height
});
}

dot(x, y, args, color, index) {
return createSVG('circle', {
style: `fill: ${color}`,
'data-point-index': index,
cx: x,
cy: y,
r: args.radius
});
}

xLine(x, label, options={}) {
if(!options.pos) options.pos = 'bottom';
if(!options.offset) options.offset = 0;
if(!options.mode) options.mode = this.xAxisMode;
console.log(this.xAxisMode);
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
if(!options.className) options.className = '';

@@ -296,7 +262,8 @@ export class AxisChartRenderer {

return makeVertLine(x, label, y1, y2, {
stroke: options.stroke,
className: options.className
className: options.className,
lineType: options.lineType
});
}

@@ -322,16 +289,102 @@ export class AxisChartRenderer {

return makeHoriLine(y, label, x1, x2, {
stroke: options.stroke,
className: options.className
className: options.className,
lineType: options.lineType
});
}


xMarker() {}
yMarker() {}
yMarker(y, label, options={}) {
let labelSvg = createSVG('text', {
className: 'chart-label',
x: this.totalWidth - getStringWidth(label, 5) - LABEL_MARGIN,
y: y - FONT_SIZE - 2,
dy: (FONT_SIZE / 2) + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': 'start',
innerHTML: label+""
});

let line = makeHoriLine(y, '', 0, this.totalWidth, {
stroke: options.stroke || BASE_LINE_COLOR,
className: options.className || '',
lineType: options.lineType
});

line.appendChild(labelSvg);

xRegion() {}
yRegion() {}
return line;
}

xRegion() {
return createSVG('rect', {
className: `bar mini`, // remove class
style: `fill: rgba(228, 234, 239, 0.49)`,
// 'data-point-index': index,
x: 0,
y: y2,
width: this.totalWidth,
height: y1 - y2
});

return region;
}

yRegion(y1, y2, label) {
// return a group

let rect = createSVG('rect', {
className: `bar mini`, // remove class
style: `fill: rgba(228, 234, 239, 0.49)`,
// 'data-point-index': index,
x: 0,
y: y2,
width: this.totalWidth,
height: y1 - y2
});

let upperBorder = createSVG('line', {
className: 'line-horizontal',
x1: 0,
x2: this.totalWidth,
y1: y2,
y2: y2,
styles: {
stroke: BASE_LINE_COLOR
}
});
let lowerBorder = createSVG('line', {
className: 'line-horizontal',
x1: 0,
x2: this.totalWidth,
y1: y1,
y2: y1,
styles: {
stroke: BASE_LINE_COLOR
}
});

let labelSvg = createSVG('text', {
className: 'chart-label',
x: this.totalWidth - getStringWidth(label, 4.5) - LABEL_MARGIN,
y: y2 - FONT_SIZE - 2,
dy: (FONT_SIZE / 2) + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': 'start',
innerHTML: label+""
});

let region = createSVG('g', {});

region.appendChild(rect);
region.appendChild(upperBorder);
region.appendChild(lowerBorder);
region.appendChild(labelSvg);

return region;
}

animatebar(bar, x, yTop, index, noOfDatasets) {
let start = x - this.avgUnitWidth/4;


Loading…
Откажи
Сачувај