[major] regions, markers, tooltips, unit control

This commit is contained in:
Prateeksha Singh 2018-02-16 15:32:55 +05:30
parent 5e705db263
commit 55c8b6861f
15 changed files with 1293 additions and 590 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

@ -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<this.oldState.xExtra; i++) {
let unit = lastUnit.cloneNode(true);
parentNode.appendChild(unit);
svgUnits.push(unit);
}
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(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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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