Skip to content

Commit 6c02577

Browse files
Frontend: Refactors Rate bar chart to D3 (#3713)
# Description and Motivation <!--- bulleted, high level items. use keywords (eg "closes #144" or "fixes #4323") --> - create drop in replacement component for the rate chart using d3 rather than Vega - splits out sub components like Axes, Gridlines etc for smaller code files and mimicking pattern from Mia (d3 trend charts) and @eriwarr (stacked share vs population charts) - rename rate chart card component that wraps the chart - renames ambiguous prop `metric` to more specific `mertricConfig` - hides the demographic type label (like "race and ethnicity") vertically along the y axis on small screens to give more horizontal space to the chart - hide the bar label qualifier ` per 100k` from the smallest screens ## Has this been tested? How? - e2e tests updated and passing - passing unit tests added for rate bar chart helper functions ## Screenshots (if appropriate) ### Screen Reader Walk Through Demo (Captions) https://github.com/user-attachments/assets/9fd9ff56-df1e-441d-b545-c34dc80376aa ### Mobile, Compare Mode, Hover Tooltip <img width="927" alt="Screenshot 2024-10-11 at 8 37 30 AM" src="https://github.com/user-attachments/assets/0c2f7eb9-77f7-4da3-8df0-4e7d82f3bcd2"> <img width="927" alt="Screenshot 2024-10-11 at 8 37 41 AM" src="https://github.com/user-attachments/assets/8cdf4817-d975-4f12-8762-7015cae4c4f7"> <img width="927" alt="Screenshot 2024-10-11 at 8 37 51 AM" src="https://github.com/user-attachments/assets/5faedd6c-f7f1-4249-83c4-42ddb4dd7b72"> <img width="927" alt="Screenshot 2024-10-11 at 8 38 31 AM" src="https://github.com/user-attachments/assets/ff85245d-464b-4e8d-9865-7ae3844cc3d3"> <img width="927" alt="Screenshot 2024-10-11 at 8 38 34 AM" src="https://github.com/user-attachments/assets/9b33de81-ff17-4485-8fbb-896c179515d8"> <img width="927" alt="Screenshot 2024-10-11 at 8 39 08 AM" src="https://github.com/user-attachments/assets/ffaa00cf-67fe-4de3-a5db-064da92b3f1c"> ### Desktop Various Units ![HET RATE-CHART Investigate rates of Depression in United States Oct 2024 (1)](https://github.com/user-attachments/assets/3e047cb0-a4be-4ae8-81a6-d70ecdfdf2d0) ![HET RATE-CHART Investigate rates of Depression in United States Oct 2024](https://github.com/user-attachments/assets/32433d2c-396f-422b-8c01-1fab49a20d2b) ![HET RATE-CHART Investigate rates of Excessive Drinking in United States Oct 2024](https://github.com/user-attachments/assets/592d3b6c-2d64-445f-a1f2-5a931f195701) ![HET RATE-CHART Investigate rates of HIV Stigma in United States Oct 2024](https://github.com/user-attachments/assets/e4eec7dc-4675-4f29-b2a6-a4dede506d80) ![HET RATE-CHART Investigate rates of Linkage to HIV Care in United States Oct 2024](https://github.com/user-attachments/assets/be17c5ff-23f8-4e26-b791-f5a3f2d6daa3) ## Types of changes (leave all that apply) - New content or feature - Refactor / chore ## New frontend preview link is below in the Netlify comment 😎
1 parent 613b523 commit 6c02577

25 files changed

+897
-481
lines changed

frontend/playwright-tests/depression.nightly.spec.ts

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,55 @@ import { test } from '@playwright/test'
33
test('Depression Flow', async ({ page }) => {
44
await page.goto('/exploredata?mls=1.depression-3.00&group1=All')
55
await page
6-
.getByLabel(
7-
'Map showing Depression in the United States : including data from 50 states/territories'
8-
)
9-
.getByRole('img')
10-
await page.getByRole('button', { name: 'Expand state/territory rate' }).click();
6+
.locator('#rate-map')
7+
.getByRole('heading', { name: 'Depression in the United' })
8+
.click()
9+
await page
10+
.locator('#rate-map')
11+
.getByRole('heading', { name: 'Ages 18+' })
12+
.click()
13+
await page
14+
.getByRole('button', { name: 'Rates over time', exact: true })
15+
.click()
16+
await page
17+
.getByRole('heading', { name: 'Depression cases over time in' })
18+
.click()
1119
await page
12-
.getByLabel(
13-
'Bar Chart showing Depression in the United States, by Race and Ethnicity'
14-
)
15-
.getByRole('img')
20+
.locator('#rates-over-time')
21+
.getByRole('heading', { name: 'Ages 18+' })
1622
.click()
23+
await page.getByText('cases per 100k adults →').click()
24+
await page.getByText('time →').click()
25+
await page
26+
.locator('#rates-over-time')
27+
.getByText('Note. (NH) indicates ‘Non-')
28+
.click()
29+
await page.getByRole('button', { name: 'Rate chart' }).click()
30+
await page
31+
.locator('#rate-chart')
32+
.getByRole('heading', { name: 'Depression in the United' })
33+
.click()
34+
await page
35+
.locator('#rate-chart')
36+
.getByRole('heading', { name: 'Ages 18+' })
37+
.click()
38+
await page.locator('#rate-chart').getByText('All').click()
39+
await page.locator('#rate-chart').getByText('race and ethnicity').click()
40+
await page.locator('#rate-chart').getByText('cases per 100k adults').click()
1741
await page.getByRole('button', { name: 'Unknown demographic map' }).click()
42+
await page.getByText('No unknown values for race').click()
43+
await page.getByRole('button', { name: 'Data table' }).click()
44+
await page
45+
.getByRole('heading', { name: 'Summary for depression in the' })
46+
.click()
1847
await page
19-
.getByRole('heading', {
20-
name: 'Share of total adult depression cases with unknown race and ethnicity in the United States',
21-
})
48+
.getByRole('figure', { name: 'Summary for depression in the' })
49+
.locator('h4')
2250
.click()
51+
await page.getByRole('columnheader', { name: 'Race and Ethnicity' }).click()
2352
await page
24-
.getByText(
25-
'No unknown values for race and ethnicity reported in this dataset at the state/t'
26-
)
53+
.getByRole('columnheader', { name: 'Cases of depression per 100k' })
2754
.click()
55+
await page.getByRole('columnheader', { name: 'Share of total adult' }).click()
56+
await page.getByRole('columnheader', { name: 'Population share' }).click()
2857
})

frontend/playwright-tests/diabetes.ci.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ test('Diabetes County', async ({ page }) => {
4242
.locator('#rate-map')
4343
.getByRole('heading', { name: 'Ages 18+' })
4444
.click()
45-
await page.getByLabel('Legend for rate map').getByRole('img').click()
45+
await page.getByLabel('Legend for rate map').click()
4646
await page.locator('li').filter({ hasText: 'Denver County' }).click()
4747
await page
4848
.locator('#rate-chart')

frontend/playwright-tests/modals.ci.spec.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { test, expect } from '@playwright/test'
22

3-
test.setTimeout(120000);
3+
test.setTimeout(120000)
44

55
test('Topic Info Modal from Sidebar', async ({ page }) => {
66
// Compare Topics Page Loads
77
await page.goto(
88
'/exploredata?mls=1.incarceration-3.poverty-5.13&mlp=comparevars&dt1=prison',
9-
{ waitUntil: 'commit' }
9+
{ waitUntil: 'commit' },
1010
)
1111

1212
// Clicking topic info modal button launched modal
@@ -27,24 +27,29 @@ test('Topic Info Modal from Sidebar', async ({ page }) => {
2727

2828
test.describe('Topic Info Modal from Map Legend', () => {
2929
test('Topic Info Modal from Map Legend', async ({ page }) => {
30-
await page.goto('/', { waitUntil: 'commit' });
31-
await page.locator('#landingPageCTA').click();
32-
const reportSection = await page.getByRole('heading', { name: 'Uninsurance in FL' }).locator('..');
33-
await reportSection.locator('text=Explore this report').click();
34-
await page.locator('#rate-map').getByRole('button', { name: 'Click for more info on uninsured people' }).click();
35-
});
36-
});
30+
await page.goto('/', { waitUntil: 'commit' })
31+
await page.locator('#landingPageCTA').click()
32+
const reportSection = await page
33+
.getByRole('heading', { name: 'Uninsurance in FL' })
34+
.locator('..')
35+
await reportSection.locator('text=Explore this report').click()
36+
await page
37+
.locator('#rate-map')
38+
.getByRole('button', { name: 'Click for more info on uninsured people' })
39+
.click()
40+
})
41+
})
3742

3843
test('Multiple Maps 1 (Left Side)', async ({ page }) => {
3944
// Compare Topics Page With Multimap Open Loads
4045
await page.goto(
4146
'/exploredata?mls=1.incarceration-3.poverty-5.13&mlp=comparevars&dt1=prison&multiple-maps=true',
42-
{ waitUntil: 'commit' }
47+
{ waitUntil: 'commit' },
4348
)
4449
await expect(
4550
page.getByRole('heading', {
4651
name: 'Prison incarceration in Georgia across all race and ethnicity groups',
47-
})
52+
}),
4853
).toBeVisible()
4954

5055
// CLOSE IT
@@ -56,21 +61,21 @@ test('Multiple Maps 2 (Right Side)', async ({ page }) => {
5661
// Compare Topics Page Loads
5762
await page.goto(
5863
'/exploredata?mls=1.incarceration-3.poverty-5.13&mlp=comparevars&dt1=prison',
59-
{ waitUntil: 'commit' }
64+
{ waitUntil: 'commit' },
6065
)
6166

6267
// Clicking right side multiple maps button launches POVERTY multimap modal
6368
await page
6469
.locator('#rate-map2')
6570
.getByLabel(
66-
'Launch multiple maps view with side-by-side maps of each race and ethnicity group'
71+
'Launch multiple maps view with side-by-side maps of each race and ethnicity group',
6772
)
6873
.click()
6974
await expect(page).toHaveURL(/.*multiple-maps2=true/)
7075
await expect(
7176
page.getByRole('heading', {
7277
name: 'People below the poverty line in Georgia across all race and ethnicity groups',
73-
})
78+
}),
7479
).toBeVisible()
7580

7681
// CLOSE IT

frontend/playwright-tests/voter_participation.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ test('Voter Participation Flow', async ({ page }) => {
2323
.locator('#rate-chart')
2424
.getByRole('heading', { name: 'U.S. citizens, Ages 18+' })
2525
.click()
26-
await page.getByLabel('Bar Chart showing Voter').getByRole('img').click()
26+
await page.getByLabel('Bar Chart showing Voter').click()
2727
await page.getByRole('heading', { name: 'Share of all voter' }).click()
2828
await page.getByText('No unknown values for race').click()
2929
await page

frontend/src/cards/SimpleBarChartCard.tsx renamed to frontend/src/cards/RateBarChartCard.tsx

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { addComparisonAllsRowToIntersectionalData } from '../charts/simpleBarHelperFunctions'
2-
import { SimpleHorizontalBarChart } from '../charts/SimpleHorizontalBarChart'
1+
import { addComparisonAllsRowToIntersectionalData } from '../charts/rateBarChart/helpers'
2+
import { RateBarChart } from '../charts/rateBarChart/Index'
33
import { generateChartTitle, generateSubtitle } from '../charts/utils'
44
import type { DataTypeConfig, MetricId } from '../data/config/MetricConfigTypes'
55
import { isPctType } from '../data/config/MetricConfigUtils'
@@ -35,8 +35,7 @@ import MissingDataAlert from './ui/MissingDataAlert'
3535
/* minimize layout shift */
3636
const PRELOAD_HEIGHT = 668
3737

38-
interface SimpleBarChartCardProps {
39-
key?: string
38+
interface RateBarChartCardProps {
4039
demographicType: DemographicType
4140
dataTypeConfig: DataTypeConfig
4241
fips: Fips
@@ -46,16 +45,7 @@ interface SimpleBarChartCardProps {
4645

4746
// This wrapper ensures the proper key is set to create a new instance when
4847
// required rather than relying on the card caller.
49-
export default function SimpleBarChartCard(props: SimpleBarChartCardProps) {
50-
return (
51-
<SimpleBarChartCardWithKey
52-
key={props.dataTypeConfig.dataTypeId + props.demographicType}
53-
{...props}
54-
/>
55-
)
56-
}
57-
58-
function SimpleBarChartCardWithKey(props: SimpleBarChartCardProps) {
48+
export default function RateBarChartCard(props: RateBarChartCardProps) {
5949
const rateConfig =
6050
props.dataTypeConfig.metrics?.per100k ??
6151
props.dataTypeConfig.metrics?.pct_rate ??
@@ -185,11 +175,10 @@ function SimpleBarChartCardWithKey(props: SimpleBarChartCardProps) {
185175
) : (
186176
<>
187177
<ChartTitle title={chartTitle} subtitle={subtitle} />
188-
189-
<SimpleHorizontalBarChart
178+
<RateBarChart
190179
data={data}
191180
demographicType={props.demographicType}
192-
metric={rateConfig}
181+
metricConfig={rateConfig}
193182
filename={filename}
194183
usePercentSuffix={isPctType(rateConfig.type)}
195184
fips={props.fips}

frontend/src/charts/SimpleHorizontalBarChart.tsx

Lines changed: 0 additions & 86 deletions
This file was deleted.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export interface BarChartTooltipData {
2+
x: number
3+
y: number
4+
content: string
5+
}
6+
7+
interface BarChartTooltipProps {
8+
data: BarChartTooltipData | null
9+
}
10+
11+
export default function BarChartTooltip({ data }: BarChartTooltipProps) {
12+
if (!data) return null
13+
const clickIsLeftHalfOfScreen = data.x < window.innerWidth / 2
14+
return (
15+
<div
16+
className='bg-white text-altBlack rounded-sm p-3 text-title absolute cursor-help z-top shadow-raised opacity-95 smMd:whitespace-nowrap'
17+
style={{
18+
left: `${data.x}px`,
19+
top: `${data.y}px`,
20+
transform: clickIsLeftHalfOfScreen
21+
? 'translate(0, 5%)'
22+
: 'translate(-100%, 5%)',
23+
}}
24+
>
25+
{data.content}
26+
</div>
27+
)
28+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { ScaleBand } from 'd3'
2+
import type { MetricConfig } from '../../data/config/MetricConfigTypes'
3+
import { formatValue } from './helpers'
4+
5+
interface EndOfBarLabelProps {
6+
metricConfig: MetricConfig
7+
d: Record<string, any>
8+
shouldLabelBeInside: boolean
9+
barWidth: number
10+
yScale: ScaleBand<string>
11+
barLabelColor: string
12+
isTinyAndUp: boolean
13+
}
14+
15+
export default function EndOfBarLabel(props: EndOfBarLabelProps) {
16+
return (
17+
<text
18+
x={props.shouldLabelBeInside ? props.barWidth - 5 : props.barWidth + 5}
19+
y={props.yScale.bandwidth() / 2}
20+
dy='1.3em'
21+
textAnchor={props.shouldLabelBeInside ? 'end' : 'start'}
22+
className={`text-smallest ${props.barLabelColor}`}
23+
aria-hidden='true'
24+
tabIndex={-1}
25+
>
26+
{formatValue(
27+
props.d[props.metricConfig.metricId],
28+
props.metricConfig,
29+
props.isTinyAndUp,
30+
)}
31+
</text>
32+
)
33+
}

0 commit comments

Comments
 (0)