diff --git a/sqlpage/apexcharts.js b/sqlpage/apexcharts.js index 2935b212..db85c8f9 100644 --- a/sqlpage/apexcharts.js +++ b/sqlpage/apexcharts.js @@ -398,6 +398,7 @@ sqlpage_chart = (() => { if (data.xticks) options.xaxis.tickAmount = data.xticks; const chart = new ApexCharts(chartContainer, options); chart.render(); + keepLinkedTooltipOpen(chartContainer); if (window.charts) window.charts.push(chart); else window.charts = [chart]; c.removeAttribute("data-pre-init"); @@ -418,9 +419,10 @@ sqlpage_chart = (() => { tooltip.className = "apexcharts-tooltip-text"; tooltip.style.fontFamily = "inherit"; - const seriesName = document.createElement("div"); + const seriesName = document.createElement(link ? "a" : "div"); seriesName.className = "apexcharts-tooltip-y-group"; seriesName.style.fontWeight = "bold"; + if (seriesName instanceof HTMLAnchorElement) seriesName.href = link; seriesName.innerText = name; tooltip.appendChild(seriesName); @@ -447,7 +449,6 @@ sqlpage_chart = (() => { axisValue.appendChild(valueSpan); tooltip.appendChild(axisValue); } - addLinkToTooltip(tooltip, link); return tooltip.outerHTML; } @@ -455,16 +456,20 @@ sqlpage_chart = (() => { return chartTooltip(args, []); } - /** @param {HTMLElement} tooltip @param {string|undefined} link */ - function addLinkToTooltip(tooltip, link) { - if (!link) return; - const linkContainer = document.createElement("div"); - linkContainer.className = "apexcharts-tooltip-y-group"; - const anchor = document.createElement("a"); - anchor.href = link; - anchor.textContent = "Open link"; - linkContainer.appendChild(anchor); - tooltip.appendChild(linkContainer); + /** @param {HTMLElement} chartContainer */ + function keepLinkedTooltipOpen(chartContainer) { + chartContainer.addEventListener( + "mouseout", + (event) => { + const nextTarget = event.relatedTarget; + if ( + nextTarget instanceof Element && + nextTarget.closest(".apexcharts-tooltip")?.querySelector("a") + ) + event.stopPropagation(); + }, + true, + ); } return sqlpage_chart; diff --git a/tests/end-to-end/fixtures/chart/test.ts b/tests/end-to-end/fixtures/chart/test.ts index 7d06689a..f0c2197c 100644 --- a/tests/end-to-end/fixtures/chart/test.ts +++ b/tests/end-to-end/fixtures/chart/test.ts @@ -373,8 +373,10 @@ test("shows an interactive data point link in a rangeBar tooltip", async ({ await renderChart(page, "link"); await page.locator("#test-chart .apexcharts-rangebar-area").first().hover(); - const link = page.locator("#test-chart .apexcharts-tooltip a"); - await expect(link).toHaveText("Open link"); + const link = page.locator( + "#test-chart .apexcharts-tooltip .apexcharts-tooltip-text > a", + ); + await expect(link).toHaveText("Design"); await expect(link).toHaveAttribute( "href", "/workpackage_edit.sql?workpackage_name=Design", @@ -392,6 +394,8 @@ test("shows an interactive data point link in a rangeBar tooltip", async ({ }; }); expect(colors.link).toBe(colors.tooltip); + await link.hover(); + await expect(link).toBeVisible(); await page.locator("#test-chart .apexcharts-rangebar-area").nth(1).hover(); await expect(page.locator("#test-chart .apexcharts-tooltip a")).toHaveCount( 0, @@ -417,6 +421,23 @@ for (const [type, mark] of [ }); } +for (const [type, mark] of [ + ["bar", ".apexcharts-bar-area"], + ["scatter", ".apexcharts-marker"], +]) { + test(`keeps a data point link open when entering a ${type} tooltip`, async ({ + page, + }) => { + await renderChart(page, `link-${type}`); + + await page.locator(`#test-chart ${mark}`).nth(0).hover({ force: true }); + const link = page.locator("#test-chart .apexcharts-tooltip a"); + await expect(link).toHaveCount(1); + await link.hover(); + await expect(link).toBeVisible(); + }); +} + test("links each slice of a pie chart from its own row", async ({ page }) => { const chart = await renderChart(page, "link-pie");