@cdc/chart 4.25.6-2 → 4.25.7

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.
Files changed (54) hide show
  1. package/dist/cdcchart.js +52634 -30848
  2. package/package.json +3 -2
  3. package/src/CdcChartComponent.tsx +13 -9
  4. package/src/_stories/Chart.BoxPlot.stories.tsx +35 -0
  5. package/src/_stories/Chart.stories.tsx +0 -7
  6. package/src/_stories/Chart.tooltip.stories.tsx +35 -275
  7. package/src/_stories/_mock/bar-chart-suppressed.json +2 -80
  8. package/src/_stories/_mock/boxplot_multiseries.json +252 -166
  9. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
  10. package/src/components/AreaChart/components/AreaChart.jsx +4 -8
  11. package/src/components/BarChart/components/BarChart.Horizontal.tsx +34 -2
  12. package/src/components/BarChart/components/BarChart.Vertical.tsx +15 -0
  13. package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +131 -0
  14. package/src/components/BoxPlot/{BoxPlot.tsx → BoxPlot.Vertical.tsx} +4 -4
  15. package/src/components/BoxPlot/helpers/index.ts +32 -12
  16. package/src/components/BrushChart.tsx +1 -1
  17. package/src/components/EditorPanel/EditorPanel.tsx +3 -3
  18. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +2 -2
  19. package/src/components/Forecasting/{Forecasting.jsx → Forecasting.tsx} +32 -12
  20. package/src/components/Legend/LegendGroup/LegendGroup.tsx +1 -0
  21. package/src/components/Legend/helpers/index.ts +2 -2
  22. package/src/components/LinearChart.tsx +63 -15
  23. package/src/data/initial-state.js +1 -5
  24. package/src/helpers/filterAndShiftLinearDateTicks.ts +58 -0
  25. package/src/helpers/getBridgedData.ts +13 -0
  26. package/src/helpers/tests/getBridgedData.test.ts +64 -0
  27. package/src/hooks/useScales.ts +42 -42
  28. package/src/hooks/useTooltip.tsx +3 -2
  29. package/src/scss/main.scss +2 -8
  30. package/src/store/chart.actions.ts +2 -2
  31. package/src/store/chart.reducer.ts +4 -12
  32. package/src/types/ChartConfig.ts +0 -5
  33. package/examples/private/0527.json +0 -1
  34. package/examples/private/DEV-8850-2.json +0 -493
  35. package/examples/private/DEV-9822.json +0 -574
  36. package/examples/private/DEV-9840.json +0 -553
  37. package/examples/private/DEV-9850-3.json +0 -461
  38. package/examples/private/chart.json +0 -1084
  39. package/examples/private/ci_formatted.json +0 -202
  40. package/examples/private/ci_issue.json +0 -3016
  41. package/examples/private/completed.json +0 -634
  42. package/examples/private/dem-data-long.csv +0 -20
  43. package/examples/private/dem-data-long.json +0 -36
  44. package/examples/private/demographic_data.csv +0 -157
  45. package/examples/private/demographic_data.json +0 -2654
  46. package/examples/private/demographic_dynamic.json +0 -443
  47. package/examples/private/demographic_standard.json +0 -560
  48. package/examples/private/ehdi.json +0 -29939
  49. package/examples/private/line-issue.json +0 -497
  50. package/examples/private/not-loading.json +0 -360
  51. package/examples/private/test.json +0 -493
  52. package/examples/private/testing-pie.json +0 -436
  53. package/src/components/BoxPlot/index.tsx +0 -3
  54. /package/src/components/Brush/{BrushController..tsx → BrushController.tsx} +0 -0
