Merge commit '9f68f1ac5f7e8eb7a089d13b27d44fa0702d7931'

This commit is contained in:
Arjun Choudhary 2022-12-20 13:16:17 +05:30
commit 4dabb9d577
5 changed files with 891 additions and 613 deletions

1
.gitignore vendored
View File

@ -62,6 +62,7 @@ typings/
# npm build output # npm build output
dist dist
docs
docs/assets/ docs/assets/
.DS_Store .DS_Store

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -22,6 +22,7 @@ import {
MIN_BAR_PERCENT_HEIGHT, MIN_BAR_PERCENT_HEIGHT,
BAR_CHART_SPACE_RATIO, BAR_CHART_SPACE_RATIO,
LINE_CHART_DOT_SIZE, LINE_CHART_DOT_SIZE,
LEGEND_ITEM_WIDTH,
} from "../utils/constants"; } from "../utils/constants";
export default class AxisChart extends BaseChart { export default class AxisChart extends BaseChart {
@ -46,15 +47,40 @@ 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
this.config.yAxisMode = options.axisOptions.yAxisMode || "span"; ? xAxis.xAxisMode
this.config.xIsSeries = options.axisOptions.xIsSeries || 0; : axisOptions.xAxisMode || "span";
this.config.shortenYAxisNumbers =
options.axisOptions.shortenYAxisNumbers || 0; // this will pass an array
// 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 = yAxis
? yAxis.yAxisMode
: axisOptions.yAxisMode || "span";
// if we have yAxis config settings lets populate a yAxis config array.
if (yAxis && yAxis.id && yAxis.position) {
this.config.yAxisConfig = [yAxis];
}
}
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;
@ -74,7 +100,10 @@ export default class AxisChart extends BaseChart {
calc(onlyWidthChange = false) { calc(onlyWidthChange = false) {
this.calcXPositions(); this.calcXPositions();
if (!onlyWidthChange) { if (!onlyWidthChange) {
this.calcYAxisParameters(this.getAllYValues(), this.type === "line"); this.calcYAxisParameters(
this.getAllYValues(),
this.type === "line"
);
} }
this.makeDataByIndex(); this.makeDataByIndex();
} }
@ -94,22 +123,123 @@ export default class AxisChart extends BaseChart {
s.xAxis = { s.xAxis = {
labels: labels, labels: labels,
positions: labels.map((d, i) => floatTwo(s.xOffset + i * s.unitWidth)), positions: labels.map((d, i) =>
floatTwo(s.xOffset + i * s.unitWidth)
),
}; };
} }
calcYAxisParameters(dataValues, withMinimum = "false") { calcYAxisParameters(dataValues, withMinimum = "false") {
const yPts = calcChartIntervals(dataValues, withMinimum); let yPts,
const scaleMultiplier = this.height / getValueRange(yPts); scaleMultiplier,
const intervalHeight = getIntervalSize(yPts) * scaleMultiplier; intervalHeight,
const zeroLine = this.height - getZeroIndex(yPts) * intervalHeight; zeroLine,
positions,
yAxisConfigObject,
yAxisAlignment,
yKeys;
yKeys = [];
yAxisConfigObject = this.config.yAxisMode || {};
yAxisAlignment = yAxisConfigObject.position
? yAxisConfigObject.position
: "left";
// 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),
title: yAxisConfigObject.title || null,
pos: yAxisAlignment,
scaleMultiplier: scaleMultiplier, scaleMultiplier: scaleMultiplier,
zeroLine: zeroLine, zeroLine: zeroLine,
}; };
} else {
this.state.yAxis = [];
for (let key in dataValues) {
const dataValue = dataValues[key];
yAxisConfigObject =
this.config.yAxisConfig.find((item) => key === item.id) ||
[];
yAxisAlignment = yAxisConfigObject.position
? yAxisConfigObject.position
: "left";
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);
yKeys.push(key);
if (this.state.yAxis.length > 1) {
const yPtsArray = [];
const firstArr = this.state.yAxis[0];
// we need to calculate the scaleMultiplier.
// now that we have an accurate scaleMultiplier we can
// we need to loop through original positions.
scaleMultiplier = this.height / getValueRange(yPts);
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,
});
}
// the labels are not aligned in length between the two yAxis objects,
// we need to run some new calculations.
if (
this.state.yAxis[1] &&
this.state.yAxis[0].labels.length !==
this.state.yAxis[1].labels.length
) {
const newYptsArr = [];
// find the shorter array
const shortest = this.state.yAxis.reduce(
(p, c) => {
return p.length > c.labels.length ? c : p;
},
{ length: Infinity }
);
// return the longest
const longest = this.state.yAxis.reduce(
(p, c) => {
return p.length < c.labels.length ? p : c;
},
{ length: Infinity }
);
// we now need to populate the shortest obj with the new scale multiplier
// with the positions of the longest obj.
longest.positions.forEach((pos) => {
// calculate a new yPts
newYptsArr.push(Math.ceil(pos / shortest.scaleMultiplier));
});
shortest.labels = newYptsArr.reverse();
shortest.positions = longest.positions;
}
}
// Dependent if above changes // Dependent if above changes
this.calcDatasetPoints(); this.calcDatasetPoints();
@ -119,11 +249,26 @@ 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: name:
d.name && d.name &&
@ -131,13 +276,16 @@ export default class AxisChart extends BaseChart {
char == "&" ? "&amp;" : char == "<" ? "&lt;" : "&gt;" 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),
}; };
}); });
} }
@ -182,44 +330,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 ? {} : [];
let groupBy = (arr, property) => {
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);
arr.forEach((d, i) => {
let values = arr[i].values;
d[key] = cumulative = cumulative.map((c, i) => {
return c + values[i];
});
});
};
if (this.barOptions.stacked) { if (this.barOptions.stacked) {
key = "cumulativeYs"; key = "cumulativeYs";
let cumulative = new Array(this.state.datasetLength).fill(0); // we need to filter out the different yAxis ID's here.
this.data.datasets.map((d, i) => { if (multiAxis) {
let values = this.data.datasets[i].values; const groupedDataSets = groupBy(this.data.datasets, "axisID");
d[key] = cumulative = cumulative.map((c, i) => c + values[i]); // 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) {
if (this.data.yRegions && !multiAxis) {
this.data.yRegions.map((d) => { 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); //return [].concat(...allValueLists); master
} }
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",
{ {
@ -251,13 +426,49 @@ export default class AxisChart extends BaseChart {
], ],
]; ];
let barDatasets = this.state.datasets.filter((d) => d.chartType === "bar"); // 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: yAxis.yAxisMode || "span",
width: this.width,
height: this.baseHeight,
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,
height: this.baseHeight,
shortenNumbers: this.config.shortenYAxisNumbers,
},
function () {
return this.state.yAxis;
}.bind(this),
]);
}
let barDatasets = this.state.datasets.filter(
(d) => d.chartType === "bar"
);
let lineDatasets = this.state.datasets.filter( let lineDatasets = this.state.datasets.filter(
(d) => d.chartType === "line" (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,
{ {
@ -271,16 +482,35 @@ 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);
// if there are multiple yAxis we need to return the yAxis with the
// proper ID.
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
);
let xPositions = s.xAxis.positions.map((x) => x - barsWidth / 2);
if (!stacked) { if (!stacked) {
xPositions = xPositions.map((p) => p + barWidth * index); xPositions = xPositions.map(
(p) => p + barWidth * barIndex - barWidth
);
} }
let labels = new Array(s.datasetLength).fill(""); let labels = new Array(s.datasetLength).fill("");
@ -291,10 +521,11 @@ export default class AxisChart extends BaseChart {
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]
);
} }
return { return {
@ -304,7 +535,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,
}; };
@ -332,10 +563,17 @@ export default class AxisChart extends BaseChart {
function () { function () {
let s = this.state; let s = this.state;
let d = s.datasets[index]; let d = s.datasets[index];
// 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 = let minLine =
s.yAxis.positions[0] < s.yAxis.zeroLine yAxis.positions[0] < yAxis.zeroLine
? s.yAxis.positions[0] ? yAxis.positions[0]
: s.yAxis.zeroLine; : yAxis.zeroLine;
return { return {
xPositions: s.xAxis.positions, xPositions: s.xAxis.positions,
@ -374,10 +612,16 @@ export default class AxisChart extends BaseChart {
this.components = new Map( this.components = new Map(
componentConfigs componentConfigs
.filter((args) => !optionals.includes(args[0]) || this.state[args[0]]) .filter(
(args) =>
!optionals.includes(args[0]) || this.state[args[0]]
)
.map((args) => { .map((args) => {
let component = getComponent(...args); let component = getComponent(...args);
if (args[0].includes("lineGraph") || args[0].includes("barGraph")) { if (
args[0].includes("lineGraph") ||
args[0].includes("barGraph")
) {
this.dataUnitComponents.push(component); this.dataUnitComponents.push(component);
} }
return [args[0], component]; return [args[0], component];
@ -423,7 +667,10 @@ export default class AxisChart extends BaseChart {
let relX = e.pageX - o.left - getLeftOffset(m); let relX = e.pageX - o.left - getLeftOffset(m);
let relY = e.pageY - o.top; let relY = e.pageY - o.top;
if (relY < this.height + getTopOffset(m) && relY > getTopOffset(m)) { if (
relY < this.height + getTopOffset(m) &&
relY > getTopOffset(m)
) {
this.mapTooltipXPosition(relX); this.mapTooltipXPosition(relX);
} else { } else {
this.tip.hideTip(); this.tip.hideTip();
@ -458,6 +705,30 @@ export default class AxisChart extends BaseChart {
} }
} }
// Legacy
/* renderLegend() {
let s = this.data;
if (s.datasets.length > 1) {
this.legendArea.textContent = "";
console.log(s.datasets);
s.datasets.map((d, i) => {
let barWidth = LEGEND_ITEM_WIDTH;
// let rightEndPoint = this.baseWidth - this.measures.margins.left - this.measures.margins.right;
// let multiplier = s.datasets.length - i;
let rect = legendBar(
// rightEndPoint - multiplier * barWidth, // To right align
barWidth * i,
"0",
barWidth,
this.colors[i],
d.name,
this.config.truncateLegends
);
this.legendArea.appendChild(rect);
});
}
} */
makeLegend(data, index, x_pos, y_pos) { makeLegend(data, index, x_pos, y_pos) {
return legendDot( return legendDot(
x_pos, x_pos,

View File

@ -5,7 +5,12 @@ import {
getSplineCurvePointsStr, getSplineCurvePointsStr,
} from "./draw-utils"; } from "./draw-utils";
import { getStringWidth, isValidNumber, round } from "./helpers"; import { getStringWidth, isValidNumber, round } from "./helpers";
import { DOT_OVERLAY_SIZE_INCR } from "./constants";
import {
DOT_OVERLAY_SIZE_INCR,
PERCENTAGE_BAR_DEFAULT_DEPTH,
} from "./constants";
import { lightenDarkenColor } from "./colors";
export const AXIS_TICK_LENGTH = 6; export const AXIS_TICK_LENGTH = 6;
const LABEL_MARGIN = 4; const LABEL_MARGIN = 4;
@ -379,7 +384,8 @@ export function legendDot(
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 || "var(--charts-label-color)"; //let fill = options.fill || "var(--charts-label-color)";
let fill = options.fill || FONT_FILL;
let textAnchor = options.textAnchor || "start"; let textAnchor = options.textAnchor || "start";
return createSVG("text", { return createSVG("text", {
className: className, className: className,
@ -394,6 +400,7 @@ 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;
let l = createSVG("line", { let l = createSVG("line", {
className: "line-vertical " + options.className, className: "line-vertical " + options.className,
x1: 0, x1: 0,
@ -448,8 +455,8 @@ function makeHoriLine(y, label, x1, x2, options = {}) {
let l = createSVG("line", { let l = createSVG("line", {
className: className, className: className,
x1: x1, x1: lineX1Post,
x2: x2, x2: lineX2Post,
y1: 0, y1: 0,
y2: 0, y2: 0,
styles: { styles: {
@ -458,7 +465,7 @@ 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: textXPos,
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",
@ -539,7 +546,7 @@ export function yLine(y, label, width, options = {}) {
x2 = width; x2 = width;
} }
// let offset = options.pos === 'left' ? -1 * options.offset : options.offset; let offset = options.pos === "left" ? -1 * options.offset : options.offset;
// pr_366 // pr_366
//x1 += offset; //x1 += offset;
@ -565,6 +572,7 @@ export function xLine(x, label, height, options = {}) {
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.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
@ -588,6 +596,7 @@ export function xLine(x, label, height, options = {}) {
} }
return makeVertLine(x, label, y1, y2, { return makeVertLine(x, label, y1, y2, {
stroke: options.stroke,
className: options.className, className: options.className,
lineType: options.lineType, lineType: options.lineType,
}); });