import { makeSVGGroup } from '../utils/draw'; import { xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, getPaths } from '../utils/draw'; import { equilizeNoOfElements } from '../utils/draw-utils'; import { translateHoriLine, translateVertLine, animateRegion, animateBar, animateDot, animatePath } from '../utils/animate'; class ChartComponent { constructor({ layerClass = '', layerTransform = '', constants, getData, makeElements, animateElements }) { this.layerTransform = layerTransform; this.constants = constants; this.makeElements = makeElements; this.getData = getData; this.animateElements = animateElements; this.store = []; this.layerClass = layerClass; this.layerClass = typeof(this.layerClass) === 'function' ? this.layerClass() : this.layerClass; this.refresh(); } refresh(data) { this.data = data || this.getData(); } setup(parent) { this.layer = makeSVGGroup(parent, this.layerClass, this.layerTransform); } make() { this.render(this.data); this.oldData = this.data; } render(data) { this.store = this.makeElements(data); this.layer.textContent = ''; this.store.forEach(element => { this.layer.appendChild(element); }); } update(animate = true) { this.refresh(); let animateElements = [] if(animate) { animateElements = this.animateElements(this.data); } return animateElements; } } let componentConfigs = { yAxis: { layerClass: 'y axis', makeElements(data) { return data.positions.map((position, i) => yLine(position, data.labels[i], this.constants.width, {mode: this.constants.mode, pos: this.constants.pos}) ); }, animateElements(newData) { let newPos = newData.positions; let newLabels = newData.labels; let oldPos = this.oldData.positions; let oldLabels = this.oldData.labels; [oldPos, newPos] = equilizeNoOfElements(oldPos, newPos); [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); this.render({ positions: oldPos, labels: newLabels }); return this.store.map((line, i) => { return translateHoriLine( line, newPos[i], oldPos[i] ); }); } }, xAxis: { layerClass: 'x axis', makeElements(data) { return data.positions.map((position, i) => xLine(position, data.calcLabels[i], this.constants.height, {mode: this.constants.mode, pos: this.constants.pos}) ); }, animateElements(newData) { let newPos = newData.positions; let newLabels = newData.calcLabels; let oldPos = this.oldData.positions; let oldLabels = this.oldData.calcLabels; [oldPos, newPos] = equilizeNoOfElements(oldPos, newPos); [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); this.render({ positions: oldPos, calcLabels: newLabels }); return this.store.map((line, i) => { return translateVertLine( line, newPos[i], oldPos[i] ); }); } }, yMarkers: { layerClass: 'y-markers', makeElements(data) { return data.map(marker => yMarker(marker.position, marker.label, this.constants.width, {pos:'right', mode: 'span', lineType: 'dashed'}) ); }, animateElements(newData) { [this.oldData, newData] = equilizeNoOfElements(this.oldData, newData); let newPos = newData.map(d => d.position); let newLabels = newData.map(d => d.label); let oldPos = this.oldData.map(d => d.position); let oldLabels = this.oldData.map(d => d.label); this.render(oldPos.map((pos, i) => { return { position: oldPos[i], label: newLabels[i] } })); return this.store.map((line, i) => { return translateHoriLine( line, newPos[i], oldPos[i] ); }); } }, yRegions: { layerClass: 'y-regions', makeElements(data) { return data.map(region => yRegion(region.start, region.end, this.constants.width, region.label) ); }, animateElements(newData) { [this.oldData, newData] = equilizeNoOfElements(this.oldData, newData); let newPos = newData.map(d => d.end); let newLabels = newData.map(d => d.label); let newStarts = newData.map(d => d.start); let oldPos = this.oldData.map(d => d.end); let oldLabels = this.oldData.map(d => d.label); let oldStarts = this.oldData.map(d => d.start); this.render(oldPos.map((pos, i) => { return { start: oldStarts[i], end: oldPos[i], label: newLabels[i] } })); let animateElements = []; this.store.map((rectGroup, i) => { animateElements = animateElements.concat(animateRegion( rectGroup, newStarts[i], newPos[i], oldPos[i] )); }); return animateElements; } }, barGraph: { layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; }, makeElements(data) { let c = this.constants; this.unitType = 'bar'; this.units = data.yPositions.map((y, j) => { return datasetBar( data.xPositions[j], y, data.barWidth, c.color, data.labels[j], j, data.offsets[j], { zeroLine: data.zeroLine, barsWidth: data.barsWidth, minHeight: c.minHeight } ) }); return this.units; }, animateElements(newData) { let c = this.constants; let newXPos = newData.xPositions; let newYPos = newData.yPositions; let newOffsets = newData.offsets; let newLabels = newData.labels; let oldXPos = this.oldData.xPositions; let oldYPos = this.oldData.yPositions; let oldOffsets = this.oldData.offsets; let oldLabels = this.oldData.labels; [oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos); [oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos); [oldOffsets, newOffsets] = equilizeNoOfElements(oldOffsets, newOffsets); [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); this.render({ xPositions: oldXPos, yPositions: oldYPos, offsets: oldOffsets, labels: newLabels, zeroLine: this.oldData.zeroLine, barsWidth: this.oldData.barsWidth, barWidth: this.oldData.barWidth, }); let animateElements = []; this.store.map((bar, i) => { animateElements = animateElements.concat(animateBar( bar, newXPos[i], newYPos[i], newData.barWidth, newOffsets[i], c.index, {zeroLine: newData.zeroLine} )); }); return animateElements; } }, lineGraph: { layerClass: function() { return 'dataset-units dataset-line dataset-' + this.constants.index; }, makeElements(data) { let c = this.constants; this.unitType = 'dot'; this.paths = {}; if(!c.hideLine) { this.paths = getPaths( data.xPositions, data.yPositions, c.color, { heatline: c.heatline, regionFill: c.regionFill }, { svgDefs: c.svgDefs, zeroLine: data.zeroLine } ) } this.units = [] if(!c.hideDots) { this.units = data.yPositions.map((y, j) => { return datasetDot( data.xPositions[j], y, data.radius, c.color, (c.valuesOverPoints ? data.values[j] : ''), j ) }); } return Object.values(this.paths).concat(this.units); }, animateElements(newData) { let c = this.constants; let newXPos = newData.xPositions; let newYPos = newData.yPositions; let newValues = newData.values; let oldXPos = this.oldData.xPositions; let oldYPos = this.oldData.yPositions; let oldValues = this.oldData.values; [oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos); [oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos); [oldValues, newValues] = equilizeNoOfElements(oldValues, newValues); this.render({ xPositions: oldXPos, yPositions: oldYPos, values: newValues, zeroLine: this.oldData.zeroLine, radius: this.oldData.radius, }); let animateElements = []; if(Object.keys(this.paths).length) { animateElements = animateElements.concat(animatePath( this.paths, newXPos, newYPos, newData.zeroLine)); } if(this.units.length) { this.units.map((dot, i) => { animateElements = animateElements.concat(animateDot( dot, newXPos[i], newYPos[i])); }); } return animateElements; } } } export function getComponent(name, constants, getData) { let keys = Object.keys(componentConfigs).filter(k => name.includes(k)); let config = componentConfigs[keys[0]]; Object.assign(config, { constants: constants, getData: getData }) return new ChartComponent(config); }