@cdc/waffle-chart 4.23.10-alpha → 4.23.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/waffle-chart",
3
- "version": "4.23.10-alpha",
3
+ "version": "4.23.11",
4
4
  "description": "React component for displaying a single piece of data in a card module",
5
5
  "moduleName": "CdcWaffleChart",
6
6
  "main": "dist/cdcwafflechart",
@@ -26,7 +26,7 @@
26
26
  "license": "Apache-2.0",
27
27
  "homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
28
28
  "dependencies": {
29
- "@cdc/core": "^4.23.10-alpha",
29
+ "@cdc/core": "^4.23.11",
30
30
  "@visx/group": "^3.0.0",
31
31
  "@visx/scale": "^3.0.0",
32
32
  "@visx/shape": "^3.0.0",
@@ -41,5 +41,5 @@
41
41
  "react": "^18.2.0",
42
42
  "react-dom": "^18.2.0"
43
43
  },
44
- "gitHead": "47278535fb20bb4e6b3320f8075051bedc5bca8e"
44
+ "gitHead": "ecad213667a3cb96c921eaddba43a31c84caaa08"
45
45
  }
@@ -1,10 +1,16 @@
1
- import React, { useCallback, useEffect, useState } from 'react'
2
- import parse from 'html-react-parser'
3
- import { Group } from '@visx/group'
1
+ import React, { useCallback, useEffect, useReducer } from 'react'
2
+
3
+ // visx
4
4
  import { Circle, Bar } from '@visx/shape'
5
+ import { Group } from '@visx/group'
5
6
  import { scaleLinear } from '@visx/scale'
6
7
 
8
+ // external
9
+ import parse from 'html-react-parser'
7
10
  import ResizeObserver from 'resize-observer-polyfill'
11
+
12
+ // cdc
13
+ import { Config } from './types/Config'
8
14
  import getViewport from '@cdc/core/helpers/getViewport'
9
15
  import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
10
16
 
@@ -14,30 +20,43 @@ import Loading from '@cdc/core/components/Loading'
14
20
  import ConfigContext from './ConfigContext'
15
21
  import EditorPanel from './components/EditorPanel'
16
22
  import defaults from './data/initial-state'
17
-
18
23
  import { publish } from '@cdc/core/helpers/events'
19
-
20
- import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
24
+ import chartReducer from './store/chart.reducer'
21
25
  import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
26
+ import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
22
27
 
23
28
  import './scss/main.scss'
