@@ -1,20 +1,20 @@ | |||||
function $$1(expr, con) { | |||||
function $(expr, con) { | |||||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; | return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; | ||||
} | } | ||||
$$1.create = (tag, o) => { | |||||
$.create = (tag, o) => { | |||||
var element = document.createElement(tag); | var element = document.createElement(tag); | ||||
for (var i in o) { | for (var i in o) { | ||||
var val = o[i]; | var val = o[i]; | ||||
if (i === "inside") { | if (i === "inside") { | ||||
$$1(val).appendChild(element); | |||||
$(val).appendChild(element); | |||||
} | } | ||||
else if (i === "around") { | else if (i === "around") { | ||||
var ref = $$1(val); | |||||
var ref = $(val); | |||||
ref.parentNode.insertBefore(element, ref); | ref.parentNode.insertBefore(element, ref); | ||||
element.appendChild(ref); | element.appendChild(ref); | ||||
@@ -66,6 +66,22 @@ function getElementContentWidth(element) { | |||||
return element.clientWidth - padding; | return element.clientWidth - padding; | ||||
} | } | ||||
function fire(target, type, properties) { | |||||
var evt = document.createEvent("HTMLEvents"); | |||||
evt.initEvent(type, true, true ); | |||||
for (var j in properties) { | |||||
evt[j] = properties[j]; | |||||
} | |||||
return target.dispatchEvent(evt); | |||||
} | |||||
class SvgTip { | class SvgTip { | ||||
constructor({ | constructor({ | ||||
parent = null, | parent = null, | ||||
@@ -98,7 +114,7 @@ class SvgTip { | |||||
} | } | ||||
make_tooltip() { | make_tooltip() { | ||||
this.container = $$1.create('div', { | |||||
this.container = $.create('div', { | |||||
inside: this.parent, | inside: this.parent, | ||||
className: 'graph-svg-tip comparison', | className: 'graph-svg-tip comparison', | ||||
innerHTML: `<span class="title"></span> | innerHTML: `<span class="title"></span> | ||||
@@ -128,7 +144,7 @@ class SvgTip { | |||||
this.list_values.map((set, i) => { | this.list_values.map((set, i) => { | ||||
const color = this.colors[i] || 'black'; | const color = this.colors[i] || 'black'; | ||||
let li = $$1.create('li', { | |||||
let li = $.create('li', { | |||||
styles: { | styles: { | ||||
'border-top': `3px solid ${color}` | 'border-top': `3px solid ${color}` | ||||
}, | }, | ||||
@@ -376,11 +392,39 @@ function animatePath(paths, newXList, newYList, zeroLine) { | |||||
return pathComponents; | return pathComponents; | ||||
} | } | ||||
const Y_AXIS_MARGIN = 60; | |||||
const DEFAULT_AXIS_CHART_TYPE = 'line'; | |||||
const AXIS_DATASET_CHART_TYPES = ['line', 'bar']; | |||||
const BAR_CHART_SPACE_RATIO = 0.5; | |||||
const MIN_BAR_PERCENT_HEIGHT = 0.01; | |||||
const LINE_CHART_DOT_SIZE = 4; | |||||
const DOT_OVERLAY_SIZE_INCR = 4; | |||||
/* | |||||
<filter id="glow" x="-10%" y="-10%" width="120%" height="120%"> | |||||
<feGaussianBlur stdDeviation="0.5 0.5" result="glow"></feGaussianBlur> | |||||
<feMerge> | |||||
<feMergeNode in="glow"></feMergeNode> | |||||
<feMergeNode in="glow"></feMergeNode> | |||||
<feMergeNode in="glow"></feMergeNode> | |||||
</feMerge> | |||||
</filter> | |||||
filter: url(#glow); | |||||
fill: #fff; | |||||
*/ | |||||
const AXIS_TICK_LENGTH = 6; | const AXIS_TICK_LENGTH = 6; | ||||
const LABEL_MARGIN = 4; | const LABEL_MARGIN = 4; | ||||
const FONT_SIZE = 10; | const FONT_SIZE = 10; | ||||
const BASE_LINE_COLOR = '#dadada'; | const BASE_LINE_COLOR = '#dadada'; | ||||
function $$2(expr, con) { | |||||
function $$1(expr, con) { | |||||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; | return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; | ||||
} | } | ||||
@@ -391,10 +435,10 @@ function createSVG(tag, o) { | |||||
var val = o[i]; | var val = o[i]; | ||||
if (i === "inside") { | if (i === "inside") { | ||||
$$2(val).appendChild(element); | |||||
$$1(val).appendChild(element); | |||||
} | } | ||||
else if (i === "around") { | else if (i === "around") { | ||||
var ref = $$2(val); | |||||
var ref = $$1(val); | |||||
ref.parentNode.insertBefore(element, ref); | ref.parentNode.insertBefore(element, ref); | ||||
element.appendChild(ref); | element.appendChild(ref); | ||||
@@ -710,7 +754,6 @@ function yRegion(y1, y2, width, label) { | |||||
function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) { | function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) { | ||||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); | let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); | ||||
// console.log(yTop, meta.zeroLine, y, offset); | |||||
y -= offset; | y -= offset; | ||||
let rect = createSVG('rect', { | let rect = createSVG('rect', { | ||||
@@ -810,6 +853,80 @@ function getPaths(xList, yList, color, options={}, meta={}) { | |||||
return paths; | return paths; | ||||
} | } | ||||
let makeOverlay = { | |||||
'bar': (unit) => { | |||||
let transformValue; | |||||
if(unit.nodeName !== 'rect') { | |||||
transformValue = unit.getAttribute('transform'); | |||||
unit = unit.childNodes[0]; | |||||
} | |||||
let overlay = unit.cloneNode(); | |||||
overlay.style.fill = '#000000'; | |||||
overlay.style.opacity = '0.4'; | |||||
if(transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | |||||
} | |||||
return overlay; | |||||
}, | |||||
'dot': (unit) => { | |||||
let transformValue; | |||||
if(unit.nodeName !== 'circle') { | |||||
transformValue = unit.getAttribute('transform'); | |||||
unit = unit.childNodes[0]; | |||||
} | |||||
let overlay = unit.cloneNode(); | |||||
let radius = unit.getAttribute('r'); | |||||
overlay.setAttribute('r', radius + DOT_OVERLAY_SIZE_INCR); | |||||
overlay.style.fill = '#000000'; | |||||
overlay.style.opacity = '0.4'; | |||||
if(transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | |||||
} | |||||
return overlay; | |||||
} | |||||
}; | |||||
let updateOverlay = { | |||||
'bar': (unit, overlay) => { | |||||
let transformValue; | |||||
if(unit.nodeName !== 'rect') { | |||||
transformValue = unit.getAttribute('transform'); | |||||
unit = unit.childNodes[0]; | |||||
} | |||||
let attributes = ['x', 'y', 'width', 'height']; | |||||
Object.values(unit.attributes) | |||||
.filter(attr => attributes.includes(attr.name) && attr.specified) | |||||
.map(attr => { | |||||
overlay.setAttribute(attr.name, attr.nodeValue); | |||||
}); | |||||
if(transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | |||||
} | |||||
}, | |||||
'dot': (unit, overlay) => { | |||||
let transformValue; | |||||
if(unit.nodeName !== 'circle') { | |||||
transformValue = unit.getAttribute('transform'); | |||||
unit = unit.childNodes[0]; | |||||
} | |||||
let attributes = ['cx', 'cy']; | |||||
Object.values(unit.attributes) | |||||
.filter(attr => attributes.includes(attr.name) && attr.specified) | |||||
.map(attr => { | |||||
overlay.setAttribute(attr.name, attr.nodeValue); | |||||
}); | |||||
if(transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | |||||
} | |||||
} | |||||
}; | |||||
const PRESET_COLOR_MAP = { | const PRESET_COLOR_MAP = { | ||||
'light-blue': '#7cd6fd', | 'light-blue': '#7cd6fd', | ||||
'blue': '#5e64ff', | 'blue': '#5e64ff', | ||||
@@ -1046,30 +1163,28 @@ class BaseChart { | |||||
this.argHeight = height; | this.argHeight = height; | ||||
this.type = type; | this.type = type; | ||||
this.isNavigable = isNavigable; | |||||
if(this.isNavigable) { | |||||
this.currentIndex = 0; | |||||
} | |||||
this.realData = this.prepareData(data); | this.realData = this.prepareData(data); | ||||
this.data = this.prepareFirstData(this.realData); | this.data = this.prepareFirstData(this.realData); | ||||
this.colors = []; | this.colors = []; | ||||
this.config = {}; | |||||
this.config = { | |||||
showTooltip: 1, // calculate | |||||
showLegend: 1, | |||||
isNavigable: isNavigable, | |||||
animate: 1 | |||||
}; | |||||
this.state = {}; | this.state = {}; | ||||
this.options = {}; | this.options = {}; | ||||
if(this.config.isNavigable) { | |||||
this.state.currentIndex = 0; | |||||
this.overlays = []; | |||||
} | |||||
this.configure(arguments[0]); | this.configure(arguments[0]); | ||||
} | } | ||||
configure(args) { | configure(args) { | ||||
this.setColors(); | this.setColors(); | ||||
this.config = { | |||||
showTooltip: 1, // calculate | |||||
showLegend: 1, | |||||
isNavigable: 0, | |||||
animate: 1 | |||||
}; | |||||
this.setMargins(); | this.setMargins(); | ||||
// Bind window events | // Bind window events | ||||
@@ -1127,14 +1242,12 @@ class BaseChart { | |||||
this.draw(true); | this.draw(true); | ||||
} | } | ||||
initComponents() {} | |||||
setupComponents() { | setupComponents() { | ||||
this.components = new Map(); | this.components = new Map(); | ||||
} | } | ||||
makeContainer() { | makeContainer() { | ||||
this.container = $$1.create('div', { | |||||
this.container = $.create('div', { | |||||
className: 'chart-container', | className: 'chart-container', | ||||
innerHTML: `<h6 class="title">${this.title}</h6> | innerHTML: `<h6 class="title">${this.title}</h6> | ||||
<h6 class="sub-title uppercase">${this.subtitle}</h6> | <h6 class="sub-title uppercase">${this.subtitle}</h6> | ||||
@@ -1164,21 +1277,19 @@ class BaseChart { | |||||
this.calcWidth(); | this.calcWidth(); | ||||
this.calc(); | this.calc(); | ||||
this.makeChartArea(); | this.makeChartArea(); | ||||
this.initComponents(); | |||||
this.setupComponents(); | this.setupComponents(); | ||||
this.components.forEach(c => c.setup(this.drawArea)); // or c.build() | this.components.forEach(c => c.setup(this.drawArea)); // or c.build() | ||||
this.components.forEach(c => c.make()); // or c.build() | this.components.forEach(c => c.make()); // or c.build() | ||||
this.renderLegend(); | |||||
this.setupNavigation(init); | |||||
// TODO: remove timeout and decrease post animate time in chart component | // TODO: remove timeout and decrease post animate time in chart component | ||||
if(init) { | if(init) { | ||||
this.data = this.realData; | this.data = this.realData; | ||||
setTimeout(() => {this.update();}, 1000); | setTimeout(() => {this.update();}, 1000); | ||||
} | } | ||||
this.renderLegend(); | |||||
this.setupNavigation(init); | |||||
} | } | ||||
calcWidth() { | calcWidth() { | ||||
@@ -1205,19 +1316,37 @@ class BaseChart { | |||||
calc() {} // builds state | calc() {} // builds state | ||||
render(components=this.components, animate=true) { | render(components=this.components, animate=true) { | ||||
// Can decouple to this.refreshComponents() first to save animation timeout | |||||
if(this.config.isNavigable) { | |||||
// Remove all existing overlays | |||||
this.overlays.map(o => o.parentNode.removeChild(o)); | |||||
// ref.parentNode.insertBefore(element, ref); | |||||
} | |||||
let elementsToAnimate = []; | let elementsToAnimate = []; | ||||
// Can decouple to this.refreshComponents() first to save animation timeout | |||||
components.forEach(c => { | components.forEach(c => { | ||||
elementsToAnimate = elementsToAnimate.concat(c.update(animate)); | elementsToAnimate = elementsToAnimate.concat(c.update(animate)); | ||||
}); | }); | ||||
if(elementsToAnimate.length > 0) { | if(elementsToAnimate.length > 0) { | ||||
runSMILAnimation(this.chartWrapper, this.svg, elementsToAnimate); | runSMILAnimation(this.chartWrapper, this.svg, elementsToAnimate); | ||||
setTimeout(() => { | |||||
components.forEach(c => c.make()); | |||||
this.updateNav(); | |||||
}, 400); | |||||
} else { | |||||
this.updateNav(); | |||||
} | } | ||||
} | |||||
// TODO: rebind new units | |||||
// if(this.isNavigable) { | |||||
// this.bind_units(units_array); | |||||
// } | |||||
updateNav() { | |||||
if(this.config.isNavigable) { | |||||
// Make new overlays | |||||
if(!this.overlayGuides){ | |||||
this.makeOverlays(); | |||||
this.bindUnits(); | |||||
} else { | |||||
this.updateOverlays(); | |||||
} | |||||
} | |||||
} | } | ||||
makeChartArea() { | makeChartArea() { | ||||
@@ -1249,36 +1378,31 @@ class BaseChart { | |||||
renderLegend() {} | renderLegend() {} | ||||
setupNavigation(init=false) { | setupNavigation(init=false) { | ||||
if(this.isNavigable) return; | |||||
this.makeOverlay(); | |||||
if(!this.config.isNavigable) return; | |||||
if(init) { | if(init) { | ||||
this.bindOverlay(); | this.bindOverlay(); | ||||
this.keyActions = { | |||||
'13': this.onEnterKey.bind(this), | |||||
'37': this.onLeftArrow.bind(this), | |||||
'38': this.onUpArrow.bind(this), | |||||
'39': this.onRightArrow.bind(this), | |||||
'40': this.onDownArrow.bind(this), | |||||
}; | |||||
document.addEventListener('keydown', (e) => { | document.addEventListener('keydown', (e) => { | ||||
if(isElementInViewport(this.chartWrapper)) { | if(isElementInViewport(this.chartWrapper)) { | ||||
e = e || window.event; | e = e || window.event; | ||||
if (e.keyCode == '37') { | |||||
this.onLeftArrow(); | |||||
} else if (e.keyCode == '39') { | |||||
this.onRightArrow(); | |||||
} else if (e.keyCode == '38') { | |||||
this.onUpArrow(); | |||||
} else if (e.keyCode == '40') { | |||||
this.onDownArrow(); | |||||
} else if (e.keyCode == '13') { | |||||
this.onEnterKey(); | |||||
} | |||||
this.keyActions[e.keyCode](); | |||||
} | } | ||||
}); | }); | ||||
} | } | ||||
} | } | ||||
makeOverlay() {} | |||||
makeOverlays() {} | |||||
bindOverlay() {} | bindOverlay() {} | ||||
bind_units() {} | |||||
bindUnits() {} | |||||
onLeftArrow() {} | onLeftArrow() {} | ||||
onRightArrow() {} | onRightArrow() {} | ||||
@@ -1286,13 +1410,6 @@ class BaseChart { | |||||
onDownArrow() {} | onDownArrow() {} | ||||
onEnterKey() {} | onEnterKey() {} | ||||
// ???????????? | |||||
// Update the data here, then do relevant updates | |||||
// and drawing in child classes by overriding | |||||
// The Child chart will only know what a particular update means | |||||
// and what components are affected, | |||||
// BaseChart shouldn't be doing the animating | |||||
getDataPoint(index = 0) {} | getDataPoint(index = 0) {} | ||||
setCurrentDataPoint(point) {} | setCurrentDataPoint(point) {} | ||||
@@ -1311,16 +1428,6 @@ class BaseChart { | |||||
} | } | ||||
} | } | ||||
const Y_AXIS_MARGIN = 60; | |||||
const DEFAULT_AXIS_CHART_TYPE = 'line'; | |||||
const AXIS_DATASET_CHART_TYPES = ['line', 'bar']; | |||||
const BAR_CHART_SPACE_RATIO = 0.5; | |||||
const MIN_BAR_PERCENT_HEIGHT = 0.01; | |||||
const LINE_CHART_DOT_SIZE = 4; | |||||
function dataPrep(data, type) { | function dataPrep(data, type) { | ||||
data.labels = data.labels || []; | data.labels = data.labels || []; | ||||
@@ -1465,10 +1572,6 @@ class ChartComponent$1 { | |||||
if(animate) { | if(animate) { | ||||
animateElements = this.animateElements(this.data); | animateElements = this.animateElements(this.data); | ||||
} | } | ||||
// TODO: Can we remove this? | |||||
setTimeout(() => { | |||||
this.make(); | |||||
}, 1400); | |||||
return animateElements; | return animateElements; | ||||
} | } | ||||
} | } | ||||
@@ -1608,9 +1711,10 @@ let componentConfigs = { | |||||
}, | }, | ||||
barGraph: { | barGraph: { | ||||
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; }, | |||||
layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; }, | |||||
makeElements(data) { | makeElements(data) { | ||||
let c = this.constants; | let c = this.constants; | ||||
this.unitType = 'bar'; | |||||
return data.yPositions.map((y, j) => { | return data.yPositions.map((y, j) => { | ||||
return datasetBar( | return datasetBar( | ||||
data.xPositions[j], | data.xPositions[j], | ||||
@@ -1676,9 +1780,11 @@ let componentConfigs = { | |||||
}, | }, | ||||
lineGraph: { | lineGraph: { | ||||
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; }, | |||||
layerClass: function() { return 'dataset-units dataset-line dataset-' + this.constants.index; }, | |||||
makeElements(data) { | makeElements(data) { | ||||
let c = this.constants; | let c = this.constants; | ||||
this.unitType = 'dot'; | |||||
this.paths = getPaths( | this.paths = getPaths( | ||||
data.xPositions, | data.xPositions, | ||||
data.yPositions, | data.yPositions, | ||||
@@ -1709,6 +1815,7 @@ let componentConfigs = { | |||||
} | } | ||||
return Object.values(this.paths).concat(this.dots); | return Object.values(this.paths).concat(this.dots); | ||||
// return this.dots; | |||||
}, | }, | ||||
animateElements(newData) { | animateElements(newData) { | ||||
let newXPos = newData.xPositions; | let newXPos = newData.xPositions; | ||||
@@ -1992,9 +2099,6 @@ class AxisChart extends BaseChart { | |||||
configure(args) {3; | configure(args) {3; | ||||
super.configure(); | super.configure(); | ||||
// TODO: set in options and use | |||||
this.config.xAxisMode = args.xAxisMode; | this.config.xAxisMode = args.xAxisMode; | ||||
this.config.yAxisMode = args.yAxisMode; | this.config.yAxisMode = args.yAxisMode; | ||||
} | } | ||||
@@ -2051,12 +2155,13 @@ class AxisChart extends BaseChart { | |||||
zeroLine: zeroLine, | zeroLine: zeroLine, | ||||
}; | }; | ||||
this.calcYUnits(); | |||||
// Dependent if above changes | |||||
this.calcDatasetPoints(); | |||||
this.calcYExtremes(); | this.calcYExtremes(); | ||||
this.calcYRegions(); | this.calcYRegions(); | ||||
} | } | ||||
calcYUnits() { | |||||
calcDatasetPoints() { | |||||
let s = this.state; | let s = this.state; | ||||
let scaleAll = values => values.map(val => scale(val, s.yAxis)); | let scaleAll = values => values.map(val => scale(val, s.yAxis)); | ||||
@@ -2079,7 +2184,7 @@ class AxisChart extends BaseChart { | |||||
calcYExtremes() { | calcYExtremes() { | ||||
let s = this.state; | let s = this.state; | ||||
if(this.barOptions && this.barOptions.stacked) { | |||||
if(this.barOptions.stacked) { | |||||
s.yExtremes = s.datasets[s.datasets.length - 1].cumulativeYPos; | s.yExtremes = s.datasets[s.datasets.length - 1].cumulativeYPos; | ||||
return; | return; | ||||
} | } | ||||
@@ -2115,7 +2220,7 @@ class AxisChart extends BaseChart { | |||||
// TODO: yMarkers, regions, sums, every Y value ever | // TODO: yMarkers, regions, sums, every Y value ever | ||||
let key = 'values'; | let key = 'values'; | ||||
if(this.barOptions && this.barOptions.stacked) { | |||||
if(this.barOptions.stacked) { | |||||
key = 'cumulativeYs'; | key = 'cumulativeYs'; | ||||
let cumulative = new Array(this.state.datasetLength).fill(0); | let cumulative = new Array(this.state.datasetLength).fill(0); | ||||
this.data.datasets.map((d, i) => { | this.data.datasets.map((d, i) => { | ||||
@@ -2127,8 +2232,8 @@ class AxisChart extends BaseChart { | |||||
return [].concat(...this.data.datasets.map(d => d[key])); | return [].concat(...this.data.datasets.map(d => d[key])); | ||||
} | } | ||||
initComponents() { | |||||
this.componentConfigs = [ | |||||
setupComponents() { | |||||
let componentConfigs = [ | |||||
[ | [ | ||||
'yAxis', | 'yAxis', | ||||
{ | { | ||||
@@ -2156,7 +2261,7 @@ class AxisChart extends BaseChart { | |||||
], | ], | ||||
]; | ]; | ||||
this.componentConfigs.map(args => { | |||||
componentConfigs.map(args => { | |||||
args.push( | args.push( | ||||
function() { | function() { | ||||
return this.state[args[0]]; | return this.state[args[0]]; | ||||
@@ -2176,6 +2281,7 @@ class AxisChart extends BaseChart { | |||||
{ | { | ||||
index: index, | index: index, | ||||
color: this.colors[index], | color: this.colors[index], | ||||
stacked: this.barOptions.stacked, | |||||
// same for all datasets | // same for all datasets | ||||
valuesOverPoints: this.valuesOverPoints, | valuesOverPoints: this.valuesOverPoints, | ||||
@@ -2189,7 +2295,10 @@ class AxisChart extends BaseChart { | |||||
let barsWidth = s.unitWidth * (1 - spaceRatio); | let barsWidth = s.unitWidth * (1 - spaceRatio); | ||||
let barWidth = barsWidth/(this.barOptions.stacked ? 1 : barDatasets.length); | let barWidth = barsWidth/(this.barOptions.stacked ? 1 : barDatasets.length); | ||||
let xPositions = s.xAxis.positions.map(x => x - barsWidth/2 + barWidth * index); | |||||
let xPositions = s.xAxis.positions.map(x => x - barsWidth/2); | |||||
if(!this.barOptions.stacked) { | |||||
xPositions = xPositions.map(p => p + barWidth * index); | |||||
} | |||||
return { | return { | ||||
xPositions: xPositions, | xPositions: xPositions, | ||||
@@ -2257,15 +2366,19 @@ class AxisChart extends BaseChart { | |||||
); | ); | ||||
}); | }); | ||||
this.componentConfigs = this.componentConfigs.concat(barsConfigs, lineConfigs, markerConfigs); | |||||
} | |||||
componentConfigs = componentConfigs.concat(barsConfigs, lineConfigs, markerConfigs); | |||||
setupComponents() { | |||||
let optionals = ['yMarkers', 'yRegions']; | let optionals = ['yMarkers', 'yRegions']; | ||||
this.components = new Map(this.componentConfigs | |||||
.filter(args => !optionals.includes(args[0]) || this.state[args[0]] || args[0] === 'barGraph') | |||||
this.dataUnitComponents = []; | |||||
this.components = new Map(componentConfigs | |||||
.filter(args => !optionals.includes(args[0]) || this.state[args[0]]) | |||||
.map(args => { | .map(args => { | ||||
return [args[0], getComponent(...args)]; | |||||
let component = getComponent(...args); | |||||
if(args[0].includes('lineGraph') || args[0].includes('barGraph')) { | |||||
this.dataUnitComponents.push(component); | |||||
} | |||||
return [args[0], component]; | |||||
})); | })); | ||||
} | } | ||||
@@ -2317,27 +2430,91 @@ class AxisChart extends BaseChart { | |||||
} | } | ||||
} | } | ||||
getDataPoint(index=this.current_index) { | |||||
makeOverlays() { | |||||
// Just make one out of the first element | |||||
// let index = this.xAxisLabels.length - 1; | |||||
// let unit = this.y[0].svg_units[index]; | |||||
// this.setCurrentDataPoint(index); | |||||
// if(this.overlay) { | |||||
// this.overlay.parentNode.removeChild(this.overlay); | |||||
// } | |||||
// this.overlay = unit.cloneNode(); | |||||
// this.overlay.style.fill = '#000000'; | |||||
// this.overlay.style.opacity = '0.4'; | |||||
// this.drawArea.appendChild(this.overlay); | |||||
this.overlayGuides = this.dataUnitComponents.map(c => { | |||||
return { | |||||
type: c.unitType, | |||||
overlay: undefined, | |||||
units: c.store, | |||||
} | |||||
}); | |||||
this.state.currentIndex = 0; | |||||
// Render overlays | |||||
this.overlayGuides.map(d => { | |||||
let currentUnit = d.units[this.state.currentIndex]; | |||||
d.overlay = makeOverlay[d.type](currentUnit); | |||||
this.drawArea.appendChild(d.overlay); | |||||
}); | |||||
} | |||||
bindOverlay() { | |||||
// on event, update overlay | |||||
this.parent.addEventListener('data-select', (e) => { | |||||
this.updateOverlay(e.svg_unit); | |||||
}); | |||||
} | |||||
bindUnits(units_array) { | |||||
// units_array.map(unit => { | |||||
// unit.addEventListener('click', () => { | |||||
// let index = unit.getAttribute('data-point-index'); | |||||
// this.setCurrentDataPoint(index); | |||||
// }); | |||||
// }); | |||||
} | |||||
updateOverlay() { | |||||
this.overlayGuides.map(d => { | |||||
let currentUnit = d.units[this.state.currentIndex]; | |||||
updateOverlay[d.type](currentUnit, d.overlay); | |||||
}); | |||||
} | |||||
onLeftArrow() { | |||||
this.setCurrentDataPoint(this.state.currentIndex - 1); | |||||
} | |||||
onRightArrow() { | |||||
this.setCurrentDataPoint(this.state.currentIndex + 1); | |||||
} | |||||
getDataPoint(index=this.state.currentIndex) { | |||||
// check for length | // check for length | ||||
let data_point = { | let data_point = { | ||||
index: index | 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.xAxis.labels[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.xAxis.labels[index]; | |||||
return data_point; | return data_point; | ||||
} | } | ||||
setCurrentDataPoint(index) { | setCurrentDataPoint(index) { | ||||
let s = this.state; | |||||
index = parseInt(index); | index = parseInt(index); | ||||
if(index < 0) index = 0; | if(index < 0) index = 0; | ||||
if(index >= this.xAxis.labels.length) index = this.xAxis.labels.length - 1; | |||||
if(index === this.current_index) return; | |||||
this.current_index = index; | |||||
$.fire(this.parent, "data-select", this.getDataPoint()); | |||||
if(index >= s.xAxis.labels.length) index = s.xAxis.labels.length - 1; | |||||
if(index === s.currentIndex) return; | |||||
s.currentIndex = index; | |||||
fire(this.parent, "data-select", this.getDataPoint()); | |||||
} | } | ||||
// API | // API | ||||
@@ -2362,12 +2539,21 @@ class AxisChart extends BaseChart { | |||||
this.update(this.data); | this.update(this.data); | ||||
} | } | ||||
// updateData() { | |||||
// // animate if same no. of datasets, | |||||
// // else return new chart | |||||
// getDataPoint(index = 0) {} | |||||
// setCurrentDataPoint(point) {} | |||||
// // | |||||
// } | |||||
updateDataset(datasetValues, index=0) { | |||||
this.data.datasets[index].values = datasetValues; | |||||
this.update(this.data); | |||||
} | |||||
// addDataset(dataset, index) {} | |||||
// removeDataset(index = 0) {} | |||||
// updateDatasets(datasets) {} | |||||
// updateDataPoint(dataPoint, index = 0) {} | |||||
// addDataPoint(dataPoint, index = 0) {} | |||||
// removeDataPoint(index = 0) {} | |||||
} | } | ||||
@@ -2561,19 +2747,19 @@ class PercentageChart extends BaseChart { | |||||
this.statsWrapper.style.marginBottom = '30px'; | this.statsWrapper.style.marginBottom = '30px'; | ||||
this.statsWrapper.style.paddingTop = '0px'; | this.statsWrapper.style.paddingTop = '0px'; | ||||
this.chartDiv = $$1.create('div', { | |||||
this.chartDiv = $.create('div', { | |||||
className: 'div', | className: 'div', | ||||
inside: this.chartWrapper | inside: this.chartWrapper | ||||
}); | }); | ||||
this.chart = $$1.create('div', { | |||||
this.chart = $.create('div', { | |||||
className: 'progress-chart', | className: 'progress-chart', | ||||
inside: this.chartDiv | inside: this.chartDiv | ||||
}); | }); | ||||
} | } | ||||
setupLayers() { | setupLayers() { | ||||
this.percentageBar = $$1.create('div', { | |||||
this.percentageBar = $.create('div', { | |||||
className: 'progress', | className: 'progress', | ||||
inside: this.chart | inside: this.chart | ||||
}); | }); | ||||
@@ -2618,7 +2804,7 @@ class PercentageChart extends BaseChart { | |||||
this.grand_total = this.slice_totals.reduce((a, b) => a + b, 0); | this.grand_total = this.slice_totals.reduce((a, b) => a + b, 0); | ||||
this.slices = []; | this.slices = []; | ||||
this.slice_totals.map((total, i) => { | this.slice_totals.map((total, i) => { | ||||
let slice = $$1.create('div', { | |||||
let slice = $.create('div', { | |||||
className: `progress-bar`, | className: `progress-bar`, | ||||
inside: this.percentageBar, | inside: this.percentageBar, | ||||
styles: { | styles: { | ||||
@@ -2654,7 +2840,7 @@ class PercentageChart extends BaseChart { | |||||
? this.formatted_labels : this.labels; | ? this.formatted_labels : this.labels; | ||||
this.legend_totals.map((d, i) => { | this.legend_totals.map((d, i) => { | ||||
if(d) { | if(d) { | ||||
let stats = $$1.create('div', { | |||||
let stats = $.create('div', { | |||||
className: 'stats', | className: 'stats', | ||||
inside: this.statsWrapper | inside: this.statsWrapper | ||||
}); | }); | ||||
@@ -2842,7 +3028,7 @@ class PieChart extends BaseChart { | |||||
const color = this.colors[i]; | const color = this.colors[i]; | ||||
if(d) { | if(d) { | ||||
let stats = $$1.create('div', { | |||||
let stats = $.create('div', { | |||||
className: 'stats', | className: 'stats', | ||||
inside: this.statsWrapper | inside: this.statsWrapper | ||||
}); | }); | ||||
@@ -42,22 +42,22 @@ let line_composite_data = { | |||||
}] | }] | ||||
}; | }; | ||||
let more_line_data = { | |||||
0: {values: [4, 0, 3, 1, 1, 2, 1, 2, 1, 0, 1, 1]}, | |||||
// 0: {values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}, | |||||
1: {values: [2, 3, 3, 2, 1, 4, 0, 1, 2, 7, 11, 4]}, | |||||
2: {values: [7, 7, 2, 4, 0, 1, 5, 3, 1, 2, 0, 1]}, | |||||
3: {values: [0, 2, 6, 2, 2, 1, 2, 3, 6, 3, 7, 10]}, | |||||
4: {values: [9, 10, 8, 10, 6, 5, 8, 8, 24, 15, 10, 13]}, | |||||
5: {values: [9, 13, 16, 9, 4, 5, 7, 10, 14, 22, 23, 24]}, | |||||
6: {values: [20, 22, 28, 19, 28, 19, 14, 19, 51, 37, 29, 38]}, | |||||
7: {values: [29, 20, 22, 16, 16, 19, 24, 26, 57, 31, 46, 27]}, | |||||
8: {values: [36, 24, 38, 27, 15, 22, 24, 38, 32, 57, 139, 26]}, | |||||
9: {values: [37, 36, 32, 33, 12, 34, 52, 45, 58, 57, 64, 35]}, | |||||
10: {values: [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 0, 0]} | |||||
// 10: {values: [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 40, 40]} | |||||
// 10: {values: [-36, -46, -45, -32, -27, -31, -30, -36, -39, -49, -40, -40]} | |||||
}; | |||||
let more_line_data = [ | |||||
[4, 0, 3, 1, 1, 2, 1, 2, 1, 0, 1, 1], | |||||
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |||||
[2, 3, 3, 2, 1, 4, 0, 1, 2, 7, 11, 4], | |||||
[7, 7, 2, 4, 0, 1, 5, 3, 1, 2, 0, 1], | |||||
[0, 2, 6, 2, 2, 1, 2, 3, 6, 3, 7, 10], | |||||
[9, 10, 8, 10, 6, 5, 8, 8, 24, 15, 10, 13], | |||||
[9, 13, 16, 9, 4, 5, 7, 10, 14, 22, 23, 24], | |||||
[20, 22, 28, 19, 28, 19, 14, 19, 51, 37, 29, 38], | |||||
[29, 20, 22, 16, 16, 19, 24, 26, 57, 31, 46, 27], | |||||
[36, 24, 38, 27, 15, 22, 24, 38, 32, 57, 139, 26], | |||||
[37, 36, 32, 33, 12, 34, 52, 45, 58, 57, 64, 35], | |||||
[36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 0, 0], | |||||
// [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 40, 40] | |||||
// [-36, -46, -45, -32, -27, -31, -30, -36, -39, -49, -40, -40] | |||||
]; | |||||
let c1 = document.querySelector("#chart-composite-1"); | let c1 = document.querySelector("#chart-composite-1"); | ||||
let c2 = document.querySelector("#chart-composite-2"); | let c2 = document.querySelector("#chart-composite-2"); | ||||
@@ -89,39 +89,8 @@ let line_composite_chart = new Chart ({ | |||||
valuesOverPoints: 1, | valuesOverPoints: 1, | ||||
}); | }); | ||||
// Assuming this data structure for all, what would the most used APIs? | |||||
// chart.updateDataset([], index) | |||||
// chart.updateDatasets([[], [], []]) | |||||
// chart.addDataset([], index) | |||||
// chart.removeDatasets(index) | |||||
// chart.addDataPoint({'asd': [20, 10, 30]}) | |||||
// chart.removeDataPoint(index) | |||||
// chart.updatePoint('asd': [20, 10, 30]}, index) | |||||
// chart.update(data) | |||||
// let bar_update = []; | |||||
// setInterval(() => { | |||||
// line_composite_data.datasets = [more_line_data[5]]; | |||||
// line_composite_chart.update(line_composite_data); | |||||
// bar_composite_data.datasets = [more_line_data[5]]; | |||||
// bar_composite_chart.update(bar_composite_data); | |||||
// }, 2000); | |||||
bar_composite_chart.parent.addEventListener('data-select', (e) => { | bar_composite_chart.parent.addEventListener('data-select', (e) => { | ||||
line_composite_chart.updateData([more_line_data[e.index]]); | |||||
line_composite_chart.updateDataset(more_line_data[e.index]); | |||||
}); | }); | ||||
@@ -177,7 +146,7 @@ let type_data = { | |||||
{ | { | ||||
name: "Yet Another", | name: "Yet Another", | ||||
values: [15, 20, -3, -15, 58, 12, -17, 37], | values: [15, 20, -3, -15, 58, 12, -17, 37], | ||||
chartType: 'bar' | |||||
chartType: 'line' | |||||
} | } | ||||
// temp : Stacked | // temp : Stacked | ||||
@@ -5,6 +5,7 @@ import { getComponent } from '../objects/ChartComponents'; | |||||
import { getOffset, fire } from '../utils/dom'; | import { getOffset, fire } from '../utils/dom'; | ||||
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale } from '../utils/intervals'; | import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale } from '../utils/intervals'; | ||||
import { floatTwo } from '../utils/helpers'; | import { floatTwo } from '../utils/helpers'; | ||||
import { makeOverlay, updateOverlay } from '../utils/draw'; | |||||
import { MIN_BAR_PERCENT_HEIGHT, DEFAULT_AXIS_CHART_TYPE, BAR_CHART_SPACE_RATIO, LINE_CHART_DOT_SIZE } from '../utils/constants'; | import { MIN_BAR_PERCENT_HEIGHT, DEFAULT_AXIS_CHART_TYPE, BAR_CHART_SPACE_RATIO, LINE_CHART_DOT_SIZE } from '../utils/constants'; | ||||
export default class AxisChart extends BaseChart { | export default class AxisChart extends BaseChart { | ||||
@@ -28,9 +29,6 @@ export default class AxisChart extends BaseChart { | |||||
configure(args) {3 | configure(args) {3 | ||||
super.configure(); | super.configure(); | ||||
// TODO: set in options and use | |||||
this.config.xAxisMode = args.xAxisMode; | this.config.xAxisMode = args.xAxisMode; | ||||
this.config.yAxisMode = args.yAxisMode; | this.config.yAxisMode = args.yAxisMode; | ||||
} | } | ||||
@@ -87,12 +85,13 @@ export default class AxisChart extends BaseChart { | |||||
zeroLine: zeroLine, | zeroLine: zeroLine, | ||||
} | } | ||||
this.calcYUnits(); | |||||
// Dependent if above changes | |||||
this.calcDatasetPoints(); | |||||
this.calcYExtremes(); | this.calcYExtremes(); | ||||
this.calcYRegions(); | this.calcYRegions(); | ||||
} | } | ||||
calcYUnits() { | |||||
calcDatasetPoints() { | |||||
let s = this.state; | let s = this.state; | ||||
let scaleAll = values => values.map(val => scale(val, s.yAxis)); | let scaleAll = values => values.map(val => scale(val, s.yAxis)); | ||||
@@ -115,7 +114,7 @@ export default class AxisChart extends BaseChart { | |||||
calcYExtremes() { | calcYExtremes() { | ||||
let s = this.state; | let s = this.state; | ||||
if(this.barOptions && this.barOptions.stacked) { | |||||
if(this.barOptions.stacked) { | |||||
s.yExtremes = s.datasets[s.datasets.length - 1].cumulativeYPos; | s.yExtremes = s.datasets[s.datasets.length - 1].cumulativeYPos; | ||||
return; | return; | ||||
} | } | ||||
@@ -151,7 +150,7 @@ export default class AxisChart extends BaseChart { | |||||
// TODO: yMarkers, regions, sums, every Y value ever | // TODO: yMarkers, regions, sums, every Y value ever | ||||
let key = 'values'; | let key = 'values'; | ||||
if(this.barOptions && this.barOptions.stacked) { | |||||
if(this.barOptions.stacked) { | |||||
key = 'cumulativeYs'; | key = 'cumulativeYs'; | ||||
let cumulative = new Array(this.state.datasetLength).fill(0); | let cumulative = new Array(this.state.datasetLength).fill(0); | ||||
this.data.datasets.map((d, i) => { | this.data.datasets.map((d, i) => { | ||||
@@ -163,11 +162,9 @@ export default class AxisChart extends BaseChart { | |||||
return [].concat(...this.data.datasets.map(d => d[key])); | return [].concat(...this.data.datasets.map(d => d[key])); | ||||
} | } | ||||
initComponents() { | |||||
setupComponents() { | |||||
let s = this.state; | let s = this.state; | ||||
// console.log('this.state', Object.assign({}, this.state)); | |||||
// console.log('this.state', this.state); | |||||
this.componentConfigs = [ | |||||
let componentConfigs = [ | |||||
[ | [ | ||||
'yAxis', | 'yAxis', | ||||
{ | { | ||||
@@ -195,7 +192,7 @@ export default class AxisChart extends BaseChart { | |||||
], | ], | ||||
]; | ]; | ||||
this.componentConfigs.map(args => { | |||||
componentConfigs.map(args => { | |||||
args.push( | args.push( | ||||
function() { | function() { | ||||
return this.state[args[0]]; | return this.state[args[0]]; | ||||
@@ -215,6 +212,7 @@ export default class AxisChart extends BaseChart { | |||||
{ | { | ||||
index: index, | index: index, | ||||
color: this.colors[index], | color: this.colors[index], | ||||
stacked: this.barOptions.stacked, | |||||
// same for all datasets | // same for all datasets | ||||
valuesOverPoints: this.valuesOverPoints, | valuesOverPoints: this.valuesOverPoints, | ||||
@@ -228,7 +226,10 @@ export default class AxisChart extends BaseChart { | |||||
let barsWidth = s.unitWidth * (1 - spaceRatio); | let barsWidth = s.unitWidth * (1 - spaceRatio); | ||||
let barWidth = barsWidth/(this.barOptions.stacked ? 1 : barDatasets.length); | let barWidth = barsWidth/(this.barOptions.stacked ? 1 : barDatasets.length); | ||||
let xPositions = s.xAxis.positions.map(x => x - barsWidth/2 + barWidth * index); | |||||
let xPositions = s.xAxis.positions.map(x => x - barsWidth/2); | |||||
if(!this.barOptions.stacked) { | |||||
xPositions = xPositions.map(p => p + barWidth * index); | |||||
} | |||||
return { | return { | ||||
xPositions: xPositions, | xPositions: xPositions, | ||||
@@ -296,15 +297,19 @@ export default class AxisChart extends BaseChart { | |||||
); | ); | ||||
}); | }); | ||||
this.componentConfigs = this.componentConfigs.concat(barsConfigs, lineConfigs, markerConfigs); | |||||
} | |||||
componentConfigs = componentConfigs.concat(barsConfigs, lineConfigs, markerConfigs); | |||||
setupComponents() { | |||||
let optionals = ['yMarkers', 'yRegions']; | let optionals = ['yMarkers', 'yRegions']; | ||||
this.components = new Map(this.componentConfigs | |||||
.filter(args => !optionals.includes(args[0]) || this.state[args[0]] || args[0] === 'barGraph') | |||||
this.dataUnitComponents = []; | |||||
this.components = new Map(componentConfigs | |||||
.filter(args => !optionals.includes(args[0]) || this.state[args[0]]) | |||||
.map(args => { | .map(args => { | ||||
return [args[0], getComponent(...args)]; | |||||
let component = getComponent(...args); | |||||
if(args[0].includes('lineGraph') || args[0].includes('barGraph')) { | |||||
this.dataUnitComponents.push(component); | |||||
} | |||||
return [args[0], component]; | |||||
})); | })); | ||||
} | } | ||||
@@ -356,27 +361,91 @@ export default class AxisChart extends BaseChart { | |||||
} | } | ||||
} | } | ||||
getDataPoint(index=this.current_index) { | |||||
makeOverlays() { | |||||
// Just make one out of the first element | |||||
// let index = this.xAxisLabels.length - 1; | |||||
// let unit = this.y[0].svg_units[index]; | |||||
// this.setCurrentDataPoint(index); | |||||
// if(this.overlay) { | |||||
// this.overlay.parentNode.removeChild(this.overlay); | |||||
// } | |||||
// this.overlay = unit.cloneNode(); | |||||
// this.overlay.style.fill = '#000000'; | |||||
// this.overlay.style.opacity = '0.4'; | |||||
// this.drawArea.appendChild(this.overlay); | |||||
this.overlayGuides = this.dataUnitComponents.map(c => { | |||||
return { | |||||
type: c.unitType, | |||||
overlay: undefined, | |||||
units: c.store, | |||||
} | |||||
}); | |||||
this.state.currentIndex = 0; | |||||
// Render overlays | |||||
this.overlayGuides.map(d => { | |||||
let currentUnit = d.units[this.state.currentIndex]; | |||||
d.overlay = makeOverlay[d.type](currentUnit); | |||||
this.drawArea.appendChild(d.overlay); | |||||
}) | |||||
} | |||||
bindOverlay() { | |||||
// on event, update overlay | |||||
this.parent.addEventListener('data-select', (e) => { | |||||
this.updateOverlay(e.svg_unit); | |||||
}); | |||||
} | |||||
bindUnits(units_array) { | |||||
// units_array.map(unit => { | |||||
// unit.addEventListener('click', () => { | |||||
// let index = unit.getAttribute('data-point-index'); | |||||
// this.setCurrentDataPoint(index); | |||||
// }); | |||||
// }); | |||||
} | |||||
updateOverlay() { | |||||
this.overlayGuides.map(d => { | |||||
let currentUnit = d.units[this.state.currentIndex]; | |||||
updateOverlay[d.type](currentUnit, d.overlay); | |||||
}) | |||||
} | |||||
onLeftArrow() { | |||||
this.setCurrentDataPoint(this.state.currentIndex - 1); | |||||
} | |||||
onRightArrow() { | |||||
this.setCurrentDataPoint(this.state.currentIndex + 1); | |||||
} | |||||
getDataPoint(index=this.state.currentIndex) { | |||||
// check for length | // check for length | ||||
let data_point = { | let data_point = { | ||||
index: index | 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.xAxis.labels[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.xAxis.labels[index]; | |||||
return data_point; | return data_point; | ||||
} | } | ||||
setCurrentDataPoint(index) { | setCurrentDataPoint(index) { | ||||
let s = this.state; | |||||
index = parseInt(index); | index = parseInt(index); | ||||
if(index < 0) index = 0; | if(index < 0) index = 0; | ||||
if(index >= this.xAxis.labels.length) index = this.xAxis.labels.length - 1; | |||||
if(index === this.current_index) return; | |||||
this.current_index = index; | |||||
$.fire(this.parent, "data-select", this.getDataPoint()); | |||||
if(index >= s.xAxis.labels.length) index = s.xAxis.labels.length - 1; | |||||
if(index === s.currentIndex) return; | |||||
s.currentIndex = index; | |||||
fire(this.parent, "data-select", this.getDataPoint()); | |||||
} | } | ||||
// API | // API | ||||
@@ -401,12 +470,21 @@ export default class AxisChart extends BaseChart { | |||||
this.update(this.data); | this.update(this.data); | ||||
} | } | ||||
// updateData() { | |||||
// // animate if same no. of datasets, | |||||
// // else return new chart | |||||
// getDataPoint(index = 0) {} | |||||
// setCurrentDataPoint(point) {} | |||||
updateDataset(datasetValues, index=0) { | |||||
this.data.datasets[index].values = datasetValues; | |||||
this.update(this.data); | |||||
} | |||||
// addDataset(dataset, index) {} | |||||
// removeDataset(index = 0) {} | |||||
// updateDatasets(datasets) {} | |||||
// // | |||||
// } | |||||
// updateDataPoint(dataPoint, index = 0) {} | |||||
// addDataPoint(dataPoint, index = 0) {} | |||||
// removeDataPoint(index = 0) {} | |||||
} | } | ||||
@@ -30,30 +30,28 @@ export default class BaseChart { | |||||
this.argHeight = height; | this.argHeight = height; | ||||
this.type = type; | this.type = type; | ||||
this.isNavigable = isNavigable; | |||||
if(this.isNavigable) { | |||||
this.currentIndex = 0; | |||||
} | |||||
this.realData = this.prepareData(data); | this.realData = this.prepareData(data); | ||||
this.data = this.prepareFirstData(this.realData); | this.data = this.prepareFirstData(this.realData); | ||||
this.colors = []; | this.colors = []; | ||||
this.config = {}; | |||||
this.config = { | |||||
showTooltip: 1, // calculate | |||||
showLegend: 1, | |||||
isNavigable: isNavigable, | |||||
animate: 1 | |||||
}; | |||||
this.state = {}; | this.state = {}; | ||||
this.options = {}; | this.options = {}; | ||||
if(this.config.isNavigable) { | |||||
this.state.currentIndex = 0; | |||||
this.overlays = []; | |||||
} | |||||
this.configure(arguments[0]); | this.configure(arguments[0]); | ||||
} | } | ||||
configure(args) { | configure(args) { | ||||
this.setColors(); | this.setColors(); | ||||
this.config = { | |||||
showTooltip: 1, // calculate | |||||
showLegend: 1, | |||||
isNavigable: 0, | |||||
animate: 1 | |||||
}; | |||||
this.setMargins(); | this.setMargins(); | ||||
// Bind window events | // Bind window events | ||||
@@ -113,8 +111,6 @@ export default class BaseChart { | |||||
this.draw(true); | this.draw(true); | ||||
} | } | ||||
initComponents() {} | |||||
setupComponents() { | setupComponents() { | ||||
this.components = new Map(); | this.components = new Map(); | ||||
} | } | ||||
@@ -150,21 +146,19 @@ export default class BaseChart { | |||||
this.calcWidth(); | this.calcWidth(); | ||||
this.calc(); | this.calc(); | ||||
this.makeChartArea(); | this.makeChartArea(); | ||||
this.initComponents(); | |||||
this.setupComponents(); | this.setupComponents(); | ||||
this.components.forEach(c => c.setup(this.drawArea)); // or c.build() | this.components.forEach(c => c.setup(this.drawArea)); // or c.build() | ||||
this.components.forEach(c => c.make()); // or c.build() | this.components.forEach(c => c.make()); // or c.build() | ||||
this.renderLegend(); | |||||
this.setupNavigation(init); | |||||
// TODO: remove timeout and decrease post animate time in chart component | // TODO: remove timeout and decrease post animate time in chart component | ||||
if(init) { | if(init) { | ||||
this.data = this.realData; | this.data = this.realData; | ||||
setTimeout(() => {this.update();}, 1000); | setTimeout(() => {this.update();}, 1000); | ||||
} | } | ||||
this.renderLegend(); | |||||
this.setupNavigation(init); | |||||
} | } | ||||
calcWidth() { | calcWidth() { | ||||
@@ -191,19 +185,37 @@ export default class BaseChart { | |||||
calc() {} // builds state | calc() {} // builds state | ||||
render(components=this.components, animate=true) { | render(components=this.components, animate=true) { | ||||
// Can decouple to this.refreshComponents() first to save animation timeout | |||||
if(this.config.isNavigable) { | |||||
// Remove all existing overlays | |||||
this.overlays.map(o => o.parentNode.removeChild(o)); | |||||
// ref.parentNode.insertBefore(element, ref); | |||||
} | |||||
let elementsToAnimate = []; | let elementsToAnimate = []; | ||||
// Can decouple to this.refreshComponents() first to save animation timeout | |||||
components.forEach(c => { | components.forEach(c => { | ||||
elementsToAnimate = elementsToAnimate.concat(c.update(animate)); | elementsToAnimate = elementsToAnimate.concat(c.update(animate)); | ||||
}); | }); | ||||
if(elementsToAnimate.length > 0) { | if(elementsToAnimate.length > 0) { | ||||
runSMILAnimation(this.chartWrapper, this.svg, elementsToAnimate); | runSMILAnimation(this.chartWrapper, this.svg, elementsToAnimate); | ||||
setTimeout(() => { | |||||
components.forEach(c => c.make()); | |||||
this.updateNav(); | |||||
}, 400); | |||||
} else { | |||||
this.updateNav(); | |||||
} | } | ||||
} | |||||
// TODO: rebind new units | |||||
// if(this.isNavigable) { | |||||
// this.bind_units(units_array); | |||||
// } | |||||
updateNav() { | |||||
if(this.config.isNavigable) { | |||||
// Make new overlays | |||||
if(!this.overlayGuides){ | |||||
this.makeOverlays(); | |||||
this.bindUnits(); | |||||
} else { | |||||
this.updateOverlays(); | |||||
} | |||||
} | |||||
} | } | ||||
makeChartArea() { | makeChartArea() { | ||||
@@ -235,36 +247,31 @@ export default class BaseChart { | |||||
renderLegend() {} | renderLegend() {} | ||||
setupNavigation(init=false) { | setupNavigation(init=false) { | ||||
if(this.isNavigable) return; | |||||
this.makeOverlay(); | |||||
if(!this.config.isNavigable) return; | |||||
if(init) { | if(init) { | ||||
this.bindOverlay(); | this.bindOverlay(); | ||||
this.keyActions = { | |||||
'13': this.onEnterKey.bind(this), | |||||
'37': this.onLeftArrow.bind(this), | |||||
'38': this.onUpArrow.bind(this), | |||||
'39': this.onRightArrow.bind(this), | |||||
'40': this.onDownArrow.bind(this), | |||||
} | |||||
document.addEventListener('keydown', (e) => { | document.addEventListener('keydown', (e) => { | ||||
if(isElementInViewport(this.chartWrapper)) { | if(isElementInViewport(this.chartWrapper)) { | ||||
e = e || window.event; | e = e || window.event; | ||||
if (e.keyCode == '37') { | |||||
this.onLeftArrow(); | |||||
} else if (e.keyCode == '39') { | |||||
this.onRightArrow(); | |||||
} else if (e.keyCode == '38') { | |||||
this.onUpArrow(); | |||||
} else if (e.keyCode == '40') { | |||||
this.onDownArrow(); | |||||
} else if (e.keyCode == '13') { | |||||
this.onEnterKey(); | |||||
} | |||||
this.keyActions[e.keyCode](); | |||||
} | } | ||||
}); | }); | ||||
} | } | ||||
} | } | ||||
makeOverlay() {} | |||||
makeOverlays() {} | |||||
bindOverlay() {} | bindOverlay() {} | ||||
bind_units() {} | |||||
bindUnits() {} | |||||
onLeftArrow() {} | onLeftArrow() {} | ||||
onRightArrow() {} | onRightArrow() {} | ||||
@@ -272,13 +279,6 @@ export default class BaseChart { | |||||
onDownArrow() {} | onDownArrow() {} | ||||
onEnterKey() {} | onEnterKey() {} | ||||
// ???????????? | |||||
// Update the data here, then do relevant updates | |||||
// and drawing in child classes by overriding | |||||
// The Child chart will only know what a particular update means | |||||
// and what components are affected, | |||||
// BaseChart shouldn't be doing the animating | |||||
getDataPoint(index = 0) {} | getDataPoint(index = 0) {} | ||||
setCurrentDataPoint(point) {} | setCurrentDataPoint(point) {} | ||||
@@ -1,130 +0,0 @@ | |||||
import { getBarHeightAndYAttr } from '../utils/draw-utils'; | |||||
import { createSVG, makePath, makeGradient, wrapInSVGGroup, FONT_SIZE } 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.meta = meta || {}; | |||||
this.setupArgs(); | |||||
} | |||||
setupArgs() { | |||||
this.consts = {}; | |||||
} | |||||
setup() {} | |||||
refreshMeta(meta) { | |||||
this.meta = Object.assign((this.meta || {}), meta); | |||||
} | |||||
draw() {} | |||||
animate() {} | |||||
} | |||||
export class AxisController extends AxisChartController { | |||||
constructor(meta) { | |||||
super(meta); | |||||
} | |||||
draw(x, y, color, index) { | |||||
return createSVG('circle', { | |||||
style: `fill: ${color}`, | |||||
'data-point-index': index, | |||||
cx: x, | |||||
cy: y, | |||||
r: this.consts.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 LineChartController extends AxisChartController { | |||||
constructor(meta) { | |||||
super(meta); | |||||
} | |||||
setupArgs() { | |||||
this.consts = { | |||||
radius: this.meta.dotSize || 4 | |||||
}; | |||||
} | |||||
} | |||||
// class BarChart extends AxisChart { | |||||
// constructor(args) { | |||||
// super(args); | |||||
// this.type = 'bar'; | |||||
// this.setup(); | |||||
// } | |||||
// configure(args) { | |||||
// super.configure(args); | |||||
// this.config.xAxisMode = args.xAxisMode || 'tick'; | |||||
// this.config.yAxisMode = args.yAxisMode || 'span'; | |||||
// } | |||||
// // ================================= | |||||
// makeOverlay() { | |||||
// // Just make one out of the first element | |||||
// let index = this.xAxisLabels.length - 1; | |||||
// let unit = this.y[0].svg_units[index]; | |||||
// this.setCurrentDataPoint(index); | |||||
// if(this.overlay) { | |||||
// this.overlay.parentNode.removeChild(this.overlay); | |||||
// } | |||||
// this.overlay = unit.cloneNode(); | |||||
// this.overlay.style.fill = '#000000'; | |||||
// this.overlay.style.opacity = '0.4'; | |||||
// this.drawArea.appendChild(this.overlay); | |||||
// } | |||||
// bindOverlay() { | |||||
// // on event, update overlay | |||||
// this.parent.addEventListener('data-select', (e) => { | |||||
// this.update_overlay(e.svg_unit); | |||||
// }); | |||||
// } | |||||
// bind_units(units_array) { | |||||
// units_array.map(unit => { | |||||
// unit.addEventListener('click', () => { | |||||
// let index = unit.getAttribute('data-point-index'); | |||||
// this.setCurrentDataPoint(index); | |||||
// }); | |||||
// }); | |||||
// } | |||||
// update_overlay(unit) { | |||||
// let attributes = []; | |||||
// Object.keys(unit.attributes).map(index => { | |||||
// attributes.push(unit.attributes[index]); | |||||
// }); | |||||
// attributes.filter(attr => attr.specified).map(attr => { | |||||
// this.overlay.setAttribute(attr.name, attr.nodeValue); | |||||
// }); | |||||
// this.overlay.style.fill = '#000000'; | |||||
// this.overlay.style.opacity = '0.4'; | |||||
// } | |||||
// onLeftArrow() { | |||||
// this.setCurrentDataPoint(this.currentIndex - 1); | |||||
// } | |||||
// onRightArrow() { | |||||
// this.setCurrentDataPoint(this.currentIndex + 1); | |||||
// } | |||||
// } | |||||
@@ -58,10 +58,6 @@ class ChartComponent { | |||||
if(animate) { | if(animate) { | ||||
animateElements = this.animateElements(this.data); | animateElements = this.animateElements(this.data); | ||||
} | } | ||||
// TODO: Can we remove this? | |||||
setTimeout(() => { | |||||
this.make(); | |||||
}, 1400); | |||||
return animateElements; | return animateElements; | ||||
} | } | ||||
} | } | ||||
@@ -201,9 +197,10 @@ let componentConfigs = { | |||||
}, | }, | ||||
barGraph: { | barGraph: { | ||||
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; }, | |||||
layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; }, | |||||
makeElements(data) { | makeElements(data) { | ||||
let c = this.constants; | let c = this.constants; | ||||
this.unitType = 'bar'; | |||||
return data.yPositions.map((y, j) => { | return data.yPositions.map((y, j) => { | ||||
return datasetBar( | return datasetBar( | ||||
data.xPositions[j], | data.xPositions[j], | ||||
@@ -269,9 +266,11 @@ let componentConfigs = { | |||||
}, | }, | ||||
lineGraph: { | lineGraph: { | ||||
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; }, | |||||
layerClass: function() { return 'dataset-units dataset-line dataset-' + this.constants.index; }, | |||||
makeElements(data) { | makeElements(data) { | ||||
let c = this.constants; | let c = this.constants; | ||||
this.unitType = 'dot'; | |||||
this.paths = getPaths( | this.paths = getPaths( | ||||
data.xPositions, | data.xPositions, | ||||
data.yPositions, | data.yPositions, | ||||
@@ -302,6 +301,7 @@ let componentConfigs = { | |||||
} | } | ||||
return Object.values(this.paths).concat(this.dots); | return Object.values(this.paths).concat(this.dots); | ||||
// return this.dots; | |||||
}, | }, | ||||
animateElements(newData) { | animateElements(newData) { | ||||
let c = this.constants; | let c = this.constants; | ||||
@@ -6,4 +6,6 @@ export const AXIS_DATASET_CHART_TYPES = ['line', 'bar']; | |||||
export const BAR_CHART_SPACE_RATIO = 0.5; | export const BAR_CHART_SPACE_RATIO = 0.5; | ||||
export const MIN_BAR_PERCENT_HEIGHT = 0.01; | export const MIN_BAR_PERCENT_HEIGHT = 0.01; | ||||
export const LINE_CHART_DOT_SIZE = 4; | |||||
export const LINE_CHART_DOT_SIZE = 4; | |||||
export const DOT_OVERLAY_SIZE_INCR = 4; |
@@ -1,6 +1,7 @@ | |||||
import { getBarHeightAndYAttr } from './draw-utils'; | import { getBarHeightAndYAttr } from './draw-utils'; | ||||
import { getStringWidth } from './helpers'; | import { getStringWidth } from './helpers'; | ||||
import { STD_EASING, UNIT_ANIM_DUR, MARKER_LINE_ANIM_DUR, PATH_ANIM_DUR } from './animate'; | import { STD_EASING, UNIT_ANIM_DUR, MARKER_LINE_ANIM_DUR, PATH_ANIM_DUR } from './animate'; | ||||
import { DOT_OVERLAY_SIZE_INCR } from './constants'; | |||||
/* | /* | ||||
@@ -362,7 +363,6 @@ export function yRegion(y1, y2, width, label) { | |||||
export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) { | export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) { | ||||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); | let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); | ||||
// console.log(yTop, meta.zeroLine, y, offset); | |||||
y -= offset; | y -= offset; | ||||
let rect = createSVG('rect', { | let rect = createSVG('rect', { | ||||
@@ -461,3 +461,78 @@ export function getPaths(xList, yList, color, options={}, meta={}) { | |||||
return paths; | return paths; | ||||
} | } | ||||
export let makeOverlay = { | |||||
'bar': (unit) => { | |||||
let transformValue; | |||||
if(unit.nodeName !== 'rect') { | |||||
transformValue = unit.getAttribute('transform'); | |||||
unit = unit.childNodes[0]; | |||||
} | |||||
let overlay = unit.cloneNode(); | |||||
overlay.style.fill = '#000000'; | |||||
overlay.style.opacity = '0.4'; | |||||
if(transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | |||||
} | |||||
return overlay; | |||||
}, | |||||
'dot': (unit) => { | |||||
let transformValue; | |||||
if(unit.nodeName !== 'circle') { | |||||
transformValue = unit.getAttribute('transform'); | |||||
unit = unit.childNodes[0]; | |||||
} | |||||
let overlay = unit.cloneNode(); | |||||
let radius = unit.getAttribute('r'); | |||||
overlay.setAttribute('r', radius + DOT_OVERLAY_SIZE_INCR); | |||||
overlay.style.fill = '#000000'; | |||||
overlay.style.opacity = '0.4'; | |||||
if(transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | |||||
} | |||||
return overlay; | |||||
} | |||||
} | |||||
export let updateOverlay = { | |||||
'bar': (unit, overlay) => { | |||||
let transformValue; | |||||
if(unit.nodeName !== 'rect') { | |||||
transformValue = unit.getAttribute('transform'); | |||||
unit = unit.childNodes[0]; | |||||
} | |||||
let attributes = ['x', 'y', 'width', 'height']; | |||||
Object.values(unit.attributes) | |||||
.filter(attr => attributes.includes(attr.name) && attr.specified) | |||||
.map(attr => { | |||||
overlay.setAttribute(attr.name, attr.nodeValue); | |||||
}); | |||||
if(transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | |||||
} | |||||
}, | |||||
'dot': (unit, overlay) => { | |||||
let transformValue; | |||||
if(unit.nodeName !== 'circle') { | |||||
transformValue = unit.getAttribute('transform'); | |||||
unit = unit.childNodes[0]; | |||||
} | |||||
let attributes = ['cx', 'cy']; | |||||
Object.values(unit.attributes) | |||||
.filter(attr => attributes.includes(attr.name) && attr.specified) | |||||
.map(attr => { | |||||
overlay.setAttribute(attr.name, attr.nodeValue); | |||||
}); | |||||
if(transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | |||||
} | |||||
} | |||||
} | |||||