Skip to content
Snippets Groups Projects
Commit ec0011da authored by Marcos Pieras's avatar Marcos Pieras Committed by Marcos Pieras
Browse files

feat: piechart with strings

parent a1897316
No related branches found
No related tags found
No related merge requests found
import { Vis1DInputData } from '../subPlots/types';
import { getCommonLayout, getCSSVariableHSL, sharedTickFont } from '../prepareLayout/LayoutCommon';
import { PlotType } from 'plotly.js';
import { scaleOrdinal, scaleQuantize } from 'd3';
import { visualizationColors } from 'ts-common';
import { groupByTime } from './utilsTime';
export const preparePlotDataAndLayoutHistogramCount = (
xData: Vis1DInputData,
yData: Vis1DInputData,
zData: Vis1DInputData,
showAxis: boolean,
stackVariable: boolean,
groupBy?: string,
) => {
const primaryColor = getCSSVariableHSL('--clr-sec--400');
const mainColors = visualizationColors.GPCat.colors[14];
// data preparation
let xValues: (string | number)[] = [];
let yValues: (string | number)[] = [];
if (!groupBy) {
if (xData.data.length !== 0 && yData && yData.data.length !== 0) {
xValues = xData.data;
yValues = yData.data;
} else if (xData.data.length !== 0 && (!yData || yData.data.length === 0)) {
xValues = xData.data;
yValues = xData.data.map((_, index) => index + 1);
} else if (xData.data.length === 0 && yData && yData.data.length !== 0) {
xValues = yData.data.map((_, index) => index + 1);
yValues = yData.data;
}
} else {
if (groupBy) {
if (yData.data && yData.data.length !== 0) {
const { xValuesGrouped, yValuesGrouped } = groupByTime(xData.data as string[], groupBy, yData.data);
xValues = xValuesGrouped;
yValues = yValuesGrouped.flat();
} else {
const { xValuesGrouped, yValuesGrouped } = groupByTime(xData.data as string[], groupBy);
xValues = xValuesGrouped;
yValues = yValuesGrouped.flat();
}
} else {
// need it ?
xValues = xData.data;
yValues = xData.data.map((_, index) => index + 1);
}
}
let colorScale: any;
let colorDataZ: string[] = [];
let colorbar: any = {};
let sortedLabels: string[] = [];
if (zData.data && zData.data.length > 0 && zData.type === 'number') {
const mainColorsSeq = visualizationColors.GPSeq.colors[9];
//const numericZAxisData = zData.data.filter((item): item is number => typeof item === 'number');
const zMin = zData.data.reduce((min, val) => (val < min ? val : min), zData.data[0]);
const zMax = zData.data.reduce((max, val) => (val > max ? val : max), zData.data[0]);
// !TODO: option to have a linear or quantize scale
colorScale = scaleQuantize<string>()
.domain([zMin as number, zMax as number])
.range(mainColorsSeq);
colorDataZ = zData.data?.map(item => colorScale(item) || primaryColor);
colorbar = {
title: 'Color Legend',
tickvals: [zMin, zMax],
ticktext: [`${zMin}`, `${zMax}`],
};
} else {
const uniqueZAxisData = Array.from(new Set(zData.data));
if (zData.data && uniqueZAxisData) {
colorScale = scaleOrdinal<string>().domain(uniqueZAxisData.map(String)).range(mainColors);
colorDataZ = zData.data?.map(item => colorScale(String(item)) || primaryColor);
const sortedDomain = uniqueZAxisData.sort();
colorbar = {
title: 'Color Legend',
tickvals: sortedDomain,
ticktext: sortedDomain.map(val => String(val)),
tickmode: 'array',
};
}
}
//
const data: Plotly.Data[] = (() => {
if (typeof xData.data[0] === 'string') {
if (zData.data && zData.data?.length > 0) {
// const sortedData = xData.data
// .map((value, index) => ({ x: value, y: yAxisData ? yAxisData[index] : null }))
// .sort((a, b) => (a.x > b.x ? 1 : -1));
// const sortedLabels = sortedData.map(item => item.x);
// const sortedFrequencies = sortedData.map(item => item.y);
const frequencyMap = xData.data.reduce(
(acc, item, index) => {
const color = zData.data ? colorScale(zData.data[index]) : primaryColor;
if (!acc[item]) {
acc[item] = {
count: 0,
colors: [],
zValues: [],
zValueCounts: {},
};
}
acc[item].count = acc[item].count + ((yData.data?.[index] as number) ?? 1);
acc[item].colors.push(color);
acc[item].zValues.push(zData.data[index].toString());
// Group and count zValues
const zValue = zData.data[index] || '(Empty)';
acc[item].zValueCounts[zValue] = (acc[item].zValueCounts[zValue] || 0) + ((yData.data?.[index] as number) ?? 1);
return acc;
},
{} as Record<
string,
{
count: number;
colors: string[];
zValues: string[];
zValueCounts: Record<string, number>; // To store grouped counts
}
>,
);
const colorToLegendName = new Map();
const sortedCategories = Object.entries(frequencyMap).sort((a, b) => b[1].count - a[1].count);
const tracesByColor: Record<string, { x: string[]; y: number[] }> = {};
sortedCategories.forEach(([label, { colors, zValues, zValueCounts }]) => {
colors.forEach((color, idx) => {
const zValue = zValues[idx];
if (!colorToLegendName.has(color)) {
colorToLegendName.set(color, zValue);
}
if (!tracesByColor[color]) {
tracesByColor[color] = { x: [], y: [] };
}
tracesByColor[color].x.push(label);
tracesByColor[color].y.push(zValueCounts[zValue]);
});
});
sortedLabels = sortedCategories.map(element => element[0]);
const traces = Array.from(colorToLegendName.entries()).map(([color, legendName]) => {
const colorData = tracesByColor[color];
const categoryCountMap: Record<string, number> = {};
sortedLabels.forEach(label => {
categoryCountMap[label] = frequencyMap[label].count;
});
const yValues = colorData.x.map((label, idx) => {
const totalCount = categoryCountMap[label];
const countForColor = colorData.y[idx];
return stackVariable ? (countForColor / totalCount) * 100 : countForColor;
});
const customdata = colorData.x.map((label, idx) => {
const colorTranslation = colorToLegendName.get(color) === ' ' ? '(Empty)' : colorToLegendName.get(color);
const percentage = ((100 * frequencyMap[label].zValueCounts[colorTranslation]) / frequencyMap[label].count).toFixed(1);
return [label, !stackVariable ? frequencyMap[label]?.zValueCounts[colorTranslation] || 0 : percentage, colorTranslation || ' '];
});
return {
x: colorData.x,
y: yValues,
type: 'bar' as PlotType,
name: legendName,
marker: { color: color },
customdata: customdata,
hovertemplate:
'<b>X: %{customdata[0]}</b><br>' + '<b>Y: %{customdata[1]}</b><br>' + '<b>Color: %{customdata[2]}</b><extra></extra>',
...(stackVariable ? { stackgroup: 'one' } : {}),
};
});
return traces;
} else {
const sortedData = yData.data
? yData.data.map((value, index) => ({ y: value, x: xData.data ? xData.data[index] : null })).sort((a, b) => (a.y < b.y ? 1 : -1))
: [];
const sortedLabels = sortedData.map(item => item.x);
const sortedFrequencies = sortedData.map(item => item.y);
return [
{
type: 'bar' as PlotType,
x: sortedLabels,
y: sortedFrequencies,
text: sortedFrequencies.length < 20 ? sortedFrequencies.map(String) : [],
marker: { color: primaryColor },
customdata: sortedLabels.map(label => xData.label + ' ' + label),
hovertemplate: '<b>%{customdata}</b>: %{y}<extra></extra>',
},
];
}
} else {
if (zData.data && zData.data?.length > 0) {
const binCount = 20; // Number of bins (you can make this configurable)
const numericXAxisData = xData.data.map(val => Number(val)).filter(val => !isNaN(val));
const xMin = numericXAxisData.reduce((min, val) => Math.min(min, val), Infinity);
const xMax = numericXAxisData.reduce((max, val) => Math.max(max, val), -Infinity);
const binSize = (xMax - xMin) / binCount;
// Create bins
const bins = Array.from({ length: binCount }, (_, i) => ({
range: [xMin + i * binSize, xMin + (i + 1) * binSize],
count: 0,
zValueCounts: {} as Record<string, number>, // To track zData.data counts per bin
}));
// Assign data points to bins
numericXAxisData.forEach((xValue, index) => {
const zValue = zData.data ? zData.data[index] || '(Empty)' : '(Empty)';
const binIndex = Math.floor((xValue - xMin) / binSize);
const bin = bins[Math.min(binIndex, bins.length - 1)]; // Ensure the last value falls into the final bin
bin.count++;
bin.zValueCounts[zValue] = (bin.zValueCounts[zValue] || 0) + 1;
});
const colorToLegendName = new Map();
const tracesByColor: Record<string, { x: string[]; y: number[] }> = {};
bins.forEach((bin, binIndex) => {
const binLabel = `[${bin.range[0].toFixed(1)}, ${bin.range[1].toFixed(1)})`;
Object.entries(bin.zValueCounts).forEach(([zValue, count]) => {
const color = zData.data ? colorScale(zValue) : primaryColor;
if (!colorToLegendName.has(color)) {
colorToLegendName.set(color, zValue);
}
if (!tracesByColor[color]) {
tracesByColor[color] = { x: [], y: [] };
}
tracesByColor[color].x.push(binLabel);
tracesByColor[color].y.push(stackVariable ? (count / bin.count) * 100 : count);
});
});
const traces = Array.from(colorToLegendName.entries()).map(([color, legendName]) => {
const colorData = tracesByColor[color];
const customdata = colorData.x.map((binLabel, idx) => {
const countForColor = colorData.y[idx];
const percentage = stackVariable ? countForColor.toFixed(1) + '%' : countForColor.toFixed(0);
return [binLabel, countForColor, percentage, legendName];
});
return {
x: colorData.x,
y: colorData.y,
type: 'bar' as PlotType,
name: legendName,
marker: { color },
customdata,
autobinx: true,
hovertemplate:
'<b>Bin: %{customdata[0]}</b><br>' +
'<b>Count/Percentage: %{customdata[2]}</b><br>' +
'<b>Group: %{customdata[3]}</b><extra></extra>',
...(stackVariable ? { stackgroup: 'one' } : {}),
};
});
return traces;
} else {
// No zData.data, simple histogram logic
return [
{
type: 'histogram' as PlotType,
x: xData.data,
marker: { color: primaryColor },
customdata: xData.data,
},
];
}
}
})();
const layout: Partial<Plotly.Layout> = getCommonLayout({
showAxis,
xAxisLabel: xData.label,
yAxisLabel: yData.label,
xValues,
yValues,
plotType: 'line',
xAxisData: xValues,
sortedLabels: [],
sharedTickFont,
});
return { data, layout };
};
......@@ -5,12 +5,20 @@ import { visualizationColors } from 'ts-common';
export const preparePlotDataAndLayoutPie = (xData: Vis1DInputData, showAxis: boolean) => {
const mainColors = visualizationColors.GPCat.colors[14];
const counts: Record<string, number> = {};
xData.data.forEach(val => {
const key = String(val);
counts[key] = (counts[key] || 0) + 1;
});
const labels = Object.keys(counts);
const values = Object.values(counts);
const data: Plotly.Data[] = [
{
type: 'pie' as PlotType,
labels: xData.data.map(String),
values: xData.data,
labels,
values,
marker: { colors: mainColors },
},
];
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment