diff --git a/index.html b/index.html index 5437fc4..a29ff70 100644 --- a/index.html +++ b/index.html @@ -211,15 +211,21 @@

Explore a location

Analysis details

Crime over time

- +
+ +

Top offenses

- +
+ +

Day and hour

- +
+ +
diff --git a/scripts/tests/browser_smoke.mjs b/scripts/tests/browser_smoke.mjs index 6ae5763..dce7e32 100644 --- a/scripts/tests/browser_smoke.mjs +++ b/scripts/tests/browser_smoke.mjs @@ -421,6 +421,32 @@ try { await page.locator('.analysis-history__empty').waitFor(); assert.equal(await artifactCard(page, 'Renamed A-only').count(), 0); + const chartDetails = page.locator('#results-drawer .progressive-surface'); + if (!(await chartDetails.evaluate((element) => element.open))) { + await chartDetails.locator(':scope > summary').click(); + } + await page.waitForFunction(() => { + const canvases = [...document.querySelectorAll('#charts canvas')]; + return canvases.length === 3 && canvases.every((canvas) => canvas.clientHeight > 0); + }); + const readChartLayout = () => page.evaluate(() => [...document.querySelectorAll('#charts canvas')] + .map((canvas) => ({ + id: canvas.id, + height: canvas.clientHeight, + width: canvas.clientWidth, + frameHeight: canvas.parentElement?.clientHeight || 0, + frameWidth: canvas.parentElement?.clientWidth || 0, + }))); + const chartLayoutBefore = await readChartLayout(); + await page.waitForTimeout(750); + const chartLayoutAfter = await readChartLayout(); + assert.deepEqual(chartLayoutAfter, chartLayoutBefore, 'Expanded chart dimensions must remain stable'); + for (const chart of chartLayoutAfter) { + assert.ok(chart.height <= 300, `${chart.id} exceeded its bounded chart frame`); + assert.ok(chart.width <= chart.frameWidth + 1, `${chart.id} overflowed its chart frame horizontally`); + assert.equal(chart.height, chart.frameHeight, `${chart.id} did not fill its dedicated chart frame`); + } + const layout = await page.evaluate(() => { const side = document.getElementById('sidepanel'); const compare = document.getElementById('compare-card'); diff --git a/scripts/tests/ui_shell_contracts.mjs b/scripts/tests/ui_shell_contracts.mjs index f725254..ea6bbb8 100644 --- a/scripts/tests/ui_shell_contracts.mjs +++ b/scripts/tests/ui_shell_contracts.mjs @@ -32,6 +32,15 @@ test('analysis summary stays visible while charts are progressively disclosed', assert.doesNotMatch(panel, /chartsPanel\.parentElement\s*!==\s*resultsDrawer/); }); +test('responsive Crime charts use dedicated bounded canvas frames', () => { + const framedCharts = [...html.matchAll( + /]*class="[^"]*\bchart-frame\b[^"]*"[^>]*>\s*]*id="chart-(monthly|topn|7x24)"[^>]*><\/canvas>\s*<\/div>/gi, + )].map((match) => match[1]); + assert.deepEqual(framedCharts, ['monthly', 'topn', '7x24']); + assert.match(css, /\.chart-frame\s*\{[^}]*position:\s*relative\s*;[^}]*height:\s*220px\s*;/s); + assert.match(css, /\.chart-frame--topn\s*\{[^}]*height:\s*300px\s*;/s); +}); + test('current analysis summary is mounted before recent analyses', async () => { const { placeAnalysisHistoryAfterSummary } = await import('../../src/ui/panel.js'); assert.equal(typeof placeAnalysisHistoryAfterSummary, 'function'); diff --git a/src/style.css b/src/style.css index 140a3de..36184b6 100644 --- a/src/style.css +++ b/src/style.css @@ -1437,6 +1437,21 @@ summary { font-size: 15px; } +.chart-frame { + position: relative; + height: 220px; + min-width: 0; +} + +.chart-frame--topn { + height: 300px; +} + +.chart-frame canvas { + display: block; + max-width: 100%; +} + .mode-skeleton { display: grid; gap: 5px;