@cdc/editor 4.26.1 → 4.26.3
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/LICENSE +201 -0
- package/dist/cdceditor-8NmHlKRI.es.js +15 -0
- package/dist/cdceditor-BPoPzKPz.es.js +6 -0
- package/dist/{cdceditor-dgT_1dIT.es.js → cdceditor-DQ00cQCm.es.js} +1 -20
- package/dist/cdceditor-jiQQPkty.es.js +6 -0
- package/dist/cdceditor-vr9HZwRt.es.js +6 -0
- package/dist/cdceditor.js +123220 -124992
- package/index.html +1 -20
- package/package.json +35 -37
- package/src/CdcEditor.tsx +2 -6
- package/src/_stories/Editor.stories.tsx +111 -6
- package/src/components/ChooseTab.test.tsx +36 -0
- package/src/components/ChooseTab.tsx +84 -40
- package/src/components/ConfigureTab.tsx +2 -1
- package/src/components/DataImport/components/DataImport.tsx +10 -5
- package/src/components/DataImport/components/SampleData.tsx +21 -9
- package/src/components/DataImport/components/samples/valid-horizon-chart.json +373 -0
- package/src/components/DataImport/components/samples/valid-radar-chart.csv +3 -0
- package/src/components/PreviewDataTable.tsx +39 -44
- package/src/components/modal/Modal.jsx +0 -3
- package/src/scss/choose-vis-tab.scss +1 -1
- package/src/scss/main.scss +1 -1
- package/vite.config.js +2 -1
- package/dist/cdceditor-BnB1QM5d.es.js +0 -361528
- package/dist/cdceditor-Ct2SB0vL.es.js +0 -231049
- package/dist/cdceditor-D6CG2-Hb.es.js +0 -39377
- package/dist/cdceditor-MXgURbdZ.es.js +0 -39194
package/index.html
CHANGED
|
@@ -1,20 +1 @@
|
|
|
1
|
-
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
|
6
|
-
<style type="text/css">
|
|
7
|
-
body {
|
|
8
|
-
margin: 0;
|
|
9
|
-
}
|
|
10
|
-
.react-container {
|
|
11
|
-
min-height: 100vh;
|
|
12
|
-
}
|
|
13
|
-
</style>
|
|
14
|
-
<link rel="stylesheet prefetch" href="https://www.cdc.gov/TemplatePackage/5.0/css/app.min.css?_=71669" />
|
|
15
|
-
</head>
|
|
16
|
-
<body>
|
|
17
|
-
<div class="react-container react--editor"></div>
|
|
18
|
-
<script type="module" src="./src/index.jsx"></script>
|
|
19
|
-
</body>
|
|
20
|
-
</html>
|
|
1
|
+
<!-- index.html is generated by @cdc/core/generateViteConfig.js using the files in @cdc/core/devTemplate/ -->
|
package/package.json
CHANGED
|
@@ -1,51 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/editor",
|
|
3
|
-
"version": "4.26.
|
|
3
|
+
"version": "4.26.3",
|
|
4
4
|
"description": "React component for generating a new component entry",
|
|
5
|
-
"moduleName": "CdcEditor",
|
|
6
|
-
"main": "dist/cdceditor",
|
|
7
|
-
"type": "module",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"start": "vite --open",
|
|
10
|
-
"build": "vite build",
|
|
11
|
-
"preview": "vite preview",
|
|
12
|
-
"graph": "nx graph",
|
|
13
|
-
"prepublishOnly": "lerna run --scope @cdc/editor build",
|
|
14
|
-
"test": "vitest run --reporter verbose",
|
|
15
|
-
"test-watch": "vitest watch --reporter verbose",
|
|
16
|
-
"test-watch:ui": "vitest --ui"
|
|
17
|
-
},
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/CDCgov/cdc-open-viz",
|
|
21
|
-
"directory": "packages/editor"
|
|
22
|
-
},
|
|
23
|
-
"bugs": {
|
|
24
|
-
"url": "https://github.com/CDCgov/cdc-open-viz/issues"
|
|
25
|
-
},
|
|
26
5
|
"license": "Apache-2.0",
|
|
6
|
+
"bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
|
|
27
7
|
"dependencies": {
|
|
28
|
-
"@cdc/chart": "^4.26.
|
|
29
|
-
"@cdc/core": "^4.26.
|
|
30
|
-
"@cdc/dashboard": "^4.26.
|
|
31
|
-
"@cdc/data-bite": "^4.26.
|
|
32
|
-
"@cdc/map": "^4.26.
|
|
33
|
-
"@cdc/markup-include": "^4.26.
|
|
34
|
-
"@cdc/waffle-chart": "^4.26.
|
|
35
|
-
"axios": "^1.
|
|
8
|
+
"@cdc/chart": "^4.26.3",
|
|
9
|
+
"@cdc/core": "^4.26.3",
|
|
10
|
+
"@cdc/dashboard": "^4.26.3",
|
|
11
|
+
"@cdc/data-bite": "^4.26.3",
|
|
12
|
+
"@cdc/map": "^4.26.3",
|
|
13
|
+
"@cdc/markup-include": "^4.26.3",
|
|
14
|
+
"@cdc/waffle-chart": "^4.26.3",
|
|
15
|
+
"axios": "^1.13.2",
|
|
36
16
|
"d3": "^7.9.0",
|
|
37
17
|
"react-dropzone": "^14.3.8",
|
|
38
|
-
"use-debounce": "^10.0
|
|
39
|
-
},
|
|
40
|
-
"peerDependencies": {
|
|
41
|
-
"react": "^18.2.0",
|
|
42
|
-
"react-dom": "^18.2.0"
|
|
18
|
+
"use-debounce": "^10.1.0"
|
|
43
19
|
},
|
|
44
20
|
"devDependencies": {
|
|
45
21
|
"@rollup/plugin-dsv": "^3.0.2",
|
|
46
|
-
"@vitejs/plugin-react": "^
|
|
22
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
47
23
|
"vite-plugin-css-injected-by-js": "^2.4.0",
|
|
48
24
|
"vite-plugin-svgr": "^4.2.0"
|
|
49
25
|
},
|
|
50
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "d50e45a074fbefa56cac904917e707d57f237737",
|
|
27
|
+
"main": "dist/cdceditor",
|
|
28
|
+
"moduleName": "CdcEditor",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"react": "^18.2.0",
|
|
31
|
+
"react-dom": "^18.2.0"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/CDCgov/cdc-open-viz",
|
|
36
|
+
"directory": "packages/editor"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "vite build",
|
|
40
|
+
"graph": "nx graph",
|
|
41
|
+
"prepublishOnly": "lerna run --scope @cdc/editor build",
|
|
42
|
+
"preview": "vite preview",
|
|
43
|
+
"start": "vite --open",
|
|
44
|
+
"test": "vitest run --reporter verbose",
|
|
45
|
+
"test-watch": "vitest watch --reporter verbose",
|
|
46
|
+
"test-watch:ui": "vitest --ui"
|
|
47
|
+
},
|
|
48
|
+
"type": "module"
|
|
51
49
|
}
|
package/src/CdcEditor.tsx
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback, useReducer, useMemo } from 'react'
|
|
2
|
-
|
|
3
|
-
// IE11
|
|
4
|
-
// import 'core-js/stable' // temp remove, appears to be used with html2pdf.js which we're temp removing
|
|
5
2
|
import ResizeObserver from 'resize-observer-polyfill'
|
|
6
3
|
|
|
7
4
|
import getViewport from '@cdc/core/helpers/getViewport'
|
|
@@ -22,7 +19,6 @@ import { legacyConfigSupport } from './helpers/legacyConfigSupport'
|
|
|
22
19
|
|
|
23
20
|
import './scss/main.scss'
|
|
24
21
|
import editorReducer, { EditorState } from '@cdc/core/contexts/editor.reducer'
|
|
25
|
-
import _ from 'lodash'
|
|
26
22
|
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
27
23
|
import { WCMSProps } from '@cdc/core/types/WCMSProps'
|
|
28
24
|
import { devToolsStore } from '@cdc/core/helpers/withDevTools'
|
|
@@ -76,7 +72,7 @@ const CdcEditor: React.FC<WCMSProps> = ({ config: configObj, hostname, container
|
|
|
76
72
|
}, [])
|
|
77
73
|
|
|
78
74
|
useEffect(() => {
|
|
79
|
-
let strippedConfig = stripConfig(state.config)
|
|
75
|
+
let strippedConfig = stripConfig(state.config, true)
|
|
80
76
|
|
|
81
77
|
const parsedData = JSON.stringify(strippedConfig)
|
|
82
78
|
// Emit the data in a regular JS event so it can be consumed by anything.
|
|
@@ -102,7 +98,7 @@ const CdcEditor: React.FC<WCMSProps> = ({ config: configObj, hostname, container
|
|
|
102
98
|
<GlobalContextProvider>
|
|
103
99
|
<ConfigContext.Provider value={{ ...state, setTempConfig: setTempConfigAndUpdate }}>
|
|
104
100
|
<EditorDispatchContext.Provider value={dispatch}>
|
|
105
|
-
<div className={`
|
|
101
|
+
<div className={`cove-visualization cdc-editor ${state.currentViewport}`} ref={outerContainerRef}>
|
|
106
102
|
<Tabs className='top-level'>
|
|
107
103
|
<TabPane title='1. Choose Visualization Type' className='choose-type'>
|
|
108
104
|
<ChooseTab />
|
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import CdcEditor from '../CdcEditor'
|
|
3
|
-
import { within, userEvent } from 'storybook/test'
|
|
3
|
+
import { within, userEvent, expect } from 'storybook/test'
|
|
4
|
+
import ChartEditorConfig from '../../../chart/src/_stories/_mock/editor-tests/bar-chart-editor-test.json'
|
|
5
|
+
import MapConfig from '../../../map/src/_stories/_mock/default-patterns.json'
|
|
6
|
+
import DashboardConfig from '../../../dashboard/src/_stories/_mock/dashboard_no_filter.json'
|
|
7
|
+
import DataTableConfig from '../../../data-table/examples/data-table-example.json'
|
|
4
8
|
|
|
5
|
-
const
|
|
6
|
-
|
|
9
|
+
const loadConfigFromTextArea = async (canvasElement, config) => {
|
|
10
|
+
const user = userEvent.setup()
|
|
11
|
+
const textArea = canvasElement.querySelector('#pasteConfig') as HTMLTextAreaElement
|
|
12
|
+
const loadButton = canvasElement.querySelector('#load-data') as HTMLButtonElement
|
|
13
|
+
|
|
14
|
+
expect(textArea).toBeTruthy()
|
|
15
|
+
expect(loadButton).toBeTruthy()
|
|
16
|
+
|
|
17
|
+
await user.click(textArea)
|
|
18
|
+
await user.clear(textArea)
|
|
19
|
+
await user.paste(JSON.stringify(config))
|
|
20
|
+
await user.click(loadButton)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const assertImportDataTabAccessible = async canvas => {
|
|
24
|
+
const user = userEvent.setup()
|
|
25
|
+
await user.click(canvas.getByText('2. Import Data'))
|
|
26
|
+
await expect(canvas.findByText('Data Preview')).resolves.toBeTruthy()
|
|
7
27
|
}
|
|
8
28
|
|
|
9
29
|
const meta: Meta<typeof CdcEditor> = {
|
|
@@ -26,10 +46,95 @@ export const PreviewTableTests: Story = {
|
|
|
26
46
|
play: async ({ canvasElement }) => {
|
|
27
47
|
const canvas = within(canvasElement)
|
|
28
48
|
const user = userEvent.setup()
|
|
29
|
-
|
|
30
|
-
const mapButton = canvas.
|
|
49
|
+
|
|
50
|
+
const mapButton = await canvas.findByRole('button', { name: 'United States (State- or County-Level)' })
|
|
31
51
|
await user.click(mapButton)
|
|
32
|
-
|
|
52
|
+
|
|
53
|
+
await user.click(canvas.getByText('2. Import Data'))
|
|
54
|
+
|
|
55
|
+
const sampleData = await canvas.findByText('United States: County Sample Data')
|
|
33
56
|
await user.click(sampleData)
|
|
57
|
+
|
|
58
|
+
await expect(canvas.findByText('Data Preview')).resolves.toBeTruthy()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const LoadChartJsonConfig: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
config: {}
|
|
65
|
+
},
|
|
66
|
+
play: async ({ canvasElement }) => {
|
|
67
|
+
const canvas = within(canvasElement)
|
|
68
|
+
await loadConfigFromTextArea(canvasElement, ChartEditorConfig)
|
|
69
|
+
await assertImportDataTabAccessible(canvas)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const LoadMapJsonConfig: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
config: {}
|
|
76
|
+
},
|
|
77
|
+
play: async ({ canvasElement }) => {
|
|
78
|
+
const canvas = within(canvasElement)
|
|
79
|
+
await loadConfigFromTextArea(canvasElement, MapConfig)
|
|
80
|
+
await assertImportDataTabAccessible(canvas)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const LoadDashboardJsonConfig: Story = {
|
|
85
|
+
args: {
|
|
86
|
+
config: {}
|
|
87
|
+
},
|
|
88
|
+
play: async ({ canvasElement }) => {
|
|
89
|
+
const canvas = within(canvasElement)
|
|
90
|
+
await loadConfigFromTextArea(canvasElement, DashboardConfig)
|
|
91
|
+
await assertImportDataTabAccessible(canvas)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const LoadDataTableJsonConfig: Story = {
|
|
96
|
+
args: {
|
|
97
|
+
config: {}
|
|
98
|
+
},
|
|
99
|
+
play: async ({ canvasElement }) => {
|
|
100
|
+
const canvas = within(canvasElement)
|
|
101
|
+
await loadConfigFromTextArea(canvasElement, DataTableConfig)
|
|
102
|
+
await assertImportDataTabAccessible(canvas)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const InvalidJsonShowsValidationAlert: Story = {
|
|
107
|
+
args: {
|
|
108
|
+
config: {}
|
|
109
|
+
},
|
|
110
|
+
play: async ({ canvasElement }) => {
|
|
111
|
+
const user = userEvent.setup()
|
|
112
|
+
const textArea = canvasElement.querySelector('#pasteConfig') as HTMLTextAreaElement
|
|
113
|
+
const loadButton = canvasElement.querySelector('#load-data') as HTMLButtonElement
|
|
114
|
+
|
|
115
|
+
expect(textArea).toBeTruthy()
|
|
116
|
+
expect(loadButton).toBeTruthy()
|
|
117
|
+
|
|
118
|
+
const originalAlert = window.alert
|
|
119
|
+
const originalOnError = window.onerror
|
|
120
|
+
let alertText = ''
|
|
121
|
+
|
|
122
|
+
window.alert = message => {
|
|
123
|
+
alertText = String(message)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
window.onerror = () => true
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
await user.click(textArea)
|
|
130
|
+
await user.clear(textArea)
|
|
131
|
+
await user.paste('{"broken": true, }')
|
|
132
|
+
await user.click(loadButton)
|
|
133
|
+
|
|
134
|
+
await expect(alertText).toBe('The JSON that was entered is invalid.')
|
|
135
|
+
} finally {
|
|
136
|
+
window.alert = originalAlert
|
|
137
|
+
window.onerror = originalOnError
|
|
138
|
+
}
|
|
34
139
|
}
|
|
35
140
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, waitFor } from '@testing-library/react'
|
|
3
|
+
|
|
4
|
+
import ConfigContext, { EditorDispatchContext } from '@cdc/core/contexts/EditorContext'
|
|
5
|
+
import ChooseTab from './ChooseTab'
|
|
6
|
+
|
|
7
|
+
describe('ChooseTab', () => {
|
|
8
|
+
it('dispatches EDITOR_SAVE once when tempConfig is present', async () => {
|
|
9
|
+
const dispatch = vi.fn()
|
|
10
|
+
const tempConfig = { type: 'chart', data: [{ x: 'A', y: 1 }] }
|
|
11
|
+
|
|
12
|
+
render(
|
|
13
|
+
<ConfigContext.Provider
|
|
14
|
+
value={
|
|
15
|
+
{
|
|
16
|
+
config: { type: 'chart' },
|
|
17
|
+
tempConfig,
|
|
18
|
+
errors: [],
|
|
19
|
+
currentViewport: 'lg',
|
|
20
|
+
globalActive: 0,
|
|
21
|
+
setTempConfig: vi.fn()
|
|
22
|
+
} as any
|
|
23
|
+
}
|
|
24
|
+
>
|
|
25
|
+
<EditorDispatchContext.Provider value={dispatch}>
|
|
26
|
+
<ChooseTab />
|
|
27
|
+
</EditorDispatchContext.Provider>
|
|
28
|
+
</ConfigContext.Provider>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
await waitFor(() => {
|
|
32
|
+
expect(dispatch).toHaveBeenCalledTimes(1)
|
|
33
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'EDITOR_SAVE', payload: tempConfig })
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -1,33 +1,36 @@
|
|
|
1
|
-
import React, { useContext, useState } from 'react'
|
|
1
|
+
import React, { useContext, useEffect, useState } from 'react'
|
|
2
2
|
import '../scss/choose-vis-tab.scss'
|
|
3
3
|
|
|
4
4
|
import ConfigContext, { EditorDispatchContext } from '@cdc/core/contexts/EditorContext'
|
|
5
5
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import AlabamaGraphic from '@cdc/core/assets/icon-map-alabama.svg'
|
|
8
|
+
import AreaChartIcon from '@cdc/core/assets/icon-area-chart.svg'
|
|
8
9
|
import BarIcon from '@cdc/core/assets/icon-chart-bar.svg'
|
|
10
|
+
import BoxPlotIcon from '@cdc/core/assets/icon-chart-box-whisker.svg'
|
|
11
|
+
import CodeIcon from '@cdc/core/assets/icon-code.svg'
|
|
12
|
+
import ComboChartIcon from '@cdc/core/assets/icon-combo-chart.svg'
|
|
13
|
+
import DashboardIcon from '@cdc/core/assets/icon-dashboard.svg'
|
|
14
|
+
import DataBiteIcon from '@cdc/core/assets/icon-databite.svg'
|
|
15
|
+
import DeviationIcon from '@cdc/core/assets/icon-deviation-bar.svg'
|
|
16
|
+
import EpiChartIcon from '@cdc/core/assets/icon-epi-chart.svg'
|
|
17
|
+
import ForecastIcon from '@cdc/core/assets/icon-chart-forecast.svg'
|
|
18
|
+
import GaugeChartIcon from '@cdc/core/assets/icon-linear-gauge.svg'
|
|
19
|
+
import GlobeIcon from '@cdc/core/assets/icon-map-world.svg'
|
|
20
|
+
import HorizonChartIcon from '@cdc/core/assets/icon-chart-area.svg'
|
|
21
|
+
import HorizontalStackIcon from '@cdc/core/assets/icon-chart-bar-stacked.svg'
|
|
22
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
9
23
|
import LineIcon from '@cdc/core/assets/icon-chart-line.svg'
|
|
24
|
+
import PairedBarIcon from '@cdc/core/assets/icon-chart-bar-paired.svg'
|
|
10
25
|
import PieIcon from '@cdc/core/assets/icon-chart-pie.svg'
|
|
11
|
-
import
|
|
26
|
+
import RadarChartIcon from '@cdc/core/assets/icon-chart-radar.svg'
|
|
27
|
+
import SankeyIcon from '@cdc/core/assets/icon-sankey.svg'
|
|
28
|
+
import ScatterPlotIcon from '@cdc/core/assets/icon-chart-scatterplot.svg'
|
|
29
|
+
import TableIcon from '@cdc/core/assets/icon-table.svg'
|
|
12
30
|
import UsaIcon from '@cdc/core/assets/icon-map-usa.svg'
|
|
13
31
|
import UsaRegionIcon from '@cdc/core/assets/usa-region-graphic.svg'
|
|
14
|
-
import DataBiteIcon from '@cdc/core/assets/icon-databite.svg'
|
|
15
32
|
import WaffleChartIcon from '@cdc/core/assets/icon-grid.svg'
|
|
16
|
-
import AlabamaGraphic from '@cdc/core/assets/icon-map-alabama.svg'
|
|
17
|
-
import PairedBarIcon from '@cdc/core/assets/icon-chart-bar-paired.svg'
|
|
18
|
-
import HorizontalStackIcon from '@cdc/core/assets/icon-chart-bar-stacked.svg'
|
|
19
|
-
import ScatterPlotIcon from '@cdc/core/assets/icon-chart-scatterplot.svg'
|
|
20
|
-
import BoxPlotIcon from '@cdc/core/assets/icon-chart-box-whisker.svg'
|
|
21
|
-
import AreaChartIcon from '@cdc/core/assets/icon-area-chart.svg'
|
|
22
|
-
import GaugeChartIcon from '@cdc/core/assets/icon-linear-gauge.svg'
|
|
23
|
-
import ForecastIcon from '@cdc/core/assets/icon-chart-forecast.svg'
|
|
24
|
-
import DeviationIcon from '@cdc/core/assets/icon-deviation-bar.svg'
|
|
25
|
-
import SankeyIcon from '@cdc/core/assets/icon-sankey.svg'
|
|
26
|
-
import ComboChartIcon from '@cdc/core/assets/icon-combo-chart.svg'
|
|
27
|
-
import EpiChartIcon from '@cdc/core/assets/icon-epi-chart.svg'
|
|
28
|
-
import TableIcon from '@cdc/core/assets/icon-table.svg'
|
|
29
33
|
import WarmingStripesIcon from '@cdc/core/assets/icon-warming-stripes.svg'
|
|
30
|
-
import Icon from '@cdc/core/components/ui/Icon'
|
|
31
34
|
|
|
32
35
|
import {
|
|
33
36
|
convertVegaConfig,
|
|
@@ -48,6 +51,23 @@ interface ButtonProps {
|
|
|
48
51
|
orientation?: string | null
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
interface VizButtonProps extends ButtonProps {
|
|
55
|
+
activeVizButtonID?: number
|
|
56
|
+
onConfigure: (props: Record<string, unknown>) => void
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const VizButton: React.FC<VizButtonProps> = ({ activeVizButtonID, onConfigure, ...buttonProps }) => {
|
|
60
|
+
const { label, icon, id } = buttonProps
|
|
61
|
+
const isActive = id === activeVizButtonID || 0
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<button className={isActive ? 'active' : ''} onClick={() => onConfigure(buttonProps)} aria-label={label}>
|
|
65
|
+
{icon}
|
|
66
|
+
<span className='mt-1'>{label}</span>
|
|
67
|
+
</button>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
const ChooseTab: React.FC = (): JSX.Element => {
|
|
52
72
|
const { config, tempConfig } = useContext(ConfigContext)
|
|
53
73
|
|
|
@@ -56,6 +76,12 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
56
76
|
const dispatch = useContext(EditorDispatchContext)
|
|
57
77
|
const rowLabels = ['General', , 'Charts', 'Maps']
|
|
58
78
|
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (tempConfig) {
|
|
81
|
+
dispatch({ type: 'EDITOR_SAVE', payload: tempConfig })
|
|
82
|
+
}
|
|
83
|
+
}, [dispatch, tempConfig])
|
|
84
|
+
|
|
59
85
|
const handleUpload = e => {
|
|
60
86
|
const file = e.target.files[0]
|
|
61
87
|
const reader = new FileReader()
|
|
@@ -72,12 +98,15 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
72
98
|
newConfig = JSON.parse(text)
|
|
73
99
|
} catch (e) {
|
|
74
100
|
alert('The JSON that was entered is invalid.')
|
|
75
|
-
|
|
101
|
+
return
|
|
76
102
|
}
|
|
77
103
|
|
|
78
104
|
const isVega = isVegaConfig(newConfig)
|
|
79
105
|
if (isVega) {
|
|
80
106
|
newConfig = importVegaConfig(newConfig)
|
|
107
|
+
if (!newConfig) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
81
110
|
}
|
|
82
111
|
|
|
83
112
|
dispatch({ type: 'EDITOR_SET_CONFIG', payload: newConfig })
|
|
@@ -109,7 +138,7 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
109
138
|
|
|
110
139
|
const errorText = vegaErrors.join('\n\n')
|
|
111
140
|
alert(errorText)
|
|
112
|
-
|
|
141
|
+
return null
|
|
113
142
|
}
|
|
114
143
|
|
|
115
144
|
const generateNewConfig = props => {
|
|
@@ -171,25 +200,6 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
171
200
|
dispatch({ type: 'EDITOR_SET_GLOBALACTIVE', payload: 1 })
|
|
172
201
|
}
|
|
173
202
|
|
|
174
|
-
const VizButton: React.FC<ButtonProps> = props => {
|
|
175
|
-
const { label, icon, id } = props
|
|
176
|
-
const isActive = id === config?.activeVizButtonID || 0
|
|
177
|
-
const handleClick = () => {
|
|
178
|
-
configureTabs(props)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (tempConfig) {
|
|
182
|
-
dispatch({ type: 'EDITOR_SAVE', payload: tempConfig })
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return (
|
|
186
|
-
<button className={isActive ? 'active' : ''} onClick={handleClick} aria-label={label}>
|
|
187
|
-
{icon}
|
|
188
|
-
<span className='mt-1'>{label}</span>
|
|
189
|
-
</button>
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
203
|
return (
|
|
194
204
|
<div className='choose-vis'>
|
|
195
205
|
<a
|
|
@@ -218,7 +228,11 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
218
228
|
<li key={`${label}-button-${buttonIndex}`}>
|
|
219
229
|
<Tooltip position='right'>
|
|
220
230
|
<Tooltip.Target>
|
|
221
|
-
<VizButton
|
|
231
|
+
<VizButton
|
|
232
|
+
{...button}
|
|
233
|
+
activeVizButtonID={config?.activeVizButtonID}
|
|
234
|
+
onConfigure={configureTabs}
|
|
235
|
+
/>
|
|
222
236
|
</Tooltip.Target>
|
|
223
237
|
<Tooltip.Content>{button.content}</Tooltip.Content>
|
|
224
238
|
</Tooltip>
|
|
@@ -317,6 +331,16 @@ const buttons = [
|
|
|
317
331
|
content: `Specify the calculation of a single data point (such as a percentage value) and present it on a horizontal
|
|
318
332
|
scale.`
|
|
319
333
|
},
|
|
334
|
+
{
|
|
335
|
+
id: 26,
|
|
336
|
+
category: 'General',
|
|
337
|
+
label: 'Markup Include',
|
|
338
|
+
type: 'markup-include',
|
|
339
|
+
subType: null,
|
|
340
|
+
orientation: null,
|
|
341
|
+
icon: <CodeIcon />,
|
|
342
|
+
content: 'Include custom HTML markup or embed content from external URLs.'
|
|
343
|
+
},
|
|
320
344
|
{
|
|
321
345
|
id: 17,
|
|
322
346
|
category: 'General',
|
|
@@ -415,6 +439,16 @@ const buttons = [
|
|
|
415
439
|
icon: <ForecastIcon />,
|
|
416
440
|
content: 'Display a forecasting chart to predict future data trends.'
|
|
417
441
|
},
|
|
442
|
+
{
|
|
443
|
+
id: 27,
|
|
444
|
+
category: 'Charts',
|
|
445
|
+
label: 'Horizon Chart',
|
|
446
|
+
type: 'chart',
|
|
447
|
+
subType: 'Horizon Chart',
|
|
448
|
+
orientation: 'vertical',
|
|
449
|
+
icon: <HorizonChartIcon />,
|
|
450
|
+
content: 'Display a horizon chart to visualize quantities over time in a smaller space.'
|
|
451
|
+
},
|
|
418
452
|
{
|
|
419
453
|
id: 12,
|
|
420
454
|
category: 'Charts',
|
|
@@ -456,6 +490,16 @@ const buttons = [
|
|
|
456
490
|
icon: <PieIcon />,
|
|
457
491
|
content: 'Present the numerical proportions of a data series.'
|
|
458
492
|
},
|
|
493
|
+
{
|
|
494
|
+
id: 27,
|
|
495
|
+
category: 'Charts',
|
|
496
|
+
label: 'Radar',
|
|
497
|
+
type: 'chart',
|
|
498
|
+
subType: 'Radar',
|
|
499
|
+
orientation: 'vertical',
|
|
500
|
+
icon: <RadarChartIcon />,
|
|
501
|
+
content: 'Compare multiple quantitative variables across categories using a radial layout.'
|
|
502
|
+
},
|
|
459
503
|
{
|
|
460
504
|
id: 10,
|
|
461
505
|
category: 'Charts',
|
|
@@ -33,7 +33,8 @@ export default function ConfigureTab({ containerEl }) {
|
|
|
33
33
|
if (
|
|
34
34
|
config.visualizationType === 'Waffle' ||
|
|
35
35
|
config.visualizationType === 'TP5 Waffle' ||
|
|
36
|
-
config.visualizationType === 'Gauge'
|
|
36
|
+
config.visualizationType === 'Gauge' ||
|
|
37
|
+
config.visualizationType === 'TP5 Gauge'
|
|
37
38
|
) {
|
|
38
39
|
return (
|
|
39
40
|
<ErrorBoundary component='CdcWaffleChart'>
|
|
@@ -24,7 +24,7 @@ import { type Visualization } from '@cdc/core/types/Visualization'
|
|
|
24
24
|
import { type DataSet } from '@cdc/core/types/DataSet'
|
|
25
25
|
|
|
26
26
|
import './data-import.scss'
|
|
27
|
-
import '@cdc/core/
|
|
27
|
+
import '@cdc/core/components/managers/data-designer.scss'
|
|
28
28
|
|
|
29
29
|
import { errorMessages, maxFileSize } from '../../../helpers/errorMessages'
|
|
30
30
|
import { displaySize } from '../helpers/displaySize'
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
parseVegaConfig,
|
|
40
40
|
updateVegaData
|
|
41
41
|
} from '@cdc/core/helpers/vegaConfig'
|
|
42
|
+
import { extractDataAndMetadata } from '@cdc/core/helpers/extractDataAndMetadata'
|
|
42
43
|
|
|
43
44
|
const DataImport = () => {
|
|
44
45
|
const { config, errors, tempConfig, sharepath } = useContext(ConfigContext)
|
|
@@ -179,12 +180,13 @@ const DataImport = () => {
|
|
|
179
180
|
const filereader = new FileReader()
|
|
180
181
|
|
|
181
182
|
filereader.onload = function () {
|
|
182
|
-
const handleSetConfig = (newData: Object[], useTempConfig = false) => {
|
|
183
|
+
const handleSetConfig = (newData: Object[], useTempConfig = false, dataMetadata = {}) => {
|
|
183
184
|
const setDataURL = keepURL && fileSourceType === 'url'
|
|
184
185
|
if (config.type === 'dashboard') {
|
|
185
186
|
const dataFileFormat = mimeType.split('/')[1].toUpperCase()
|
|
186
187
|
const dataset = {
|
|
187
188
|
data: newData,
|
|
189
|
+
dataMetadata,
|
|
188
190
|
dataFileSize: fileSize,
|
|
189
191
|
dataFileName: fileSource, // new file source
|
|
190
192
|
dataFileSourceType: fileSourceType, // new file source type
|
|
@@ -210,6 +212,7 @@ const DataImport = () => {
|
|
|
210
212
|
...config,
|
|
211
213
|
...tempConfig,
|
|
212
214
|
data: newData,
|
|
215
|
+
dataMetadata,
|
|
213
216
|
dataFileName: fileSource, // new file source
|
|
214
217
|
dataFileSourceType: fileSourceType, // new file source type
|
|
215
218
|
formattedData: transform.developerStandardize(newData, config.dataDescription)
|
|
@@ -227,14 +230,16 @@ const DataImport = () => {
|
|
|
227
230
|
if (config.vegaConfig) {
|
|
228
231
|
return updateDataFromVegaData(result, fileSource, fileSourceType)
|
|
229
232
|
}
|
|
230
|
-
const
|
|
233
|
+
const { data: extractedData, dataMetadata } = extractDataAndMetadata(result)
|
|
234
|
+
const text = transform.autoStandardize(extractedData)
|
|
231
235
|
if (config.data && config.series) {
|
|
232
236
|
if (dataExists(text, config.series, config?.xAxis.dataKey)) {
|
|
233
|
-
handleSetConfig(text, true)
|
|
237
|
+
handleSetConfig(text, true, dataMetadata)
|
|
234
238
|
} else {
|
|
235
239
|
resetEditor(
|
|
236
240
|
{
|
|
237
241
|
data: text,
|
|
242
|
+
dataMetadata,
|
|
238
243
|
dataFileName: fileSource,
|
|
239
244
|
dataFileSourceType: fileSourceType
|
|
240
245
|
} as Visualization,
|
|
@@ -242,7 +247,7 @@ const DataImport = () => {
|
|
|
242
247
|
)
|
|
243
248
|
}
|
|
244
249
|
} else {
|
|
245
|
-
handleSetConfig(text)
|
|
250
|
+
handleSetConfig(text, false, dataMetadata)
|
|
246
251
|
}
|
|
247
252
|
|
|
248
253
|
if (editingDataset) {
|
|
@@ -3,20 +3,22 @@ import SampleDataContext from './samples/SampleDataContext'
|
|
|
3
3
|
import { cityTemperature } from '@visx/mock-data'
|
|
4
4
|
|
|
5
5
|
// Data Samples
|
|
6
|
-
import
|
|
7
|
-
import
|
|
6
|
+
import pivotData from './samples/pivotData.json?raw'
|
|
7
|
+
import vaidWorldData from './samples/valid-world-data.json?raw'
|
|
8
|
+
import validAreaChart from './samples/valid-area-chart.json?raw'
|
|
9
|
+
import validBoxPlotData from './samples/valid-boxplot.csv?raw'
|
|
8
10
|
import validChartData from './samples/valid-data-chart.csv?raw'
|
|
9
11
|
import validCountyMapData from './samples/valid-county-data.csv?raw'
|
|
10
|
-
import validGeoPoint from './samples/valid-geo-point.csv?raw'
|
|
11
|
-
import validScatterPlot from './samples/valid-scatterplot.csv?raw'
|
|
12
|
-
import validBoxPlotData from './samples/valid-boxplot.csv?raw'
|
|
13
|
-
import validAreaChart from './samples/valid-area-chart.json?raw'
|
|
14
|
-
import validWorldGeocodeData from './samples/valid-world-geocode.json?raw'
|
|
15
12
|
import validForecastData from './samples/valid-forecast-data.csv?raw'
|
|
16
|
-
import
|
|
13
|
+
import validGeoPoint from './samples/valid-geo-point.csv?raw'
|
|
14
|
+
import validHorizonData from './samples/valid-horizon-chart.json?raw'
|
|
15
|
+
import validMapData from './samples/valid-data-map.csv?raw'
|
|
16
|
+
import validMapDataFootnotes from './samples/valid-data-map-footnotes.csv?raw'
|
|
17
|
+
import validRadarData from './samples/valid-radar-chart.csv?raw'
|
|
17
18
|
import validRegionData from './samples/valid-region-data.json?raw'
|
|
18
19
|
import validSankeyData from './samples/valid-sankey-data.json?raw'
|
|
19
|
-
import
|
|
20
|
+
import validScatterPlot from './samples/valid-scatterplot.csv?raw'
|
|
21
|
+
import validWorldGeocodeData from './samples/valid-world-geocode.json?raw'
|
|
20
22
|
|
|
21
23
|
// Convert visx cityTemperature data to CSV format
|
|
22
24
|
const visxTemperatureData = (() => {
|
|
@@ -53,11 +55,21 @@ const sampleData = {
|
|
|
53
55
|
fileName: 'valid-forecast-data.csv',
|
|
54
56
|
data: validForecastData
|
|
55
57
|
},
|
|
58
|
+
{
|
|
59
|
+
text: 'Horizon Chart Data',
|
|
60
|
+
fileName: 'valid-horizon-data.json',
|
|
61
|
+
data: validHorizonData
|
|
62
|
+
},
|
|
56
63
|
{
|
|
57
64
|
text: 'Sankey Chart Data',
|
|
58
65
|
fileName: 'valid-sankey-data.json',
|
|
59
66
|
data: validSankeyData
|
|
60
67
|
},
|
|
68
|
+
{
|
|
69
|
+
text: 'Radar Chart Data (Program Comparison)',
|
|
70
|
+
fileName: 'valid-radar-chart.csv',
|
|
71
|
+
data: validRadarData
|
|
72
|
+
},
|
|
61
73
|
{
|
|
62
74
|
text: 'Pivot Table Data',
|
|
63
75
|
fileName: 'pivotData.json',
|