@@ -1,20 +1,20 @@ | |||
function $$1(expr, con) { | |||
function $(expr, con) { | |||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; | |||
} | |||
$$1.create = (tag, o) => { | |||
$.create = (tag, o) => { | |||
var element = document.createElement(tag); | |||
for (var i in o) { | |||
var val = o[i]; | |||
if (i === "inside") { | |||
$$1(val).appendChild(element); | |||
$(val).appendChild(element); | |||
} | |||
else if (i === "around") { | |||
var ref = $$1(val); | |||
var ref = $(val); | |||
ref.parentNode.insertBefore(element, ref); | |||
element.appendChild(ref); | |||
@@ -66,6 +66,22 @@ function getElementContentWidth(element) { | |||
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 { | |||
constructor({ | |||
parent = null, | |||
@@ -98,7 +114,7 @@ class SvgTip { | |||
} | |||
make_tooltip() { | |||
this.container = $$1.create('div', { | |||
this.container = $.create('div', { | |||
inside: this.parent, | |||
className: 'graph-svg-tip comparison', | |||
innerHTML: `<span class="title"></span> | |||
@@ -128,7 +144,7 @@ class SvgTip { | |||
this.list_values.map((set, i) => { | |||
const color = this.colors[i] || 'black'; | |||
let li = $$1.create('li', { | |||
let li = $.create('li', { | |||
styles: { | |||
'border-top': `3px solid ${color}` | |||
}, | |||
@@ -376,11 +392,39 @@ function animatePath(paths, newXList, newYList, zeroLine) { | |||
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 LABEL_MARGIN = 4; | |||
const FONT_SIZE = 10; | |||
const BASE_LINE_COLOR = '#dadada'; | |||
function $$2(expr, con) { | |||
function $$1(expr, con) { | |||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; | |||
} | |||
@@ -391,10 +435,10 @@ function createSVG(tag, o) { | |||
var val = o[i]; | |||
if (i === "inside") { | |||
$$2(val).appendChild(element); | |||
$$1(val).appendChild(element); | |||
} | |||
else if (i === "around") { | |||
var ref = $$2(val); | |||
var ref = $$1(val); | |||
ref.parentNode.insertBefore(element, 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={}) { | |||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); | |||
// console.log(yTop, meta.zeroLine, y, offset); | |||
y -= offset; | |||
let rect = createSVG('rect', { | |||
@@ -810,6 +853,80 @@ function getPaths(xList, yList, color, options={}, meta={}) { | |||
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 = { | |||
'light-blue': '#7cd6fd', | |||
'blue': '#5e64ff', | |||
@@ -1046,30 +1163,28 @@ class BaseChart { | |||
this.argHeight = height; | |||
this.type = type; | |||
this.isNavigable = isNavigable; | |||
if(this.isNavigable) { | |||
this.currentIndex = 0; | |||
} | |||
this.realData = this.prepareData(data); | |||
this.data = this.prepareFirstData(this.realData); | |||
this.colors = []; | |||
this.config = {}; | |||
this.config = { | |||
showTooltip: 1, // calculate | |||
showLegend: 1, | |||
isNavigable: isNavigable, | |||
animate: 1 | |||
}; | |||
this.state = {}; | |||
this.options = {}; | |||
if(this.config.isNavigable) { | |||
this.state.currentIndex = 0; | |||
this.overlays = []; | |||
} | |||
this.configure(arguments[0]); | |||
} | |||
configure(args) { | |||
this.setColors(); | |||
this.config = { | |||
showTooltip: 1, // calculate | |||
showLegend: 1, | |||
isNavigable: 0, | |||
animate: 1 | |||
}; | |||
this.setMargins(); | |||
// Bind window events | |||
@@ -1127,14 +1242,12 @@ class BaseChart { | |||
this.draw(true); | |||
} | |||
initComponents() {} | |||
setupComponents() { | |||
this.components = new Map(); | |||
} | |||
makeContainer() { | |||
this.container = $$1.create('div', { | |||
this.container = $.create('div', { | |||
className: 'chart-container', | |||
innerHTML: `<h6 class="title">${this.title}</h6> | |||
<h6 class="sub-title uppercase">${this.subtitle}</h6> | |||
@@ -1164,21 +1277,19 @@ class BaseChart { | |||
this.calcWidth(); | |||
this.calc(); | |||
this.makeChartArea(); | |||
this.initComponents(); | |||
this.setupComponents(); | |||
this.components.forEach(c => c.setup(this.drawArea)); // 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 | |||
if(init) { | |||
this.data = this.realData; | |||
setTimeout(() => {this.update();}, 1000); | |||
} | |||
this.renderLegend(); | |||
this.setupNavigation(init); | |||
} | |||
calcWidth() { | |||
@@ -1205,19 +1316,37 @@ class BaseChart { | |||
calc() {} // builds state | |||
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 = []; | |||
// Can decouple to this.refreshComponents() first to save animation timeout | |||
components.forEach(c => { | |||
elementsToAnimate = elementsToAnimate.concat(c.update(animate)); | |||
}); | |||
if(elementsToAnimate.length > 0) { | |||
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() { | |||
@@ -1249,36 +1378,31 @@ class BaseChart { | |||
renderLegend() {} | |||
setupNavigation(init=false) { | |||
if(this.isNavigable) return; | |||
this.makeOverlay(); | |||
if(!this.config.isNavigable) return; | |||
if(init) { | |||
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) => { | |||
if(isElementInViewport(this.chartWrapper)) { | |||
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() {} | |||
bind_units() {} | |||
bindUnits() {} | |||
onLeftArrow() {} | |||
onRightArrow() {} | |||
@@ -1286,13 +1410,6 @@ class BaseChart { | |||
onDownArrow() {} | |||
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) {} | |||
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) { | |||
data.labels = data.labels || []; | |||
@@ -1465,10 +1572,6 @@ class ChartComponent$1 { | |||
if(animate) { | |||
animateElements = this.animateElements(this.data); | |||
} | |||
// TODO: Can we remove this? | |||
setTimeout(() => { | |||
this.make(); | |||
}, 1400); | |||
return animateElements; | |||
} | |||
} | |||
@@ -1608,9 +1711,10 @@ let componentConfigs = { | |||
}, | |||
barGraph: { | |||
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; }, | |||
layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; }, | |||
makeElements(data) { | |||
let c = this.constants; | |||
this.unitType = 'bar'; | |||
return data.yPositions.map((y, j) => { | |||
return datasetBar( | |||
data.xPositions[j], | |||
@@ -1676,9 +1780,11 @@ let componentConfigs = { | |||
}, | |||
lineGraph: { | |||
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; }, | |||
layerClass: function() { return 'dataset-units dataset-line dataset-' + this.constants.index; }, | |||
makeElements(data) { | |||
let c = this.constants; | |||
this.unitType = 'dot'; | |||
this.paths = getPaths( | |||
data.xPositions, | |||
data.yPositions, | |||
@@ -1709,6 +1815,7 @@ let componentConfigs = { | |||
} | |||
return Object.values(this.paths).concat(this.dots); | |||
// return this.dots; | |||
}, | |||
animateElements(newData) { | |||
let newXPos = newData.xPositions; | |||
@@ -1992,9 +2099,6 @@ class AxisChart extends BaseChart { | |||
configure(args) {3; | |||
super.configure(); | |||
// TODO: set in options and use | |||
this.config.xAxisMode = args.xAxisMode; | |||
this.config.yAxisMode = args.yAxisMode; | |||
} | |||
@@ -2051,12 +2155,13 @@ class AxisChart extends BaseChart { | |||
zeroLine: zeroLine, | |||
}; | |||
this.calcYUnits(); | |||
// Dependent if above changes | |||
this.calcDatasetPoints(); | |||
this.calcYExtremes(); | |||
this.calcYRegions(); | |||
} | |||
calcYUnits() { | |||
calcDatasetPoints() { | |||
let s = this.state; | |||
let scaleAll = values => values.map(val => scale(val, s.yAxis)); | |||
@@ -2079,7 +2184,7 @@ class AxisChart extends BaseChart { | |||
calcYExtremes() { | |||
let s = this.state; | |||
if(this.barOptions && this.barOptions.stacked) { | |||
if(this.barOptions.stacked) { | |||
s.yExtremes = s.datasets[s.datasets.length - 1].cumulativeYPos; | |||
return; | |||
} | |||
@@ -2115,7 +2220,7 @@ class AxisChart extends BaseChart { | |||
// TODO: yMarkers, regions, sums, every Y value ever | |||
let key = 'values'; | |||
if(this.barOptions && this.barOptions.stacked) { | |||
if(this.barOptions.stacked) { | |||
key = 'cumulativeYs'; | |||
let cumulative = new Array(this.state.datasetLength).fill(0); | |||
this.data.datasets.map((d, i) => { | |||
@@ -2127,8 +2232,8 @@ class AxisChart extends BaseChart { | |||
return [].concat(...this.data.datasets.map(d => d[key])); | |||
} | |||
initComponents() { | |||
this.componentConfigs = [ | |||
setupComponents() { | |||
let componentConfigs = [ | |||
[ | |||
'yAxis', | |||
{ | |||
@@ -2156,7 +2261,7 @@ class AxisChart extends BaseChart { | |||
], | |||
]; | |||
this.componentConfigs.map(args => { | |||
componentConfigs.map(args => { | |||
args.push( | |||
function() { | |||
return this.state[args[0]]; | |||
@@ -2176,6 +2281,7 @@ class AxisChart extends BaseChart { | |||
{ | |||
index: index, | |||
color: this.colors[index], | |||
stacked: this.barOptions.stacked, | |||
// same for all datasets | |||
valuesOverPoints: this.valuesOverPoints, | |||
@@ -2189,7 +2295,10 @@ class AxisChart extends BaseChart { | |||
let barsWidth = s.unitWidth * (1 - spaceRatio); | |||
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 { | |||
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']; | |||
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 => { | |||
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 | |||
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.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; | |||
} | |||
setCurrentDataPoint(index) { | |||
let s = this.state; | |||
index = parseInt(index); | |||
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 | |||
@@ -2362,12 +2539,21 @@ class AxisChart extends BaseChart { | |||
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.paddingTop = '0px'; | |||
this.chartDiv = $$1.create('div', { | |||
this.chartDiv = $.create('div', { | |||
className: 'div', | |||
inside: this.chartWrapper | |||
}); | |||
this.chart = $$1.create('div', { | |||
this.chart = $.create('div', { | |||
className: 'progress-chart', | |||
inside: this.chartDiv | |||
}); | |||
} | |||
setupLayers() { | |||
this.percentageBar = $$1.create('div', { | |||
this.percentageBar = $.create('div', { | |||
className: 'progress', | |||
inside: this.chart | |||
}); | |||
@@ -2618,7 +2804,7 @@ class PercentageChart extends BaseChart { | |||
this.grand_total = this.slice_totals.reduce((a, b) => a + b, 0); | |||
this.slices = []; | |||
this.slice_totals.map((total, i) => { | |||
let slice = $$1.create('div', { | |||
let slice = $.create('div', { | |||
className: `progress-bar`, | |||
inside: this.percentageBar, | |||
styles: { | |||
@@ -2654,7 +2840,7 @@ class PercentageChart extends BaseChart { | |||
? this.formatted_labels : this.labels; | |||
this.legend_totals.map((d, i) => { | |||
if(d) { | |||
let stats = $$1.create('div', { | |||
let stats = $.create('div', { | |||
className: 'stats', | |||
inside: this.statsWrapper | |||
}); | |||
@@ -2842,7 +3028,7 @@ class PieChart extends BaseChart { | |||
const color = this.colors[i]; | |||
if(d) { | |||
let stats = $$1.create('div', { | |||
let stats = $.create('div', { | |||
className: 'stats', | |||
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 c2 = document.querySelector("#chart-composite-2"); | |||
@@ -89,39 +89,8 @@ let line_composite_chart = new Chart ({ | |||
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) => { | |||
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", | |||
values: [15, 20, -3, -15, 58, 12, -17, 37], | |||
chartType: 'bar' | |||
chartType: 'line' | |||
} | |||
// temp : Stacked | |||
@@ -5,6 +5,7 @@ import { getComponent } from '../objects/ChartComponents'; | |||
import { getOffset, fire } from '../utils/dom'; | |||
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale } from '../utils/intervals'; | |||
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'; | |||
export default class AxisChart extends BaseChart { | |||
@@ -28,9 +29,6 @@ export default class AxisChart extends BaseChart { | |||
configure(args) {3 | |||
super.configure(); | |||
// TODO: set in options and use | |||
this.config.xAxisMode = args.xAxisMode; | |||
this.config.yAxisMode = args.yAxisMode; | |||
} | |||
@@ -87,12 +85,13 @@ export default class AxisChart extends BaseChart { | |||
zeroLine: zeroLine, | |||
} | |||
this.calcYUnits(); | |||
// Dependent if above changes | |||
this.calcDatasetPoints(); | |||
this.calcYExtremes(); | |||
this.calcYRegions(); | |||
} | |||
calcYUnits() { | |||
calcDatasetPoints() { | |||
let s = this.state; | |||
let scaleAll = values => values.map(val => scale(val, s.yAxis)); | |||
@@ -115,7 +114,7 @@ export default class AxisChart extends BaseChart { | |||
calcYExtremes() { | |||
let s = this.state; | |||
if(this.barOptions && this.barOptions.stacked) { | |||
if(this.barOptions.stacked) { | |||
s.yExtremes = s.datasets[s.datasets.length - 1].cumulativeYPos; | |||
return; | |||
} | |||
@@ -151,7 +150,7 @@ export default class AxisChart extends BaseChart { | |||
// TODO: yMarkers, regions, sums, every Y value ever | |||
let key = 'values'; | |||
if(this.barOptions && this.barOptions.stacked) { | |||
if(this.barOptions.stacked) { | |||
key = 'cumulativeYs'; | |||
let cumulative = new Array(this.state.datasetLength).fill(0); | |||
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])); | |||
} | |||
initComponents() { | |||
setupComponents() { | |||
let s = this.state; | |||
// console.log('this.state', Object.assign({}, this.state)); | |||
// console.log('this.state', this.state); | |||
this.componentConfigs = [ | |||
let componentConfigs = [ | |||
[ | |||
'yAxis', | |||
{ | |||
@@ -195,7 +192,7 @@ export default class AxisChart extends BaseChart { | |||
], | |||
]; | |||
this.componentConfigs.map(args => { | |||
componentConfigs.map(args => { | |||
args.push( | |||
function() { | |||
return this.state[args[0]]; | |||
@@ -215,6 +212,7 @@ export default class AxisChart extends BaseChart { | |||
{ | |||
index: index, | |||
color: this.colors[index], | |||
stacked: this.barOptions.stacked, | |||
// same for all datasets | |||
valuesOverPoints: this.valuesOverPoints, | |||
@@ -228,7 +226,10 @@ export default class AxisChart extends BaseChart { | |||
let barsWidth = s.unitWidth * (1 - spaceRatio); | |||
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 { | |||
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']; | |||
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 => { | |||
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 | |||
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.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; | |||
} | |||
setCurrentDataPoint(index) { | |||
let s = this.state; | |||
index = parseInt(index); | |||
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 | |||
@@ -401,12 +470,21 @@ export default class AxisChart extends BaseChart { | |||
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.type = type; | |||
this.isNavigable = isNavigable; | |||
if(this.isNavigable) { | |||
this.currentIndex = 0; | |||
} | |||
this.realData = this.prepareData(data); | |||
this.data = this.prepareFirstData(this.realData); | |||
this.colors = []; | |||
this.config = {}; | |||
this.config = { | |||
showTooltip: 1, // calculate | |||
showLegend: 1, | |||
isNavigable: isNavigable, | |||
animate: 1 | |||
}; | |||
this.state = {}; | |||
this.options = {}; | |||
if(this.config.isNavigable) { | |||
this.state.currentIndex = 0; | |||
this.overlays = []; | |||
} | |||
this.configure(arguments[0]); | |||
} | |||
configure(args) { | |||
this.setColors(); | |||
this.config = { | |||
showTooltip: 1, // calculate | |||
showLegend: 1, | |||
isNavigable: 0, | |||
animate: 1 | |||
}; | |||
this.setMargins(); | |||
// Bind window events | |||
@@ -113,8 +111,6 @@ export default class BaseChart { | |||
this.draw(true); | |||
} | |||
initComponents() {} | |||
setupComponents() { | |||
this.components = new Map(); | |||
} | |||
@@ -150,21 +146,19 @@ export default class BaseChart { | |||
this.calcWidth(); | |||
this.calc(); | |||
this.makeChartArea(); | |||
this.initComponents(); | |||
this.setupComponents(); | |||
this.components.forEach(c => c.setup(this.drawArea)); // 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 | |||
if(init) { | |||
this.data = this.realData; | |||
setTimeout(() => {this.update();}, 1000); | |||
} | |||
this.renderLegend(); | |||
this.setupNavigation(init); | |||
} | |||
calcWidth() { | |||
@@ -191,19 +185,37 @@ export default class BaseChart { | |||
calc() {} // builds state | |||
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 = []; | |||
// Can decouple to this.refreshComponents() first to save animation timeout | |||
components.forEach(c => { | |||
elementsToAnimate = elementsToAnimate.concat(c.update(animate)); | |||
}); | |||
if(elementsToAnimate.length > 0) { | |||
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() { | |||
@@ -235,36 +247,31 @@ export default class BaseChart { | |||
renderLegend() {} | |||
setupNavigation(init=false) { | |||
if(this.isNavigable) return; | |||
this.makeOverlay(); | |||
if(!this.config.isNavigable) return; | |||
if(init) { | |||
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) => { | |||
if(isElementInViewport(this.chartWrapper)) { | |||
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() {} | |||
bind_units() {} | |||
bindUnits() {} | |||
onLeftArrow() {} | |||
onRightArrow() {} | |||
@@ -272,13 +279,6 @@ export default class BaseChart { | |||
onDownArrow() {} | |||
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) {} | |||
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) { | |||
animateElements = this.animateElements(this.data); | |||
} | |||
// TODO: Can we remove this? | |||
setTimeout(() => { | |||
this.make(); | |||
}, 1400); | |||
return animateElements; | |||
} | |||
} | |||
@@ -201,9 +197,10 @@ let componentConfigs = { | |||
}, | |||
barGraph: { | |||
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; }, | |||
layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; }, | |||
makeElements(data) { | |||
let c = this.constants; | |||
this.unitType = 'bar'; | |||
return data.yPositions.map((y, j) => { | |||
return datasetBar( | |||
data.xPositions[j], | |||
@@ -269,9 +266,11 @@ let componentConfigs = { | |||
}, | |||
lineGraph: { | |||
layerClass: function() { return 'dataset-units dataset-' + this.constants.index; }, | |||
layerClass: function() { return 'dataset-units dataset-line dataset-' + this.constants.index; }, | |||
makeElements(data) { | |||
let c = this.constants; | |||
this.unitType = 'dot'; | |||
this.paths = getPaths( | |||
data.xPositions, | |||
data.yPositions, | |||
@@ -302,6 +301,7 @@ let componentConfigs = { | |||
} | |||
return Object.values(this.paths).concat(this.dots); | |||
// return this.dots; | |||
}, | |||
animateElements(newData) { | |||
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 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 { getStringWidth } from './helpers'; | |||
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={}) { | |||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); | |||
// console.log(yTop, meta.zeroLine, y, offset); | |||
y -= offset; | |||
let rect = createSVG('rect', { | |||
@@ -461,3 +461,78 @@ export function getPaths(xList, yList, color, options={}, meta={}) { | |||
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); | |||
} | |||
} | |||
} | |||