-
Frappé Charts
+
Frappe Charts
GitHub-inspired simple and modern charts for the web
with zero dependencies.
@@ -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
...
diff --git a/src/js/chart.js b/src/js/chart.js
index 5d247f4..eabea1f 100644
--- a/src/js/chart.js
+++ b/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);
diff --git a/src/js/charts/AxisChart.js b/src/js/charts/AxisChart.js
index 12c93c1..57157b8 100644
--- a/src/js/charts/AxisChart.js
+++ b/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;
+ 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;
+ }
- 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;
+ getDataUnitComponent(d, index, unitRenderer) {
+ return new ChartComponent({
+ layerClass: 'dataset-units dataset-' + index,
+ make: () => {
+ let d = this.state.datasets[index];
- // have been updated in axis render;
- let newX = this.state.xAxisPositions;
- let newY = this.state.datasets[index].positions;
+ 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;
+ let lastUnit = svgUnits[svgUnits.length - 1];
+ let parentNode = lastUnit.parentNode;
- if(this.oldState.xExtra > 0) {
- for(var i = 0; i 0) {
+ for(var i = 0; 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
- ));
- });
}
+
+ 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
+ ));
+ });
+ }
+ });
+ }
+
+ 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() {
+ 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: () => {}
});
});
}
- getPathComponents() {
- return [];
+ // 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: () => {}
+ });
+ });
}
- getYMarkerLines() {
- return [];
- }
-
- 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
+
diff --git a/src/js/charts/BarChart.js b/src/js/charts/BarChart.js
deleted file mode 100644
index 04a7783..0000000
--- a/src/js/charts/BarChart.js
+++ /dev/null
@@ -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);
- // }
-
-}
diff --git a/src/js/charts/LineChart.js b/src/js/charts/LineChart.js
index d793bb0..83e7a23 100644
--- a/src/js/charts/LineChart.js
+++ b/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;
- }
}
diff --git a/src/js/charts/MultiAxisChart.js b/src/js/charts/MultiAxisChart.js
index 361b68e..8e15925 100644
--- a/src/js/charts/MultiAxisChart.js
+++ b/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,
diff --git a/src/js/objects/AxisChartControllers.js b/src/js/objects/AxisChartControllers.js
new file mode 100644
index 0000000..885be17
--- /dev/null
+++ b/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);
+// }
+// }
+
diff --git a/src/js/utils/draw-utils.js b/src/js/utils/draw-utils.js
index f36575d..06b44c7 100644
--- a/src/js/utils/draw-utils.js
+++ b/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) {
diff --git a/src/js/utils/draw.js b/src/js/utils/draw.js
index 4383652..0468d1d 100644
--- a/src/js/utils/draw.js
+++ b/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+""
+ });
- xRegion() {}
- yRegion() {}
+ let line = makeHoriLine(y, '', 0, this.totalWidth, {
+ stroke: options.stroke || BASE_LINE_COLOR,
+ className: options.className || '',
+ lineType: options.lineType
+ });
+
+ line.appendChild(labelSvg);
+
+ 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;