Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions packages/waffle-chart/src/CdcWaffleChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -385,20 +385,18 @@ const WaffleChart = ({ config, isEditor, link = '', showConfigConfirm, updateCon
Number.isFinite(parsedDenominator) &&
parsedDenominator > 0 &&
Number.isFinite(parsedNumerator) &&
parsedNumerator >= 0
parsedNumerator >= 0 &&
parsedNumerator <= parsedDenominator
) {
const roundedDenominator = Math.round(parsedDenominator)
const roundedNumerator = Math.round(parsedNumerator)

if (
roundedDenominator >= 1 &&
roundedDenominator <= 99 &&
roundedNumerator >= 0 &&
roundedNumerator <= roundedDenominator
) {

if (roundedDenominator >= 1 && roundedDenominator <= 99) {
renderMode = 'dynamic-denominator'
unitCount = roundedDenominator
filledCount = roundedNumerator
filledCount = Math.max(
0,
Math.min(roundedDenominator, Math.round((parsedNumerator / parsedDenominator) * roundedDenominator))
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,11 @@ export const DataSectionTests: Story = {
expect(el).toBeTruthy()
return el
}
const getValueText = () => getPrimaryEl().textContent?.trim() || ''
const getValueText = () => getPrimaryEl().textContent?.replace(/\s+/g, ' ').trim() || ''
const getDynamicDenominatorVisualState = () => ({
valueText: getValueText(),
...getWaffleNodeState()
})
const getWaffleNodeState = () => {
const nodes = Array.from(canvasElement.querySelectorAll('.cove-waffle-chart svg .cdc-waffle-chart__node'))
const chartType = (canvasElement.querySelector('select[name="visualizationType"]') as HTMLSelectElement)?.value
Expand Down Expand Up @@ -554,12 +558,12 @@ export const DataSectionTests: Story = {
)

// ============================================================================
// TEST 23: Non-Integer Numerator/Denominator Rounding
// Expectation: Mean 17.5 and denominator 20.2 round to 18 out of 20.
// TEST 23: Non-Integer Numerator/Denominator Rounding (Round=0)
// Expectation: Mean 17.5 and denominator 20.2 render 18 filled nodes out of 20 to match the rounded display value.
// ============================================================================
await performAndAssert(
'Dynamic Rounding Non-Integer Values',
getWaffleNodeState,
'Dynamic Rounding Non-Integer Values Round 0',
getDynamicDenominatorVisualState,
async () => {
await userEvent.selectOptions(dataFunctionSelect, 'Mean (Average)')
await userEvent.selectOptions(dataColumnSelect, 'Deaths')
Expand All @@ -569,14 +573,32 @@ export const DataSectionTests: Story = {
await userEvent.type(conditionalValueInput, '20')
await userEvent.clear(staticDenomInput)
await userEvent.type(staticDenomInput, '20.2')
await userEvent.clear(roundingInput)
await userEvent.type(roundingInput, '0')
await setCheckboxState(showPercentCheckbox, false)
await setCheckboxState(showDenominatorCheckbox, true)
},
(_before, after) => after.total === 20 && after.filled === 18
(_before, after) =>
after.total === 20 && after.filled === 18 && after.valueText.includes('18 deaths out of total 20.2')
)

// ============================================================================
// TEST 24: Non-Integer Numerator/Denominator Rounding (Round=1)
// Expectation: Mean 17.5 and denominator 20.2 render 17 filled nodes out of 20 while display shows 17.5.
// ============================================================================
await performAndAssert(
'Dynamic Rounding Non-Integer Values Round 1',
getDynamicDenominatorVisualState,
async () => {
await userEvent.clear(roundingInput)
await userEvent.type(roundingInput, '1')
},
(_before, after) =>
after.total === 20 && after.filled === 17 && after.valueText.includes('17.5 deaths out of total 20.2')
)

// ============================================================================
// TEST 24: Numerator Greater Than Denominator Fallback
// TEST 25: Numerator Greater Than Denominator Fallback
// Expectation: Falls back to fixed 100 nodes.
// ============================================================================
await performAndAssert(
Expand All @@ -589,7 +611,7 @@ export const DataSectionTests: Story = {
)

// ============================================================================
// TEST 25: TP5 Waffle Dynamic Denominator (7 out of 12)
// TEST 26: TP5 Waffle Dynamic Denominator (7 out of 12)
// Expectation: 12 total TP5 nodes, 7 filled nodes.
// ============================================================================
await performAndAssert(
Expand Down
96 changes: 96 additions & 0 deletions packages/waffle-chart/src/test/CdcWaffleChart.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,35 @@ describe('Waffle Chart', () => {
...overrides
})

const getRenderedNodeCounts = container => {
const nodes = Array.from(container.querySelectorAll('.cove-waffle-chart svg .cdc-waffle-chart__node'))
const filled = nodes.filter(node => Number(node.getAttribute('fill-opacity') ?? 1) >= 1).length

return {
total: nodes.length,
filled
}
}

const getPrimaryValueText = container =>
container.querySelector('.cove-waffle-chart__data--primary')?.textContent?.replace(/\s+/g, ' ').trim() || ''

const renderDynamicDenominatorChart = ({ numerator, denominator, roundToPlace = '' }) =>
render(
<CdcWaffleChart
config={createBaseConfig({
data: [{ value: numerator }],
dataFunction: 'Sum',
dataDenom: String(denominator),
showPercent: false,
showDenominator: true,
suffix: '',
roundToPlace,
valueDescription: 'out of'
})}
/>
)

it('Can be built in isolation', async () => {
const pkgDir = path.join(__dirname, '..')
const result = await testStandaloneBuild(pkgDir)
Expand Down Expand Up @@ -361,4 +390,71 @@ describe('Waffle Chart', () => {
expect(scaledGroup?.getAttribute('transform')).toContain('scale(')
expect(scaledGroup?.getAttribute('transform')).not.toContain('scale(1)')
})

it.each([
['1.2 out of 7 as 1 filled node', 1.2, 7, 7, 1],
['1.6 out of 7 as 2 filled nodes', 1.6, 7, 7, 2],
['2.6 out of 3.4 as 2 filled nodes', 2.6, 3.4, 3, 2],
['1.49 out of 1.51 as 2 filled nodes', 1.49, 1.51, 2, 2]
])(
'rounds dynamic denominator fills by raw ratio for %s',
async (_label, numerator, denominator, expectedTotal, expectedFilled) => {
const { container } = renderDynamicDenominatorChart({ numerator, denominator })

await waitFor(() => {
expect(getRenderedNodeCounts(container)).toEqual({
total: expectedTotal,
filled: expectedFilled
})
})
}
)

it('uses the rounded displayed numerator for dynamic denominator waffle geometry when roundToPlace is 0', async () => {
const { container } = renderDynamicDenominatorChart({ numerator: 17.5, denominator: 20.2, roundToPlace: 0 })

await waitFor(() => {
expect(getRenderedNodeCounts(container)).toEqual({
total: 20,
filled: 18
})
})

expect(getPrimaryValueText(container)).toContain('18 out of 20.2')
})

it('uses the rounded displayed numerator for dynamic denominator waffle geometry when roundToPlace is 1', async () => {
const { container } = renderDynamicDenominatorChart({ numerator: 17.5, denominator: 20.2, roundToPlace: 1 })

await waitFor(() => {
expect(getRenderedNodeCounts(container)).toEqual({
total: 20,
filled: 17
})
})

expect(getPrimaryValueText(container)).toContain('17.5 out of 20.2')
})

it('preserves exact numerator and denominator text while discretizing dynamic denominator dots when roundToPlace is blank', async () => {
const { container } = renderDynamicDenominatorChart({ numerator: 17.5, denominator: 20.2 })

await waitFor(() => {
expect(getRenderedNodeCounts(container)).toEqual({
total: 20,
filled: 17
})
})

expect(container.querySelector('.cove-waffle-chart__data--primary')).toHaveTextContent('17.5')
expect(container.querySelector('.cove-waffle-chart__data--primary')).toHaveTextContent('20.2')
})

it('falls back to fixed 100-node mode when numerator exceeds denominator', async () => {
const { container } = renderDynamicDenominatorChart({ numerator: 21, denominator: 20 })

await waitFor(() => {
expect(getRenderedNodeCounts(container).total).toBe(100)
})
})
})
Loading