182 lines
5.3 KiB
JavaScript
182 lines
5.3 KiB
JavaScript
import { DateFormatter, Utils } from 'arm-common';
|
|
import p from 'prop-types';
|
|
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
|
|
|
import { ChartContainer, useAm5, useAm5Colors, getRoot } from '../Am5.jsx';
|
|
import { AutoSize } from 'arm-core-layouts';
|
|
|
|
|
|
const classNamePrefix = 'armu_ai_chatbot-stacked-chart';
|
|
|
|
/**
|
|
* Generates a stacked chart using the provided data and configuration.
|
|
*
|
|
* @param {Object} props - The configuration object for the chart.
|
|
* @param {string} props.id - The unique identifier for the chart.
|
|
* @param {string} props.xAxis - The field to use for the x-axis.
|
|
* @param {string} props.yAxis - The field to use for the y-axis.
|
|
* @param {Array} props.series - The series configuration for the chart.
|
|
* @param {Array} props.data - The data to be displayed on the chart.
|
|
* @return {ReactElement} The rendered stacked chart component.
|
|
*/
|
|
export default function StackedChart(props) {
|
|
const { id, xAxis, series, isDateXAxis } = props;
|
|
|
|
const [chartId] = useState(id || Utils.uuid(classNamePrefix));
|
|
const am5 = useAm5();
|
|
const am5Colors = useAm5Colors();
|
|
const data = useMemo(() => {
|
|
if (isDateXAxis) {
|
|
return props.data.map((d) => ({...d, [xAxis]: DateFormatter.formatDate(d[xAxis], "DD/MM/YYYY") }))
|
|
}
|
|
return props.data;
|
|
}, [props.data])
|
|
|
|
useLayoutEffect(() => {
|
|
if (am5 && data) {
|
|
const root = getRoot(chartId);
|
|
|
|
const chart = root.container.children.push(
|
|
am5xy.XYChart.new(root, {
|
|
layout: root.verticalLayout
|
|
})
|
|
);
|
|
|
|
const xRenderer = am5xy.AxisRendererX.new(root, {});
|
|
|
|
const xAxisAm5 = chart.xAxes.push(
|
|
am5xy.CategoryAxis.new(root, {
|
|
categoryField: xAxis,
|
|
renderer: xRenderer,
|
|
tooltip: am5.Tooltip.new(root, {})
|
|
})
|
|
);
|
|
|
|
xRenderer.grid.template.setAll({
|
|
location: 1
|
|
})
|
|
|
|
xAxisAm5.data.setAll(data);
|
|
|
|
const yAxisAm5 = chart.yAxes.push(
|
|
am5xy.ValueAxis.new(root, {
|
|
min: 0,
|
|
max: 100,
|
|
renderer: am5xy.AxisRendererY.new(root, {
|
|
strokeOpacity: 0.1
|
|
})
|
|
})
|
|
);
|
|
|
|
chart.set("colors", am5.ColorSet.new(root, {
|
|
colors: am5Colors
|
|
}));
|
|
|
|
if (series?.length) {
|
|
const legend = chart.children.push(am5.Legend.new(root, {
|
|
centerX: am5.p50,
|
|
x: am5.p50,
|
|
}));
|
|
|
|
series.forEach(addSeries.bind({ root, chart, xAxisAm5, yAxisAm5, xAxis, data }));
|
|
|
|
legend.data.setAll(chart.series.values);
|
|
}
|
|
}
|
|
}, [am5, data]);
|
|
|
|
return (
|
|
<ChartContainer id={chartId} className={classNamePrefix} />
|
|
);
|
|
}
|
|
|
|
StackedChart.defaultProps = {
|
|
id: '',
|
|
xAxis: '',
|
|
series: [],
|
|
data: null
|
|
}
|
|
|
|
StackedChart.propTypes = {
|
|
id: p.string,
|
|
xAxis: p.string.isRequired,
|
|
series: p.arrayOf(p.shape({
|
|
name: p.string,
|
|
yAxis: p.string
|
|
})).isRequired,
|
|
data: p.arrayOf(p.object).isRequired
|
|
}
|
|
|
|
function addSeries(seriesConfig) {
|
|
const { root, chart, xAxisAm5, yAxisAm5, xAxis, data } = this;
|
|
|
|
const { name, yAxis } = seriesConfig;
|
|
|
|
const series = chart.series.push(
|
|
am5xy.ColumnSeries.new(root, {
|
|
name,
|
|
stacked: true,
|
|
xAxis: xAxisAm5,
|
|
yAxis: yAxisAm5,
|
|
categoryXField: xAxis,
|
|
valueYField: yAxis
|
|
})
|
|
);
|
|
|
|
series.columns.template.setAll({
|
|
tooltipText: "[bold]{name}[/]: {valueY}",
|
|
tooltipY: am5.percent(10)
|
|
});
|
|
|
|
series.bullets.push(function () {
|
|
return am5.Bullet.new(root, {
|
|
sprite: am5.Label.new(root, {
|
|
text: "{valueY}",
|
|
fill: root.interfaceColors.get("alternativeText"),
|
|
centerY: am5.p50,
|
|
centerX: am5.p50,
|
|
populateText: true
|
|
})
|
|
});
|
|
});
|
|
|
|
series.data.setAll(data);
|
|
addRoundedCorners(series, chart, xAxisAm5);
|
|
series.appear();
|
|
}
|
|
|
|
//TODO doesnt work
|
|
function addRoundedCorners(series, chart, xAxisAm5) {
|
|
series.columns.template.adapters.add("cornerRadiusTL", function (radius, target) {
|
|
return isTop.call({ chart, series, xAxisAm5 }, target.dataItem) ? 5 : 0;
|
|
});
|
|
series.columns.template.adapters.add("cornerRadiusTR", function (radius, target) {
|
|
return isTop.call({ chart, series, xAxisAm5 }, target.dataItem) ? 5 : 0;
|
|
});
|
|
}
|
|
function getCategoryDataItems(dataItem) {
|
|
const index = this.xAxisAm5.categoryToIndex(dataItem.get("categoryX"));
|
|
const items = [];
|
|
this.chart.series.each((series) => {
|
|
if (series.get("visible")) {
|
|
const item = this.series.dataItems[index];
|
|
if (item.get("valueY") >= 0) {
|
|
items.push(item);
|
|
}
|
|
else {
|
|
items.unshift(item);
|
|
}
|
|
}
|
|
});
|
|
return items;
|
|
}
|
|
|
|
function isTop(dataItem) {
|
|
const items = getCategoryDataItems.call(this, dataItem);
|
|
if (dataItem.get("valueY") >= 0) {
|
|
return items.indexOf(dataItem) == (items.length - 1);
|
|
}
|
|
else {
|
|
return items.indexOf(dataItem) == 0;
|
|
}
|
|
} |