Skip to content

Commit ee156d9

Browse files
committed
Sort chart categories by name instead of first appearance
fixes #951
1 parent 7004ebc commit ee156d9

File tree

2 files changed

+22
-15
lines changed

2 files changed

+22
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
Migration file created: sqlpage/migrations/20250627095944_my_new_table.sql
1111
```
1212
- New [modal](https://sql-page.com/component.sql?component=modal) component
13+
- In bar charts: Sort chart categories by name instead of first appearance. This is useful when displaying cumulative bar charts with some series missing data for some x values.
1314

1415
## v0.35.2
1516
- Fix a bug with zero values being displayed with a non-zero height in stacked bar charts.

sqlpage/apexcharts.js

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ sqlpage_chart = (() => {
4040
/**
4141
* Aligns series data points by their x-axis categories, ensuring all series have data points
4242
* for each unique category. Missing values are filled with zeros.
43-
* Categories are ordered by their first appearance across all series.
43+
* Categories are ordered by their name.
4444
*
4545
* @example
4646
* // Input series:
@@ -49,31 +49,36 @@ sqlpage_chart = (() => {
4949
* { name: "B", data: [{x: "X1", y: 25}, {x: "X2", y: 20}] }
5050
* ];
5151
*
52-
* // Output after align_categories (orderedCategories will be ["X2", "X3", "X1"]):
52+
* // Output after align_categories (orderedCategories will be ["X1","X2", "X3"]):
5353
* // [
54-
* // { name: "A", data: [{x: "X2", y: 10}, {x: "X3", y: 30}, {x: "X1", y: 0}] },
55-
* // { name: "B", data: [{x: "X2", y: 20}, {x: "X3", y: 0}, {x: "X1", y: 25}] }
54+
* // { name: "A", data: [{x: "X1", y: 0}, {x: "X2", y: 10}, {x: "X3", y: 30}] },
55+
* // { name: "B", data: [{x: "X1", y: 25}, {x: "X2", y: 20}, {x: "X3", y: 0}] }
5656
* // ]
5757
*
5858
* @param {(Series[string])[]} series - Array of series objects, each containing name and data points
5959
* @returns {Series[string][]} Aligned series with consistent categories across all series
6060
*/
6161
function align_categories(series) {
6262
const categoriesSet = new Set();
63-
const pointers = series.map((_) => 0);
63+
const pointers = series.map((_) => 0); // Index of current data point in each series
64+
const x_at = (series_idx) =>
65+
series[series_idx].data[pointers[series_idx]].x;
66+
let series_idxs = Array.from({ length: series.length }, (_, i) => i);
6467
while (true) {
65-
const series_idxs = series.flatMap((series, i) =>
66-
pointers[i] < series.data.length ? [i] : [],
68+
// indices of series that have data points left
69+
series_idxs = series_idxs.filter(
70+
(i) => pointers[i] < series[i].data.length,
6771
);
6872
if (series_idxs.length === 0) break;
69-
const min_ptr = Math.min(...series_idxs.map((i) => pointers[i]));
70-
const min_series_idx =
71-
series_idxs.find((i) => pointers[i] === min_ptr) | 0;
72-
const min_series = series[min_series_idx];
73-
const min_point = min_series.data[min_ptr];
74-
const new_category = min_point.x;
73+
74+
let idx_of_xmin = series_idxs[0];
75+
for (const series_idx of series_idxs) {
76+
if (x_at(series_idx) < x_at(idx_of_xmin)) idx_of_xmin = series_idx;
77+
}
78+
79+
const new_category = x_at(idx_of_xmin);
7580
if (!categoriesSet.has(new_category)) categoriesSet.add(new_category);
76-
pointers[min_series_idx]++;
81+
pointers[idx_of_xmin]++;
7782
}
7883
// Create a map of category -> value for each series and rebuild
7984
return series.map((s) => {
@@ -164,7 +169,8 @@ sqlpage_chart = (() => {
164169
: data.type === "pie"
165170
? (value, { seriesIndex, w }) =>
166171
`${w.config.labels[seriesIndex]}: ${value.toFixed()}%`
167-
: (value, { seriesIndex, w }) => value?.toLocaleString?.() || value,
172+
: (value, { seriesIndex, w }) =>
173+
value?.toLocaleString?.() || value,
168174
},
169175
fill: {
170176
type: data.type === "area" ? "gradient" : "solid",

0 commit comments

Comments
 (0)