Feat: Adding multi y-Axis support and configurable labes

This commit is contained in:
Kaleb White 2021-11-12 18:57:02 -08:00
parent 10de973608
commit 539bc50883
6 changed files with 1054 additions and 737 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -31,14 +31,31 @@ export default class AxisChart extends BaseChart {
configure(options) { configure(options) {
super.configure(options); super.configure(options);
const { axisOptions = {} } = options;
const { xAxis, yAxis } = axisOptions || {};
options.axisOptions = options.axisOptions || {};
options.tooltipOptions = options.tooltipOptions || {}; options.tooltipOptions = options.tooltipOptions || {};
this.config.xAxisMode = options.axisOptions.xAxisMode || 'span'; this.config.xAxisMode = xAxis ? xAxis.xAxisMode : axisOptions.xAxisMode || 'span';
this.config.yAxisMode = options.axisOptions.yAxisMode || 'span';
this.config.xIsSeries = options.axisOptions.xIsSeries || 0; // this will pass an array
this.config.shortenYAxisNumbers = options.axisOptions.shortenYAxisNumbers || 0; // lets determine if we need two yAxis based on if there is length
// to the yAxis array
if (yAxis && yAxis.length) {
this.config.yAxisConfig = yAxis.map((item) => {
return {
yAxisMode: item.yAxisMode,
id: item.id,
position: item.position,
title: item.title
};
});
} else {
this.config.yAxisMode = axisOptions.yAxisMode || 'span';
}
this.config.xIsSeries = axisOptions.xIsSeries || 0;
this.config.shortenYAxisNumbers = axisOptions.shortenYAxisNumbers || 0;
this.config.formatTooltipX = options.tooltipOptions.formatTooltipX; this.config.formatTooltipX = options.tooltipOptions.formatTooltipX;
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY; this.config.formatTooltipY = options.tooltipOptions.formatTooltipY;
@ -83,18 +100,61 @@ export default class AxisChart extends BaseChart {
}; };
} }
calcYAxisParameters(dataValues, withMinimum = 'false') {
const yPts = calcChartIntervals(dataValues, withMinimum);
const scaleMultiplier = this.height / getValueRange(yPts);
const intervalHeight = getIntervalSize(yPts) * scaleMultiplier;
const zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight);
calcYAxisParameters(dataValues, withMinimum = 'false') {
let yPts, scaleMultiplier, intervalHeight, zeroLine, positions;
// if we have an object we have multiple yAxisParameters.
if (dataValues instanceof Array) {
yPts = calcChartIntervals(dataValues, withMinimum);
scaleMultiplier = this.height / getValueRange(yPts);
intervalHeight = getIntervalSize(yPts) * scaleMultiplier;
zeroLine = this.height - getZeroIndex(yPts) * intervalHeight;
this.state.yAxis = { this.state.yAxis = {
labels: yPts, labels: yPts,
positions: yPts.map(d => zeroLine - d * scaleMultiplier), positions: yPts.map((d) => zeroLine - d * scaleMultiplier),
scaleMultiplier: scaleMultiplier, scaleMultiplier: scaleMultiplier,
zeroLine: zeroLine, zeroLine: zeroLine
}; };
} else {
this.state.yAxis = [];
for (let key in dataValues) {
const dataValue = dataValues[key];
yPts = calcChartIntervals(dataValue, withMinimum);
scaleMultiplier = this.height / getValueRange(yPts);
intervalHeight = getIntervalSize(yPts) * scaleMultiplier;
zeroLine = this.height - getZeroIndex(yPts) * intervalHeight;
positions = yPts.map((d) => zeroLine - d * scaleMultiplier);
const yAxisConfigObject =
this.config.yAxisConfig.find((item) => key === item.id) || [];
const yAxisAlignment = yAxisConfigObject
? yAxisConfigObject.position
: 'right';
if (this.state.yAxis.length) {
const yPtsArray = [];
const firstArr = this.state.yAxis[0];
// we need to loop through original positions.
firstArr.positions.forEach((pos) => {
yPtsArray.push(Math.ceil(pos / scaleMultiplier));
});
yPts = yPtsArray.reverse();
zeroLine = this.height - getZeroIndex(yPts) * intervalHeight;
positions = firstArr.positions;
}
this.state.yAxis.push({
axisID: key || 'left-axis',
labels: yPts,
title: yAxisConfigObject.title,
pos: yAxisAlignment,
scaleMultiplier,
zeroLine,
positions
});
}
}
// Dependent if above changes // Dependent if above changes
this.calcDatasetPoints(); this.calcDatasetPoints();
@ -104,21 +164,39 @@ export default class AxisChart extends BaseChart {
calcDatasetPoints() { calcDatasetPoints() {
let s = this.state; let s = this.state;
let scaleAll = values => values.map(val => scale(val, s.yAxis)); let scaleAll = (values, id) => {
return values.map((val) => {
let { yAxis } = s;
if (yAxis instanceof Array) {
yAxis = yAxis.length > 1 ? yAxis.find((axis) => id === axis.axisID) : s.yAxis[0];
}
return scale(val, yAxis);
});
};
s.barChartIndex = 1;
s.datasets = this.data.datasets.map((d, i) => { s.datasets = this.data.datasets.map((d, i) => {
let values = d.values; let values = d.values;
let cumulativeYs = d.cumulativeYs || []; let cumulativeYs = d.cumulativeYs || [];
return { return {
name: d.name && d.name.replace(/<|>|&/g, (char) => char == '&' ? '&amp;' : char == '<' ? '&lt;' : '&gt;'), name:
d.name &&
d.name.replace(/<|>|&/g, (char) =>
char == '&' ? '&amp;' : char == '<' ? '&lt;' : '&gt;'
),
index: i, index: i,
barIndex: d.chartType === 'bar' ? s.barChartIndex++ : s.barChartIndex,
chartType: d.chartType, chartType: d.chartType,
values: values, values: values,
yPositions: scaleAll(values), yPositions: scaleAll(values, d.axisID),
id: d.axisID,
cumulativeYs: cumulativeYs, cumulativeYs: cumulativeYs,
cumulativeYPos: scaleAll(cumulativeYs), cumulativeYPos: scaleAll(cumulativeYs, d.axisID)
}; };
}); });
} }
@ -163,44 +241,71 @@ export default class AxisChart extends BaseChart {
getAllYValues() { getAllYValues() {
let key = 'values'; let key = 'values';
let multiAxis = this.config.yAxisConfig ? true : false;
let allValueLists = multiAxis ? {} : [];
if(this.barOptions.stacked) { let groupBy = (arr, property) => {
key = 'cumulativeYs'; return arr.reduce((acc, cur) => {
acc[cur[property]] = [...(acc[cur[property]] || []), cur];
return acc;
}, {});
};
let generateCumulative = (arr) => {
let cumulative = new Array(this.state.datasetLength).fill(0); let cumulative = new Array(this.state.datasetLength).fill(0);
this.data.datasets.map((d, i) => { arr.forEach((d, i) => {
let values = this.data.datasets[i].values; let values = arr[i].values;
d[key] = cumulative = cumulative.map((c, i) => c + values[i]); d[key] = cumulative = cumulative.map((c, i) => {
return c + values[i];
});
});
};
if (this.barOptions.stacked) {
key = 'cumulativeYs';
// we need to filter out the different yAxis ID's here.
if (multiAxis) {
const groupedDataSets = groupBy(this.data.datasets, 'axisID');
// const dataSetsByAxis = this.data.dd
for (var axisID in groupedDataSets) {
generateCumulative(groupedDataSets[axisID]);
}
} else {
generateCumulative(this.data.datasets);
}
}
// this is the trouble maker, we don't want to merge all
// datasets since we are trying to run two yAxis.
if (multiAxis) {
this.data.datasets.forEach((d) => {
// if the array exists already just push more data into it.
// otherwise create a new array into the object.
allValueLists[d.axisID || key]
? allValueLists[d.axisID || key].push(...d[key])
: (allValueLists[d.axisID || key] = [...d[key]]);
});
} else {
allValueLists = this.data.datasets.map((d) => {
return d[key];
}); });
} }
let allValueLists = this.data.datasets.map(d => d[key]); if (this.data.yMarkers && !multiAxis) {
if(this.data.yMarkers) { allValueLists.push(this.data.yMarkers.map((d) => d.value));
allValueLists.push(this.data.yMarkers.map(d => d.value));
} }
if(this.data.yRegions) {
this.data.yRegions.map(d => { if (this.data.yRegions && !multiAxis) {
this.data.yRegions.map((d) => {
allValueLists.push([d.end, d.start]); allValueLists.push([d.end, d.start]);
}); });
} }
return [].concat(...allValueLists); return multiAxis ? allValueLists : [].concat(...allValueLists);
} }
setupComponents() { setupComponents() {
let componentConfigs = [ let componentConfigs = [
[
'yAxis',
{
mode: this.config.yAxisMode,
width: this.width,
shortenNumbers: this.config.shortenYAxisNumbers
// pos: 'right'
},
function() {
return this.state.yAxis;
}.bind(this)
],
[ [
'xAxis', 'xAxis',
{ {
@ -229,11 +334,43 @@ export default class AxisChart extends BaseChart {
], ],
]; ];
// if we have multiple yAxisConfigs we need to update the yAxisDefault
// components to multiple yAxis components.
if (this.config.yAxisConfig && this.config.yAxisConfig.length) {
this.config.yAxisConfig.forEach((yAxis) => {
componentConfigs.push([
'yAxis',
{
mode: this.config.yAxisMode,
width: this.width,
shortenNumbers: this.config.shortenYAxisNumbers,
pos: yAxis.position || 'left'
},
function () {
return this.state.yAxis;
}.bind(this)
]);
});
} else {
componentConfigs.push([
'yAxis',
{
mode: this.config.yAxisMode,
width: this.width,
shortenNumbers: this.config.shortenYAxisNumbers
},
function () {
return this.state.yAxis;
}.bind(this)
]);
}
let barDatasets = this.state.datasets.filter(d => d.chartType === 'bar'); let barDatasets = this.state.datasets.filter(d => d.chartType === 'bar');
let lineDatasets = this.state.datasets.filter(d => d.chartType === 'line'); let lineDatasets = this.state.datasets.filter(d => d.chartType === 'line');
let barsConfigs = barDatasets.map(d => { let barsConfigs = barDatasets.map(d => {
let index = d.index; let index = d.index;
let barIndex = d.barIndex || index;
return [ return [
'barGraph' + '-' + d.index, 'barGraph' + '-' + d.index,
{ {
@ -247,29 +384,41 @@ export default class AxisChart extends BaseChart {
}, },
function() { function() {
let s = this.state; let s = this.state;
let { yAxis } = s;
let d = s.datasets[index]; let d = s.datasets[index];
let { id = 'left-axis' } = d;
let stacked = this.barOptions.stacked; let stacked = this.barOptions.stacked;
let spaceRatio = this.barOptions.spaceRatio || BAR_CHART_SPACE_RATIO; let spaceRatio = this.barOptions.spaceRatio || BAR_CHART_SPACE_RATIO;
let barsWidth = s.unitWidth * (1 - spaceRatio); let barsWidth = s.unitWidth * (1 - spaceRatio);
let barWidth = barsWidth/(stacked ? 1 : barDatasets.length); let barWidth = barsWidth / (stacked ? 1 : barDatasets.length);
let xPositions = s.xAxis.positions.map(x => x - barsWidth/2); // if there are multiple yAxis we need to return the yAxis with the
if(!stacked) { // proper ID.
xPositions = xPositions.map(p => p + barWidth * index); if (yAxis instanceof Array) {
// if the person only configured one yAxis in the array return the first.
yAxis = yAxis.length > 1 ? yAxis.find((axis) => id === axis.axisID) : s.yAxis[0];
}
let xPositions = s.xAxis.positions.map((x) => x - barsWidth / 2);
if (!stacked) {
xPositions = xPositions.map((p) => {
return p + barWidth * barIndex - barWidth;
});
} }
let labels = new Array(s.datasetLength).fill(''); let labels = new Array(s.datasetLength).fill('');
if(this.config.valuesOverPoints) { if (this.config.valuesOverPoints) {
if(stacked && d.index === s.datasets.length - 1) { if (stacked && d.index === s.datasets.length - 1) {
labels = d.cumulativeYs; labels = d.cumulativeYs;
} else { } else {
labels = d.values; labels = d.values;
} }
} }
let offsets = new Array(s.datasetLength).fill(0); let offsets = new Array(s.datasetLength).fill(0);
if(stacked) { if (stacked) {
offsets = d.yPositions.map((y, j) => y - d.cumulativeYPos[j]); offsets = d.yPositions.map((y, j) => y - d.cumulativeYPos[j]);
} }
@ -280,7 +429,7 @@ export default class AxisChart extends BaseChart {
// values: d.values, // values: d.values,
labels: labels, labels: labels,
zeroLine: s.yAxis.zeroLine, zeroLine: yAxis.zeroLine,
barsWidth: barsWidth, barsWidth: barsWidth,
barWidth: barWidth, barWidth: barWidth,
}; };
@ -288,7 +437,7 @@ export default class AxisChart extends BaseChart {
]; ];
}); });
let lineConfigs = lineDatasets.map(d => { let lineConfigs = lineDatasets.map((d) => {
let index = d.index; let index = d.index;
return [ return [
'lineGraph' + '-' + d.index, 'lineGraph' + '-' + d.index,
@ -303,13 +452,21 @@ export default class AxisChart extends BaseChart {
hideLine: this.lineOptions.hideLine, hideLine: this.lineOptions.hideLine,
// same for all datasets // same for all datasets
valuesOverPoints: this.config.valuesOverPoints, valuesOverPoints: this.config.valuesOverPoints
}, },
function() { function () {
let s = this.state; let s = this.state;
let d = s.datasets[index]; let d = s.datasets[index];
let minLine = s.yAxis.positions[0] < s.yAxis.zeroLine
? s.yAxis.positions[0] : s.yAxis.zeroLine; // if we have more than one yindex lets map the values
const yAxis = s.yAxis.length
? s.yAxis.find((axis) => d.id === axis.axisID) || s.yAxis[0]
: s.yAxis;
let minLine =
yAxis.positions[0] < yAxis.zeroLine
? yAxis.positions[0]
: yAxis.zeroLine;
return { return {
xPositions: s.xAxis.positions, xPositions: s.xAxis.positions,
@ -318,7 +475,7 @@ export default class AxisChart extends BaseChart {
values: d.values, values: d.values,
zeroLine: minLine, zeroLine: minLine,
radius: this.lineOptions.dotSize || LINE_CHART_DOT_SIZE, radius: this.lineOptions.dotSize || LINE_CHART_DOT_SIZE
}; };
}.bind(this) }.bind(this)
]; ];

View File

@ -1,5 +1,5 @@
import { makeSVGGroup } from '../utils/draw'; import { makeSVGGroup } from '../utils/draw';
import { makeText, makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, percentageBar, getPaths, heatSquare } from '../utils/draw'; import { makeText, generateAxisLabel, makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, percentageBar, getPaths, heatSquare } from '../utils/draw';
import { equilizeNoOfElements } from '../utils/draw-utils'; import { equilizeNoOfElements } from '../utils/draw-utils';
import { translateHoriLine, translateVertLine, animateRegion, animateBar, import { translateHoriLine, translateVertLine, animateRegion, animateBar,
animateDot, animatePath, animatePathStr } from '../utils/animate'; animateDot, animatePath, animatePathStr } from '../utils/animate';
@ -50,8 +50,12 @@ class ChartComponent {
this.store = this.makeElements(data); this.store = this.makeElements(data);
this.layer.textContent = ''; this.layer.textContent = '';
this.store.forEach(element => { this.store.forEach((element) => {
this.layer.appendChild(element); element.length
? element.forEach((el) => {
this.layer.appendChild(el);
})
: this.layer.appendChild(element);
}); });
this.labels.forEach(element => { this.labels.forEach(element => {
this.layer.appendChild(element); this.layer.appendChild(element);
@ -117,13 +121,72 @@ let componentConfigs = {
yAxis: { yAxis: {
layerClass: 'y axis', layerClass: 'y axis',
makeElements(data) { makeElements(data) {
return data.positions.map((position, i) => let elements = [];
yLine(position, data.labels[i], this.constants.width,
{mode: this.constants.mode, pos: this.constants.pos, shortenNumbers: this.constants.shortenNumbers}) 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
})
); );
});
// we need to make yAxis titles if they are defined
if (item.title) {
elements.push(
generateAxisLabel({
title: item.title,
position: item.pos,
height: item.zeroLine,
width: this.constants.width
})
);
}
});
return elements;
}
return data.positions.map((position, i) => {
return yLine(position, data.labels[i], this.constants.width, {
mode: this.constants.mode,
pos: this.constants.pos,
shortenNumbers: this.constants.shortenNumbers
});
});
}, },
animateElements(newData) { animateElements(newData) {
const animateMultipleElements = (oldData, newData) => {
let newPos = newData.positions;
let newLabels = newData.labels;
let oldPos = oldData.positions;
let oldLabels = 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]);
});
};
// 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]);
});
}
let newPos = newData.positions; let newPos = newData.positions;
let newLabels = newData.labels; let newLabels = newData.labels;
let oldPos = this.oldData.positions; let oldPos = this.oldData.positions;
@ -138,9 +201,7 @@ let componentConfigs = {
}); });
return this.store.map((line, i) => { return this.store.map((line, i) => {
return translateHoriLine( return translateHoriLine(line, newPos[i], oldPos[i]);
line, newPos[i], oldPos[i]
);
}); });
} }
}, },

View File

@ -63,13 +63,15 @@ export function zeroDataPrep(realData) {
let zeroData = { let zeroData = {
labels: realData.labels.slice(0, -1), labels: realData.labels.slice(0, -1),
datasets: realData.datasets.map(d => { datasets: realData.datasets.map((d) => {
const { axisID } = d;
return { return {
axisID,
name: '', name: '',
values: zeroArray.slice(0, -1), values: zeroArray.slice(0, -1),
chartType: d.chartType chartType: d.chartType
}; };
}), })
}; };
if(realData.yMarkers) { if(realData.yMarkers) {

View File

@ -1,4 +1,9 @@
import { getBarHeightAndYAttr, truncateString, shortenLargeNumber, getSplineCurvePointsStr } from './draw-utils'; import {
getBarHeightAndYAttr,
truncateString,
shortenLargeNumber,
getSplineCurvePointsStr
} from './draw-utils';
import { getStringWidth, isValidNumber } from './helpers'; import { getStringWidth, isValidNumber } from './helpers';
import { DOT_OVERLAY_SIZE_INCR, PERCENTAGE_BAR_DEFAULT_DEPTH } from './constants'; import { DOT_OVERLAY_SIZE_INCR, PERCENTAGE_BAR_DEFAULT_DEPTH } from './constants';
import { lightenDarkenColor } from './colors'; import { lightenDarkenColor } from './colors';
@ -11,32 +16,32 @@ const BASE_LINE_COLOR = '#dadada';
const FONT_FILL = '#555b51'; const FONT_FILL = '#555b51';
function $(expr, con) { function $(expr, con) {
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; return typeof expr === 'string' ? (con || document).querySelector(expr) : expr || null;
} }
export function createSVG(tag, o) { 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) { for (var i in o) {
var val = o[i]; var val = o[i];
if (i === "inside") { if (i === 'inside') {
$(val).appendChild(element); $(val).appendChild(element);
} } else if (i === 'around') {
else if (i === "around") {
var ref = $(val); var ref = $(val);
ref.parentNode.insertBefore(element, ref); ref.parentNode.insertBefore(element, ref);
element.appendChild(ref); element.appendChild(ref);
} else if (i === 'styles') {
} else if (i === "styles") { if (typeof val === 'object') {
if(typeof val === "object") { Object.keys(val).map((prop) => {
Object.keys(val).map(prop => {
element.style[prop] = val[prop]; element.style[prop] = val[prop];
}); });
} }
} else { } else {
if(i === "className") { i = "class"; } if (i === 'className') {
if(i === "innerHTML") { i = 'class';
}
if (i === 'innerHTML') {
element['textContent'] = val; element['textContent'] = val;
} else { } else {
element.setAttribute(i, val); element.setAttribute(i, val);
@ -60,9 +65,9 @@ function renderVerticalGradient(svgDefElem, gradientId) {
function setGradientStop(gradElem, offset, color, opacity) { function setGradientStop(gradElem, offset, color, opacity) {
return createSVG('stop', { return createSVG('stop', {
'inside': gradElem, inside: gradElem,
'style': `stop-color: ${color}`, style: `stop-color: ${color}`,
'offset': offset, offset: offset,
'stop-opacity': opacity 'stop-opacity': opacity
}); });
} }
@ -78,28 +83,34 @@ export function makeSVGContainer(parent, className, width, height) {
export function makeSVGDefs(svgContainer) { export function makeSVGDefs(svgContainer) {
return createSVG('defs', { return createSVG('defs', {
inside: svgContainer, inside: svgContainer
}); });
} }
export function makeSVGGroup(className, transform='', parent=undefined) { export function makeSVGGroup(className, transform = '', parent = undefined) {
let args = { let args = {
className: className, className: className,
transform: transform transform: transform
}; };
if(parent) args.inside = parent; if (parent) args.inside = parent;
return createSVG('g', args); return createSVG('g', args);
} }
export function wrapInSVGGroup(elements, className='') { export function wrapInSVGGroup(elements, className = '') {
let g = createSVG('g', { let g = createSVG('g', {
className: className className: className
}); });
elements.forEach(e => g.appendChild(e)); elements.forEach((e) => g.appendChild(e));
return g; return g;
} }
export function makePath(pathStr, className='', stroke='none', fill='none', strokeWidth=2) { export function makePath(
pathStr,
className = '',
stroke = 'none',
fill = 'none',
strokeWidth = 2
) {
return createSVG('path', { return createSVG('path', {
className: className, className: className,
d: pathStr, d: pathStr,
@ -111,7 +122,14 @@ export function makePath(pathStr, className='', stroke='none', fill='none', stro
}); });
} }
export function makeArcPathStr(startPosition, endPosition, center, radius, clockWise=1, largeArc=0){ export function makeArcPathStr(
startPosition,
endPosition,
center,
radius,
clockWise = 1,
largeArc = 0
) {
let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y]; let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y];
let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y]; let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y];
return `M${center.x} ${center.y} return `M${center.x} ${center.y}
@ -120,9 +138,20 @@ export function makeArcPathStr(startPosition, endPosition, center, radius, clock
${arcEndX} ${arcEndY} z`; ${arcEndX} ${arcEndY} z`;
} }
export function makeCircleStr(startPosition, endPosition, center, radius, clockWise=1, largeArc=0){ export function makeCircleStr(
startPosition,
endPosition,
center,
radius,
clockWise = 1,
largeArc = 0
) {
let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.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]; let [arcEndX, midArc, arcEndY] = [
center.x + endPosition.x,
center.y * 2,
center.y + endPosition.y
];
return `M${center.x} ${center.y} return `M${center.x} ${center.y}
L${arcStartX} ${arcStartY} L${arcStartX} ${arcStartY}
A ${radius} ${radius} 0 ${largeArc} ${clockWise ? 1 : 0} A ${radius} ${radius} 0 ${largeArc} ${clockWise ? 1 : 0}
@ -132,7 +161,14 @@ export function makeCircleStr(startPosition, endPosition, center, radius, clockW
${arcEndX} ${arcEndY} z`; ${arcEndX} ${arcEndY} z`;
} }
export function makeArcStrokePathStr(startPosition, endPosition, center, radius, clockWise=1, largeArc=0){ export function makeArcStrokePathStr(
startPosition,
endPosition,
center,
radius,
clockWise = 1,
largeArc = 0
) {
let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y]; let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y];
let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y]; let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y];
@ -141,9 +177,20 @@ export function makeArcStrokePathStr(startPosition, endPosition, center, radius,
${arcEndX} ${arcEndY}`; ${arcEndX} ${arcEndY}`;
} }
export function makeStrokeCircleStr(startPosition, endPosition, center, radius, clockWise=1, largeArc=0){ export function makeStrokeCircleStr(
startPosition,
endPosition,
center,
radius,
clockWise = 1,
largeArc = 0
) {
let [arcStartX, arcStartY] = [center.x + startPosition.x, 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]; 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} A ${radius} ${radius} 0 ${largeArc} ${clockWise ? 1 : 0}
@ -154,23 +201,29 @@ export function makeStrokeCircleStr(startPosition, endPosition, center, radius,
} }
export function makeGradient(svgDefElem, color, lighter = false) { export function makeGradient(svgDefElem, color, lighter = false) {
let gradientId ='path-fill-gradient' + '-' + color + '-' +(lighter ? 'lighter' : 'default'); let gradientId =
'path-fill-gradient' + '-' + color + '-' + (lighter ? 'lighter' : 'default');
let gradientDef = renderVerticalGradient(svgDefElem, gradientId); let gradientDef = renderVerticalGradient(svgDefElem, gradientId);
let opacities = [1, 0.6, 0.2]; let opacities = [1, 0.6, 0.2];
if(lighter) { if (lighter) {
opacities = [0.4, 0.2, 0]; opacities = [0.4, 0.2, 0];
} }
setGradientStop(gradientDef, "0%", color, opacities[0]); setGradientStop(gradientDef, '0%', color, opacities[0]);
setGradientStop(gradientDef, "50%", color, opacities[1]); setGradientStop(gradientDef, '50%', color, opacities[1]);
setGradientStop(gradientDef, "100%", color, opacities[2]); setGradientStop(gradientDef, '100%', color, opacities[2]);
return gradientId; return gradientId;
} }
export function percentageBar(x, y, width, height, export function percentageBar(
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') { x,
y,
width,
height,
depth = PERCENTAGE_BAR_DEFAULT_DEPTH,
fill = 'none'
) {
let args = { let args = {
className: 'percentage-bar', className: 'percentage-bar',
x: x, x: x,
@ -179,18 +232,18 @@ export function percentageBar(x, y, width, height,
height: height, height: height,
fill: fill, fill: fill,
styles: { styles: {
'stroke': lightenDarkenColor(fill, -25), stroke: lightenDarkenColor(fill, -25),
// Diabolically good: https://stackoverflow.com/a/9000859 // Diabolically good: https://stackoverflow.com/a/9000859
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray
'stroke-dasharray': `0, ${height + width}, ${width}, ${height}`, 'stroke-dasharray': `0, ${height + width}, ${width}, ${height}`,
'stroke-width': depth 'stroke-width': depth
}, }
}; };
return createSVG("rect", args); return createSVG('rect', args);
} }
export function heatSquare(className, x, y, size, radius, fill='none', data={}) { export function heatSquare(className, x, y, size, radius, fill = 'none', data = {}) {
let args = { let args = {
className: className, className: className,
x: x, x: x,
@ -201,14 +254,14 @@ export function heatSquare(className, x, y, size, radius, fill='none', data={})
fill: fill fill: fill
}; };
Object.keys(data).map(key => { Object.keys(data).map((key) => {
args[key] = data[key]; args[key] = data[key];
}); });
return createSVG("rect", args); return createSVG('rect', args);
} }
export function legendBar(x, y, size, fill='none', label, truncate=false) { export function legendBar(x, y, size, fill = 'none', label, truncate = false) {
label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label; label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label;
let args = { let args = {
@ -223,8 +276,8 @@ export function legendBar(x, y, size, fill='none', label, truncate=false) {
className: 'legend-dataset-text', className: 'legend-dataset-text',
x: 0, x: 0,
y: 0, y: 0,
dy: (FONT_SIZE * 2) + 'px', dy: FONT_SIZE * 2 + 'px',
'font-size': (FONT_SIZE * 1.2) + 'px', 'font-size': FONT_SIZE * 1.2 + 'px',
'text-anchor': 'start', 'text-anchor': 'start',
fill: FONT_FILL, fill: FONT_FILL,
innerHTML: label innerHTML: label
@ -233,13 +286,13 @@ export function legendBar(x, y, size, fill='none', label, truncate=false) {
let group = createSVG('g', { let group = createSVG('g', {
transform: `translate(${x}, ${y})` transform: `translate(${x}, ${y})`
}); });
group.appendChild(createSVG("rect", args)); group.appendChild(createSVG('rect', args));
group.appendChild(text); group.appendChild(text);
return group; return group;
} }
export function legendDot(x, y, size, fill='none', label, truncate=false) { export function legendDot(x, y, size, fill = 'none', label, truncate = false) {
label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label; label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label;
let args = { let args = {
@ -253,9 +306,9 @@ export function legendDot(x, y, size, fill='none', label, truncate=false) {
className: 'legend-dataset-text', className: 'legend-dataset-text',
x: 0, x: 0,
y: 0, y: 0,
dx: (FONT_SIZE) + 'px', dx: FONT_SIZE + 'px',
dy: (FONT_SIZE/3) + 'px', dy: FONT_SIZE / 3 + 'px',
'font-size': (FONT_SIZE * 1.2) + 'px', 'font-size': FONT_SIZE * 1.2 + 'px',
'text-anchor': 'start', 'text-anchor': 'start',
fill: FONT_FILL, fill: FONT_FILL,
innerHTML: label innerHTML: label
@ -264,7 +317,7 @@ export function legendDot(x, y, size, fill='none', label, truncate=false) {
let group = createSVG('g', { let group = createSVG('g', {
transform: `translate(${x}, ${y})` transform: `translate(${x}, ${y})`
}); });
group.appendChild(createSVG("circle", args)); group.appendChild(createSVG('circle', args));
group.appendChild(text); group.appendChild(text);
return group; return group;
@ -272,7 +325,7 @@ export function legendDot(x, y, size, fill='none', label, truncate=false) {
export function makeText(className, x, y, content, options = {}) { export function makeText(className, x, y, content, options = {}) {
let fontSize = options.fontSize || FONT_SIZE; let fontSize = options.fontSize || FONT_SIZE;
let dy = options.dy !== undefined ? options.dy : (fontSize / 2); let dy = options.dy !== undefined ? options.dy : fontSize / 2;
let fill = options.fill || FONT_FILL; let fill = options.fill || FONT_FILL;
let textAnchor = options.textAnchor || 'start'; let textAnchor = options.textAnchor || 'start';
return createSVG('text', { return createSVG('text', {
@ -287,8 +340,8 @@ export function makeText(className, x, y, content, options = {}) {
}); });
} }
function makeVertLine(x, label, y1, y2, options={}) { function makeVertLine(x, label, y1, y2, options = {}) {
if(!options.stroke) options.stroke = BASE_LINE_COLOR; if (!options.stroke) options.stroke = BASE_LINE_COLOR;
let l = createSVG('line', { let l = createSVG('line', {
className: 'line-vertical ' + options.className, className: 'line-vertical ' + options.className,
x1: 0, x1: 0,
@ -306,11 +359,11 @@ function makeVertLine(x, label, y1, y2, options={}) {
dy: FONT_SIZE + 'px', dy: FONT_SIZE + 'px',
'font-size': FONT_SIZE + 'px', 'font-size': FONT_SIZE + 'px',
'text-anchor': 'middle', 'text-anchor': 'middle',
innerHTML: label + "" innerHTML: label + ''
}); });
let line = createSVG('g', { let line = createSVG('g', {
transform: `translate(${ x }, 0)` transform: `translate(${x}, 0)`
}); });
line.appendChild(l); line.appendChild(l);
@ -319,13 +372,16 @@ function makeVertLine(x, label, y1, y2, options={}) {
return line; return line;
} }
function makeHoriLine(y, label, x1, x2, options={}) { function makeHoriLine(y, label, x1, x2, options = {}) {
if(!options.stroke) options.stroke = BASE_LINE_COLOR; if (!options.stroke) options.stroke = BASE_LINE_COLOR;
if(!options.lineType) options.lineType = ''; if (!options.lineType) options.lineType = '';
if (!options.alignment) options.alignment = 'left';
if (options.shortenNumbers) label = shortenLargeNumber(label); if (options.shortenNumbers) label = shortenLargeNumber(label);
let className = 'line-horizontal ' + options.className + let className =
(options.lineType === "dashed" ? "dashed": ""); 'line-horizontal ' +
options.className +
(options.lineType === 'dashed' ? 'dashed' : '');
let l = createSVG('line', { let l = createSVG('line', {
className: className, className: className,
@ -339,12 +395,12 @@ function makeHoriLine(y, label, x1, x2, options={}) {
}); });
let text = createSVG('text', { let text = createSVG('text', {
x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN, x: options.alignment === 'left' ? x1 - LABEL_MARGIN : x2 + LABEL_MARGIN * 4,
y: 0, y: 0,
dy: (FONT_SIZE / 2 - 2) + 'px', dy: FONT_SIZE / 2 - 2 + 'px',
'font-size': FONT_SIZE + 'px', 'font-size': FONT_SIZE + 'px',
'text-anchor': x1 < x2 ? 'end' : 'start', 'text-anchor': x1 < x2 ? 'end' : 'start',
innerHTML: label+"" innerHTML: label + ''
}); });
let line = createSVG('g', { let line = createSVG('g', {
@ -352,8 +408,8 @@ function makeHoriLine(y, label, x1, x2, options={}) {
'stroke-opacity': 1 'stroke-opacity': 1
}); });
if(text === 0 || text === '0') { if (text === 0 || text === '0') {
line.style.stroke = "rgba(27, 31, 35, 0.6)"; line.style.stroke = 'rgba(27, 31, 35, 0.6)';
} }
line.appendChild(l); line.appendChild(l);
@ -362,44 +418,69 @@ function makeHoriLine(y, label, x1, x2, options={}) {
return line; return line;
} }
export function yLine(y, label, width, options={}) { export function generateAxisLabel(options) {
if (!options.title) return;
const x = options.position === 'left' ? LABEL_MARGIN : options.width;
// - getStringWidth(options.title, 5);
const rotation =
options.position === 'right'
? `rotate(90, ${options.width}, ${options.height / 2})`
: `rotate(270, 0, ${options.height / 2})`;
const labelSvg = createSVG('text', {
className: 'chart-label',
x: x - getStringWidth(options.title, 5) / 2,
y: options.height / 2 - LABEL_MARGIN,
dy: FONT_SIZE / -2 + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': 'start',
transform: rotation,
innerHTML: options.title + ''
});
return labelSvg;
}
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.pos) options.pos = 'left';
if(!options.offset) options.offset = 0; if (!options.offset) options.offset = 0;
if(!options.mode) options.mode = 'span'; if (!options.mode) options.mode = 'span';
if(!options.stroke) options.stroke = BASE_LINE_COLOR; if (!options.stroke) options.stroke = BASE_LINE_COLOR;
if(!options.className) options.className = ''; if (!options.className) options.className = '';
let x1 = -1 * AXIS_TICK_LENGTH; let x1 = -1 * AXIS_TICK_LENGTH;
let x2 = options.mode === 'span' ? width + AXIS_TICK_LENGTH : 0; let x2 = options.mode === 'span' ? width + AXIS_TICK_LENGTH : 0;
if(options.mode === 'tick' && options.pos === 'right') { if (options.mode === 'tick' && options.pos === 'right') {
x1 = width + AXIS_TICK_LENGTH; x1 = width + AXIS_TICK_LENGTH;
x2 = width; 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; x1 += offset;
x2 += options.offset; x2 += offset;
return makeHoriLine(y, label, x1, x2, { return makeHoriLine(y, label, x1, x2, {
stroke: options.stroke, stroke: options.stroke,
className: options.className, className: options.className,
lineType: options.lineType, lineType: options.lineType,
alignment: options.pos,
shortenNumbers: options.shortenNumbers shortenNumbers: options.shortenNumbers
}); });
} }
export function xLine(x, label, height, options={}) { 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.pos) options.pos = 'bottom';
if(!options.offset) options.offset = 0; if (!options.offset) options.offset = 0;
if(!options.mode) options.mode = 'span'; if (!options.mode) options.mode = 'span';
if(!options.stroke) options.stroke = BASE_LINE_COLOR; if (!options.stroke) options.stroke = BASE_LINE_COLOR;
if(!options.className) options.className = ''; if (!options.className) options.className = '';
// Draw X axis line in span/tick mode with optional label // Draw X axis line in span/tick mode with optional label
// y2(span) // y2(span)
@ -415,7 +496,7 @@ export function xLine(x, label, height, options={}) {
let y1 = height + AXIS_TICK_LENGTH; let y1 = height + AXIS_TICK_LENGTH;
let y2 = options.mode === 'span' ? -1 * AXIS_TICK_LENGTH : height; let y2 = options.mode === 'span' ? -1 * AXIS_TICK_LENGTH : height;
if(options.mode === 'tick' && options.pos === 'top') { if (options.mode === 'tick' && options.pos === 'top') {
// top axis ticks // top axis ticks
y1 = -1 * AXIS_TICK_LENGTH; y1 = -1 * AXIS_TICK_LENGTH;
y2 = 0; y2 = 0;
@ -428,19 +509,21 @@ export function xLine(x, label, height, options={}) {
}); });
} }
export function yMarker(y, label, width, options={}) { export function yMarker(y, label, width, options = {}) {
if(!options.labelPos) options.labelPos = 'right'; if (!options.labelPos) options.labelPos = 'right';
let x = options.labelPos === 'left' ? LABEL_MARGIN let x =
options.labelPos === 'left'
? LABEL_MARGIN
: width - getStringWidth(label, 5) - LABEL_MARGIN; : width - getStringWidth(label, 5) - LABEL_MARGIN;
let labelSvg = createSVG('text', { let labelSvg = createSVG('text', {
className: 'chart-label', className: 'chart-label',
x: x, x: x,
y: 0, y: 0,
dy: (FONT_SIZE / -2) + 'px', dy: FONT_SIZE / -2 + 'px',
'font-size': FONT_SIZE + 'px', 'font-size': FONT_SIZE + 'px',
'text-anchor': 'start', 'text-anchor': 'start',
innerHTML: label+"" innerHTML: label + ''
}); });
let line = makeHoriLine(y, '', 0, width, { let line = makeHoriLine(y, '', 0, width, {
@ -454,7 +537,7 @@ export function yMarker(y, label, width, options={}) {
return line; return line;
} }
export function yRegion(y1, y2, width, label, options={}) { export function yRegion(y1, y2, width, label, options = {}) {
// return a group // return a group
let height = y1 - y2; let height = y1 - y2;
@ -472,18 +555,20 @@ export function yRegion(y1, y2, width, label, options={}) {
height: height height: height
}); });
if(!options.labelPos) options.labelPos = 'right'; if (!options.labelPos) options.labelPos = 'right';
let x = options.labelPos === 'left' ? LABEL_MARGIN let x =
: width - getStringWidth(label+"", 4.5) - LABEL_MARGIN; options.labelPos === 'left'
? LABEL_MARGIN
: width - getStringWidth(label + '', 4.5) - LABEL_MARGIN;
let labelSvg = createSVG('text', { let labelSvg = createSVG('text', {
className: 'chart-label', className: 'chart-label',
x: x, x: x,
y: 0, y: 0,
dy: (FONT_SIZE / -2) + 'px', dy: FONT_SIZE / -2 + 'px',
'font-size': FONT_SIZE + 'px', 'font-size': FONT_SIZE + 'px',
'text-anchor': 'start', 'text-anchor': 'start',
innerHTML: label+"" innerHTML: label + ''
}); });
let region = createSVG('g', { let region = createSVG('g', {
@ -496,11 +581,20 @@ export function yRegion(y1, y2, width, label, options={}) {
return region; return region;
} }
export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) { export function datasetBar(
x,
yTop,
width,
color,
label = '',
index = 0,
offset = 0,
meta = {}
) {
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine);
y -= offset; y -= offset;
if(height === 0) { if (height === 0) {
height = meta.minHeight; height = meta.minHeight;
y -= meta.minHeight; y -= meta.minHeight;
} }
@ -521,18 +615,18 @@ export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, m
height: height height: height
}); });
label += ""; label += '';
if(!label && !label.length) { if (!label && !label.length) {
return rect; return rect;
} else { } else {
rect.setAttribute('y', 0); rect.setAttribute('y', 0);
rect.setAttribute('x', 0); rect.setAttribute('x', 0);
let text = createSVG('text', { let text = createSVG('text', {
className: 'data-point-value', className: 'data-point-value',
x: width/2, x: width / 2,
y: 0, y: 0,
dy: (FONT_SIZE / 2 * -1) + 'px', dy: (FONT_SIZE / 2) * -1 + 'px',
'font-size': FONT_SIZE + 'px', 'font-size': FONT_SIZE + 'px',
'text-anchor': 'middle', 'text-anchor': 'middle',
innerHTML: label innerHTML: label
@ -549,7 +643,7 @@ export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, m
} }
} }
export function datasetDot(x, y, radius, color, label='', index=0) { export function datasetDot(x, y, radius, color, label = '', index = 0) {
let dot = createSVG('circle', { let dot = createSVG('circle', {
style: `fill: ${color}`, style: `fill: ${color}`,
'data-point-index': index, 'data-point-index': index,
@ -558,9 +652,9 @@ export function datasetDot(x, y, radius, color, label='', index=0) {
r: radius r: radius
}); });
label += ""; label += '';
if(!label && !label.length) { if (!label && !label.length) {
return dot; return dot;
} else { } else {
dot.setAttribute('cy', 0); dot.setAttribute('cy', 0);
@ -570,7 +664,7 @@ export function datasetDot(x, y, radius, color, label='', index=0) {
className: 'data-point-value', className: 'data-point-value',
x: 0, x: 0,
y: 0, y: 0,
dy: (FONT_SIZE / 2 * -1 - radius) + 'px', dy: (FONT_SIZE / 2) * -1 - radius + 'px',
'font-size': FONT_SIZE + 'px', 'font-size': FONT_SIZE + 'px',
'text-anchor': 'middle', 'text-anchor': 'middle',
innerHTML: label innerHTML: label
@ -587,18 +681,17 @@ export function datasetDot(x, y, radius, color, label='', index=0) {
} }
} }
export function getPaths(xList, yList, color, options={}, meta={}) { export function getPaths(xList, yList, color, options = {}, meta = {}) {
let pointsList = yList.map((y, i) => (xList[i] + ',' + y)); let pointsList = yList.map((y, i) => xList[i] + ',' + y);
let pointsStr = pointsList.join("L"); let pointsStr = pointsList.join('L');
// Spline // Spline
if (options.spline) if (options.spline) pointsStr = getSplineCurvePointsStr(xList, yList);
pointsStr = getSplineCurvePointsStr(xList, yList);
let path = makePath("M"+pointsStr, 'line-graph-path', color); let path = makePath('M' + pointsStr, 'line-graph-path', color);
// HeatLine // HeatLine
if(options.heatline) { if (options.heatline) {
let gradient_id = makeGradient(meta.svgDefs, color); let gradient_id = makeGradient(meta.svgDefs, color);
path.style.stroke = `url(#${gradient_id})`; path.style.stroke = `url(#${gradient_id})`;
} }
@ -608,10 +701,14 @@ export function getPaths(xList, yList, color, options={}, meta={}) {
}; };
// Region // Region
if(options.regionFill) { if (options.regionFill) {
let gradient_id_region = makeGradient(meta.svgDefs, color, true); 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}`; 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})`); paths.region = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id_region})`);
} }
@ -619,9 +716,9 @@ export function getPaths(xList, yList, color, options={}, meta={}) {
} }
export let makeOverlay = { export let makeOverlay = {
'bar': (unit) => { bar: (unit) => {
let transformValue; let transformValue;
if(unit.nodeName !== 'rect') { if (unit.nodeName !== 'rect') {
transformValue = unit.getAttribute('transform'); transformValue = unit.getAttribute('transform');
unit = unit.childNodes[0]; unit = unit.childNodes[0];
} }
@ -629,15 +726,15 @@ export let makeOverlay = {
overlay.style.fill = '#000000'; overlay.style.fill = '#000000';
overlay.style.opacity = '0.4'; overlay.style.opacity = '0.4';
if(transformValue) { if (transformValue) {
overlay.setAttribute('transform', transformValue); overlay.setAttribute('transform', transformValue);
} }
return overlay; return overlay;
}, },
'dot': (unit) => { dot: (unit) => {
let transformValue; let transformValue;
if(unit.nodeName !== 'circle') { if (unit.nodeName !== 'circle') {
transformValue = unit.getAttribute('transform'); transformValue = unit.getAttribute('transform');
unit = unit.childNodes[0]; unit = unit.childNodes[0];
} }
@ -648,15 +745,15 @@ export let makeOverlay = {
overlay.setAttribute('fill', fill); overlay.setAttribute('fill', fill);
overlay.style.opacity = '0.6'; overlay.style.opacity = '0.6';
if(transformValue) { if (transformValue) {
overlay.setAttribute('transform', transformValue); overlay.setAttribute('transform', transformValue);
} }
return overlay; return overlay;
}, },
'heat_square': (unit) => { heat_square: (unit) => {
let transformValue; let transformValue;
if(unit.nodeName !== 'circle') { if (unit.nodeName !== 'circle') {
transformValue = unit.getAttribute('transform'); transformValue = unit.getAttribute('transform');
unit = unit.childNodes[0]; unit = unit.childNodes[0];
} }
@ -667,7 +764,7 @@ export let makeOverlay = {
overlay.setAttribute('fill', fill); overlay.setAttribute('fill', fill);
overlay.style.opacity = '0.6'; overlay.style.opacity = '0.6';
if(transformValue) { if (transformValue) {
overlay.setAttribute('transform', transformValue); overlay.setAttribute('transform', transformValue);
} }
return overlay; return overlay;
@ -675,57 +772,57 @@ export let makeOverlay = {
}; };
export let updateOverlay = { export let updateOverlay = {
'bar': (unit, overlay) => { bar: (unit, overlay) => {
let transformValue; let transformValue;
if(unit.nodeName !== 'rect') { if (unit.nodeName !== 'rect') {
transformValue = unit.getAttribute('transform'); transformValue = unit.getAttribute('transform');
unit = unit.childNodes[0]; unit = unit.childNodes[0];
} }
let attributes = ['x', 'y', 'width', 'height']; let attributes = ['x', 'y', 'width', 'height'];
Object.values(unit.attributes) Object.values(unit.attributes)
.filter(attr => attributes.includes(attr.name) && attr.specified) .filter((attr) => attributes.includes(attr.name) && attr.specified)
.map(attr => { .map((attr) => {
overlay.setAttribute(attr.name, attr.nodeValue); overlay.setAttribute(attr.name, attr.nodeValue);
}); });
if(transformValue) { if (transformValue) {
overlay.setAttribute('transform', transformValue); overlay.setAttribute('transform', transformValue);
} }
}, },
'dot': (unit, overlay) => { dot: (unit, overlay) => {
let transformValue; let transformValue;
if(unit.nodeName !== 'circle') { if (unit.nodeName !== 'circle') {
transformValue = unit.getAttribute('transform'); transformValue = unit.getAttribute('transform');
unit = unit.childNodes[0]; unit = unit.childNodes[0];
} }
let attributes = ['cx', 'cy']; let attributes = ['cx', 'cy'];
Object.values(unit.attributes) Object.values(unit.attributes)
.filter(attr => attributes.includes(attr.name) && attr.specified) .filter((attr) => attributes.includes(attr.name) && attr.specified)
.map(attr => { .map((attr) => {
overlay.setAttribute(attr.name, attr.nodeValue); overlay.setAttribute(attr.name, attr.nodeValue);
}); });
if(transformValue) { if (transformValue) {
overlay.setAttribute('transform', transformValue); overlay.setAttribute('transform', transformValue);
} }
}, },
'heat_square': (unit, overlay) => { heat_square: (unit, overlay) => {
let transformValue; let transformValue;
if(unit.nodeName !== 'circle') { if (unit.nodeName !== 'circle') {
transformValue = unit.getAttribute('transform'); transformValue = unit.getAttribute('transform');
unit = unit.childNodes[0]; unit = unit.childNodes[0];
} }
let attributes = ['cx', 'cy']; let attributes = ['cx', 'cy'];
Object.values(unit.attributes) Object.values(unit.attributes)
.filter(attr => attributes.includes(attr.name) && attr.specified) .filter((attr) => attributes.includes(attr.name) && attr.specified)
.map(attr => { .map((attr) => {
overlay.setAttribute(attr.name, attr.nodeValue); overlay.setAttribute(attr.name, attr.nodeValue);
}); });
if(transformValue) { if (transformValue) {
overlay.setAttribute('transform', transformValue); overlay.setAttribute('transform', transformValue);
} }
}, }
}; };