Skip to content

Commit cd5dda6

Browse files
authored
refactor(chart) :: import and use 3rd party ApexCharts types
`apexcharts.ts` now has one implicit any, `TooltipArgs` keeps its `w: any` until upstream ApexCharts updates it's tooltip types. Fixing this smoked out six workarounds :: 1. `options as ApexOptions` hid that `zaxis` is not an ApexCharts option at all. 2. `yasix` has to be normalized into an array and forwarded within `bubbleTooltip`. Now `chartTooltip` takes the axis titles directly. 3. `formatTooltipX` returned a value where `tooltip.x.formatter` is declared to return a `string`. 4. pie `labels` were `(string | number)[]` where `ApexOptions` wants `string[]`. 5. `xaxis.tickAmount` was assigned after the literal instead of inside it. 6. the `dataLabels` formatters destructured an `opts` that ApexCharts declares optional, so they are now three named functions.
1 parent 31f7022 commit cd5dda6

3 files changed

Lines changed: 85 additions & 32 deletions

File tree

‎frontend/src/apexcharts.ts‎

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ type DataPoint = {
1818
link?: string;
1919
};
2020

21+
type AxisTitles = Record<"x" | "y" | "z", string | undefined>;
22+
2123
type TooltipArgs = {
2224
seriesIndex: number;
2325
dataPointIndex: number;
@@ -29,13 +31,25 @@ function formatTooltipX(
2931
value: string | number | null,
3032
link: string | undefined,
3133
) {
32-
if (!link || !value) return value;
34+
const text = value == null ? "" : String(value);
35+
if (!link || !value) return text;
3336
const anchor = document.createElement("a");
3437
anchor.setAttribute("href", link);
35-
anchor.textContent = String(value);
38+
anchor.textContent = text;
3639
return anchor.outerHTML;
3740
}
3841

42+
const rangeBarLabel = (_value: string | number, args?: TooltipArgs) =>
43+
args ? args.w.config.series[args.seriesIndex].name : "";
44+
45+
const pieLabel = (value: string | number, args?: TooltipArgs) =>
46+
args
47+
? `${args.w.config.labels[args.seriesIndex]}: ${Number(value).toFixed()}%`
48+
: "";
49+
50+
const numberLabel = (value: string | number) =>
51+
value == null ? "" : value.toLocaleString?.() || String(value);
52+
3953
const sqlpage_chart = (() => {
4054
function sqlpage_chart() {
4155
const charts = document.querySelectorAll<HTMLElement>(
@@ -195,7 +209,9 @@ const sqlpage_chart = (() => {
195209
);
196210

197211
const labels =
198-
chart_type === "pie" ? points.map(({ name, x }) => x || name) : undefined;
212+
chart_type === "pie"
213+
? points.map(({ name, x }) => String(x || name))
214+
: undefined;
199215
const series =
200216
chart_type === "pie"
201217
? points.map(({ y }) => Number.parseFloat(String(y)))
@@ -217,11 +233,16 @@ const sqlpage_chart = (() => {
217233
chart_type === "rangeBar" || (chart_type === "bar" && !!data.horizontal);
218234
const value_axis = inverted ? "x" : "y";
219235
const category_axis = inverted ? "y" : "x";
236+
const axis_titles: AxisTitles = {
237+
x: data.xtitle || undefined,
238+
y: data.ytitle || undefined,
239+
z: data.ztitle || undefined,
240+
};
220241
const has_point_links = points.some((point) => point.link);
221242
const text_x_values = chart_series.every(({ data }) =>
222243
data.every(({ x }) => x == null || typeof x === "string"),
223244
);
224-
const options = {
245+
const options: ApexOptions = {
225246
annotations: {
226247
[`${value_axis}axis`]: reference_lines(
227248
reference_rows,
@@ -254,7 +275,7 @@ const sqlpage_chart = (() => {
254275
},
255276
events: {
256277
dataPointSelection: (_event, _chart, args) => {
257-
const link = pointLink(args, points);
278+
const link = args && pointLink(args, points);
258279
if (link) window.location.assign(link);
259280
},
260281
},
@@ -274,11 +295,10 @@ const sqlpage_chart = (() => {
274295
},
275296
formatter:
276297
chart_type === "rangeBar"
277-
? (_val, { seriesIndex, w }) => w.config.series[seriesIndex].name
298+
? rangeBarLabel
278299
: chart_type === "pie"
279-
? (value, { seriesIndex, w }) =>
280-
`${w.config.labels[seriesIndex]}: ${value.toFixed()}%`
281-
: (value) => value?.toLocaleString?.() || value,
300+
? pieLabel
301+
: numberLabel,
282302
},
283303
fill: {
284304
type: chart_type === "area" ? "gradient" : "solid",
@@ -295,12 +315,15 @@ const sqlpage_chart = (() => {
295315
min: data.xmin,
296316
max: data.xmax,
297317
title: {
298-
text: data.xtitle || undefined,
318+
text: axis_titles.x,
299319
},
300320
type: xaxis_type,
301321
labels: {
302322
datetimeUTC: false,
303323
},
324+
// Numeric axes count intervals; category and time axes use tickAmount
325+
// as a target for label density.
326+
tickAmount: data.xticks || undefined,
304327
},
305328
yaxis: {
306329
logarithmic: !!data.logarithmic,
@@ -309,12 +332,7 @@ const sqlpage_chart = (() => {
309332
stepSize: data.ystep,
310333
tickAmount: data.yticks,
311334
title: {
312-
text: data.ytitle || undefined,
313-
},
314-
},
315-
zaxis: {
316-
title: {
317-
text: data.ztitle || undefined,
335+
text: axis_titles.y,
318336
},
319337
},
320338
markers: {
@@ -329,7 +347,7 @@ const sqlpage_chart = (() => {
329347
interactive: has_point_links,
330348
custom:
331349
chart_type === "bubble" || chart_type === "scatter"
332-
? bubbleTooltip
350+
? (args: TooltipArgs) => chartTooltip(args, axis_titles)
333351
: undefined,
334352
x: {
335353
formatter:
@@ -363,19 +381,18 @@ const sqlpage_chart = (() => {
363381
colors,
364382
series,
365383
};
366-
if (labels) (options as { labels?: unknown }).labels = labels;
367-
// Numeric axes count intervals; category and time axes use tickAmount as a
368-
// target for label density.
369-
if (data.xticks)
370-
(options.xaxis as { tickAmount?: number }).tickAmount = data.xticks;
371-
const chart = new ApexCharts(chartContainer, options as ApexOptions);
384+
if (labels) options.labels = labels;
385+
const chart = new ApexCharts(chartContainer, options);
372386
chart.render().catch(console.error);
373387
if (window.charts) window.charts.push(chart);
374388
else window.charts = [chart];
375389
c.removeAttribute("data-pre-init");
376390
}
377391

378-
function chartTooltip({ seriesIndex, dataPointIndex, w }: TooltipArgs) {
392+
function chartTooltip(
393+
{ seriesIndex, dataPointIndex, w }: TooltipArgs,
394+
titles: AxisTitles,
395+
) {
379396
const series = w.config.series[seriesIndex];
380397
const name = series?.name || "";
381398
const point = series?.data[dataPointIndex];
@@ -390,14 +407,12 @@ const sqlpage_chart = (() => {
390407
seriesName.innerText = name;
391408
tooltip.appendChild(seriesName);
392409

393-
for (const axis of ["x", "y", "z"]) {
410+
for (const axis of ["x", "y", "z"] as const) {
394411
const value = point[axis];
395412
if (value == null) continue;
396413
const axisValue = document.createElement("div");
397414
axisValue.className = "apexcharts-tooltip-y-group";
398-
let axis_conf = w.config[`${axis}axis`];
399-
if (axis_conf.length) axis_conf = axis_conf[0];
400-
const title = axis_conf.title.text || axis;
415+
const title = titles[axis] || axis;
401416
const labelSpan = document.createElement("span");
402417
labelSpan.className = "apexcharts-tooltip-text-y-label";
403418
labelSpan.innerText = `${title}: `;
@@ -426,10 +441,6 @@ const sqlpage_chart = (() => {
426441
: points[seriesIndex]?.link;
427442
}
428443

429-
function bubbleTooltip(args: TooltipArgs) {
430-
return chartTooltip(args);
431-
}
432-
433444
return sqlpage_chart;
434445
})();
435446

‎tests/end-to-end/fixtures/chart/test.ts‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ declare global {
1010
chart: { type: string; stacked: boolean };
1111
xaxis: { type?: string; tickAmount?: number };
1212
series: { name: string | number; data?: ChartPoint[] }[];
13+
tooltip: {
14+
custom?: (args: {
15+
seriesIndex: number;
16+
dataPointIndex: number;
17+
w: unknown;
18+
}) => string;
19+
};
1320
};
1421
globals: { labels: (string | number)[] };
1522
};
@@ -582,3 +589,35 @@ test("keeps coloring reference lines from their own row", async ({ page }) => {
582589
expect(chart.failures).toEqual([]);
583590
expect(chart.referenceLines.strokes).toEqual([GREEN]);
584591
});
592+
593+
test("labels each axis of a bubble tooltip with its own title", async ({
594+
page,
595+
}) => {
596+
await renderChart(page, "titled-bubble");
597+
598+
const rows = await page.evaluate(() => {
599+
const chart = window.charts?.[0];
600+
if (!chart) throw new Error("Chart fixture did not render");
601+
const custom = chart.w.config.tooltip.custom;
602+
if (!custom) throw new Error("A bubble chart needs the custom tooltip");
603+
const holder = document.createElement("div");
604+
holder.innerHTML = custom({
605+
seriesIndex: 0,
606+
dataPointIndex: 1,
607+
w: chart.w,
608+
});
609+
const values = holder.querySelectorAll(".apexcharts-tooltip-text-y-value");
610+
return [...holder.querySelectorAll(".apexcharts-tooltip-text-y-label")].map(
611+
(label, i): [string, string] => [
612+
label.textContent?.trim() ?? "",
613+
values[i]?.textContent?.trim() ?? "",
614+
],
615+
);
616+
});
617+
618+
expect(rows).toEqual([
619+
["Weekday:", "Tue"],
620+
["Hours:", "4"],
621+
["Weight:", "30"],
622+
]);
623+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'bubble' AS type,
2+
'Weekday' AS xtitle, 'Hours' AS ytitle, 'Weight' AS ztitle;
3+
WITH points(series, x, y, z) AS (VALUES ('Coding', 'Mon', 6, 30), ('Coding', 'Tue', 4, 30)) SELECT * FROM points;

0 commit comments

Comments
 (0)