@@ -89,11 +89,7 @@ export default {
89
89
  topAxis: {
90
90
  hasLine: false
91
91
  },
92
- brush: {
93
- isActive: false,
94
- isBrushing: false,
95
- data: []
96
- },
92
+
97
93
  isLegendValue: false,
98
94
  barThickness: 0.35,
99
95
  barHeight: 25,
@@ -0,0 +1,58 @@
1
+ // Ensure that the last tick is shown for charts with a "Date (Linear Scale)" scale
2
+ import { ChartConfig } from '../types/ChartConfig'
3
+ import { getTicks } from '@visx/scale'
4
+ export const filterAndShiftLinearDateTicks = (
5
+ config: ChartConfig,
6
+ axisProps: { scale: any; numTicks: number; ticks: { value: any; formattedValue?: string }[] },
7
+ xAxisDataMapped: any[],
8
+ formatDate: (value: any, index: number, all: any[]) => string
9
+ ) => {
10
+ // Guard #1: must have a scale & ticks array
11
+ if (!axisProps?.scale || !Array.isArray(axisProps.ticks)) {
12
+ return []
13
+ }
14
+
15
+ // Guard #2: if no domain data, just format what we have
16
+ if (!Array.isArray(xAxisDataMapped) || xAxisDataMapped.length === 0) {
17
+ axisProps.ticks.forEach((t, i) => {
18
+ t.formattedValue = formatDate(t.value, i, axisProps.ticks)
19
+ })
20
+ return axisProps.ticks
21
+ }
22
+
23
+ // get our filtered tick *values*
24
+ const filteredTickValues = getTicks(axisProps.scale, axisProps.numTicks) || []
25
+
26
+ let ticks = axisProps.ticks
27
+
28
+ if (filteredTickValues.length > 0 && filteredTickValues.length < xAxisDataMapped.length) {
29
+ const lastFiltered = filteredTickValues[filteredTickValues.length - 1]
30
+ const lastIdx = xAxisDataMapped.indexOf(lastFiltered)
31
+
32
+ let shift = 0
33
+ if (lastIdx >= 0 && lastIdx < xAxisDataMapped.length - 1) {
34
+ shift = config.xAxis.sortByRecentDate
35
+ ? -xAxisDataMapped.indexOf(filteredTickValues[0])
36
+ : xAxisDataMapped.length - 1 - lastIdx
37
+ }
38
+
39
+ ticks = filteredTickValues.map(value => {
40
+ const baseIndex = axisProps.ticks.findIndex(t => t.value === value)
41
+ const targetIndex = baseIndex + shift
42
+
43
+ // Guard #3: if shifting would go out of bounds, clamp
44
+ const safeIndex = Math.max(0, Math.min(axisProps.ticks.length - 1, targetIndex))
45
+ return axisProps.ticks[safeIndex]
46
+ })
47
+ }
48
+
49
+ // Finally, format all ticks
50
+ ticks.forEach((tick, i) => {
51
+ // Guard #4: only format if we actually have a value
52
+ if (tick && tick.value != null) {
53
+ tick.formattedValue = formatDate(tick.value, i, ticks)
54
+ }
55
+ })
56
+
57
+ return ticks
58
+ }
@@ -0,0 +1,13 @@
1
+ export const getBridgedData = (stageKey: string, stageColumn: string, data: Record<string, any>[]) => {
2
+ const allStages: string[] = Array.from(new Set(data.map(d => d?.[stageColumn]).filter(Boolean)))
3
+
4
+ const stageIndex: number = allStages.indexOf(stageKey)
5
+ if (stageIndex === -1) return []
6
+ const currentStage = data.filter(d => d?.[stageColumn] === stageKey)
7
+ const nextKey = allStages[stageIndex + 1]
8
+ if (nextKey) {
9
+ const nextSlice = data.filter(d => d[stageColumn] === nextKey)
10
+ return [...currentStage, nextSlice[0]]
11
+ }
12
+ return currentStage
13
+ }
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { getBridgedData } from '../getBridgedData'
3
+
4
+ describe('getBridgedData', () => {
5
+ const sampleData = [
6
+ { Month: 'Jan', value: 1 },
7
+ { Month: 'Jan', value: 2 },
8
+ { Month: 'Feb', value: 3 },
9
+ { Month: 'Feb', value: 4 },
10
+ { Month: 'Mar', value: 5 }
11
+ ]
12
+
13
+ it('returns correct slice for stageKey with next stage', () => {
14
+ const result = getBridgedData('Jan', 'Month', sampleData)
15
+
16
+ expect(result).toEqual([
17
+ { Month: 'Jan', value: 1 },
18
+ { Month: 'Jan', value: 2 },
19
+ { Month: 'Feb', value: 3 } // only first item from next stage
20
+ ])
21
+ })
22
+
23
+ it('returns correct slice for stageKey with no next stage', () => {
24
+ const result = getBridgedData('Mar', 'Month', sampleData)
25
+
26
+ expect(result).toEqual([{ Month: 'Mar', value: 5 }])
27
+ })
28
+
29
+ it('returns empty if stageKey not found', () => {
30
+ const result = getBridgedData('Dec', 'Month', sampleData)
31
+
32
+ expect(result).toEqual([])
33
+ })
34
+
35
+ it('handles unordered input data and preserves stage order by first appearance', () => {
36
+ const unorderedData = [
37
+ { step: 'B', val: 1 },
38
+ { step: 'A', val: 2 },
39
+ { step: 'C', val: 3 },
40
+ { step: 'A', val: 4 },
41
+ { step: 'B', val: 5 }
42
+ ]
43
+
44
+ const result = getBridgedData('A', 'step', unorderedData)
45
+
46
+ expect(result).toEqual([
47
+ { step: 'A', val: 2 },
48
+ { step: 'A', val: 4 },
49
+ { step: 'C', val: 3 }
50
+ ])
51
+ })
52
+
53
+ it('returns only items matching current stage if next stage has no data', () => {
54
+ const dataWithEmptyStage = [
55
+ { stage: '1', x: 10 },
56
+ { stage: '2', x: 20 },
57
+ { stage: '3', x: 30 }
58
+ ]
59
+
60
+ const result = getBridgedData('3', 'stage', dataWithEmptyStage)
61
+
62
+ expect(result).toEqual([{ stage: '3', x: 30 }])
63
+ })
64
+ })
@@ -44,7 +44,7 @@ const useScales = (properties: useScaleProps) => {
44
44
  const xAxisType = config.runtime.xAxis.type
45
45
  const isHorizontal = config.orientation === 'horizontal'
46
46
  const { visualizationType, xAxis, forestPlot } = config
47
-
47
+ const paddingRange = ['Area Chart', 'Forecasting'].includes(config.visualizationType) ? 1 : 1 - config.barThickness
48
48
  // define scales
49
49
  let xScale = null
50
50
  let yScale = null
@@ -68,22 +68,16 @@ const useScales = (properties: useScaleProps) => {
68
68
 
69
69
  // handle Vertical bars
70
70
  if (!isHorizontal) {
71
- xScale = composeScaleBand(xAxisDataMapped, [0, xMax], 1 - config.barThickness)
71
+ xScale = composeScaleBand(xAxisDataMapped, [0, xMax], paddingRange)
72
72
  yScale = composeYScale(properties)
73
73
  seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
74
74
  }
75
75
 
76
- // handle Linear scaled viz
77
- if (config.xAxis.type === 'date' && !isHorizontal) {
78
- const xAxisDataMappedSorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
79
- xScale = composeScaleBand(xAxisDataMappedSorted, [0, xMax], 1 - config.barThickness)
80
- }
81
-
82
76
  // handle Linear scaled viz
83
77
  if (config.xAxis.type === 'date' && !isHorizontal) {
84
78
  const sorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
85
79
 
86
- xScale = composeScaleBand(sorted, [0, xMax], 1 - config.barThickness)
80
+ xScale = composeScaleBand(sorted, [0, xMax], paddingRange)
87
81
  xScale.type = scaleTypes.BAND
88
82
  }
89
83
 
@@ -153,44 +147,50 @@ const useScales = (properties: useScaleProps) => {
153
147
 
154
148
  // handle Box plot
155
149
  if (visualizationType === 'Box Plot') {
156
- const allOutliers = []
157
- const hasOutliers =
158
- config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) &&
159
- !config.boxplot.hideOutliers
160
-
161
- // check if outliers are lower
162
- if (hasOutliers) {
163
- let outlierMin = Math.min(...allOutliers)
164
- let outlierMax = Math.max(...allOutliers)
165
-
166
- // check if outliers exceed standard bounds
167
- if (outlierMin < min) min = outlierMin
168
- if (outlierMax > max) max = outlierMax
150
+ const {
151
+ boxplot: { plots, hideOutliers },
152
+ xAxis: { dataKey },
153
+ orientation,
154
+ runtime: { seriesKeys },
155
+ series,
156
+ barThickness
157
+ } = config
158
+
159
+ // 1) merge outliers + fences
160
+ let lo = min,
161
+ hi = max
162
+ for (const { columnOutliers = [], columnLowerBounds: lb, columnUpperBounds: ub } of plots) {
163
+ if (!hideOutliers && columnOutliers.length) {
164
+ lo = Math.min(lo, ...columnOutliers)
165
+ hi = Math.max(hi, ...columnOutliers)
166
+ }
167
+ lo = Math.min(lo, lb)
168
+ hi = Math.max(hi, ub)
169
169
  }
170
+ ;[min, max] = [lo, hi]
170
171
 
171
- // check fences for max/min
172
- let lowestFence = Math.min(...config.boxplot.plots.map(item => item.columnLowerBounds))
173
- let highestFence = Math.max(...config.boxplot.plots.map(item => item.columnUpperBounds))
172
+ // 2) unique categories
173
+ const cats = Array.from(new Set(data.map(d => d[dataKey])))
174
174
 
175
- if (lowestFence < min) min = lowestFence
176
- if (highestFence > max) max = highestFence
175
+ if (orientation === 'horizontal') {
176
+ xScale = composeXScale({ min, max, xMax, config })
177
+ xScale.type = scaleTypes.LINEAR
177
178
 
178
- // Set Scales
179
+ yScale = composeScaleBand(cats, [0, yMax], 0.4)
180
+ seriesScale = composeScaleBand(seriesKeys, [0, yScale.bandwidth()], 0.3)
181
+ } else {
182
+ xScale = composeScaleBand(cats, [0, xMax], 1 - barThickness)
183
+ xScale.type = scaleTypes.BAND
179
184
 
180
- const categories = _.uniq(data.map(d => d[config.xAxis.dataKey]))
181
- const range = [0, config.barThickness * 100 || 1]
182
- const domain = _.map(config.series, 'dataKey')
183
- yScale = scaleLinear({
184
- range: [yMax, 0],
185
- round: true,
186
- domain: [min, max]
187
- })
188
- xScale = scaleBand({
189
- range: [0, xMax],
190
- domain: categories
191
- })
192
- xScale.type = scaleTypes.BAND
193
- seriesScale = composeScaleBand(domain, range)
185
+ // numeric Y
186
+ yScale = composeYScale({ min, max, yMax, config, leftMax: 0 })
187
+
188
+ seriesScale = composeScaleBand(
189
+ series.map(s => s.dataKey),
190
+ [0, xScale.bandwidth()],
191
+ 0
192
+ )
193
+ }
194
194
  }
195
195
 
196
196
  // handle Paired bar
@@ -286,10 +286,11 @@ export const useTooltip = props => {
286
286
  let minDistance = Number.MAX_VALUE
287
287
  let offset = x
288
288
 
289
+ const barThicknessOffset = config.xAxis.type === 'date' ? xScale.bandwidth() / 2 : 0
289
290
  data.forEach(d => {
290
291
  const xPosition = isDateScale(xAxis) ? xScale(parseDate(d[xAxis.dataKey])) : xScale(d[xAxis.dataKey])
291
292
  let bwOffset = config.barHeight
292
- const distance = Math.abs(Number(xPosition - offset + (isClick ? bwOffset * 2 : 0)))
293
+ const distance = Math.abs(Number(xPosition + barThicknessOffset - offset + (isClick ? bwOffset * 2 : 0)))
293
294
 
294
295
  if (distance <= minDistance) {
295
296
  minDistance = distance
@@ -593,7 +594,7 @@ export const useTooltip = props => {
593
594
 
594
595
  return (
595
596
  <li style={style} className='tooltip-body mb-1'>
596
- {displayText}
597
+ {parse(displayText)}
597
598
  </li>
598
599
  )
599
600
  }
@@ -1,3 +1,5 @@
1
+ @import '@cdc/core/styles/accessibility';
2
+
1
3
  @mixin breakpoint($class) {
2
4
  @if $class == xs {
3
5
  @media (max-width: 767px) {
@@ -739,11 +741,3 @@
739
741
  .cdc-open-viz-module .debug {
740
742
  border: 2px solid red;
741
743
  }
742
-
743
- // Only frontend styles are applied in WCMS/TP
744
- // This helps match those styles when viewing in the editor
745
- .modal.cdc-cove-editor *:focus-visible,
746
- .cdc-open-viz-module *:focus-visible {
747
- outline: dashed 2px rgba(255, 102, 1, 0.9) !important;
748
- outline-offset: 3px !important;
749
- }
@@ -16,7 +16,7 @@ type SET_DIMENSIONS = Action<'SET_DIMENSIONS', DimensionsType>
16
16
  type SET_CONTAINER = Action<'SET_CONTAINER', object>
17
17
  type SET_LOADED_EVENT = Action<'SET_LOADED_EVENT', boolean>
18
18
  type SET_DRAG_ANNOTATIONS = Action<'SET_DRAG_ANNOTATIONS', boolean>
19
- type SET_BRUSH_CONFIG = Action<'SET_BRUSH_CONFIG', object>
19
+ type SET_BRUSH_DATA = Action<'SET_BRUSH_DATA', object[]>
20
20
  type ChartActions =
21
21
  | SET_CONFIG
22
22
  | UPDATE_CONFIG
@@ -31,6 +31,6 @@ type ChartActions =
31
31
  | SET_LOADED_EVENT
32
32
  | SET_DRAG_ANNOTATIONS
33
33
  | SET_LOADING
34
- | SET_BRUSH_CONFIG
34
+ | SET_BRUSH_DATA
35
35
 
36
36
  export default ChartActions
@@ -18,11 +18,7 @@ type ChartState = {
18
18
  coveLoadedEventRan: boolean
19
19
  isDraggingAnnotation: boolean
20
20
  imageId: string
21
- brushConfig: {
22
- data: object[]
23
- isActive: boolean
24
- isBrushing: boolean
25
- }
21
+ brushData: object[] | []
26
22
  }
27
23
 
28
24
  export const getInitialState = (configObj: ChartConfig): ChartState => {
@@ -41,11 +37,7 @@ export const getInitialState = (configObj: ChartConfig): ChartState => {
41
37
  coveLoadedEventRan: false,
42
38
  isDraggingAnnotation: false,
43
39
  imageId: `cove-${Math.random().toString(16).slice(-4)}`,
44
- brushConfig: {
45
- data: [],
46
- isActive: false,
47
- isBrushing: false
48
- }
40
+ brushData: []
49
41
  }
50
42
  }
51
43
 
@@ -77,7 +69,7 @@ export const reducer = (state: ChartState, action: ChartActions): ChartState =>
77
69
  return { ...state, coveLoadedEventRan: action.payload }
78
70
  case 'SET_DRAG_ANNOTATIONS':
79
71
  return { ...state, isDraggingAnnotation: action.payload }
80
- case 'SET_BRUSH_CONFIG':
81
- return { ...state, brushConfig: action.payload }
72
+ case 'SET_BRUSH_DATA':
73
+ return { ...state, brushData: action.payload }
82
74
  }
83
75
  }
@@ -120,11 +120,6 @@ export type AllChartsConfig = {
120
120
  barStyle: 'lollipop' | 'rounded' | 'flat'
121
121
  barThickness: number
122
122
  boxplot: BoxPlot
123
- brush: {
124
- active: boolean
125
- data: object[]
126
- isBrushing: boolean
127
- }
128
123
  chartMessage: { noData?: string }
129
124
  color: string
130
125
  colorMatchLineSeriesLabels: boolean
@@ -1 +0,0 @@
1
- {"type":"chart","visualizationType":"Bar","id":1,"category":"Charts","label":"Bar","subType":"Bar","orientation":"horizontal","barThickness":"0.37","visualizationSubType":"regular","xAxis":{"sortDates":false,"anchors":[{"value":"95","color":"red","lineStyle":"Dashed Small"},{"value":"84.7","color":"black","lineStyle":"Dashed Small"}],"type":"categorical","showTargetLabel":true,"targetLabel":"Target","hideAxis":false,"hideLabel":false,"hideTicks":false,"size":"350","tickRotation":0,"min":"","max":"","labelColor":"#333","tickLabelColor":"#333","tickColor":"#333","numTicks":"","labelOffset":0,"axisPadding":200,"target":0,"maxTickRotation":45,"padding":5,"showYearsOnce":false,"sortByRecentDate":false,"dataKey":"State/cities","tickWidthMax":29,"axisBBox":29.860000610351562},"icon":{"key":null,"ref":null,"props":{},"_owner":null},"content":"Use bars to show comparisons between data categories.","datasets":{},"activeVizButtonID":1,"data":[{"State/cities":"Vermont","Completed cases":"2","Total cases":"2","Percentage who completed therapy":"100","Cases category":"1 to 52 TB cases"},{"State/cities":"Nevada","Completed cases":"44","Total cases":"44","Percentage who completed therapy":"100","Cases category":"1 to 52 TB cases"},{"State/cities":"Mississippi","Completed cases":"34","Total cases":"35","Percentage who completed therapy":"97","Cases category":"1 to 52 TB cases"},{"State/cities":"New Mexico","Completed cases":"15","Total cases":"16","Percentage who completed therapy":"94","Cases category":"1 to 52 TB cases"},{"State/cities":"Kansas","Completed cases":"27","Total cases":"29","Percentage who completed therapy":"93","Cases category":"1 to 52 TB cases"},{"State/cities":"Utah","Completed cases":"22","Total cases":"24","Percentage who completed therapy":"92","Cases category":"1 to 52 TB cases"},{"State/cities":"Delaware","Completed cases":"12","Total cases":"13","Percentage who completed therapy":"92","Cases category":"1 to 52 TB cases"},{"State/cities":"Wisconsin","Completed cases":"21","Total cases":"23","Percentage who completed therapy":"91","Cases category":"1 to 52 TB cases"},{"State/cities":"Colorado","Completed cases":"36","Total cases":"40","Percentage who completed therapy":"90","Cases category":"1 to 52 TB cases"},{"State/cities":"New Hampshire","Completed cases":"8","Total cases":"9","Percentage who completed therapy":"89","Cases category":"1 to 52 TB cases"},{"State/cities":"Chicago, IL","Completed cases":"47","Total cases":"53","Percentage who completed therapy":"89","Cases category":"1 to 52 TB cases"},{"State/cities":"Iowa","Completed cases":"28","Total cases":"32","Percentage who completed therapy":"88","Cases category":"1 to 52 TB cases"},{"State/cities":"Idaho","Completed cases":"6","Total cases":"7","Percentage who completed therapy":"86","Cases category":"1 to 52 TB cases"},{"State/cities":"District of Columbia","Completed cases":"12","Total cases":"14","Percentage who completed therapy":"86","Cases category":"1 to 52 TB cases"},{"State/cities":"Arkansas","Completed cases":"38","Total cases":"44","Percentage who completed therapy":"86","Cases category":"1 to 52 TB cases"},{"State/cities":"Alaska","Completed cases":"42","Total cases":"49","Percentage who completed therapy":"86","Cases category":"1 to 52 TB cases"},{"State/cities":"West Virginia","Completed cases":"11","Total cases":"13","Percentage who completed therapy":"85","Cases category":"1 to 52 TB cases"},{"State/cities":"Connecticut","Completed cases":"34","Total cases":"40","Percentage who completed therapy":"85","Cases category":"1 to 52 TB cases"},{"State/cities":"Oklahoma","Completed cases":"40","Total cases":"48","Percentage who completed therapy":"83","Cases category":"1 to 52 TB cases"},{"State/cities":"Maine","Completed cases":"11","Total cases":"14","Percentage who completed therapy":"79","Cases category":"1 to 52 TB cases"},{"State/cities":"South Dakota","Completed cases":"10","Total cases":"13","Percentage who completed therapy":"77","Cases category":"1 to 52 TB cases"},{"State/cities":"Rhode Island","Completed cases":"3","Total cases":"4","Percentage who completed therapy":"75","Cases category":"1 to 52 TB cases"},{"State/cities":"Baltimore, MD","Completed cases":"6","Total cases":"12","Percentage who completed therapy":"50","Cases category":"1 to 52 TB cases"},{"State/cities":"Montana","Completed cases":"1","Total cases":"3","Percentage who completed therapy":"33","Cases category":"1 to 52 TB cases"},{"State/cities":"Oregon","Completed cases":"54","Total cases":"57","Percentage who completed therapy":"95","Cases category":"53 to 130 TB cases"},{"State/cities":"South Carolina","Completed cases":"50","Total cases":"53","Percentage who completed therapy":"94","Cases category":"53 to 130 TB cases"},{"State/cities":"Indiana","Completed cases":"67","Total cases":"71","Percentage who completed therapy":"94","Cases category":"53 to 130 TB cases"},{"State/cities":"Philadelphia, PA","Completed cases":"42","Total cases":"45","Percentage who completed therapy":"93","Cases category":"53 to 130 TB cases"},{"State/cities":"San Francisco, CA","Completed cases":"44","Total cases":"48","Percentage who completed therapy":"92","Cases category":"53 to 130 TB cases"},{"State/cities":"New York [excluding New York City]","Completed cases":"315","Total cases":"346","Percentage who completed therapy":"91","Cases category":"53 to 130 TB cases"},{"State/cities":"San Diego, CA","Completed cases":"159","Total cases":"175","Percentage who completed therapy":"91","Cases category":"53 to 130 TB cases"},{"State/cities":"Pennsylvania [excluding Philadelphia]","Completed cases":"70","Total cases":"78","Percentage who completed therapy":"90","Cases category":"53 to 130 TB cases"},{"State/cities":"Minnesota","Completed cases":"85","Total cases":"93","Percentage who completed therapy":"91","Cases category":"53 to 130 TB cases"},{"State/cities":"North Dakota","Completed cases":"9","Total cases":"10","Percentage who completed therapy":"90","Cases category":"53 to 130 TB cases"},{"State/cities":"North Carolina","Completed cases":"116","Total cases":"129","Percentage who completed therapy":"90","Cases category":"53 to 130 TB cases"},{"State/cities":"Michigan","Completed cases":"75","Total cases":"83","Percentage who completed therapy":"90","Cases category":"53 to 130 TB cases"},{"State/cities":"Hawaii","Completed cases":"66","Total cases":"73","Percentage who completed therapy":"90","Cases category":"53 to 130 TB cases"},{"State/cities":"Arizona","Completed cases":"101","Total cases":"112","Percentage who completed therapy":"90","Cases category":"53 to 130 TB cases"},{"State/cities":"Kentucky","Completed cases":"47","Total cases":"53","Percentage who completed therapy":"89","Cases category":"53 to 130 TB cases"},{"State/cities":"Massachusetts","Completed cases":"102","Total cases":"116","Percentage who completed therapy":"88","Cases category":"53 to 130 TB cases"},{"State/cities":"Ohio","Completed cases":"99","Total cases":"115","Percentage who completed therapy":"86","Cases category":"53 to 130 TB cases"},{"State/cities":"Houston, TX","Completed cases":"97","Total cases":"113","Percentage who completed therapy":"86","Cases category":"53 to 130 TB cases"},{"State/cities":"Alabama","Completed cases":"49","Total cases":"58","Percentage who completed therapy":"84","Cases category":"53 to 130 TB cases"},{"State/cities":"Maryland [excluding Baltimore]","Completed cases":"93","Total cases":"112","Percentage who completed therapy":"83","Cases category":"53 to 130 TB cases"},{"State/cities":"Texas [excluding Houston]","Completed cases":"502","Total cases":"604","Percentage who completed therapy":"83","Cases category":"53 to 130 TB cases"},{"State/cities":"Tennessee","Completed cases":"78","Total cases":"95","Percentage who completed therapy":"82","Cases category":"53 to 130 TB cases"},{"State/cities":"Missouri","Completed cases":"47","Total cases":"59","Percentage who completed therapy":"80","Cases category":"53 to 130 TB cases"},{"State/cities":"Louisiana","Completed cases":"66","Total cases":"84","Percentage who completed therapy":"79","Cases category":"53 to 130 TB cases"},{"State/cities":"Nebraska","Completed cases":"11","Total cases":"26","Percentage who completed therapy":"42","Cases category":"53 to 130 TB cases"},{"State/cities":"Illinois [excluding Chicago]","Completed cases":"101","Total cases":"103","Percentage who completed therapy":"98","Cases category":"131 or more TB cases"},{"State/cities":"Washington","Completed cases":"128","Total cases":"134","Percentage who completed therapy":"96","Cases category":"131 or more TB cases"},{"State/cities":"Florida","Completed cases":"324","Total cases":"339","Percentage who completed therapy":"96","Cases category":"131 or more TB cases"},{"State/cities":"New York, NY ","Completed cases":"126","Total cases":"136","Percentage who completed therapy":"93","Cases category":"131 or more TB cases"},{"State/cities":"Los Angeles, CA","Completed cases":"321","Total cases":"345","Percentage who completed therapy":"93","Cases category":"131 or more TB cases"},{"State/cities":"Virginia","Completed cases":"133","Total cases":"144","Percentage who completed therapy":"92","Cases category":"131 or more TB cases"},{"State/cities":"New Jersey","Completed cases":"177","Total cases":"192","Percentage who completed therapy":"92","Cases category":"131 or more TB cases"},{"State/cities":"Georgia","Completed cases":"158","Total cases":"173","Percentage who completed therapy":"91","Cases category":"131 or more TB cases"},{"State/cities":"California [excluding LA, SD, SF]","Completed cases":"703","Total cases":"790","Percentage who completed therapy":"89","Cases category":"131 or more TB cases"}],"dataFileName":"COT-12Months-v2.csv","dataFileSourceType":"file","annotations":[{"text":"National average 84.7%","snapToNearestPoint":false,"fontSize":16,"bezier":10,"show":{"desktop":true,"tablet":true,"mobile":true},"connectorType":"line","colors":{"label":"black","connector":"black","marker":"black"},"selected":true,"anchor":{"vertical":false,"horizontal":false},"marker":"circle","edit":{"subject":true,"label":true},"seriesKey":"","x":76.75190558150972,"y":216.92999267578125,"xKey":"1/15/2016","yKey":"","dx":20,"dy":-20,"opacity":"87","savedDimensions":[null,null],"connectionType":"select"},{"text":"2025 target: 95%","snapToNearestPoint":false,"fontSize":16,"bezier":10,"show":{"desktop":true,"tablet":true,"mobile":true},"connectorType":"line","colors":{"label":"black","connector":"black","marker":"black"},"selected":true,"anchor":{"vertical":false,"horizontal":false},"marker":"circle","edit":{"subject":true,"label":true},"seriesKey":"","x":89.14433243176788,"y":105.92999267578125,"xKey":"1/15/2016","yKey":"","dx":20,"dy":-20,"opacity":"100","savedDimensions":[null,null],"connectionType":"select"}],"debugSvg":false,"chartMessage":{"noData":"No Data Available"},"title":"","showTitle":true,"showDownloadMediaButton":false,"theme":"theme-blue","animate":false,"lineDatapointStyle":"hover","lineDatapointColor":"Same as Line","barHasBorder":"true","isLollipopChart":false,"lollipopShape":"circle","lollipopColorStyle":"two-tone","barStyle":"","roundingStyle":"standard","tipRounding":"top","isResponsiveTicks":false,"general":{"annotationDropdownText":"Annotations","showMissingDataLabel":true,"showSuppressedSymbol":true,"showZeroValueData":true,"hideNullValue":true,"showDownloadButton":false,"showAnnotationDropdown":false},"padding":{"left":5,"right":5},"preliminaryData":[],"yAxis":{"hideAxis":false,"displayNumbersOnBar":false,"hideLabel":false,"hideTicks":false,"size":50,"gridLines":false,"enablePadding":false,"min":"","max":"","labelColor":"#333","tickLabelColor":"#333","tickColor":"#333","rightHideAxis":false,"rightAxisSize":0,"rightLabel":"","rightLabelOffsetSize":0,"rightAxisLabelColor":"#333","rightAxisTickLabelColor":"#333","rightAxisTickColor":"#333","numTicks":"","axisPadding":0,"scalePadding":"30","tickRotation":0,"anchors":[],"shoMissingDataLabel":true,"showMissingDataLine":true,"categories":[],"labelPlacement":"On Date/Category Axis","type":"linear","labelOffset":"-1"},"boxplot":{"plots":[],"borders":"true","plotOutlierValues":false,"plotNonOutlierValues":true,"labels":{"q1":"Lower Quartile","q2":"q2","q3":"Upper Quartile","q4":"q4","minimum":"Minimum","maximum":"Maximum","mean":"Mean","median":"Median","sd":"Standard Deviation","iqr":"Interquartile Range","count":"Count","outliers":"Outliers","values":"Values","lowerBounds":"Lower Bounds","upperBounds":"Upper Bounds"}},"topAxis":{"hasLine":false},"isLegendValue":false,"barHeight":25,"barSpace":"5","heights":{"vertical":300,"horizontal":1740},"table":{"label":"Data Table","expanded":true,"limitHeight":false,"height":"","caption":"","showDownloadUrl":false,"showDataTableLink":true,"showDownloadLinkBelow":true,"indexLabel":"","download":true,"showVertical":false,"dateDisplayFormat":"","showMissingDataLabel":true,"showSuppressedSymbol":true,"show":false},"color":"pinkpurple","columns":{"Percentage who completed therapy":{"label":"Percentage who completed therapy","dataTable":true,"tooltips":true,"prefix":"","suffix":"%","forestPlot":false,"startingPoint":"0","forestPlotAlignRight":false,"roundToPlace":0,"commas":false,"showInViz":false,"forestPlotStartingPoint":0,"name":"Percentage who completed therapy","series":""},"Completed cases":{"label":"Completed cases","dataTable":true,"tooltips":true,"prefix":"","suffix":"","forestPlot":false,"startingPoint":"0","forestPlotAlignRight":false,"roundToPlace":0,"commas":false,"showInViz":false,"forestPlotStartingPoint":0,"name":"Completed cases"},"Total cases":{"label":"Total cases","dataTable":true,"tooltips":true,"prefix":"","suffix":"","forestPlot":false,"startingPoint":"0","forestPlotAlignRight":false,"roundToPlace":0,"commas":false,"showInViz":false,"forestPlotStartingPoint":0,"name":"Total cases"}},"legend":{"hide":false,"behavior":"isolate","axisAlign":true,"singleRow":true,"colorCode":"Cases category","reverseLabelOrder":false,"description":"","dynamicLegend":false,"dynamicLegendDefaultText":"Show All","dynamicLegendItemLimit":5,"dynamicLegendItemLimitMessage":"Dynamic Legend Item Limit Hit.","dynamicLegendChartMessage":"Select Options from the Legend","label":"Completion of therapy by U.S. states and cities","lineMode":false,"verticalSorted":false,"highlightOnHover":false,"hideSuppressedLabels":false,"hideSuppressionLink":false,"seriesHighlight":[],"style":"boxes","subStyle":"linear blocks","groupBy":"Cases category","shape":"circle","tickRotation":"","hideBorder":{"side":false,"topBottom":true},"position":"bottom","unified":true},"brush":{"height":45,"active":false},"exclusions":{"active":false,"keys":[]},"palette":"sequential-blue","isPaletteReversed":false,"twoColor":{"palette":"monochrome-1","isPaletteReversed":false},"labels":false,"dataFormat":{"commas":false,"prefix":"","suffix":"","abbreviated":false,"bottomSuffix":"","bottomPrefix":"","bottomAbbreviated":false},"filters":[],"confidenceKeys":{},"visual":{"border":true,"accent":true,"background":true,"verticalHoverLine":false,"horizontalHoverLine":false,"lineDatapointSymbol":"none","maximumShapeAmount":7},"useLogScale":false,"filterBehavior":"Filter Change","highlightedBarValues":[],"series":[{"dataKey":"Percentage who completed therapy","type":"Bar","axis":"Left","tooltip":false,"name":"Percentage who completed therapy"}],"tooltips":{"opacity":90,"singleSeries":false,"dateDisplayFormat":""},"forestPlot":{"startAt":0,"colors":{"line":"","shape":""},"lineOfNoEffect":{"show":true},"type":"","pooledResult":{"diamondHeight":5,"column":""},"estimateField":"","estimateRadius":"","shape":"square","rowHeight":20,"description":{"show":true,"text":"description","location":0},"result":{"show":true,"text":"result","location":100},"radius":{"min":2,"max":10,"scalingColumn":""},"regression":{"lower":0,"upper":0,"estimateField":0},"leftWidthOffset":0,"rightWidthOffset":0,"showZeroLine":false,"leftLabel":"","rightLabel":""},"area":{"isStacked":false},"sankey":{"title":{"defaultColor":"black"},"iterations":1,"rxValue":0.9,"overallSize":{"width":900,"height":700},"margin":{"margin_y":25,"margin_x":0},"nodeSize":{"nodeWidth":26,"nodeHeight":40},"nodePadding":55,"nodeFontColor":"black","nodeColor":{"default":"#ff8500","inactive":"#808080"},"linkColor":{"default":"#ffc900","inactive":"#D3D3D3"},"opacity":{"nodeOpacityDefault":1,"nodeOpacityInactive":0.1,"LinkOpacityDefault":1,"LinkOpacityInactive":0.1},"storyNodeFontColor":"#006778","storyNodeText":[],"nodeValueStyle":{"textBefore":"(","textAfter":")"},"data":[]},"version":"4.25.1","migrations":{"addColorMigration":true},"dynamicMarginTop":0}