From ae2ae01ed744a105d277efccfcdc03b989c8e579 Mon Sep 17 00:00:00 2001 From: Arjun Choudhary Date: Mon, 19 Dec 2022 11:34:00 +0530 Subject: [PATCH] feat: pr 366 initial merge --- src/js/objects/ChartComponents.js | 997 +++++++++++---------- src/js/utils/axis-chart-utils.js | 223 ++--- src/js/utils/draw.js | 1382 +++++++++++++++-------------- 3 files changed, 1384 insertions(+), 1218 deletions(-) diff --git a/src/js/objects/ChartComponents.js b/src/js/objects/ChartComponents.js index 8200e8d..5bb9477 100644 --- a/src/js/objects/ChartComponents.js +++ b/src/js/objects/ChartComponents.js @@ -1,529 +1,624 @@ import { makeSVGGroup } from "../utils/draw"; import { - makeText, - makePath, - xLine, - yLine, - yMarker, - yRegion, - datasetBar, - datasetDot, - percentageBar, - getPaths, - heatSquare, + makeText, + makePath, + xLine, + yLine, + generateAxisLabel, + yMarker, + yRegion, + datasetBar, + datasetDot, + percentageBar, + getPaths, + heatSquare, } from "../utils/draw"; import { equilizeNoOfElements } from "../utils/draw-utils"; import { - translateHoriLine, - translateVertLine, - animateRegion, - animateBar, - animateDot, - animatePath, - animatePathStr, + translateHoriLine, + translateVertLine, + animateRegion, + animateBar, + animateDot, + animatePath, + animatePathStr, } from "../utils/animate"; import { getMonthName } from "../utils/date-utils"; class ChartComponent { - constructor({ - layerClass = "", - layerTransform = "", - constants, + constructor({ + layerClass = "", + layerTransform = "", + constants, - getData, - makeElements, - animateElements, - }) { - this.layerTransform = layerTransform; - this.constants = constants; + getData, + makeElements, + animateElements, + }) { + this.layerTransform = layerTransform; + this.constants = constants; - this.makeElements = makeElements; - this.getData = getData; + this.makeElements = makeElements; + this.getData = getData; - this.animateElements = animateElements; + this.animateElements = animateElements; - this.store = []; - this.labels = []; + this.store = []; + this.labels = []; - this.layerClass = layerClass; - this.layerClass = - typeof this.layerClass === "function" - ? this.layerClass() - : this.layerClass; + this.layerClass = layerClass; + this.layerClass = + typeof this.layerClass === "function" + ? this.layerClass() + : this.layerClass; - this.refresh(); - } + this.refresh(); + } - refresh(data) { - this.data = data || this.getData(); - } + refresh(data) { + this.data = data || this.getData(); + } - setup(parent) { - this.layer = makeSVGGroup(this.layerClass, this.layerTransform, parent); - } + setup(parent) { + this.layer = makeSVGGroup(this.layerClass, this.layerTransform, parent); + } - make() { - this.render(this.data); - this.oldData = this.data; - } + make() { + this.render(this.data); + this.oldData = this.data; + } - render(data) { - this.store = this.makeElements(data); + render(data) { + this.store = this.makeElements(data); - this.layer.textContent = ""; - this.store.forEach((element) => { - this.layer.appendChild(element); - }); - this.labels.forEach((element) => { - this.layer.appendChild(element); - }); - } + this.layer.textContent = ""; + this.store.forEach((element) => { + element.length + ? element.forEach((el) => { + this.layer.appendChild(el); + }) + : this.layer.appendChild(element); + }); + this.labels.forEach((element) => { + this.layer.appendChild(element); + }); + } - update(animate = true) { - this.refresh(); - let animateElements = []; - if (animate) { - animateElements = this.animateElements(this.data) || []; - } - return animateElements; - } + update(animate = true) { + this.refresh(); + let animateElements = []; + if (animate) { + animateElements = this.animateElements(this.data) || []; + } + return animateElements; + } } let componentConfigs = { - donutSlices: { - layerClass: "donut-slices", - makeElements(data) { - return data.sliceStrings.map((s, i) => { - let slice = makePath( - s, - "donut-path", - data.colors[i], - "none", - data.strokeWidth - ); - slice.style.transition = "transform .3s;"; - return slice; - }); - }, + donutSlices: { + layerClass: "donut-slices", + makeElements(data) { + return data.sliceStrings.map((s, i) => { + let slice = makePath( + s, + "donut-path", + data.colors[i], + "none", + data.strokeWidth + ); + slice.style.transition = "transform .3s;"; + return slice; + }); + }, - animateElements(newData) { - return this.store.map((slice, i) => - animatePathStr(slice, newData.sliceStrings[i]) - ); - }, - }, - pieSlices: { - layerClass: "pie-slices", - makeElements(data) { - return data.sliceStrings.map((s, i) => { - let slice = makePath(s, "pie-path", "none", data.colors[i]); - slice.style.transition = "transform .3s;"; - return slice; - }); - }, + animateElements(newData) { + return this.store.map((slice, i) => + animatePathStr(slice, newData.sliceStrings[i]) + ); + }, + }, + pieSlices: { + layerClass: "pie-slices", + makeElements(data) { + return data.sliceStrings.map((s, i) => { + let slice = makePath(s, "pie-path", "none", data.colors[i]); + slice.style.transition = "transform .3s;"; + return slice; + }); + }, - animateElements(newData) { - return this.store.map((slice, i) => - animatePathStr(slice, newData.sliceStrings[i]) - ); - }, - }, - percentageBars: { - layerClass: "percentage-bars", - makeElements(data) { - const numberOfPoints = data.xPositions.length; - return data.xPositions.map((x, i) => { - let y = 0; + animateElements(newData) { + return this.store.map((slice, i) => + animatePathStr(slice, newData.sliceStrings[i]) + ); + }, + }, + percentageBars: { + layerClass: "percentage-bars", + makeElements(data) { + const numberOfPoints = data.xPositions.length; + return data.xPositions.map((x, i) => { + let y = 0; - let isLast = i == numberOfPoints - 1; - let isFirst = i == 0; + let isLast = i == numberOfPoints - 1; + let isFirst = i == 0; - let bar = percentageBar( - x, - y, - data.widths[i], - this.constants.barHeight, - isFirst, - isLast, - data.colors[i] - ); - return bar; - }); - }, + let bar = percentageBar( + x, + y, + data.widths[i], + this.constants.barHeight, + isFirst, + isLast, + data.colors[i] + ); + return bar; + }); + }, - animateElements(newData) { - if (newData) return []; - }, - }, - 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, - shortenNumbers: this.constants.shortenNumbers, - }) - ); - }, + animateElements(newData) { + if (newData) return []; + }, + }, + yAxis: { + layerClass: "y axis", + makeElements(data) { + let elements = []; + // will loop through each yaxis dataset if it exists + if (data.length) { + data.forEach((item, i) => { + item.positions.map((position, i) => { + elements.push( + yLine( + position, + item.labels[i], + this.constants.width, + { + mode: this.constants.mode, + pos: item.pos || this.constants.pos, + shortenNumbers: + this.constants.shortenNumbers, + title: item.title, + } + ) + ); + }); + // we need to make yAxis titles if they are defined + if (item.title) { + elements.push( + generateAxisLabel({ + title: item.title, + position: item.pos, + height: this.constants.height || data.zeroLine, + width: this.constants.width, + }) + ); + } + }); - animateElements(newData) { - let newPos = newData.positions; - let newLabels = newData.labels; - let oldPos = this.oldData.positions; - let oldLabels = this.oldData.labels; + return elements; + } - [oldPos, newPos] = equilizeNoOfElements(oldPos, newPos); - [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); + data.positions.forEach((position, i) => { + elements.push( + yLine(position, data.labels[i], this.constants.width, { + mode: this.constants.mode, + pos: data.pos || this.constants.pos, + shortenNumbers: this.constants.shortenNumbers, + }) + ); + }); - this.render({ - positions: oldPos, - labels: newLabels, - }); + if (data.title) { + elements.push( + generateAxisLabel({ + title: data.title, + position: data.pos, + height: this.constants.height || data.zeroLine, + width: this.constants.width, + }) + ); + } - return this.store.map((line, i) => { - return translateHoriLine(line, newPos[i], oldPos[i]); - }); - }, - }, + return elements; + }, - 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) { + const animateMultipleElements = (oldData, newData) => { + let newPos = newData.positions; + let newLabels = newData.labels; + let oldPos = oldData.positions; + let oldLabels = oldData.labels; - 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 + ); - [oldPos, newPos] = equilizeNoOfElements(oldPos, newPos); - [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); + this.render({ + positions: oldPos, + labels: newLabels, + }); - this.render({ - positions: oldPos, - calcLabels: newLabels, - }); + return this.store.map((line, i) => { + return translateHoriLine(line, newPos[i], oldPos[i]); + }); + }; - return this.store.map((line, i) => { - return translateVertLine(line, newPos[i], oldPos[i]); - }); - }, - }, + // we will need to animate both axis if we have more than one. + // so check if the oldData is an array of values. + if (this.oldData instanceof Array) { + return this.oldData.forEach((old, i) => { + animateMultipleElements(old, newData[i]); + }); + } - yMarkers: { - layerClass: "y-markers", - makeElements(data) { - return data.map((m) => - yMarker(m.position, m.label, this.constants.width, { - labelPos: m.options.labelPos, - stroke: m.options.stroke, - mode: "span", - lineType: m.options.lineType, - }) - ); - }, - animateElements(newData) { - [this.oldData, newData] = equilizeNoOfElements(this.oldData, newData); + let newPos = newData.positions; + let newLabels = newData.labels; + let oldPos = this.oldData.positions; + let oldLabels = this.oldData.labels; - let newPos = newData.map((d) => d.position); - let newLabels = newData.map((d) => d.label); - let newOptions = newData.map((d) => d.options); + [oldPos, newPos] = equilizeNoOfElements(oldPos, newPos); + [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); - let oldPos = this.oldData.map((d) => d.position); + this.render({ + positions: oldPos, + labels: newLabels, + }); - this.render( - oldPos.map((pos, i) => { - return { - position: oldPos[i], - label: newLabels[i], - options: newOptions[i], - }; - }) - ); + return this.store.map((line, i) => { + return translateHoriLine(line, newPos[i], oldPos[i]); + }); + }, + }, - 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, + }) + ); + }, - yRegions: { - layerClass: "y-regions", - makeElements(data) { - return data.map((r) => - yRegion(r.startPos, r.endPos, this.constants.width, r.label, { - labelPos: r.options.labelPos, - }) - ); - }, - animateElements(newData) { - [this.oldData, newData] = equilizeNoOfElements(this.oldData, newData); + animateElements(newData) { + let newPos = newData.positions; + let newLabels = newData.calcLabels; + let oldPos = this.oldData.positions; + let oldLabels = this.oldData.calcLabels; - let newPos = newData.map((d) => d.endPos); - let newLabels = newData.map((d) => d.label); - let newStarts = newData.map((d) => d.startPos); - let newOptions = newData.map((d) => d.options); + [oldPos, newPos] = equilizeNoOfElements(oldPos, newPos); + [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); - let oldPos = this.oldData.map((d) => d.endPos); - let oldStarts = this.oldData.map((d) => d.startPos); + this.render({ + positions: oldPos, + calcLabels: newLabels, + }); - this.render( - oldPos.map((pos, i) => { - return { - startPos: oldStarts[i], - endPos: oldPos[i], - label: newLabels[i], - options: newOptions[i], - }; - }) - ); + return this.store.map((line, i) => { + return translateVertLine(line, newPos[i], oldPos[i]); + }); + }, + }, - let animateElements = []; + yMarkers: { + layerClass: "y-markers", + makeElements(data) { + return data.map((m) => + yMarker(m.position, m.label, this.constants.width, { + labelPos: m.options.labelPos, + stroke: m.options.stroke, + mode: "span", + lineType: m.options.lineType, + }) + ); + }, + animateElements(newData) { + [this.oldData, newData] = equilizeNoOfElements( + this.oldData, + newData + ); - this.store.map((rectGroup, i) => { - animateElements = animateElements.concat( - animateRegion(rectGroup, newStarts[i], newPos[i], oldPos[i]) - ); - }); + let newPos = newData.map((d) => d.position); + let newLabels = newData.map((d) => d.label); + let newOptions = newData.map((d) => d.options); - return animateElements; - }, - }, + let oldPos = this.oldData.map((d) => d.position); - heatDomain: { - layerClass: function () { - return "heat-domain domain-" + this.constants.index; - }, - makeElements(data) { - let { index, colWidth, rowHeight, squareSize, radius, xTranslate } = - this.constants; - let monthNameHeight = -12; - let x = xTranslate, - y = 0; + this.render( + oldPos.map((pos, i) => { + return { + position: oldPos[i], + label: newLabels[i], + options: newOptions[i], + }; + }) + ); - this.serializedSubDomains = []; + return this.store.map((line, i) => { + return translateHoriLine(line, newPos[i], oldPos[i]); + }); + }, + }, - data.cols.map((week, weekNo) => { - if (weekNo === 1) { - this.labels.push( - makeText( - "domain-name", - x, - monthNameHeight, - getMonthName(index, true).toUpperCase(), - { - fontSize: 9, - } - ) - ); - } - week.map((day, i) => { - if (day.fill) { - let data = { - "data-date": day.yyyyMmDd, - "data-value": day.dataValue, - "data-day": i, - }; - let square = heatSquare( - "day", - x, - y, - squareSize, - radius, - day.fill, - data - ); - this.serializedSubDomains.push(square); - } - y += rowHeight; - }); - y = 0; - x += colWidth; - }); + yRegions: { + layerClass: "y-regions", + makeElements(data) { + return data.map((r) => + yRegion(r.startPos, r.endPos, this.constants.width, r.label, { + labelPos: r.options.labelPos, + }) + ); + }, + animateElements(newData) { + [this.oldData, newData] = equilizeNoOfElements( + this.oldData, + newData + ); - return this.serializedSubDomains; - }, + let newPos = newData.map((d) => d.endPos); + let newLabels = newData.map((d) => d.label); + let newStarts = newData.map((d) => d.startPos); + let newOptions = newData.map((d) => d.options); - animateElements(newData) { - if (newData) return []; - }, - }, + let oldPos = this.oldData.map((d) => d.endPos); + let oldStarts = this.oldData.map((d) => d.startPos); - 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 newXPos = newData.xPositions; - let newYPos = newData.yPositions; - let newOffsets = newData.offsets; - let newLabels = newData.labels; + this.render( + oldPos.map((pos, i) => { + return { + startPos: oldStarts[i], + endPos: oldPos[i], + label: newLabels[i], + options: newOptions[i], + }; + }) + ); - let oldXPos = this.oldData.xPositions; - let oldYPos = this.oldData.yPositions; - let oldOffsets = this.oldData.offsets; - let oldLabels = this.oldData.labels; + let animateElements = []; - [oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos); - [oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos); - [oldOffsets, newOffsets] = equilizeNoOfElements(oldOffsets, newOffsets); - [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); + this.store.map((rectGroup, i) => { + animateElements = animateElements.concat( + animateRegion(rectGroup, newStarts[i], newPos[i], oldPos[i]) + ); + }); - this.render({ - xPositions: oldXPos, - yPositions: oldYPos, - offsets: oldOffsets, - labels: newLabels, + return animateElements; + }, + }, - zeroLine: this.oldData.zeroLine, - barsWidth: this.oldData.barsWidth, - barWidth: this.oldData.barWidth, - }); + heatDomain: { + layerClass: function () { + return "heat-domain domain-" + this.constants.index; + }, + makeElements(data) { + let { index, colWidth, rowHeight, squareSize, radius, xTranslate } = + this.constants; + let monthNameHeight = -12; + let x = xTranslate, + y = 0; - let animateElements = []; + this.serializedSubDomains = []; - this.store.map((bar, i) => { - animateElements = animateElements.concat( - animateBar( - bar, - newXPos[i], - newYPos[i], - newData.barWidth, - newOffsets[i], - { zeroLine: newData.zeroLine } - ) - ); - }); + data.cols.map((week, weekNo) => { + if (weekNo === 1) { + this.labels.push( + makeText( + "domain-name", + x, + monthNameHeight, + getMonthName(index, true).toUpperCase(), + { + fontSize: 9, + } + ) + ); + } + week.map((day, i) => { + if (day.fill) { + let data = { + "data-date": day.yyyyMmDd, + "data-value": day.dataValue, + "data-day": i, + }; + let square = heatSquare( + "day", + x, + y, + squareSize, + radius, + day.fill, + data + ); + this.serializedSubDomains.push(square); + } + y += rowHeight; + }); + y = 0; + x += colWidth; + }); - return animateElements; - }, - }, + return this.serializedSubDomains; + }, - 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, - spline: c.spline, - }, - { - svgDefs: c.svgDefs, - zeroLine: data.zeroLine, - } - ); - } + animateElements(newData) { + if (newData) return []; + }, + }, - 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 - ); - }); - } + 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 newXPos = newData.xPositions; + let newYPos = newData.yPositions; + let newOffsets = newData.offsets; + let newLabels = newData.labels; - return Object.values(this.paths).concat(this.units); - }, - animateElements(newData) { - let newXPos = newData.xPositions; - let newYPos = newData.yPositions; - let newValues = newData.values; + let oldXPos = this.oldData.xPositions; + let oldYPos = this.oldData.yPositions; + let oldOffsets = this.oldData.offsets; + let oldLabels = this.oldData.labels; - 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); + [oldOffsets, newOffsets] = equilizeNoOfElements( + oldOffsets, + newOffsets + ); + [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels); - [oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos); - [oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos); - [oldValues, newValues] = equilizeNoOfElements(oldValues, newValues); + this.render({ + xPositions: oldXPos, + yPositions: oldYPos, + offsets: oldOffsets, + labels: newLabels, - this.render({ - xPositions: oldXPos, - yPositions: oldYPos, - values: newValues, + zeroLine: this.oldData.zeroLine, + barsWidth: this.oldData.barsWidth, + barWidth: this.oldData.barWidth, + }); - zeroLine: this.oldData.zeroLine, - radius: this.oldData.radius, - }); + let animateElements = []; - let animateElements = []; + this.store.map((bar, i) => { + animateElements = animateElements.concat( + animateBar( + bar, + newXPos[i], + newYPos[i], + newData.barWidth, + newOffsets[i], + { zeroLine: newData.zeroLine } + ) + ); + }); - if (Object.keys(this.paths).length) { - animateElements = animateElements.concat( - animatePath( - this.paths, - newXPos, - newYPos, - newData.zeroLine, - this.constants.spline - ) - ); - } + return animateElements; + }, + }, - if (this.units.length) { - this.units.map((dot, i) => { - animateElements = animateElements.concat( - animateDot(dot, newXPos[i], newYPos[i]) - ); - }); - } + 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, + spline: c.spline, + }, + { + svgDefs: c.svgDefs, + zeroLine: data.zeroLine, + } + ); + } - return animateElements; - }, - }, + 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 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, + this.constants.spline + ) + ); + } + + 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); + 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); } diff --git a/src/js/utils/axis-chart-utils.js b/src/js/utils/axis-chart-utils.js index bc4a24d..f299a38 100644 --- a/src/js/utils/axis-chart-utils.js +++ b/src/js/utils/axis-chart-utils.js @@ -1,137 +1,140 @@ import { fillArray } from "../utils/helpers"; import { - DEFAULT_AXIS_CHART_TYPE, - AXIS_DATASET_CHART_TYPES, - DEFAULT_CHAR_WIDTH, - SERIES_LABEL_SPACE_RATIO, + DEFAULT_AXIS_CHART_TYPE, + AXIS_DATASET_CHART_TYPES, + DEFAULT_CHAR_WIDTH, + SERIES_LABEL_SPACE_RATIO, } from "../utils/constants"; export function dataPrep(data, type, config) { - data.labels = data.labels || []; + data.labels = data.labels || []; - let datasetLength = data.labels.length; + let datasetLength = data.labels.length; - // Datasets - let datasets = data.datasets; - let zeroArray = new Array(datasetLength).fill(0); - if (!datasets) { - // default - datasets = [ - { - values: zeroArray, - }, - ]; - } + // Datasets + let datasets = data.datasets; + let zeroArray = new Array(datasetLength).fill(0); + if (!datasets) { + // default + datasets = [ + { + values: zeroArray, + }, + ]; + } - datasets.map((d) => { - // Set values - if (!d.values) { - d.values = zeroArray; - } else { - // Check for non values - let vals = d.values; - vals = vals.map((val) => (!isNaN(val) ? val : 0)); + datasets.map((d) => { + // Set values + if (!d.values) { + d.values = zeroArray; + } else { + // Check for non values + let vals = d.values; + vals = vals.map((val) => (!isNaN(val) ? val : 0)); - // Trim or extend - if (vals.length > datasetLength) { - vals = vals.slice(0, datasetLength); - } if (config) { - vals = fillArray(vals, datasetLength - vals.length, null); - } else { - vals = fillArray(vals, datasetLength - vals.length, 0); - } - d.values = vals; - } + // Trim or extend + if (vals.length > datasetLength) { + vals = vals.slice(0, datasetLength); + } + if (config) { + vals = fillArray(vals, datasetLength - vals.length, null); + } else { + vals = fillArray(vals, datasetLength - vals.length, 0); + } + d.values = vals; + } - // Set type - if (!d.chartType) { - if (!AXIS_DATASET_CHART_TYPES.includes(type)) - type = DEFAULT_AXIS_CHART_TYPE; - d.chartType = type; - } - }); + // Set type + if (!d.chartType) { + if (!AXIS_DATASET_CHART_TYPES.includes(type)) + type = DEFAULT_AXIS_CHART_TYPE; + d.chartType = type; + } + }); - // Markers + // Markers - // Regions - // data.yRegions = data.yRegions || []; - if (data.yRegions) { - data.yRegions.map((d) => { - if (d.end < d.start) { - [d.start, d.end] = [d.end, d.start]; - } - }); - } + // Regions + // data.yRegions = data.yRegions || []; + if (data.yRegions) { + data.yRegions.map((d) => { + if (d.end < d.start) { + [d.start, d.end] = [d.end, d.start]; + } + }); + } - return data; + return data; } export function zeroDataPrep(realData) { - let datasetLength = realData.labels.length; - let zeroArray = new Array(datasetLength).fill(0); + let datasetLength = realData.labels.length; + let zeroArray = new Array(datasetLength).fill(0); - let zeroData = { - labels: realData.labels.slice(0, -1), - datasets: realData.datasets.map((d) => { - return { - name: "", - values: zeroArray.slice(0, -1), - chartType: d.chartType, - }; - }), - }; + let zeroData = { + labels: realData.labels.slice(0, -1), + datasets: realData.datasets.map((d) => { + const { axisID } = d; + return { + axisID, + name: "", + values: zeroArray.slice(0, -1), + chartType: d.chartType, + }; + }), + }; - if (realData.yMarkers) { - zeroData.yMarkers = [ - { - value: 0, - label: "", - }, - ]; - } + if (realData.yMarkers) { + zeroData.yMarkers = [ + { + value: 0, + label: "", + }, + ]; + } - if (realData.yRegions) { - zeroData.yRegions = [ - { - start: 0, - end: 0, - label: "", - }, - ]; - } + if (realData.yRegions) { + zeroData.yRegions = [ + { + start: 0, + end: 0, + label: "", + }, + ]; + } - return zeroData; + return zeroData; } export function getShortenedLabels(chartWidth, labels = [], isSeries = true) { - let allowedSpace = (chartWidth / labels.length) * SERIES_LABEL_SPACE_RATIO; - if (allowedSpace <= 0) allowedSpace = 1; - let allowedLetters = allowedSpace / DEFAULT_CHAR_WIDTH; + let allowedSpace = (chartWidth / labels.length) * SERIES_LABEL_SPACE_RATIO; + if (allowedSpace <= 0) allowedSpace = 1; + let allowedLetters = allowedSpace / DEFAULT_CHAR_WIDTH; - let seriesMultiple; - if (isSeries) { - // Find the maximum label length for spacing calculations - let maxLabelLength = Math.max(...labels.map((label) => label.length)); - seriesMultiple = Math.ceil(maxLabelLength / allowedLetters); - } + let seriesMultiple; + if (isSeries) { + // Find the maximum label length for spacing calculations + let maxLabelLength = Math.max(...labels.map((label) => label.length)); + seriesMultiple = Math.ceil(maxLabelLength / allowedLetters); + } - let calcLabels = labels.map((label, i) => { - label += ""; - if (label.length > allowedLetters) { - if (!isSeries) { - if (allowedLetters - 3 > 0) { - label = label.slice(0, allowedLetters - 3) + " ..."; - } else { - label = label.slice(0, allowedLetters) + ".."; - } - } else { - if (i % seriesMultiple !== 0 && i !== labels.length - 1) { - label = ""; - } - } - } - return label; - }); + let calcLabels = labels.map((label, i) => { + label += ""; + if (label.length > allowedLetters) { + if (!isSeries) { + if (allowedLetters - 3 > 0) { + label = label.slice(0, allowedLetters - 3) + " ..."; + } else { + label = label.slice(0, allowedLetters) + ".."; + } + } else { + if (i % seriesMultiple !== 0 && i !== labels.length - 1) { + label = ""; + } + } + } + return label; + }); - return calcLabels; + return calcLabels; } diff --git a/src/js/utils/draw.js b/src/js/utils/draw.js index e1a7902..5d2ce8e 100644 --- a/src/js/utils/draw.js +++ b/src/js/utils/draw.js @@ -1,164 +1,169 @@ import { - getBarHeightAndYAttr, - truncateString, - shortenLargeNumber, - getSplineCurvePointsStr, + getBarHeightAndYAttr, + truncateString, + shortenLargeNumber, + getSplineCurvePointsStr, } from "./draw-utils"; import { getStringWidth, isValidNumber, round } from "./helpers"; import { DOT_OVERLAY_SIZE_INCR } from "./constants"; export const AXIS_TICK_LENGTH = 6; const LABEL_MARGIN = 4; +const LABEL_WIDTH = 25; +const TOTAL_PADDING = 120; const LABEL_MAX_CHARS = 18; export const FONT_SIZE = 10; const BASE_LINE_COLOR = "#E2E6E9"; function $(expr, con) { - return typeof expr === "string" - ? (con || document).querySelector(expr) - : expr || null; + return typeof expr === "string" + ? (con || document).querySelector(expr) + : expr || null; } export function createSVG(tag, o) { - var element = document.createElementNS("http://www.w3.org/2000/svg", tag); + var element = document.createElementNS("http://www.w3.org/2000/svg", tag); - for (var i in o) { - var val = o[i]; + for (var i in o) { + var val = o[i]; - if (i === "inside") { - $(val).appendChild(element); - } else if (i === "around") { - var ref = $(val); - ref.parentNode.insertBefore(element, ref); - element.appendChild(ref); - } else if (i === "styles") { - if (typeof val === "object") { - Object.keys(val).map((prop) => { - element.style[prop] = val[prop]; - }); - } - } else { - if (i === "className") { - i = "class"; - } - if (i === "innerHTML") { - element["textContent"] = val; - } else { - element.setAttribute(i, val); - } - } - } + if (i === "inside") { + $(val).appendChild(element); + } else if (i === "around") { + var ref = $(val); + ref.parentNode.insertBefore(element, ref); + element.appendChild(ref); + } else if (i === "styles") { + if (typeof val === "object") { + Object.keys(val).map((prop) => { + element.style[prop] = val[prop]; + }); + } + } else { + if (i === "className") { + i = "class"; + } + if (i === "innerHTML") { + element["textContent"] = val; + } else { + element.setAttribute(i, val); + } + } + } - return element; + return element; } function renderVerticalGradient(svgDefElem, gradientId) { - return createSVG("linearGradient", { - inside: svgDefElem, - id: gradientId, - x1: 0, - x2: 0, - y1: 0, - y2: 1, - }); + return createSVG("linearGradient", { + inside: svgDefElem, + id: gradientId, + x1: 0, + x2: 0, + y1: 0, + y2: 1, + }); } function setGradientStop(gradElem, offset, color, opacity) { - return createSVG("stop", { - inside: gradElem, - style: `stop-color: ${color}`, - offset: offset, - "stop-opacity": opacity, - }); + return createSVG("stop", { + inside: gradElem, + style: `stop-color: ${color}`, + offset: offset, + "stop-opacity": opacity, + }); } export function makeSVGContainer(parent, className, width, height) { - return createSVG("svg", { - className: className, - inside: parent, - width: width, - height: height, - }); + return createSVG("svg", { + className: className, + inside: parent, + width: width, + height: height, + }); } export function makeSVGDefs(svgContainer) { - return createSVG("defs", { - inside: svgContainer, - }); + return createSVG("defs", { + inside: svgContainer, + }); } export function makeSVGGroup(className, transform = "", parent = undefined) { - let args = { - className: className, - transform: transform, - }; - if (parent) args.inside = parent; - return createSVG("g", args); + let args = { + className: className, + transform: transform, + }; + if (parent) args.inside = parent; + return createSVG("g", args); } export function wrapInSVGGroup(elements, className = "") { - let g = createSVG("g", { - className: className, - }); - elements.forEach((e) => g.appendChild(e)); - return g; + let g = createSVG("g", { + className: className, + }); + elements.forEach((e) => g.appendChild(e)); + return g; } export function makePath( - pathStr, - className = "", - stroke = "none", - fill = "none", - strokeWidth = 2 + pathStr, + className = "", + stroke = "none", + fill = "none", + strokeWidth = 2 ) { - return createSVG("path", { - className: className, - d: pathStr, - styles: { - stroke: stroke, - fill: fill, - "stroke-width": strokeWidth, - }, - }); + return createSVG("path", { + className: className, + d: pathStr, + styles: { + stroke: stroke, + fill: fill, + "stroke-width": strokeWidth, + }, + }); } export function makeArcPathStr( - startPosition, - endPosition, - center, - radius, - clockWise = 1, - largeArc = 0 + startPosition, + endPosition, + center, + radius, + clockWise = 1, + largeArc = 0 ) { - let [arcStartX, arcStartY] = [ - center.x + startPosition.x, - center.y + startPosition.y, - ]; - let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y]; - return `M${center.x} ${center.y} + let [arcStartX, arcStartY] = [ + center.x + startPosition.x, + center.y + startPosition.y, + ]; + let [arcEndX, arcEndY] = [ + center.x + endPosition.x, + center.y + endPosition.y, + ]; + return `M${center.x} ${center.y} L${arcStartX} ${arcStartY} A ${radius} ${radius} 0 ${largeArc} ${clockWise ? 1 : 0} ${arcEndX} ${arcEndY} z`; } export function makeCircleStr( - startPosition, - endPosition, - center, - radius, - clockWise = 1, - largeArc = 0 + startPosition, + endPosition, + center, + radius, + clockWise = 1, + largeArc = 0 ) { - let [arcStartX, arcStartY] = [ - center.x + startPosition.x, - center.y + startPosition.y, - ]; - let [arcEndX, midArc, arcEndY] = [ - center.x + endPosition.x, - center.y * 2, - center.y + endPosition.y, - ]; - return `M${center.x} ${center.y} + let [arcStartX, arcStartY] = [ + center.x + startPosition.x, + center.y + startPosition.y, + ]; + let [arcEndX, midArc, arcEndY] = [ + center.x + endPosition.x, + center.y * 2, + center.y + endPosition.y, + ]; + return `M${center.x} ${center.y} L${arcStartX} ${arcStartY} A ${radius} ${radius} 0 ${largeArc} ${clockWise ? 1 : 0} ${arcEndX} ${midArc} z @@ -168,43 +173,46 @@ export function makeCircleStr( } export function makeArcStrokePathStr( - startPosition, - endPosition, - center, - radius, - clockWise = 1, - largeArc = 0 + startPosition, + endPosition, + center, + radius, + clockWise = 1, + largeArc = 0 ) { - let [arcStartX, arcStartY] = [ - center.x + startPosition.x, - center.y + startPosition.y, - ]; - let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y]; + let [arcStartX, arcStartY] = [ + center.x + startPosition.x, + center.y + startPosition.y, + ]; + let [arcEndX, arcEndY] = [ + center.x + endPosition.x, + center.y + endPosition.y, + ]; - return `M${arcStartX} ${arcStartY} + return `M${arcStartX} ${arcStartY} A ${radius} ${radius} 0 ${largeArc} ${clockWise ? 1 : 0} ${arcEndX} ${arcEndY}`; } export function makeStrokeCircleStr( - startPosition, - endPosition, - center, - radius, - clockWise = 1, - largeArc = 0 + startPosition, + endPosition, + center, + radius, + clockWise = 1, + largeArc = 0 ) { - let [arcStartX, arcStartY] = [ - center.x + startPosition.x, - center.y + startPosition.y, - ]; - let [arcEndX, midArc, arcEndY] = [ - center.x + endPosition.x, - radius * 2 + arcStartY, - center.y + startPosition.y, - ]; + let [arcStartX, arcStartY] = [ + center.x + startPosition.x, + center.y + startPosition.y, + ]; + let [arcEndX, midArc, arcEndY] = [ + center.x + endPosition.x, + radius * 2 + arcStartY, + center.y + startPosition.y, + ]; - return `M${arcStartX} ${arcStartY} + return `M${arcStartX} ${arcStartY} A ${radius} ${radius} 0 ${largeArc} ${clockWise ? 1 : 0} ${arcEndX} ${midArc} M${arcStartX} ${midArc} @@ -213,660 +221,720 @@ export function makeStrokeCircleStr( } export function makeGradient(svgDefElem, color, lighter = false) { - let gradientId = - "path-fill-gradient" + - "-" + - color + - "-" + - (lighter ? "lighter" : "default"); - let gradientDef = renderVerticalGradient(svgDefElem, gradientId); - let opacities = [1, 0.6, 0.2]; - if (lighter) { - opacities = [0.4, 0.2, 0]; - } + let gradientId = + "path-fill-gradient" + + "-" + + color + + "-" + + (lighter ? "lighter" : "default"); + let gradientDef = renderVerticalGradient(svgDefElem, gradientId); + let opacities = [1, 0.6, 0.2]; + if (lighter) { + opacities = [0.4, 0.2, 0]; + } - setGradientStop(gradientDef, "0%", color, opacities[0]); - setGradientStop(gradientDef, "50%", color, opacities[1]); - setGradientStop(gradientDef, "100%", color, opacities[2]); + setGradientStop(gradientDef, "0%", color, opacities[0]); + setGradientStop(gradientDef, "50%", color, opacities[1]); + setGradientStop(gradientDef, "100%", color, opacities[2]); - return gradientId; + return gradientId; } export function rightRoundedBar(x, width, height) { - // https://medium.com/@dennismphil/one-side-rounded-rectangle-using-svg-fb31cf318d90 - let radius = height / 2; - let xOffset = width - radius; + // https://medium.com/@dennismphil/one-side-rounded-rectangle-using-svg-fb31cf318d90 + let radius = height / 2; + let xOffset = width - radius; - return `M${x},0 h${xOffset} q${radius},0 ${radius},${radius} q0,${radius} -${radius},${radius} h-${xOffset} v${height}z`; + return `M${x},0 h${xOffset} q${radius},0 ${radius},${radius} q0,${radius} -${radius},${radius} h-${xOffset} v${height}z`; } export function leftRoundedBar(x, width, height) { - let radius = height / 2; - let xOffset = width - radius; + let radius = height / 2; + let xOffset = width - radius; - return `M${ - x + radius - },0 h${xOffset} v${height} h-${xOffset} q-${radius}, 0 -${radius},-${radius} q0,-${radius} ${radius},-${radius}z`; + return `M${ + x + radius + },0 h${xOffset} v${height} h-${xOffset} q-${radius}, 0 -${radius},-${radius} q0,-${radius} ${radius},-${radius}z`; } export function percentageBar( - x, - y, - width, - height, - isFirst, - isLast, - fill = "none" + x, + y, + width, + height, + isFirst, + isLast, + fill = "none" ) { - if (isLast) { - let pathStr = rightRoundedBar(x, width, height); - return makePath(pathStr, "percentage-bar", null, fill); - } + if (isLast) { + let pathStr = rightRoundedBar(x, width, height); + return makePath(pathStr, "percentage-bar", null, fill); + } - if (isFirst) { - let pathStr = leftRoundedBar(x, width, height); - return makePath(pathStr, "percentage-bar", null, fill); - } + if (isFirst) { + let pathStr = leftRoundedBar(x, width, height); + return makePath(pathStr, "percentage-bar", null, fill); + } - let args = { - className: "percentage-bar", - x: x, - y: y, - width: width, - height: height, - fill: fill, - }; + let args = { + className: "percentage-bar", + x: x, + y: y, + width: width, + height: height, + fill: fill, + }; - return createSVG("rect", args); + return createSVG("rect", args); } export function heatSquare( - className, - x, - y, - size, - radius, - fill = "none", - data = {} + className, + x, + y, + size, + radius, + fill = "none", + data = {} ) { - let args = { - className: className, - x: x, - y: y, - width: size, - height: size, - rx: radius, - fill: fill, - }; + let args = { + className: className, + x: x, + y: y, + width: size, + height: size, + rx: radius, + fill: fill, + }; - Object.keys(data).map((key) => { - args[key] = data[key]; - }); + Object.keys(data).map((key) => { + args[key] = data[key]; + }); - return createSVG("rect", args); + return createSVG("rect", args); } export function legendDot( - x, - y, - size, - radius, - fill = "none", - label, - value, - font_size = null, - truncate = false + x, + y, + size, + radius, + fill = "none", + label, + value, + font_size = null, + truncate = false ) { - label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label; - if (!font_size) font_size = FONT_SIZE; + label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label; + if (!font_size) font_size = FONT_SIZE; - let args = { - className: "legend-dot", - x: 0, - y: 4 - size, - height: size, - width: size, - rx: radius, - fill: fill, - }; + let args = { + className: "legend-dot", + x: 0, + y: 4 - size, + height: size, + width: size, + rx: radius, + fill: fill, + }; - let textLabel = createSVG("text", { - className: "legend-dataset-label", - x: size, - y: 0, - dx: font_size + "px", - dy: font_size / 3 + "px", - "font-size": font_size * 1.6 + "px", - "text-anchor": "start", - innerHTML: label, - }); + let textLabel = createSVG("text", { + className: "legend-dataset-label", + x: size, + y: 0, + dx: font_size + "px", + dy: font_size / 3 + "px", + "font-size": font_size * 1.6 + "px", + "text-anchor": "start", + innerHTML: label, + }); - let textValue = null; - if (value) { - textValue = createSVG("text", { - className: "legend-dataset-value", - x: size, - y: FONT_SIZE + 10, - dx: FONT_SIZE + "px", - dy: FONT_SIZE / 3 + "px", - "font-size": FONT_SIZE * 1.2 + "px", - "text-anchor": "start", - innerHTML: value, - }); - } + let textValue = null; + if (value) { + textValue = createSVG("text", { + className: "legend-dataset-value", + x: size, + y: FONT_SIZE + 10, + dx: FONT_SIZE + "px", + dy: FONT_SIZE / 3 + "px", + "font-size": FONT_SIZE * 1.2 + "px", + "text-anchor": "start", + innerHTML: value, + }); + } - let group = createSVG("g", { - transform: `translate(${x}, ${y})`, - }); - group.appendChild(createSVG("rect", args)); - group.appendChild(textLabel); + let group = createSVG("g", { + transform: `translate(${x}, ${y})`, + }); + group.appendChild(createSVG("rect", args)); + group.appendChild(textLabel); - if (value && textValue) { - group.appendChild(textValue); - } + if (value && textValue) { + group.appendChild(textValue); + } - return group; + return group; } export function makeText(className, x, y, content, options = {}) { - let fontSize = options.fontSize || FONT_SIZE; - let dy = options.dy !== undefined ? options.dy : fontSize / 2; - let fill = options.fill || "var(--charts-label-color)"; - let textAnchor = options.textAnchor || "start"; - return createSVG("text", { - className: className, - x: x, - y: y, - dy: dy + "px", - "font-size": fontSize + "px", - fill: fill, - "text-anchor": textAnchor, - innerHTML: content, - }); + let fontSize = options.fontSize || FONT_SIZE; + let dy = options.dy !== undefined ? options.dy : fontSize / 2; + let fill = options.fill || "var(--charts-label-color)"; + let textAnchor = options.textAnchor || "start"; + return createSVG("text", { + className: className, + x: x, + y: y, + dy: dy + "px", + "font-size": fontSize + "px", + fill: fill, + "text-anchor": textAnchor, + innerHTML: content, + }); } function makeVertLine(x, label, y1, y2, options = {}) { - let l = createSVG("line", { - className: "line-vertical " + options.className, - x1: 0, - x2: 0, - y1: y1, - y2: y2, - styles: { - stroke: options.stroke, - }, - }); + let l = createSVG("line", { + className: "line-vertical " + options.className, + x1: 0, + x2: 0, + y1: y1, + y2: y2, + styles: { + stroke: options.stroke, + }, + }); - let text = createSVG("text", { - x: 0, - y: y1 > y2 ? y1 + LABEL_MARGIN : y1 - LABEL_MARGIN - FONT_SIZE, - dy: FONT_SIZE + "px", - "font-size": FONT_SIZE + "px", - "text-anchor": "middle", - innerHTML: label + "", - }); + let text = createSVG("text", { + x: 0, + y: y1 > y2 ? y1 + LABEL_MARGIN : y1 - LABEL_MARGIN - FONT_SIZE, + dy: FONT_SIZE + "px", + "font-size": FONT_SIZE + "px", + "text-anchor": "middle", + innerHTML: label + "", + }); - let line = createSVG("g", { - transform: `translate(${x}, 0)`, - }); + let line = createSVG("g", { + transform: `translate(${x}, 0)`, + }); - line.appendChild(l); - line.appendChild(text); + line.appendChild(l); + line.appendChild(text); - return line; + return line; } function makeHoriLine(y, label, x1, x2, options = {}) { - if (!options.lineType) options.lineType = ""; - if (options.shortenNumbers) label = shortenLargeNumber(label); + if (!options.stroke) options.stroke = BASE_LINE_COLOR; + if (!options.lineType) options.lineType = ""; + if (!options.alignment) options.alignment = "left"; + if (options.shortenNumbers) label = shortenLargeNumber(label); - let className = - "line-horizontal " + - options.className + - (options.lineType === "dashed" ? "dashed" : ""); + let className = + "line-horizontal " + + options.className + + (options.lineType === "dashed" ? "dashed" : ""); - let l = createSVG("line", { - className: className, - x1: x1, - x2: x2, - y1: 0, - y2: 0, - styles: { - stroke: options.stroke, - }, - }); + const textXPos = + options.alignment === "left" + ? options.title + ? x1 - LABEL_MARGIN + LABEL_WIDTH + : x1 - LABEL_MARGIN + : options.title + ? x2 + LABEL_MARGIN * 4 - LABEL_WIDTH + : x2 + LABEL_MARGIN * 4; + const lineX1Post = options.title ? x1 + LABEL_WIDTH : x1; + const lineX2Post = options.title ? x2 - LABEL_WIDTH : x2; - let text = createSVG("text", { - x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN, - y: 0, - dy: FONT_SIZE / 2 - 2 + "px", - "font-size": FONT_SIZE + "px", - "text-anchor": x1 < x2 ? "end" : "start", - innerHTML: label + "", - }); + let l = createSVG("line", { + className: className, + x1: x1, + x2: x2, + y1: 0, + y2: 0, + styles: { + stroke: options.stroke, + }, + }); - let line = createSVG("g", { - transform: `translate(0, ${y})`, - "stroke-opacity": 1, - }); + let text = createSVG("text", { + x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN, + y: 0, + dy: FONT_SIZE / 2 - 2 + "px", + "font-size": FONT_SIZE + "px", + "text-anchor": x1 < x2 ? "end" : "start", + innerHTML: label + "", + }); - if (text === 0 || text === "0") { - line.style.stroke = "rgba(27, 31, 35, 0.6)"; - } + let line = createSVG("g", { + transform: `translate(0, ${y})`, + "stroke-opacity": 1, + }); - line.appendChild(l); - line.appendChild(text); + if (text === 0 || text === "0") { + line.style.stroke = "rgba(27, 31, 35, 0.6)"; + } - return line; + line.appendChild(l); + line.appendChild(text); + + return line; +} + +export function generateAxisLabel(options) { + if (!options.title) return; + + const y = + options.position === "left" + ? (options.height - TOTAL_PADDING) / 2 + + getStringWidth(options.title, 5) / 2 + : (options.height - TOTAL_PADDING) / 2 - + getStringWidth(options.title, 5) / 2; + const x = options.position === "left" ? 0 : options.width; + const y2 = + options.position === "left" + ? FONT_SIZE - LABEL_WIDTH + : FONT_SIZE + LABEL_WIDTH * -1; + + const rotation = + options.position === "right" ? `rotate(90)` : `rotate(270)`; + + const labelSvg = createSVG("text", { + className: "chart-label", + x: 0, // getStringWidth(options.title, 5) / 2, + y: 0, // y, + dy: `${y2}px`, + "font-size": `${FONT_SIZE}px`, + "text-anchor": "start", + innerHTML: `${options.title} `, + }); + + let wrapper = createSVG("g", { + x: 0, + y: 0, + transformBox: "fill-box", + transform: `translate(${x}, ${y}) ${rotation}`, + className: `test-${options.position}`, + }); + + wrapper.appendChild(labelSvg); + + return wrapper; } export function yLine(y, label, width, options = {}) { - if (!isValidNumber(y)) y = 0; + if (!isValidNumber(y)) y = 0; - if (!options.pos) options.pos = "left"; - if (!options.offset) options.offset = 0; - if (!options.mode) options.mode = "span"; - if (!options.stroke) options.stroke = BASE_LINE_COLOR; - if (!options.className) options.className = ""; + if (!options.pos) options.pos = "left"; + if (!options.offset) options.offset = 0; + if (!options.mode) options.mode = "span"; + if (!options.stroke) options.stroke = BASE_LINE_COLOR; + if (!options.className) options.className = ""; - let x1 = -1 * AXIS_TICK_LENGTH; - let x2 = options.mode === "span" ? width + AXIS_TICK_LENGTH : 0; + let x1 = -1 * AXIS_TICK_LENGTH; + let x2 = options.mode === "span" ? width + AXIS_TICK_LENGTH : 0; - if (options.mode === "tick" && options.pos === "right") { - x1 = width + AXIS_TICK_LENGTH; - x2 = width; - } + if (options.mode === "tick" && options.pos === "right") { + x1 = width + AXIS_TICK_LENGTH; + x2 = width; + } - // let offset = options.pos === 'left' ? -1 * options.offset : options.offset; + // let offset = options.pos === 'left' ? -1 * options.offset : options.offset; - x1 += options.offset; - x2 += options.offset; + // pr_366 + //x1 += offset; + //x2 += offset; + x1 += options.offset; + x2 += options.offset; - if (typeof label === "number") label = round(label); + if (typeof label === "number") label = round(label); - return makeHoriLine(y, label, x1, x2, { - className: options.className, - lineType: options.lineType, - shortenNumbers: options.shortenNumbers, - }); + return makeHoriLine(y, label, x1, x2, { + stroke: options.stroke, + className: options.className, + lineType: options.lineType, + alignment: options.pos, + title: options.title, + shortenNumbers: options.shortenNumbers, + }); } export function xLine(x, label, height, options = {}) { - if (!isValidNumber(x)) x = 0; + if (!isValidNumber(x)) x = 0; - if (!options.pos) options.pos = "bottom"; - if (!options.offset) options.offset = 0; - if (!options.mode) options.mode = "span"; - if (!options.className) options.className = ""; + if (!options.pos) options.pos = "bottom"; + if (!options.offset) options.offset = 0; + if (!options.mode) options.mode = "span"; + if (!options.className) options.className = ""; - // Draw X axis line in span/tick mode with optional label - // y2(span) - // | - // | - // x line | - // | - // | - // ---------------------+-- y2(tick) - // | - // y1 + // Draw X axis line in span/tick mode with optional label + // y2(span) + // | + // | + // x line | + // | + // | + // ---------------------+-- y2(tick) + // | + // y1 - let y1 = height + AXIS_TICK_LENGTH; - let y2 = options.mode === "span" ? -1 * AXIS_TICK_LENGTH : height; + let y1 = height + AXIS_TICK_LENGTH; + let y2 = options.mode === "span" ? -1 * AXIS_TICK_LENGTH : height; - if (options.mode === "tick" && options.pos === "top") { - // top axis ticks - y1 = -1 * AXIS_TICK_LENGTH; - y2 = 0; - } + if (options.mode === "tick" && options.pos === "top") { + // top axis ticks + y1 = -1 * AXIS_TICK_LENGTH; + y2 = 0; + } - return makeVertLine(x, label, y1, y2, { - className: options.className, - lineType: options.lineType, - }); + return makeVertLine(x, label, y1, y2, { + className: options.className, + lineType: options.lineType, + }); } export function yMarker(y, label, width, options = {}) { - if (!isValidNumber(y)) y = 0; + if (!isValidNumber(y)) y = 0; - if (!options.labelPos) options.labelPos = "right"; - if (!options.lineType) options.lineType = "dashed"; - let x = - options.labelPos === "left" - ? LABEL_MARGIN - : width - getStringWidth(label, 5) - LABEL_MARGIN; + if (!options.labelPos) options.labelPos = "right"; + if (!options.lineType) options.lineType = "dashed"; + let x = + options.labelPos === "left" + ? LABEL_MARGIN + : width - getStringWidth(label, 5) - LABEL_MARGIN; - let labelSvg = createSVG("text", { - className: "chart-label", - x: x, - y: 0, - dy: FONT_SIZE / -2 + "px", - "font-size": FONT_SIZE + "px", - "text-anchor": "start", - innerHTML: label + "", - }); + let labelSvg = createSVG("text", { + className: "chart-label", + x: x, + y: 0, + dy: FONT_SIZE / -2 + "px", + "font-size": FONT_SIZE + "px", + "text-anchor": "start", + innerHTML: label + "", + }); - let line = makeHoriLine(y, "", 0, width, { - stroke: options.stroke || BASE_LINE_COLOR, - className: options.className || "", - lineType: options.lineType, - }); + let line = makeHoriLine(y, "", 0, width, { + stroke: options.stroke || BASE_LINE_COLOR, + className: options.className || "", + lineType: options.lineType, + }); - line.appendChild(labelSvg); + line.appendChild(labelSvg); - return line; + return line; } export function yRegion(y1, y2, width, label, options = {}) { - // return a group - let height = y1 - y2; + // return a group + let height = y1 - y2; - let rect = createSVG("rect", { - className: `bar mini`, // remove class - styles: { - fill: options.fill || `rgba(228, 234, 239, 0.49)`, - stroke: options.stroke || BASE_LINE_COLOR, - "stroke-dasharray": `${width}, ${height}`, - }, - // 'data-point-index': index, - x: 0, - y: 0, - width: width, - height: height, - }); + let rect = createSVG("rect", { + className: `bar mini`, // remove class + styles: { + fill: options.fill || `rgba(228, 234, 239, 0.49)`, + stroke: options.stroke || BASE_LINE_COLOR, + "stroke-dasharray": `${width}, ${height}`, + }, + // 'data-point-index': index, + x: 0, + y: 0, + width: width, + height: height, + }); - if (!options.labelPos) options.labelPos = "right"; - let x = - options.labelPos === "left" - ? LABEL_MARGIN - : width - getStringWidth(label + "", 4.5) - LABEL_MARGIN; + if (!options.labelPos) options.labelPos = "right"; + let x = + options.labelPos === "left" + ? LABEL_MARGIN + : width - getStringWidth(label + "", 4.5) - LABEL_MARGIN; - let labelSvg = createSVG("text", { - className: "chart-label", - x: x, - y: 0, - dy: FONT_SIZE / -2 + "px", - "font-size": FONT_SIZE + "px", - "text-anchor": "start", - innerHTML: label + "", - }); + let labelSvg = createSVG("text", { + className: "chart-label", + x: x, + y: 0, + dy: FONT_SIZE / -2 + "px", + "font-size": FONT_SIZE + "px", + "text-anchor": "start", + innerHTML: label + "", + }); - let region = createSVG("g", { - transform: `translate(0, ${y2})`, - }); + let region = createSVG("g", { + transform: `translate(0, ${y2})`, + }); - region.appendChild(rect); - region.appendChild(labelSvg); + region.appendChild(rect); + region.appendChild(labelSvg); - return region; + return region; } export function datasetBar( - x, - yTop, - width, - color, - label = "", - index = 0, - offset = 0, - meta = {} + x, + yTop, + width, + color, + label = "", + index = 0, + offset = 0, + meta = {} ) { - let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); - y -= offset; + let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); + y -= offset; - if (height === 0) { - height = meta.minHeight; - y -= meta.minHeight; - } + if (height === 0) { + height = meta.minHeight; + y -= meta.minHeight; + } - // Preprocess numbers to avoid svg building errors - if (!isValidNumber(x)) x = 0; - if (!isValidNumber(y)) y = 0; - if (!isValidNumber(height, true)) height = 0; - if (!isValidNumber(width, true)) width = 0; + // Preprocess numbers to avoid svg building errors + if (!isValidNumber(x)) x = 0; + if (!isValidNumber(y)) y = 0; + if (!isValidNumber(height, true)) height = 0; + if (!isValidNumber(width, true)) width = 0; - // x y h w + // x y h w - // M{x},{y+r} - // q0,-{r} {r},-{r} - // q{r},0 {r},{r} - // v{h-r} - // h-{w}z + // M{x},{y+r} + // q0,-{r} {r},-{r} + // q{r},0 {r},{r} + // v{h-r} + // h-{w}z - // let radius = width/2; - // let pathStr = `M${x},${y+radius} q0,-${radius} ${radius},-${radius} q${radius},0 ${radius},${radius} v${height-radius} h-${width}z` + // let radius = width/2; + // let pathStr = `M${x},${y+radius} q0,-${radius} ${radius},-${radius} q${radius},0 ${radius},${radius} v${height-radius} h-${width}z` - // let rect = createSVG('path', { - // className: 'bar mini', - // d: pathStr, - // styles: { fill: color }, - // x: x, - // y: y, - // 'data-point-index': index, - // }); + // let rect = createSVG('path', { + // className: 'bar mini', + // d: pathStr, + // styles: { fill: color }, + // x: x, + // y: y, + // 'data-point-index': index, + // }); - let rect = createSVG("rect", { - className: `bar mini`, - style: `fill: ${color}`, - "data-point-index": index, - x: x, - y: y, - width: width, - height: height, - }); + let rect = createSVG("rect", { + className: `bar mini`, + style: `fill: ${color}`, + "data-point-index": index, + x: x, + y: y, + width: width, + height: height, + }); - label += ""; + label += ""; - if (!label && !label.length) { - return rect; - } else { - rect.setAttribute("y", 0); - rect.setAttribute("x", 0); - let text = createSVG("text", { - className: "data-point-value", - x: width / 2, - y: 0, - dy: (FONT_SIZE / 2) * -1 + "px", - "font-size": FONT_SIZE + "px", - "text-anchor": "middle", - innerHTML: label, - }); + if (!label && !label.length) { + return rect; + } else { + rect.setAttribute("y", 0); + rect.setAttribute("x", 0); + let text = createSVG("text", { + className: "data-point-value", + x: width / 2, + y: 0, + dy: (FONT_SIZE / 2) * -1 + "px", + "font-size": FONT_SIZE + "px", + "text-anchor": "middle", + innerHTML: label, + }); - let group = createSVG("g", { - "data-point-index": index, - transform: `translate(${x}, ${y})`, - }); - group.appendChild(rect); - group.appendChild(text); + let group = createSVG("g", { + "data-point-index": index, + transform: `translate(${x}, ${y})`, + }); + group.appendChild(rect); + group.appendChild(text); - return group; - } + return group; + } } export function datasetDot(x, y, radius, color, label = "", index = 0) { - let dot = createSVG("circle", { - style: `fill: ${color}`, - "data-point-index": index, - cx: x, - cy: y, - r: radius, - }); + let dot = createSVG("circle", { + style: `fill: ${color}`, + "data-point-index": index, + cx: x, + cy: y, + r: radius, + }); - label += ""; + label += ""; - if (!label && !label.length) { - return dot; - } else { - dot.setAttribute("cy", 0); - dot.setAttribute("cx", 0); + if (!label && !label.length) { + return dot; + } else { + dot.setAttribute("cy", 0); + dot.setAttribute("cx", 0); - let text = createSVG("text", { - className: "data-point-value", - x: 0, - y: 0, - dy: (FONT_SIZE / 2) * -1 - radius + "px", - "font-size": FONT_SIZE + "px", - "text-anchor": "middle", - innerHTML: label, - }); + let text = createSVG("text", { + className: "data-point-value", + x: 0, + y: 0, + dy: (FONT_SIZE / 2) * -1 - radius + "px", + "font-size": FONT_SIZE + "px", + "text-anchor": "middle", + innerHTML: label, + }); - let group = createSVG("g", { - "data-point-index": index, - transform: `translate(${x}, ${y})`, - }); - group.appendChild(dot); - group.appendChild(text); + let group = createSVG("g", { + "data-point-index": index, + transform: `translate(${x}, ${y})`, + }); + group.appendChild(dot); + group.appendChild(text); - return group; - } + return group; + } } export function getPaths(xList, yList, color, options = {}, meta = {}) { - let pointsList = yList.map((y, i) => xList[i] + "," + y); - let pointsStr = pointsList.join("L"); + let pointsList = yList.map((y, i) => xList[i] + "," + y); + let pointsStr = pointsList.join("L"); - // Spline - if (options.spline) pointsStr = getSplineCurvePointsStr(xList, yList); + // Spline + if (options.spline) pointsStr = getSplineCurvePointsStr(xList, yList); - let path = makePath("M" + pointsStr, "line-graph-path", color); + let path = makePath("M" + pointsStr, "line-graph-path", color); - // HeatLine - if (options.heatline) { - let gradient_id = makeGradient(meta.svgDefs, color); - path.style.stroke = `url(#${gradient_id})`; - } + // HeatLine + if (options.heatline) { + let gradient_id = makeGradient(meta.svgDefs, color); + path.style.stroke = `url(#${gradient_id})`; + } - let paths = { - path: path, - }; + let paths = { + path: path, + }; - // Region - if (options.regionFill) { - let gradient_id_region = makeGradient(meta.svgDefs, color, true); + // Region + if (options.regionFill) { + let gradient_id_region = makeGradient(meta.svgDefs, color, true); - let pathStr = - "M" + - `${xList[0]},${meta.zeroLine}L` + - pointsStr + - `L${xList.slice(-1)[0]},${meta.zeroLine}`; - paths.region = makePath( - pathStr, - `region-fill`, - "none", - `url(#${gradient_id_region})` - ); - } + let pathStr = + "M" + + `${xList[0]},${meta.zeroLine}L` + + pointsStr + + `L${xList.slice(-1)[0]},${meta.zeroLine}`; + paths.region = makePath( + pathStr, + `region-fill`, + "none", + `url(#${gradient_id_region})` + ); + } - 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"; + 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; - }, + 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"); - let fill = unit.getAttribute("fill"); - overlay.setAttribute("r", parseInt(radius) + DOT_OVERLAY_SIZE_INCR); - overlay.setAttribute("fill", fill); - overlay.style.opacity = "0.6"; + 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"); + let fill = unit.getAttribute("fill"); + overlay.setAttribute("r", parseInt(radius) + DOT_OVERLAY_SIZE_INCR); + overlay.setAttribute("fill", fill); + overlay.style.opacity = "0.6"; - if (transformValue) { - overlay.setAttribute("transform", transformValue); - } - return overlay; - }, + if (transformValue) { + overlay.setAttribute("transform", transformValue); + } + return overlay; + }, - heat_square: (unit) => { - let transformValue; - if (unit.nodeName !== "circle") { - transformValue = unit.getAttribute("transform"); - unit = unit.childNodes[0]; - } - let overlay = unit.cloneNode(); - let radius = unit.getAttribute("r"); - let fill = unit.getAttribute("fill"); - overlay.setAttribute("r", parseInt(radius) + DOT_OVERLAY_SIZE_INCR); - overlay.setAttribute("fill", fill); - overlay.style.opacity = "0.6"; + heat_square: (unit) => { + let transformValue; + if (unit.nodeName !== "circle") { + transformValue = unit.getAttribute("transform"); + unit = unit.childNodes[0]; + } + let overlay = unit.cloneNode(); + let radius = unit.getAttribute("r"); + let fill = unit.getAttribute("fill"); + overlay.setAttribute("r", parseInt(radius) + DOT_OVERLAY_SIZE_INCR); + overlay.setAttribute("fill", fill); + overlay.style.opacity = "0.6"; - if (transformValue) { - overlay.setAttribute("transform", transformValue); - } - return overlay; - }, + 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); - }); + 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); - } - }, + 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); - }); + 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); - } - }, + if (transformValue) { + overlay.setAttribute("transform", transformValue); + } + }, - heat_square: (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); - }); + heat_square: (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); - } - }, + if (transformValue) { + overlay.setAttribute("transform", transformValue); + } + }, };