24
-
25
- const themeColor = {
26
- 'theme-blue': '#005eaa',
27
- 'theme-purple': '#712177',
28
- 'theme-brown': '#705043',
29
- 'theme-teal': '#00695c',
30
- 'theme-pink': '#af4448',
31
- 'theme-orange': '#bb4d00',
32
- 'theme-slate': '#29434e',
33
- 'theme-indigo': '#26418f',
34
- 'theme-cyan': '#006778',
35
- 'theme-green': '#4b830d',
36
- 'theme-amber': '#fbab18'
29
+ import Title from '@cdc/core/components/ui/Title'
30
+
31
+ type CdcWaffleChartProps = {
32
+ configUrl?: string
33
+ config?: Config
34
+ isDashboard?: boolean
35
+ isEditor?: boolean
36
+ link?: string
37
+ setConfig?: () => void
37
38
  }
38
39
 
39
- const WaffleChart = ({ config, isEditor, link }) => {
40
- let { title, theme, shape, nodeWidth, nodeSpacer, prefix, suffix, subtext, content, orientation, filters, dataColumn, dataFunction, dataConditionalColumn, dataConditionalOperator, dataConditionalComparate, customDenom, dataDenom, dataDenomColumn, dataDenomFunction, roundToPlace } = config
40
+ const WaffleChart = ({ config, isEditor, link = '' }) => {
41
+ const { title, theme, shape, nodeWidth, nodeSpacer, prefix, suffix, subtext, content, orientation, filters, dataColumn, dataFunction, dataConditionalColumn, dataConditionalOperator, dataConditionalComparate, customDenom, dataDenom, dataDenomColumn, dataDenomFunction, roundToPlace } = config
42
+
43
+ const handleWaffleChartAriaLabel = (state, testing = false): string => {
44
+ // eslint-disable-next-line no-console
45
+ if (testing) console.log(`handleWaffleChartAriaLabels Testing On:`, state)
46
+ try {
47
+ let ariaLabel = 'Waffle chart'
48
+ if (state.title) {
49
+ ariaLabel += ` with the title: ${state.title}`
50
+ }
51
+ return ariaLabel
52
+ } catch (e) {
53
+ // eslint-disable-next-line no-console
54
+ console.error(e.message)
55
+ }
56
+ }
57
+
58
+ const gaugeColor = config.visual.colors[config.theme]
59
+ let dataFontSize = config.fontSize ? { fontSize: config.fontSize + 'px' } : null
41
60
 
42
61
  const calculateData = useCallback(() => {
43
62
  //If either the column or function aren't set, do not calculate
@@ -224,7 +243,7 @@ const WaffleChart = ({ config, isEditor, link }) => {
224
243
  shape: shape,
225
244
  x: calculatePos(shape, 'x', i, nodeWidthNum, nodeSpacerNum),
226
245
  y: calculatePos(shape, 'y', i, nodeWidthNum, nodeSpacerNum),
227
- color: themeColor[theme],
246
+ color: config.visual.colors[theme],
228
247
  opacity: i + 1 > 100 - Math.round(dataPercentage) ? 1 : 0.35
229
248
  }
230
249
  waffleData.push(newNode)
@@ -253,39 +272,17 @@ const WaffleChart = ({ config, isEditor, link }) => {
253
272
  return nodeWidth * 10 + nodeSpacer * 9
254
273
  }, [nodeWidth, nodeSpacer])
255
274
 
256
- let dataFontSize = config.fontSize ? { fontSize: config.fontSize + 'px' } : null
257
-
258
275
  const { innerContainerClasses, contentClasses } = useDataVizClasses(config)
259
276
 
260
- const handleWaffleChartAriaLabel = (state, testing = false) => {
261
- if (testing) console.log(`handleWaffleChartAriaLabels Testing On:`, state)
262
- try {
263
- let ariaLabel = 'Waffle chart'
264
- if (state.title) {
265
- ariaLabel += ` with the title: ${state.title}`
266
- }
267
- return ariaLabel
268
- } catch (e) {
269
- console.error(e.message)
270
- }
271
- }
272
- const gaugeWidth = '100%'
273
- const gaugeHeight = 35
274
-
275
277
  const xScale = scaleLinear({
276
278
  domain: [0, waffleDenominator],
277
- range: [0, gaugeWidth]
279
+ range: [0, config.gauge.width]
278
280
  })
279
- const gaugeColor = themeColor[theme]
280
281
 
281
282
  return (
282
283
  <div className={innerContainerClasses.join(' ')}>
283
284
  <>
284
- {title && (
285
- <header className={`cove-component__header chart-title ${config.theme}`} aria-hidden='true'>
286
- {parse(title)}
287
- </header>
288
- )}
285
+ <Title title={title} config={config} classes={['chart-title', `${config.theme}`, 'mb-0']} />
289
286
  <div className={contentClasses.join(' ')}>
290
287
  <div className='cove-component__content-wrap'>
291
288
  {config.visualizationType === 'Gauge' && (
@@ -297,10 +294,10 @@ const WaffleChart = ({ config, isEditor, link }) => {
297
294
  {suffix ? suffix + ' ' : ' '} {config.valueDescription} {config.showDenominator && waffleDenominator ? waffleDenominator : ' '}
298
295
  </div>
299
296
  <div className='cove-waffle-chart__data--text'>{parse(content)}</div>
300
- <svg height={gaugeHeight} width={'100%'}>
297
+ <svg height={config.gauge.height} width={'100%'}>
301
298
  <Group>
302
- <foreignObject style={{ border: '1px solid black' }} x={0} y={0} width={gaugeWidth} height={gaugeHeight} fill='#fff' />
303
- <Bar x={0} y={0} width={xScale(waffleNumerator)} height={gaugeHeight} fill={gaugeColor} />
299
+ <foreignObject style={{ border: '1px solid black' }} x={0} y={0} width={config.gauge.width} height={config.gauge.height} fill='#fff' />
300
+ <Bar x={0} y={0} width={xScale(waffleNumerator)} height={config.gauge.height} fill={gaugeColor} />
304
301
  </Group>
305
302
  </svg>
306
303
  <div className={'cove-waffle-chart__subtext subtext'}>{parse(subtext)}</div>
@@ -338,14 +335,10 @@ const WaffleChart = ({ config, isEditor, link }) => {
338
335
  )
339
336
  }
340
337
 
341
- const CdcWaffleChart = ({ configUrl, config: configObj, isDashboard = false, isEditor = false, setConfig: setParentConfig }) => {
338
+ const CdcWaffleChart = ({ configUrl, config: configObj, isDashboard = false, isEditor = false, setConfig: setParentConfig }: CdcWaffleChartProps) => {
342
339
  // Default States
343
- const [config, setConfig] = useState({ ...defaults })
344
- const [loading, setLoading] = useState(true)
345
-
346
- const [currentViewport, setCurrentViewport] = useState('lg')
347
- const [coveLoadedHasRan, setCoveLoadedHasRan] = useState(false)
348
- const [container, setContainer] = useState()
340
+ const [state, dispatch] = useReducer(chartReducer, { config: configObj ?? defaults, loading: true, preview: false, viewport: 'lg', coveLoadedHasRan: false, container: null })
341
+ const { loading, config, viewport: currentViewport, coveLoadedHasRan, container } = state
349
342
 
350
343
  // Default Functions
351
344
  const updateConfig = newConfig => {
@@ -354,12 +347,10 @@ const CdcWaffleChart = ({ configUrl, config: configObj, isDashboard = false, isE
354
347
  newConfig[key] = { ...defaults[key], ...newConfig[key] }
355
348
  }
356
349
  })
357
-
358
350
  newConfig.runtime = {}
359
351
  newConfig.runtime.uniqueId = Date.now()
360
-
361
352
  newConfig.runtime.editorErrorMessage = ''
362
- setConfig(newConfig)
353
+ dispatch({ type: 'SET_CONFIG', payload: newConfig })
363
354
  }
364
355
 
365
356
  const loadConfig = useCallback(async () => {
@@ -374,7 +365,7 @@ const CdcWaffleChart = ({ configUrl, config: configObj, isDashboard = false, isE
374
365
 
375
366
  const processedConfig = { ...(await coveUpdateWorker(response)) }
376
367
  updateConfig({ ...defaults, ...processedConfig })
377
- setLoading(false)
368
+ dispatch({ type: 'SET_LOADING', payload: false })
378
369
  }, [])
379
370
 
380
371
  // Custom Functions
@@ -383,8 +374,7 @@ const CdcWaffleChart = ({ configUrl, config: configObj, isDashboard = false, isE
383
374
  const resizeObserver = new ResizeObserver(entries => {
384
375
  for (let entry of entries) {
385
376
  let newViewport = getViewport(entry.contentRect.width * 2) // Data bite is usually presented as small, so we scale it up for responsive calculations
386
-
387
- setCurrentViewport(newViewport)
377
+ dispatch({ type: 'SET_VIEWPORT', payload: newViewport })
388
378
  }
389
379
  })
390
380
 
@@ -392,23 +382,25 @@ const CdcWaffleChart = ({ configUrl, config: configObj, isDashboard = false, isE
392
382
  if (node !== null) {
393
383
  resizeObserver.observe(node)
394
384
  }
395
- setContainer(node)
385
+ dispatch({ type: 'SET_CONTAINER', payload: node })
396
386
  }, [])
397
387
 
398
388
  //Load initial config
399
389
  useEffect(() => {
390
+ // eslint-disable-next-line no-console
400
391
  loadConfig().catch(err => console.log(err))
401
392
  }, [])
402
393
 
403
394
  useEffect(() => {
404
395
  if (config && !coveLoadedHasRan && container) {
405
396
  publish('cove_loaded', { config: config })
406
- setCoveLoadedHasRan(true)
397
+ dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
407
398
  }
408
399
  }, [config, container])
409
400
 
410
401
  //Reload config if config object provided/updated
411
402
  useEffect(() => {
403
+ // eslint-disable-next-line no-console
412
404
  loadConfig().catch(err => console.log(err))
413
405
  }, [])
414
406
 
@@ -10,10 +10,147 @@ const meta: Meta<typeof WaffleChart> = {
10
10
 
11
11
  type Story = StoryObj<typeof WaffleChart>
12
12
 
13
- export const Primary: Story = {
13
+ export const Example_Waffle_Chart_Count: Story = {
14
+ args: {
15
+ configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Waffle-Chart-Example-Count.json'
16
+ },
17
+ render: args => (
18
+ <>
19
+ <WaffleChart {...args} />
20
+ </>
21
+ )
22
+ }
23
+
24
+ export const Waffle_Chart_Average_Max: Story = {
25
+ args: {
26
+ configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Waffle-Chart-Average-Max.json'
27
+ },
28
+ render: args => (
29
+ <>
30
+ <WaffleChart {...args} />
31
+ </>
32
+ )
33
+ }
34
+
35
+ export const Waffle_Chart_Demographics: Story = {
36
+ args: {
37
+ configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/waffle-chart-demographics.json'
38
+ },
39
+ render: args => (
40
+ <>
41
+ <WaffleChart {...args} />
42
+ </>
43
+ )
44
+ }
45
+
46
+ export const Linear_Gauge: Story = {
47
+ args: {
48
+ configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/linear-gauge-data-from-file.json'
49
+ },
50
+ render: args => (
51
+ <>
52
+ <WaffleChart {...args} />
53
+ </>
54
+ )
55
+ }
56
+
57
+ export const Linear_Gauge_With_Message: Story = {
58
+ args: {
59
+ configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/gauge-progress-indicator.json'
60
+ },
61
+ render: args => (
62
+ <>
63
+ <WaffleChart {...args} />
64
+ </>
65
+ )
66
+ }
67
+
68
+ export const Person: Story = {
69
+ args: {
70
+ config: {
71
+ type: 'waffle-chart',
72
+ shape: 'person',
73
+ title: 'Overdose Mortality Rates',
74
+ content: 'of overdoses resulted in death.',
75
+ subtext: 'This data is an example and does not reflect actual averages',
76
+ orientation: 'horizontal',
77
+ data: [
78
+ {
79
+ 'Resulted in Death': 250,
80
+ 'Number of Overdoses': 600,
81
+ state: 'Alabama',
82
+ Year: '2010'
83
+ },
84
+ {
85
+ 'Resulted in Death': 16,
86
+ 'Number of Overdoses': 80,
87
+ state: 'Alaska',
88
+ Year: '2008'
89
+ },
90
+ {
91
+ 'Resulted in Death': 19,
92
+ 'Number of Overdoses': 100,
93
+ state: 'Alaska',
94
+ Year: '2010'
95
+ },
96
+ {
97
+ 'Resulted in Death': 93,
98
+ 'Number of Overdoses': 400,
99
+ state: 'Alaska',
100
+ Year: '2012'
101
+ },
102
+ {
103
+ 'Resulted in Death': 82,
104
+ 'Number of Overdoses': 400,
105
+ state: 'Arizona',
106
+ Year: '2010'
107
+ }
108
+ ],
109
+ filters: [],
110
+ fontSize: null,
111
+ overallFontSize: 'medium',
112
+ dataColumn: 'Resulted in Death',
113
+ dataFunction: 'Sum',
114
+ dataConditionalColumn: '',
115
+ dataConditionalOperator: null,
116
+ dataConditionalComparate: '',
117
+ customDenom: true,
118
+ dataDenom: null,
119
+ dataDenomColumn: 'Number of Overdoses',
120
+ dataDenomFunction: 'Sum',
121
+ prefix: '',
122
+ suffix: '%',
123
+ roundToPlace: 0,
124
+ nodeWidth: 10,
125
+ nodeSpacer: 2,
126
+ theme: 'theme-blue',
127
+ visualizationType: 'Waffle',
128
+ visualizationSubType: '',
129
+ invalidComparate: false,
130
+ showDenominator: false,
131
+ showPercent: true,
132
+ valueDescription: 'testing',
133
+ visual: {
134
+ border: true,
135
+ accent: true,
136
+ background: true,
137
+ hideBackgroundColor: true,
138
+ borderColorTheme: true
139
+ }
140
+ }
141
+ },
142
+ render: args => (
143
+ <>
144
+ <WaffleChart {...args} />
145
+ </>
146
+ )
147
+ }
148
+
149
+ export const Gauge: Story = {
14
150
  args: {
15
151
  config: {
16
- shape: 'square',
152
+ type: 'waffle-chart',
153
+ shape: 'person',
17
154
  title: 'Overdose Mortality Rates',
18
155
  content: 'of overdoses resulted in death.',
19
156
  subtext: 'This data is an example and does not reflect actual averages',
@@ -67,7 +204,20 @@ export const Primary: Story = {
67
204
  roundToPlace: 0,
68
205
  nodeWidth: 10,
69
206
  nodeSpacer: 2,
70
- theme: 'blue'
207
+ theme: 'theme-blue',
208
+ visualizationType: 'Gauge',
209
+ visualizationSubType: '',
210
+ invalidComparate: false,
211
+ showDenominator: false,
212
+ showPercent: true,
213
+ valueDescription: 'testing',
214
+ visual: {
215
+ border: true,
216
+ accent: true,
217
+ background: true,
218
+ hideBackgroundColor: true,
219
+ borderColorTheme: true
220
+ }
71
221
  }
72
222
  },
73
223
  render: args => (
@@ -29,11 +29,28 @@ export default {
29
29
  nodeSpacer: '2',
30
30
  theme: 'theme-blue',
31
31
  type: 'waffle-chart',
32
+ gauge: {
33
+ height: 35,
34
+ width: '100%'
35
+ },
32
36
  visual: {
33
37
  border: true,
34
38
  accent: false,
35
39
  background: false,
36
40
  hideBackgroundColor: false,
37
- borderColorTheme: false
41
+ borderColorTheme: false,
42
+ colors: {
43
+ 'theme-blue': '#005eaa',
44
+ 'theme-purple': '#712177',
45
+ 'theme-brown': '#705043',
46
+ 'theme-teal': '#00695c',
47
+ 'theme-pink': '#af4448',
48
+ 'theme-orange': '#bb4d00',
49
+ 'theme-slate': '#29434e',
50
+ 'theme-indigo': '#26418f',
51
+ 'theme-cyan': '#006778',
52
+ 'theme-green': '#4b830d',
53
+ 'theme-amber': '#fbab18'
54
+ }
38
55
  }
39
56
  }
@@ -8,16 +8,6 @@
8
8
  }
9
9
  }
10
10
 
11
- // .cove .waffle-chart .cove-component__header {
12
- // font-size: unset;
13
- // }
14
-
15
- //@each $theme, $color in $theme-colors {
16
- // .cdc-waffle-chart.theme-#{$theme} #{&} {
17
- // @include set-theme("color", $color);
18
- // }
19
- //}
20
-
21
11
  .warning-icon {
22
12
  position: absolute;
23
13
  top: 50%;
@@ -0,0 +1,19 @@
1
+ import type { Config } from '../types/Config'
2
+
3
+ type Action<T, P> = { type: T; payload: P }
4
+
5
+ type SET_CONFIG = Action<'SET_CONFIG', Config>
6
+ type SET_CONTAINER = Action<'SET_CONTAINER', any>
7
+ type SET_COVE_LOADED_HAS_RAN = Action<'SET_COVE_LOADED_HAS_RAN', boolean>
8
+ type SET_LOADING = Action<'SET_LOADING', boolean>
9
+ type SET_VIEWPORT = Action<'SET_VIEWPORT', string>
10
+
11
+ // prettier-ignore
12
+ type ChartActions =
13
+ | SET_CONFIG
14
+ | SET_CONTAINER
15
+ | SET_COVE_LOADED_HAS_RAN
16
+ | SET_LOADING
17
+ | SET_VIEWPORT
18
+
19
+ export default ChartActions
@@ -0,0 +1,32 @@
1
+ import { Config } from '../types/Config'
2
+ import ChartActions from './chart.actions'
3
+
4
+ export type ChartState = {
5
+ config?: Config
6
+ container: any
7
+ coveLoadedHasRan: boolean
8
+ loading: boolean
9
+ preview: boolean
10
+ viewport: string
11
+ }
12
+ const reducer = (state: ChartState, action: ChartActions): ChartState => {
13
+ switch (action.type) {
14
+ case 'SET_CONFIG': {
15
+ return { ...state, config: action.payload }
16
+ }
17
+ case 'SET_LOADING': {
18
+ return { ...state, loading: action.payload }
19
+ }
20
+ case 'SET_VIEWPORT': {
21
+ return { ...state, viewport: action.payload }
22
+ }
23
+ case 'SET_COVE_LOADED_HAS_RAN': {
24
+ return { ...state, coveLoadedHasRan: action.payload }
25
+ }
26
+ case 'SET_CONTAINER': {
27
+ return { ...state, container: action.payload }
28
+ }
29
+ }
30
+ }
31
+
32
+ export default reducer
@@ -0,0 +1,40 @@
1
+ import { ComponentStyles } from '@cdc/core/types/ComponentStyles'
2
+ import { ComponentThemes } from '@cdc/core/types/ComponentThemes'
3
+
4
+ export type Config = {
5
+ // supporting text in the box
6
+ content: string
7
+ customDenom: boolean
8
+ data: Object[]
9
+ dataColumn: string
10
+ dataConditionalColumn: string
11
+ dataConditionalComparate: string
12
+ dataConditionalOperator: string
13
+ dataDenom: string
14
+ dataDenomColumn: string
15
+ dataDenomFunction: string
16
+ dataFunction: string
17
+ dataUrl?: string
18
+ filters: any[]
19
+ // data point font size (main number)
20
+ fontSize: string
21
+ invalidComparate: false
22
+ nodeSpacer: number
23
+ nodeWidth: number
24
+ orientation: 'horizontal' | 'vertical'
25
+ overallFontSize: 'small' | 'medium' | 'large'
26
+ prefix?: string
27
+ roundToPlace: number
28
+ shape: 'square' | 'circle' | 'person'
29
+ showDenominator: boolean
30
+ showPercent: boolean
31
+ subtext: string
32
+ suffix: string
33
+ theme: ComponentThemes
34
+ title: string
35
+ type: 'waffle-chart'
36
+ valueDescription: string
37
+ visual: ComponentStyles
38
+ visualizationSubType: 'linear' | ''
39
+ visualizationType: 'Gauge' | 'Waffle'
40
+ }