@evidence-dev/component-utilities
Advanced tools
Comparing version 0.0.0-6af2dedd to 0.0.0-6ba0891f
460
CHANGELOG.md
# @evidence-dev/component-utilities | ||
## 2.5.3 | ||
### Patch Changes | ||
- 629f93a0: properly update `option` for other functions to use | ||
- @evidence-dev/query-store@2.1.2 | ||
## 2.5.2 | ||
### Patch Changes | ||
- Updated dependencies [3a91fdc1] | ||
- @evidence-dev/query-store@2.1.1 | ||
## 2.5.1 | ||
### Patch Changes | ||
- aa5708f0: Add support for connected charts | ||
- 8efccce0: Fix map image download | ||
## 2.5.0 | ||
### Minor Changes | ||
- ca3e593b: - Updated major dependencies (Svelte, SvelteKit, Vite) to improve memory usage when building | ||
### Patch Changes | ||
- f9fe4d89: fix regression to resize behavior in echarts | ||
- 089a08e4: fixes null columns erroring when formatting | ||
- Updated dependencies [ca3e593b] | ||
- Updated dependencies [e3cf9809] | ||
- @evidence-dev/query-store@2.1.0 | ||
## 2.4.2 | ||
### Patch Changes | ||
- Updated dependencies [9a9ace8f] | ||
- @evidence-dev/query-store@2.0.6 | ||
## 2.4.1 | ||
### Patch Changes | ||
- 5a1e46a5: Fix for svelte vite errors | ||
- 008cf432: Roll back proxy server | ||
- 69b9ed32: Fix file imports for evidence package | ||
- Updated dependencies [5a1e46a5] | ||
- Updated dependencies [008cf432] | ||
- Updated dependencies [69b9ed32] | ||
- @evidence-dev/query-store@2.0.5 | ||
## 2.4.0 | ||
### Minor Changes | ||
- 57c81240: Add clearer dev mode errors | ||
### Patch Changes | ||
- 1ff76fdf: modify toasts store | ||
- 152b7224: Add x and y sort to Heatmap | ||
- Updated dependencies [1ff76fdf] | ||
- @evidence-dev/query-store@2.0.4 | ||
## 2.3.0 | ||
### Minor Changes | ||
- e09c5716: Add empty state to components | ||
### Patch Changes | ||
- 6ec752a7: Fix custom formatting retrieval for custom components | ||
- b864b3cd: Add additional echarts override options to charting library | ||
## 2.2.1 | ||
### Patch Changes | ||
- 6a61ea17: - buildReactiveInputQuery was accidentally setting the value of it's store to a Promise, which was not the intent. This ensures that the value is always the proper interface. | ||
## 2.2.0 | ||
### Minor Changes | ||
- 0f42e927: Add Heatmap and CalendarHeatmap components | ||
### Patch Changes | ||
- 9176c2cc: Added buildReactiveInputQuery for ensuring input query values are reactive | ||
- @evidence-dev/query-store@2.0.3 | ||
## 2.1.0 | ||
### Minor Changes | ||
- c25fc1ac: Upgraded USMap component | ||
### Patch Changes | ||
- f7903b86: Update downloaded filenames | ||
- cd9c80b2: Moved chart helper contexts from core-components to component utilites so they are accessible to 3rd party plugins | ||
- fa0faf8c: Fix treatment of nulls in getCompletedData helper function | ||
- a6de89de: Adds option to include total rows in DataTables | ||
- @evidence-dev/query-store@2.0.2 | ||
## 2.0.2 | ||
### Patch Changes | ||
- 03b3b626: Added TextInput and ButtonGroup (+ DateAgg) input components | ||
## 2.0.1 | ||
### Patch Changes | ||
- 913f5919: getCompletedData handles dates properly now | ||
- @evidence-dev/query-store@2.0.1 | ||
## 2.0.0 | ||
### Major Changes | ||
- cb0fc468: This update includes major changes to the way Evidence interacts with data. | ||
Instead of running queries against the production database, and including it | ||
with the project as pre-rendered, static JSON data; those queries are now stored as .parquet files. | ||
.parquet enables the use of DuckDB on the client, allowing for much greater levels of interactivity | ||
on pages, and interoperability between different data sources (e.g. joins across postgres & mysql). | ||
### Minor Changes | ||
- 1097e5a9: add client ddb-backed dropdown component | ||
### Patch Changes | ||
- 4ac6a688: Add support for toasts without a timeout | ||
- 5d280997: LocalStorageStores now flush values on update properly | ||
- 391282e5: QueryStore now uses a factory pattern to enforce caching | ||
- b25a95d7: Misc fixes | ||
- 71f0d481: Change default value for showing QueryViewers to include browser | ||
- 9132146b: fix vite hard refreshes, fix dropdown flickering on ssr, fix null columns | ||
- 7c4249c0: fix falsy dates in `convertColumnToDate` | ||
- e1174aa1: added profile function to note load and query times | ||
- 7c44653b: add error state to dropdowns, fix .clone() error, rename from prop to data | ||
- 130950d7: revamp toast notifications | ||
- bf4a112a: Update package.json to use new datasource field | ||
- 17a82581: standardize date objects in `standardizeDateString` | ||
- ef4155ee: echarts now replaces options rather than merging | ||
- 489a6069: Make echarts animation time forced to be 500ms | ||
- 88e1a5ee: Toasts can now be dismissable | ||
- 64ab3074: Add USQL Context wrappers to component utilities | ||
- 078fca3b: Error handling via QueryStores is more effective now | ||
- 52e114cc: move date standardization | ||
- 9e7ba37d: Remove usql context; proper approach is to use page store now. Context is not reactive; and would require a store which is the behavior already present in \$app/stores.page | ||
- fe466b13: Added a localStorage backed store | ||
- ca1f90b3: Improved Logging | ||
- 982a17c6: Properly mute profiled functions when not in debug mode | ||
- 583cea9e: Properly retrieve column types from QueryStores | ||
- 4053c976: Fix custom formatting sometimes breaking when undefined | ||
- 6505351f: Misc Fixes | ||
- 20127231: Bump all versions so version pinning works | ||
- e9a63c71: Add loading states to DataTable and Chart | ||
- 64d1405b: Loading state is now respected by Value and BigValue | ||
- 0e3eec13: Updated Toast notifications with more types and default options | ||
- 0e3eec13: Re-export steeze-ui icons from component utilities for easier access | ||
- Updated dependencies [391282e5] | ||
- Updated dependencies [840d1195] | ||
- Updated dependencies [6064fbbf] | ||
- Updated dependencies [6eb93816] | ||
- Updated dependencies [9bd1cd29] | ||
- Updated dependencies [120d22e9] | ||
- Updated dependencies [bf4a112a] | ||
- Updated dependencies [e1facffd] | ||
- Updated dependencies [f38b8920] | ||
- Updated dependencies [e2162851] | ||
- Updated dependencies [078fca3b] | ||
- Updated dependencies [f764cba4] | ||
- Updated dependencies [583cea9e] | ||
- Updated dependencies [130950d7] | ||
- Updated dependencies [043a302a] | ||
- Updated dependencies [16a17086] | ||
- Updated dependencies [4c6eae53] | ||
- Updated dependencies [64d1405b] | ||
- Updated dependencies [7a5225be] | ||
- Updated dependencies [ba0d6f50] | ||
- @evidence-dev/query-store@2.0.0 | ||
## 2.0.0-usql.32 | ||
### Patch Changes | ||
- Updated dependencies [f38b8920] | ||
- @evidence-dev/query-store@2.0.0-usql.24 | ||
## 2.0.0-usql.31 | ||
### Patch Changes | ||
- Updated dependencies [043a302a] | ||
- @evidence-dev/query-store@2.0.0-usql.23 | ||
## 2.0.0-usql.30 | ||
### Patch Changes | ||
- 489a6069: Make echarts animation time forced to be 500ms | ||
## 2.0.0-usql.29 | ||
### Patch Changes | ||
- @evidence-dev/query-store@2.0.0-usql.22 | ||
## 2.0.0-usql.28 | ||
### Patch Changes | ||
- @evidence-dev/query-store@2.0.0-usql.21 | ||
## 2.0.0-usql.27 | ||
### Patch Changes | ||
- Updated dependencies | ||
- @evidence-dev/query-store@2.0.0-usql.20 | ||
## 2.0.0-usql.26 | ||
### Patch Changes | ||
- Updated dependencies [e2162851] | ||
- @evidence-dev/query-store@2.0.0-usql.19 | ||
## 2.0.0-usql.25 | ||
### Patch Changes | ||
- 391282e5: QueryStore now uses a factory pattern to enforce caching | ||
- Updated dependencies [391282e5] | ||
- @evidence-dev/query-store@2.0.0-usql.18 | ||
## 2.0.0-usql.24 | ||
### Patch Changes | ||
- Updated dependencies | ||
- @evidence-dev/query-store@2.0.0-usql.17 | ||
## 2.0.0-usql.23 | ||
### Patch Changes | ||
- Updated dependencies [16a17086] | ||
- @evidence-dev/query-store@2.0.0-usql.16 | ||
## 2.0.0-usql.22 | ||
### Patch Changes | ||
- 9132146b: fix vite hard refreshes, fix dropdown flickering on ssr, fix null columns | ||
- Updated dependencies [4c6eae53] | ||
- Updated dependencies [ba0d6f50] | ||
- @evidence-dev/query-store@2.0.0-usql.15 | ||
## 2.0.0-usql.21 | ||
### Patch Changes | ||
- 5d280997: LocalStorageStores now flush values on update properly | ||
- Updated dependencies [7a5225be] | ||
- @evidence-dev/query-store@2.0.0-usql.14 | ||
## 2.0.0-usql.20 | ||
### Patch Changes | ||
- 71f0d481: Change default value for showing QueryViewers to include browser | ||
- ef4155ee: echarts now replaces options rather than merging | ||
- 583cea9e: Properly retrieve column types from QueryStores | ||
- Updated dependencies [583cea9e] | ||
- @evidence-dev/query-store@2.0.0-usql.13 | ||
## 2.0.0-usql.19 | ||
### Patch Changes | ||
- 4ac6a688: Add support for toasts without a timeout | ||
## 2.0.0-usql.18 | ||
### Patch Changes | ||
- @evidence-dev/query-store@2.0.0-usql.12 | ||
## 2.0.0-usql.17 | ||
### Patch Changes | ||
- 982a17c6: Properly mute profiled functions when not in debug mode | ||
- @evidence-dev/query-store@2.0.0-usql.11 | ||
## 2.0.0-usql.16 | ||
### Patch Changes | ||
- Update package.json to use new datasource field | ||
- Updated dependencies | ||
- @evidence-dev/query-store@2.0.0-usql.10 | ||
## 2.0.0-usql.15 | ||
### Patch Changes | ||
- @evidence-dev/query-store@2.0.0-usql.9 | ||
## 2.0.0-usql.14 | ||
### Patch Changes | ||
- 6505351f: Misc Fixes | ||
- Updated dependencies [840d1195] | ||
- Updated dependencies [6064fbbf] | ||
- @evidence-dev/query-store@2.0.0-usql.8 | ||
## 2.0.0-usql.13 | ||
### Patch Changes | ||
- 88e1a5ee: Toasts can now be dismissable | ||
- @evidence-dev/query-store@2.0.0-usql.7 | ||
## 2.0.0-usql.12 | ||
### Patch Changes | ||
- b25a95d7: Misc fixes | ||
- fe466b13: Added a localStorage backed store | ||
- @evidence-dev/query-store@2.0.0-usql.6 | ||
## 2.0.0-usql.11 | ||
### Patch Changes | ||
- 7c44653b: add error state to dropdowns, fix .clone() error, rename from prop to data | ||
- 0e3eec13: Updated Toast notifications with more types and default options | ||
- 0e3eec13: Re-export steeze-ui icons from component utilities for easier access | ||
## 2.0.0-usql.10 | ||
### Minor Changes | ||
- 1097e5a9: add client ddb-backed dropdown component | ||
### Patch Changes | ||
- 130950d7: revamp toast notifications | ||
## 2.0.0-usql.9 | ||
### Patch Changes | ||
- 64d1405b: Loading state is now respected by Value and BigValue | ||
## 2.0.0-usql.8 | ||
### Patch Changes | ||
- 078fca3b: Error handling via QueryStores is more effective now | ||
- e9a63c71: Add loading states to DataTable and Chart | ||
## 2.0.0-usql.7 | ||
### Patch Changes | ||
- 52e114cc: move date standardization | ||
- ca1f90b3: Improved Logging | ||
## 2.0.0-usql.6 | ||
### Patch Changes | ||
- 7c4249c0: fix falsy dates in `convertColumnToDate` | ||
- 20127231: Bump all versions so version pinning works | ||
## 2.0.0-usql.5 | ||
### Patch Changes | ||
- 17a82581: standardize date objects in `standardizeDateString` | ||
## 2.0.0-usql.4 | ||
### Patch Changes | ||
- Remove usql context; proper approach is to use page store now. Context is not reactive; and would require a store which is the behavior already present in \$app/stores.page | ||
## 2.0.0-usql.3 | ||
### Patch Changes | ||
- 64ab3074: Add USQL Context wrappers to component utilities | ||
## 2.0.0-usql.2 | ||
### Patch Changes | ||
- e1174aa1: added profile function to note load and query times | ||
## 2.0.0-usql.1 | ||
### Patch Changes | ||
- 4053c976: Fix custom formatting sometimes breaking when undefined | ||
## 2.0.0-usql.0 | ||
### Major Changes | ||
- cb0fc468: This update includes major changes to the way Evidence interacts with data. | ||
Instead of running queries against the production database, and including it | ||
with the project as pre-rendered, static JSON data; those queries are now stored as .parquet files. | ||
.parquet enables the use of DuckDB on the client, allowing for much greater levels of interactivity | ||
on pages, and interoperability between different data sources (e.g. joins across postgres & mysql). | ||
## 1.2.2 | ||
### Patch Changes | ||
- 3462a045: fix massive charts on ios | ||
## 1.2.1 | ||
### Patch Changes | ||
- 8ed2af44: Explicitly set font family for chart theme | ||
## 1.2.0 | ||
### Minor Changes | ||
- 5f660a8d: Add box plot | ||
- 56521bfb: Add value labels to charts | ||
- 9b8346f0: update core layout, tailwind config, align components to new layout, deprecate sticky alert | ||
- 71a77ca6: Add secondary y-axis for line charts | ||
### Patch Changes | ||
- aafd7135: Consolidate echarts theme imports | ||
## 1.1.3 | ||
### Patch Changes | ||
- 7112f1b8: Fix y-axis labels being truncated on horizontal bar charts | ||
## 1.1.2 | ||
@@ -4,0 +464,0 @@ |
{ | ||
"name": "@evidence-dev/component-utilities", | ||
"version": "0.0.0-6af2dedd", | ||
"version": "0.0.0-6ba0891f", | ||
"description": "", | ||
@@ -14,13 +14,17 @@ "main": "index.js", | ||
"@faker-js/faker": "^8.0.2", | ||
"@vitest/ui": "^0.34.2", | ||
"vitest": "^0.31.1" | ||
"vitest": "^0.34.1" | ||
}, | ||
"type": "module", | ||
"dependencies": { | ||
"@tidyjs/tidy": "2.4.4", | ||
"@steeze-ui/simple-icons": "^1.7.1", | ||
"@steeze-ui/svelte-icon": "1.5.0", | ||
"@steeze-ui/tabler-icons": "2.1.1", | ||
"@tidyjs/tidy": "2.5.2", | ||
"@uwdata/mosaic-sql": "^0.3.2", | ||
"debounce": "^1.2.1", | ||
"downloadjs": "1.4.7", | ||
"echarts": "5.4.2", | ||
"echarts": "5.4.3", | ||
"ssf": "^0.11.2", | ||
"svelte": "3.55.0" | ||
"svelte": "4.2.12", | ||
"@evidence-dev/sdk": "0.0.0-6ba0891f" | ||
}, | ||
@@ -27,0 +31,0 @@ "scripts": { |
@@ -186,3 +186,3 @@ import ssf from 'ssf'; | ||
} else { | ||
if (columnUnitSummary?.unitType === 'number' && columnUnitSummary?.median !== undefined) { | ||
if (columnUnitSummary?.unitType === 'number') { | ||
return generateImplicitNumberFormat(columnUnitSummary); | ||
@@ -189,0 +189,0 @@ } |
@@ -0,1 +1,3 @@ | ||
import { Query } from '@evidence-dev/sdk/usql'; | ||
export default function checkInputs(data, reqCols, optCols) { | ||
@@ -6,4 +8,4 @@ // reqCols is an array of columns to check in the dataset | ||
// Check if dataset was provided | ||
if (data == undefined) { | ||
throw Error('No dataset provided'); | ||
if (data === undefined) { | ||
throw Error('No data provided'); | ||
} else if (typeof data !== 'object') { | ||
@@ -18,3 +20,3 @@ throw Error( | ||
); | ||
} else if (data[0] == undefined) { | ||
} else if (data[0] === undefined || data.length === 0) { | ||
throw Error( | ||
@@ -54,5 +56,13 @@ 'Dataset is empty: query ran successfully, but no data was returned from the database' | ||
// const dataIsQueryStore = data instanceof QueryStore; | ||
// Get list of all columns in dataset | ||
for (const [key] of Object.entries(data[0])) { | ||
columns.push(key); | ||
if (Query.isQuery(data)) { | ||
for (const col of data.columns) { | ||
columns.push(col.column_name); | ||
} | ||
} else { | ||
for (const key of Object.keys(data[0])) { | ||
columns.push(key); | ||
} | ||
} | ||
@@ -59,0 +69,0 @@ |
@@ -1,2 +0,2 @@ | ||
export const colours = { | ||
export const uiColours = { | ||
blue100: 'hsla(202, 100%, 95%, 1)', | ||
@@ -45,3 +45,3 @@ blue200: 'hsla(204, 100%, 85%, 1)', | ||
export const colour = [ | ||
export const chartColours = [ | ||
'hsla(207, 65%, 39%, 1)', // Navy | ||
@@ -56,5 +56,14 @@ 'hsla(195, 49%, 51%, 1)', // Teal | ||
'hsla(207, 86%, 70%, 1)', // Blue | ||
'hsla(160, 40%, 46%, 1)' // Green | ||
//greyscale | ||
//'#71777d', '#7e848a', '#8c9196', '#9a9fa3', '#a8acb0', '#b7babd', '#c5c8ca', '#d4d6d7', '#e3e4e5', '#f3f3f3' | ||
'hsla(160, 40%, 46%, 1)', // Green | ||
// Grey Scale | ||
'#71777d', | ||
'#7e848a', | ||
'#8c9196', | ||
'#9a9fa3', | ||
'#a8acb0', | ||
'#b7babd', | ||
'#c5c8ca', | ||
'#d4d6d7', | ||
'#e3e4e5', | ||
'#f3f3f3' | ||
]; |
@@ -1,3 +0,3 @@ | ||
import { registerTheme, init } from 'echarts'; | ||
import { colours } from './colours'; | ||
import { registerTheme, init, connect } from 'echarts'; | ||
import { evidenceThemeLight } from './echartsThemes'; | ||
import debounce from 'debounce'; | ||
@@ -13,427 +13,100 @@ | ||
const ANIMATION_DURATION = 500; | ||
/** @type {import("svelte/action").Action<HTMLElement, ActionParams>} */ | ||
export default (node, option) => { | ||
registerTheme('evidence-light', { | ||
grid: { | ||
left: '0%', | ||
right: '4%', | ||
bottom: '0%', | ||
top: '15%', | ||
containLabel: true | ||
}, | ||
color: [ | ||
'hsla(207, 65%, 39%, 1)', // Navy | ||
'hsla(195, 49%, 51%, 1)', // Teal | ||
'hsla(207, 69%, 79%, 1)', // Light Blue | ||
'hsla(202, 28%, 65%, 1)', // Grey | ||
'hsla(179, 37%, 65%, 1)', // Light Green | ||
'hsla(40, 30%, 75%, 1)', // Tan | ||
'hsla(38, 89%, 62%, 1)', // Yellow | ||
'hsla(342, 40%, 40%, 1)', // Maroon | ||
'hsla(207, 86%, 70%, 1)', // Blue | ||
'hsla(160, 40%, 46%, 1)', // Green | ||
// Grey Scale | ||
'#71777d', | ||
'#7e848a', | ||
'#8c9196', | ||
'#9a9fa3', | ||
'#a8acb0', | ||
'#b7babd', | ||
'#c5c8ca', | ||
'#d4d6d7', | ||
'#e3e4e5', | ||
'#f3f3f3' | ||
], | ||
backgroundColor: 'rgba(255, 255, 255, 0)', | ||
textStyle: { | ||
fontFamily: 'sans-serif' | ||
}, | ||
title: { | ||
padding: 0, | ||
itemGap: 7, | ||
textStyle: { | ||
fontSize: 14, | ||
color: colours.grey700 | ||
}, | ||
subtextStyle: { | ||
fontSize: 13, | ||
color: colours.grey600, | ||
overflow: 'break' | ||
}, | ||
top: '0%' | ||
}, | ||
line: { | ||
itemStyle: { | ||
borderWidth: 0 | ||
}, | ||
lineStyle: { | ||
width: 2, | ||
join: 'round' | ||
}, | ||
symbolSize: 0, | ||
symbol: 'circle', | ||
smooth: false | ||
}, | ||
radar: { | ||
itemStyle: { | ||
borderWidth: 0 | ||
}, | ||
lineStyle: { | ||
width: 2 | ||
}, | ||
symbolSize: 0, | ||
symbol: 'circle', | ||
smooth: false | ||
}, | ||
bar: { | ||
itemStyle: { | ||
barBorderWidth: 1, | ||
barBorderColor: '#cccccc' | ||
} | ||
}, | ||
pie: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
scatter: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
boxplot: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
parallel: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
sankey: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
funnel: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
gauge: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
candlestick: { | ||
itemStyle: { | ||
color: '#eb5454', | ||
color0: '#47b262', | ||
borderColor: '#eb5454', | ||
borderColor0: '#47b262', | ||
borderWidth: 1 | ||
} | ||
}, | ||
graph: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
}, | ||
lineStyle: { | ||
width: 1, | ||
color: '#aaaaaa' | ||
}, | ||
symbolSize: 0, | ||
symbol: 'circle', | ||
smooth: false, | ||
color: [ | ||
'#923d59', | ||
'#488f96', | ||
'#518eca', | ||
'#b3a9a0', | ||
'#ffc857', | ||
'#495867', | ||
'#bfdbf7', | ||
'#bc4749', | ||
'#eeebd0' | ||
], | ||
label: { | ||
color: '#f2f2f2' | ||
} | ||
}, | ||
map: { | ||
itemStyle: { | ||
areaColor: '#eee', | ||
borderColor: '#444', | ||
borderWidth: 0.5 | ||
}, | ||
label: { | ||
color: '#000' | ||
}, | ||
emphasis: { | ||
itemStyle: { | ||
areaColor: 'rgba(255,215,0,0.8)', | ||
borderColor: '#444', | ||
borderWidth: 1 | ||
}, | ||
label: { | ||
color: 'rgb(100,0,0)' | ||
// https://github.com/evidence-dev/evidence/issues/1323 | ||
const useSvg = | ||
['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes( | ||
navigator.platform | ||
// ios breaks w/ canvas if the canvas is too large | ||
) && node.clientWidth * 3 * node.clientHeight * 3 > 16777215; | ||
registerTheme('evidence-light', evidenceThemeLight); | ||
const chart = init(node, 'evidence-light', { | ||
renderer: useSvg ? 'svg' : option.renderer ?? 'canvas' | ||
}); | ||
// If connectGroup supplied, connect chart to other charts matching that connectGroup | ||
if (option.connectGroup) { | ||
chart.group = option.connectGroup; | ||
connect(option.connectGroup); | ||
} | ||
// This function applies overrides to the echarts config generated by our chart components. | ||
// In cases where these affect the spacing of the chart, some jumping/reanimation will be visible when | ||
// the chart is animated. This is because we need to pass the initial options without the overrides to echarts | ||
// first in order to use setOption()'s built-in merge capability. I believe the only way around this is | ||
// to handle the option merge ourselves, then pass the final config object to setOption(). | ||
// Series Color override | ||
const applySeriesColors = () => { | ||
if (option.seriesColors) { | ||
/** @type {import("echarts").EChartsOption} */ | ||
const prevOption = chart.getOption(); | ||
if (!prevOption) return; | ||
const newOption = { ...prevOption }; | ||
for (const seriesName of Object.keys(option.seriesColors)) { | ||
const matchingSeriesIndex = prevOption.series.findIndex((s) => s.name === seriesName); | ||
if (matchingSeriesIndex !== -1) { | ||
newOption.series[matchingSeriesIndex] = { | ||
...newOption.series[matchingSeriesIndex], | ||
itemStyle: { | ||
...newOption.series[matchingSeriesIndex].itemStyle, | ||
color: option.seriesColors[seriesName] | ||
} | ||
}; | ||
} | ||
} | ||
}, | ||
geo: { | ||
itemStyle: { | ||
areaColor: '#eee', | ||
borderColor: '#444', | ||
borderWidth: 0.5 | ||
}, | ||
label: { | ||
color: '#000' | ||
}, | ||
emphasis: { | ||
itemStyle: { | ||
areaColor: 'rgba(255,215,0,0.8)', | ||
borderColor: '#444', | ||
borderWidth: 1 | ||
chart.setOption(newOption); | ||
} | ||
}; | ||
// Check if echartsOptions are provided and apply them | ||
const applyEchartsOptions = () => { | ||
if (option.echartsOptions) { | ||
chart.setOption({ | ||
...option.echartsOptions | ||
}); | ||
} | ||
}; | ||
// seriesOptions - loop through series and apply same changes to each | ||
const applySeriesOptions = () => { | ||
let tempSeries = []; | ||
if (option.seriesOptions) { | ||
const reference_index = option.config.series.reduce( | ||
(acc, { evidenceSeriesType }, reference_index) => { | ||
if (evidenceSeriesType === 'reference_line' || evidenceSeriesType === 'reference_area') { | ||
acc.push(reference_index); | ||
} | ||
return acc; | ||
}, | ||
label: { | ||
color: 'rgb(100,0,0)' | ||
[] | ||
); | ||
for (let i = 0; i < option.config.series.length; i++) { | ||
if (reference_index.includes(i)) { | ||
tempSeries.push({}); | ||
} else { | ||
tempSeries.push({ ...option.seriesOptions }); | ||
} | ||
} | ||
}, | ||
categoryAxis: { | ||
axisLine: { | ||
show: true, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 3, | ||
alignWithLabel: true | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: false, | ||
lineStyle: { | ||
color: [colours.grey200] | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
valueAxis: { | ||
axisLine: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 2 | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: true, | ||
lineStyle: { | ||
color: [colours.grey200], | ||
width: 1 | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
logAxis: { | ||
axisLine: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 2 | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: true, | ||
lineStyle: { | ||
color: [colours.grey200] | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
timeAxis: { | ||
axisLine: { | ||
show: true, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: true, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 3 | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: false, | ||
lineStyle: { | ||
color: [colours.grey200] | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
toolbox: { | ||
iconStyle: { | ||
borderColor: '#999999' | ||
}, | ||
emphasis: { | ||
iconStyle: { | ||
borderColor: '#459cde' | ||
} | ||
} | ||
}, | ||
legend: { | ||
textStyle: { | ||
padding: [0, 0, 0, -7], | ||
color: colours.grey500 | ||
}, | ||
// "padding": [15,0,0,0], | ||
icon: 'circle', | ||
pageIcons: { | ||
horizontal: [ | ||
'M 17 3 h 2 c 0.386 0 0.738 0.223 0.904 0.572 s 0.115 0.762 -0.13 1.062 L 11.292 15 l 8.482 10.367 c 0.245 0.299 0.295 0.712 0.13 1.062 S 19.386 27 19 27 h -2 c -0.3 0 -0.584 -0.135 -0.774 -0.367 l -9 -11 c -0.301 -0.369 -0.301 -0.898 0 -1.267 l 9 -11 C 16.416 3.135 16.7 3 17 3 Z', | ||
'M 12 27 h -2 c -0.386 0 -0.738 -0.223 -0.904 -0.572 s -0.115 -0.762 0.13 -1.062 L 17.708 15 L 9.226 4.633 c -0.245 -0.299 -0.295 -0.712 -0.13 -1.062 S 9.614 3 10 3 h 2 c 0.3 0 0.584 0.135 0.774 0.367 l 9 11 c 0.301 0.369 0.301 0.898 0 1.267 l -9 11 C 12.584 26.865 12.3 27 12 27 Z' | ||
] | ||
}, | ||
pageIconColor: colours.grey600, | ||
pageIconSize: 12, | ||
pageTextStyle: { | ||
color: 'grey' | ||
}, | ||
pageButtonItemGap: -2, | ||
animationDurationUpdate: 300 | ||
}, | ||
tooltip: { | ||
axisPointer: { | ||
lineStyle: { | ||
color: '#cccccc', | ||
width: 1 | ||
}, | ||
crossStyle: { | ||
color: '#cccccc', | ||
width: 1 | ||
} | ||
} | ||
}, | ||
timeline: { | ||
lineStyle: { | ||
color: '#e3e3e3', | ||
width: 2 | ||
}, | ||
itemStyle: { | ||
color: '#d6d6d6', | ||
borderWidth: 1 | ||
}, | ||
controlStyle: { | ||
color: '#bfbfbf', | ||
borderColor: '#bfbfbf', | ||
borderWidth: 1 | ||
}, | ||
checkpointStyle: { | ||
color: '#8f8f8f', | ||
borderColor: '#ffffff' | ||
}, | ||
label: { | ||
color: '#c9c9c9' | ||
}, | ||
emphasis: { | ||
itemStyle: { | ||
color: '#9c9c9c' | ||
}, | ||
controlStyle: { | ||
color: '#bfbfbf', | ||
borderColor: '#bfbfbf', | ||
borderWidth: 1 | ||
}, | ||
label: { | ||
color: '#c9c9c9' | ||
} | ||
} | ||
}, | ||
visualMap: { | ||
color: ['#c41621', '#e39588', '#f5ed98'] | ||
}, | ||
dataZoom: { | ||
handleSize: 'undefined%', | ||
textStyle: {} | ||
}, | ||
markPoint: { | ||
label: { | ||
color: '#f2f2f2' | ||
}, | ||
emphasis: { | ||
label: { | ||
color: '#f2f2f2' | ||
} | ||
} | ||
chart.setOption({ series: tempSeries }); | ||
} | ||
}; | ||
// Initial options set: | ||
chart.setOption({ | ||
...option.config, | ||
animationDuration: ANIMATION_DURATION, | ||
animationDurationUpdate: ANIMATION_DURATION | ||
}); | ||
const chart = init(node, 'evidence-light', { renderer: 'svg' }); | ||
applySeriesColors(); | ||
applyEchartsOptions(); | ||
applySeriesOptions(); | ||
chart.setOption(option); | ||
// Click event handler: | ||
const dispatch = option.dispatch; | ||
@@ -444,8 +117,10 @@ chart.on('click', function (params) { | ||
let resizeObserver; | ||
const containerElement = document.querySelector('div.content > article'); | ||
// Resize logic: | ||
const containerElement = document.getElementById('evidence-main-article'); | ||
// watching parent element is necessary for charts within `Fullscreen` components | ||
const parentElement = node.parentElement; | ||
const onWindowResize = debounce(() => { | ||
chart.resize({ | ||
animation: { | ||
duration: 500 | ||
duration: ANIMATION_DURATION | ||
} | ||
@@ -456,11 +131,21 @@ }); | ||
let resizeObserver; | ||
if (window.ResizeObserver && containerElement) { | ||
resizeObserver = new ResizeObserver(onWindowResize); | ||
resizeObserver.observe(containerElement); | ||
resizeObserver.observe(parentElement); | ||
} else { | ||
window.addEventListener('resize', onWindowResize); | ||
} | ||
// Label width setting: | ||
const updateLabelWidths = () => { | ||
// Make sure we operate on an up-to-date options object | ||
/** @type {import("echarts").EChartsOption} */ | ||
const prevOption = chart.getOption(); | ||
if (!prevOption) return; | ||
// If the options object includes showing all x axis labels | ||
// Note: this isn't a standard option, but right now this is the easiest way to pass something to the action. | ||
// We don't want to have multiple resize observers if we can avoid it, and this is all due for a cleanup anyways | ||
if (prevOption.showAllXAxisLabels) { | ||
if (option.showAllXAxisLabels) { | ||
// Make sure we operate on an up-to-date options object | ||
/** @type {import("echarts").EChartsOption} */ | ||
const prevOption = chart.getOption(); | ||
if (!prevOption) return; | ||
// If the options object includes showing all x axis labels | ||
// Note: this isn't a standard option, but right now this is the easiest way to pass something to the action. | ||
// We don't want to have multiple resize observers if we can avoid it, and this is all due for a cleanup anyways | ||
// Get all the possible x values | ||
@@ -471,22 +156,41 @@ const distinctXValues = new Set(prevOption.series.flatMap((s) => s.data?.map((d) => d[0]))); | ||
/** @type {import("echarts").EChartsOption} */ | ||
const newOption = { | ||
xAxis: { | ||
axisLabel: { | ||
interval: 0, | ||
overflow: 'truncate', | ||
width: (clientWidth * modConst) / distinctXValues.size | ||
// We disable this behavior because it doesn't make sense on horizontal bar charts | ||
// Category labels will grow to be visible | ||
// Value labels are interpolatable anyway | ||
if (!option.swapXY) { | ||
/** @type {import("echarts").EChartsOption} */ | ||
const newOption = { | ||
xAxis: { | ||
axisLabel: { | ||
interval: 0, | ||
overflow: 'truncate', | ||
width: (clientWidth * modConst) / distinctXValues.size | ||
} | ||
} | ||
} | ||
}; | ||
chart.setOption(newOption); | ||
}; | ||
chart.setOption(newOption); | ||
} | ||
} | ||
}; | ||
if (window.ResizeObserver && containerElement) { | ||
resizeObserver = new ResizeObserver(onWindowResize); | ||
resizeObserver.observe(containerElement); | ||
} else { | ||
window.addEventListener('resize', onWindowResize); | ||
} | ||
const updateChart = (newOption) => { | ||
option = newOption; | ||
chart.setOption( | ||
{ | ||
...option.config, | ||
animationDuration: ANIMATION_DURATION, | ||
animationDurationUpdate: ANIMATION_DURATION | ||
}, | ||
true | ||
); | ||
applySeriesColors(); | ||
applyEchartsOptions(); | ||
applySeriesOptions(); | ||
chart.resize({ | ||
animation: { | ||
duration: ANIMATION_DURATION | ||
} | ||
}); | ||
updateLabelWidths(); | ||
}; | ||
@@ -497,4 +201,3 @@ onWindowResize(); | ||
update(option) { | ||
chart.setOption(option, true, true); | ||
updateLabelWidths(); | ||
updateChart(option); | ||
}, | ||
@@ -504,2 +207,3 @@ destroy() { | ||
resizeObserver.unobserve(containerElement); | ||
resizeObserver.unobserve(parentElement); | ||
} else { | ||
@@ -506,0 +210,0 @@ window.removeEventListener('resize', onWindowResize); |
import { registerTheme, init } from 'echarts'; | ||
import { evidenceThemeLight } from './echartsThemes'; | ||
import download from 'downloadjs'; | ||
import { colours } from './colours'; | ||
export default (node, option) => { | ||
registerTheme('evidence-light', { | ||
grid: { | ||
left: '0%', | ||
right: '4%', | ||
bottom: '0%', | ||
top: '15%', | ||
containLabel: true | ||
}, | ||
color: [ | ||
'hsla(207, 65%, 39%, 1)', // Navy | ||
'hsla(195, 49%, 51%, 1)', // Teal | ||
'hsla(207, 69%, 79%, 1)', // Light Blue | ||
'hsla(202, 28%, 65%, 1)', // Grey | ||
'hsla(179, 37%, 65%, 1)', // Light Green | ||
'hsla(40, 30%, 75%, 1)', // Tan | ||
'hsla(38, 89%, 62%, 1)', // Yellow | ||
'hsla(342, 40%, 40%, 1)', // Maroon | ||
'hsla(207, 86%, 70%, 1)', // Blue | ||
'hsla(160, 40%, 46%, 1)', // Green | ||
// Grey Scale | ||
'#71777d', | ||
'#7e848a', | ||
'#8c9196', | ||
'#9a9fa3', | ||
'#a8acb0', | ||
'#b7babd', | ||
'#c5c8ca', | ||
'#d4d6d7', | ||
'#e3e4e5', | ||
'#f3f3f3' | ||
], | ||
backgroundColor: 'rgba(255, 255, 255, 0)', | ||
textStyle: { | ||
fontFamily: 'sans-serif' | ||
}, | ||
title: { | ||
padding: 0, | ||
itemGap: 7, | ||
textStyle: { | ||
fontSize: 14, | ||
color: colours.grey700 | ||
}, | ||
subtextStyle: { | ||
fontSize: 13, | ||
color: colours.grey600, | ||
overflow: 'break' | ||
}, | ||
top: '0%' | ||
}, | ||
line: { | ||
itemStyle: { | ||
borderWidth: 0 | ||
}, | ||
lineStyle: { | ||
width: 2, | ||
join: 'round' | ||
}, | ||
symbolSize: 0, | ||
symbol: 'circle', | ||
smooth: false | ||
}, | ||
radar: { | ||
itemStyle: { | ||
borderWidth: 0 | ||
}, | ||
lineStyle: { | ||
width: 2 | ||
}, | ||
symbolSize: 0, | ||
symbol: 'circle', | ||
smooth: false | ||
}, | ||
bar: { | ||
itemStyle: { | ||
barBorderWidth: 1, | ||
barBorderColor: '#cccccc' | ||
} | ||
}, | ||
pie: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
scatter: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
boxplot: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
parallel: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
sankey: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
funnel: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
gauge: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
candlestick: { | ||
itemStyle: { | ||
color: '#eb5454', | ||
color0: '#47b262', | ||
borderColor: '#eb5454', | ||
borderColor0: '#47b262', | ||
borderWidth: 1 | ||
} | ||
}, | ||
graph: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
}, | ||
lineStyle: { | ||
width: 1, | ||
color: '#aaaaaa' | ||
}, | ||
symbolSize: 0, | ||
symbol: 'circle', | ||
smooth: false, | ||
color: [ | ||
'#923d59', | ||
'#488f96', | ||
'#518eca', | ||
'#b3a9a0', | ||
'#ffc857', | ||
'#495867', | ||
'#bfdbf7', | ||
'#bc4749', | ||
'#eeebd0' | ||
], | ||
label: { | ||
color: '#f2f2f2' | ||
} | ||
}, | ||
map: { | ||
itemStyle: { | ||
areaColor: '#eee', | ||
borderColor: '#444', | ||
borderWidth: 0.5 | ||
}, | ||
label: { | ||
color: '#000' | ||
}, | ||
emphasis: { | ||
itemStyle: { | ||
areaColor: 'rgba(255,215,0,0.8)', | ||
borderColor: '#444', | ||
borderWidth: 1 | ||
}, | ||
label: { | ||
color: 'rgb(100,0,0)' | ||
registerTheme('evidence-light', evidenceThemeLight); | ||
const chart = init(node, 'evidence-light', { renderer: 'canvas' }); | ||
option.config.animation = false; | ||
chart.setOption(option.config); | ||
// Series Color override | ||
const applySeriesColors = () => { | ||
if (option.seriesColors) { | ||
/** @type {import("echarts").EChartsOption} */ | ||
const prevOption = chart.getOption(); | ||
if (!prevOption) return; | ||
const newOption = { ...prevOption }; | ||
for (const seriesName of Object.keys(option.seriesColors)) { | ||
const matchingSeriesIndex = prevOption.series.findIndex((s) => s.name === seriesName); | ||
if (matchingSeriesIndex !== -1) { | ||
newOption.series[matchingSeriesIndex] = { | ||
...newOption.series[matchingSeriesIndex], | ||
itemStyle: { | ||
...newOption.series[matchingSeriesIndex].itemStyle, | ||
color: option.seriesColors[seriesName] | ||
} | ||
}; | ||
} | ||
} | ||
}, | ||
geo: { | ||
itemStyle: { | ||
areaColor: '#eee', | ||
borderColor: '#444', | ||
borderWidth: 0.5 | ||
}, | ||
label: { | ||
color: '#000' | ||
}, | ||
emphasis: { | ||
itemStyle: { | ||
areaColor: 'rgba(255,215,0,0.8)', | ||
borderColor: '#444', | ||
borderWidth: 1 | ||
chart.setOption(newOption); | ||
} | ||
}; | ||
// Check if echartsOptions are provided and apply them | ||
const applyEchartsOptions = () => { | ||
if (option.echartsOptions) { | ||
chart.setOption({ | ||
...option.echartsOptions | ||
}); | ||
} | ||
}; | ||
// seriesOptions - loop through series and apply same changes to each | ||
const applySeriesOptions = () => { | ||
let tempSeries = []; | ||
if (option.seriesOptions) { | ||
const reference_index = option.config.series.reduce( | ||
(acc, { evidenceSeriesType }, reference_index) => { | ||
if (evidenceSeriesType === 'reference_line' || evidenceSeriesType === 'reference_area') { | ||
acc.push(reference_index); | ||
} | ||
return acc; | ||
}, | ||
label: { | ||
color: 'rgb(100,0,0)' | ||
[] | ||
); | ||
for (let i = 0; i < option.config.series.length; i++) { | ||
if (reference_index.includes(i)) { | ||
tempSeries.push({}); | ||
} else { | ||
tempSeries.push({ ...option.seriesOptions }); | ||
} | ||
} | ||
}, | ||
categoryAxis: { | ||
axisLine: { | ||
show: true, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 3, | ||
alignWithLabel: true | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: false, | ||
lineStyle: { | ||
color: [colours.grey200] | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
valueAxis: { | ||
axisLine: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 2 | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: true, | ||
lineStyle: { | ||
color: [colours.grey200], | ||
width: 1 | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
logAxis: { | ||
axisLine: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 2 | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: true, | ||
lineStyle: { | ||
color: [colours.grey200] | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
timeAxis: { | ||
axisLine: { | ||
show: true, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: true, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 3 | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: false, | ||
lineStyle: { | ||
color: [colours.grey200] | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
toolbox: { | ||
iconStyle: { | ||
borderColor: '#999999' | ||
}, | ||
emphasis: { | ||
iconStyle: { | ||
borderColor: '#459cde' | ||
} | ||
} | ||
}, | ||
legend: { | ||
textStyle: { | ||
padding: [0, 0, 0, -7], | ||
color: colours.grey500 | ||
}, | ||
// "padding": [15,0,0,0], | ||
icon: 'circle', | ||
pageIcons: { | ||
horizontal: [ | ||
'M 17 3 h 2 c 0.386 0 0.738 0.223 0.904 0.572 s 0.115 0.762 -0.13 1.062 L 11.292 15 l 8.482 10.367 c 0.245 0.299 0.295 0.712 0.13 1.062 S 19.386 27 19 27 h -2 c -0.3 0 -0.584 -0.135 -0.774 -0.367 l -9 -11 c -0.301 -0.369 -0.301 -0.898 0 -1.267 l 9 -11 C 16.416 3.135 16.7 3 17 3 Z', | ||
'M 12 27 h -2 c -0.386 0 -0.738 -0.223 -0.904 -0.572 s -0.115 -0.762 0.13 -1.062 L 17.708 15 L 9.226 4.633 c -0.245 -0.299 -0.295 -0.712 -0.13 -1.062 S 9.614 3 10 3 h 2 c 0.3 0 0.584 0.135 0.774 0.367 l 9 11 c 0.301 0.369 0.301 0.898 0 1.267 l -9 11 C 12.584 26.865 12.3 27 12 27 Z' | ||
] | ||
}, | ||
pageIconColor: colours.grey600, | ||
pageIconSize: 12, | ||
pageTextStyle: { | ||
color: 'grey' | ||
}, | ||
pageButtonItemGap: -2, | ||
animationDurationUpdate: 300 | ||
}, | ||
tooltip: { | ||
axisPointer: { | ||
lineStyle: { | ||
color: '#cccccc', | ||
width: 1 | ||
}, | ||
crossStyle: { | ||
color: '#cccccc', | ||
width: 1 | ||
} | ||
} | ||
}, | ||
timeline: { | ||
lineStyle: { | ||
color: '#e3e3e3', | ||
width: 2 | ||
}, | ||
itemStyle: { | ||
color: '#d6d6d6', | ||
borderWidth: 1 | ||
}, | ||
controlStyle: { | ||
color: '#bfbfbf', | ||
borderColor: '#bfbfbf', | ||
borderWidth: 1 | ||
}, | ||
checkpointStyle: { | ||
color: '#8f8f8f', | ||
borderColor: '#ffffff' | ||
}, | ||
label: { | ||
color: '#c9c9c9' | ||
}, | ||
emphasis: { | ||
itemStyle: { | ||
color: '#9c9c9c' | ||
}, | ||
controlStyle: { | ||
color: '#bfbfbf', | ||
borderColor: '#bfbfbf', | ||
borderWidth: 1 | ||
}, | ||
label: { | ||
color: '#c9c9c9' | ||
} | ||
} | ||
}, | ||
visualMap: { | ||
color: ['#c41621', '#e39588', '#f5ed98'] | ||
}, | ||
dataZoom: { | ||
handleSize: 'undefined%', | ||
textStyle: {} | ||
}, | ||
markPoint: { | ||
label: { | ||
color: '#f2f2f2' | ||
}, | ||
emphasis: { | ||
label: { | ||
color: '#f2f2f2' | ||
} | ||
} | ||
chart.setOption({ series: tempSeries }); | ||
} | ||
}); | ||
}; | ||
const chart = init(node, 'evidence-light', { renderer: 'canvas' }); | ||
applyEchartsOptions(); | ||
applySeriesColors(); | ||
applySeriesOptions(); | ||
option.animation = false; | ||
chart.setOption(option); | ||
let src = chart.getConnectedDataURL({ | ||
@@ -438,4 +82,13 @@ type: 'png', | ||
download(src, 'evidence-chart.png'); | ||
const date = new Date(); | ||
const localISOTime = new Date(date.getTime() - date.getTimezoneOffset() * 60000) | ||
.toISOString() | ||
.slice(0, 19) | ||
.replaceAll(':', '-'); | ||
download( | ||
src, | ||
(option.evidenceChartTitle ?? option.queryID ?? 'evidence-chart') + `_${localISOTime}.png` | ||
); | ||
chart.dispose(); | ||
@@ -442,0 +95,0 @@ |
import { registerTheme, init } from 'echarts'; | ||
import { colours } from './colours'; | ||
import { evidenceThemeLight } from './echartsThemes'; | ||
export default (node, option) => { | ||
registerTheme('evidence-light', { | ||
grid: { | ||
left: '0%', | ||
right: '4%', | ||
bottom: '0%', | ||
top: '15%', | ||
containLabel: true | ||
}, | ||
color: [ | ||
'hsla(207, 65%, 39%, 1)', // Navy | ||
'hsla(195, 49%, 51%, 1)', // Teal | ||
'hsla(207, 69%, 79%, 1)', // Light Blue | ||
'hsla(202, 28%, 65%, 1)', // Grey | ||
'hsla(179, 37%, 65%, 1)', // Light Green | ||
'hsla(40, 30%, 75%, 1)', // Tan | ||
'hsla(38, 89%, 62%, 1)', // Yellow | ||
'hsla(342, 40%, 40%, 1)', // Maroon | ||
'hsla(207, 86%, 70%, 1)', // Blue | ||
'hsla(160, 40%, 46%, 1)', // Green | ||
// Grey Scale | ||
'#71777d', | ||
'#7e848a', | ||
'#8c9196', | ||
'#9a9fa3', | ||
'#a8acb0', | ||
'#b7babd', | ||
'#c5c8ca', | ||
'#d4d6d7', | ||
'#e3e4e5', | ||
'#f3f3f3' | ||
], | ||
backgroundColor: 'rgba(255, 255, 255, 0)', | ||
textStyle: { | ||
fontFamily: 'sans-serif' | ||
}, | ||
title: { | ||
padding: 0, | ||
itemGap: 7, | ||
textStyle: { | ||
fontSize: 14, | ||
color: colours.grey700 | ||
}, | ||
subtextStyle: { | ||
fontSize: 13, | ||
color: colours.grey600, | ||
overflow: 'break' | ||
}, | ||
top: '0%' | ||
}, | ||
line: { | ||
itemStyle: { | ||
borderWidth: 0 | ||
}, | ||
lineStyle: { | ||
width: 2, | ||
join: 'round' | ||
}, | ||
symbolSize: 0, | ||
symbol: 'circle', | ||
smooth: false | ||
}, | ||
radar: { | ||
itemStyle: { | ||
borderWidth: 0 | ||
}, | ||
lineStyle: { | ||
width: 2 | ||
}, | ||
symbolSize: 0, | ||
symbol: 'circle', | ||
smooth: false | ||
}, | ||
bar: { | ||
itemStyle: { | ||
barBorderWidth: 1, | ||
barBorderColor: '#cccccc' | ||
} | ||
}, | ||
pie: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
scatter: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
boxplot: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
parallel: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
sankey: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
funnel: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
gauge: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
} | ||
}, | ||
candlestick: { | ||
itemStyle: { | ||
color: '#eb5454', | ||
color0: '#47b262', | ||
borderColor: '#eb5454', | ||
borderColor0: '#47b262', | ||
borderWidth: 1 | ||
} | ||
}, | ||
graph: { | ||
itemStyle: { | ||
borderWidth: 0, | ||
borderColor: '#cccccc' | ||
}, | ||
lineStyle: { | ||
width: 1, | ||
color: '#aaaaaa' | ||
}, | ||
symbolSize: 0, | ||
symbol: 'circle', | ||
smooth: false, | ||
color: [ | ||
'#923d59', | ||
'#488f96', | ||
'#518eca', | ||
'#b3a9a0', | ||
'#ffc857', | ||
'#495867', | ||
'#bfdbf7', | ||
'#bc4749', | ||
'#eeebd0' | ||
], | ||
label: { | ||
color: '#f2f2f2' | ||
} | ||
}, | ||
map: { | ||
itemStyle: { | ||
areaColor: '#eee', | ||
borderColor: '#444', | ||
borderWidth: 0.5 | ||
}, | ||
label: { | ||
color: '#000' | ||
}, | ||
emphasis: { | ||
itemStyle: { | ||
areaColor: 'rgba(255,215,0,0.8)', | ||
borderColor: '#444', | ||
borderWidth: 1 | ||
}, | ||
label: { | ||
color: 'rgb(100,0,0)' | ||
registerTheme('evidence-light', evidenceThemeLight); | ||
const { config, ratio, echartsOptions, seriesOptions, seriesColors, isMap, extraHeight, width } = | ||
option; | ||
let initOpts = { renderer: 'canvas' }; | ||
if (isMap) { | ||
initOpts.height = width * 0.5 + extraHeight; | ||
if (node && node.parentNode) { | ||
// node.parentNode refers to the chart's container | ||
node.style.height = initOpts.height + 'px'; | ||
node.parentNode.style.height = initOpts.height + 'px'; | ||
} | ||
} | ||
const chart = init(node, 'evidence-light', initOpts); | ||
config.animation = false; // disable animation | ||
chart.setOption(config); | ||
// Check if echartsOptions are provided and apply them | ||
if (echartsOptions) { | ||
chart.setOption(echartsOptions); | ||
} | ||
// Series Color override | ||
const applySeriesColors = () => { | ||
if (seriesColors) { | ||
/** @type {import("echarts").EChartsOption} */ | ||
const prevOption = chart.getOption(); | ||
if (!prevOption) return; | ||
const newOption = { ...prevOption }; | ||
for (const seriesName of Object.keys(seriesColors)) { | ||
const matchingSeriesIndex = prevOption.series.findIndex((s) => s.name === seriesName); | ||
if (matchingSeriesIndex !== -1) { | ||
newOption.series[matchingSeriesIndex] = { | ||
...newOption.series[matchingSeriesIndex], | ||
itemStyle: { | ||
...newOption.series[matchingSeriesIndex].itemStyle, | ||
color: seriesColors[seriesName] | ||
} | ||
}; | ||
} | ||
} | ||
}, | ||
geo: { | ||
itemStyle: { | ||
areaColor: '#eee', | ||
borderColor: '#444', | ||
borderWidth: 0.5 | ||
}, | ||
label: { | ||
color: '#000' | ||
}, | ||
emphasis: { | ||
itemStyle: { | ||
areaColor: 'rgba(255,215,0,0.8)', | ||
borderColor: '#444', | ||
borderWidth: 1 | ||
chart.setOption(newOption); | ||
} | ||
}; | ||
// Check if echartsOptions are provided and apply them | ||
const applyEchartsOptions = () => { | ||
if (echartsOptions) { | ||
chart.setOption({ | ||
...echartsOptions | ||
}); | ||
} | ||
}; | ||
// seriesOptions - loop through series and apply same changes to each | ||
const applySeriesOptions = () => { | ||
let tempSeries = []; | ||
if (seriesOptions) { | ||
const reference_index = config.series.reduce( | ||
(acc, { evidenceSeriesType }, reference_index) => { | ||
if (evidenceSeriesType === 'reference_line' || evidenceSeriesType === 'reference_area') { | ||
acc.push(reference_index); | ||
} | ||
return acc; | ||
}, | ||
label: { | ||
color: 'rgb(100,0,0)' | ||
[] | ||
); | ||
for (let i = 0; i < config.series.length; i++) { | ||
if (reference_index.includes(i)) { | ||
tempSeries.push({}); | ||
} else { | ||
tempSeries.push({ ...seriesOptions }); | ||
} | ||
} | ||
}, | ||
categoryAxis: { | ||
axisLine: { | ||
show: true, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 3, | ||
alignWithLabel: true | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: false, | ||
lineStyle: { | ||
color: [colours.grey200] | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
valueAxis: { | ||
axisLine: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 2 | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: true, | ||
lineStyle: { | ||
color: [colours.grey200], | ||
width: 1 | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
logAxis: { | ||
axisLine: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: false, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 2 | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: true, | ||
lineStyle: { | ||
color: [colours.grey200] | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
timeAxis: { | ||
axisLine: { | ||
show: true, | ||
lineStyle: { | ||
color: colours.grey500 | ||
} | ||
}, | ||
axisTick: { | ||
show: true, | ||
lineStyle: { | ||
color: colours.grey500 | ||
}, | ||
length: 3 | ||
}, | ||
axisLabel: { | ||
show: true, | ||
color: colours.grey500 | ||
}, | ||
splitLine: { | ||
show: false, | ||
lineStyle: { | ||
color: [colours.grey200] | ||
} | ||
}, | ||
splitArea: { | ||
show: false, | ||
areaStyle: { | ||
color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] | ||
} | ||
} | ||
}, | ||
toolbox: { | ||
iconStyle: { | ||
borderColor: '#999999' | ||
}, | ||
emphasis: { | ||
iconStyle: { | ||
borderColor: '#459cde' | ||
} | ||
} | ||
}, | ||
legend: { | ||
textStyle: { | ||
padding: [0, 0, 0, -7], | ||
color: colours.grey500 | ||
}, | ||
// "padding": [15,0,0,0], | ||
icon: 'circle', | ||
pageIcons: { | ||
horizontal: [ | ||
'M 17 3 h 2 c 0.386 0 0.738 0.223 0.904 0.572 s 0.115 0.762 -0.13 1.062 L 11.292 15 l 8.482 10.367 c 0.245 0.299 0.295 0.712 0.13 1.062 S 19.386 27 19 27 h -2 c -0.3 0 -0.584 -0.135 -0.774 -0.367 l -9 -11 c -0.301 -0.369 -0.301 -0.898 0 -1.267 l 9 -11 C 16.416 3.135 16.7 3 17 3 Z', | ||
'M 12 27 h -2 c -0.386 0 -0.738 -0.223 -0.904 -0.572 s -0.115 -0.762 0.13 -1.062 L 17.708 15 L 9.226 4.633 c -0.245 -0.299 -0.295 -0.712 -0.13 -1.062 S 9.614 3 10 3 h 2 c 0.3 0 0.584 0.135 0.774 0.367 l 9 11 c 0.301 0.369 0.301 0.898 0 1.267 l -9 11 C 12.584 26.865 12.3 27 12 27 Z' | ||
] | ||
}, | ||
pageIconColor: colours.grey600, | ||
pageIconSize: 12, | ||
pageTextStyle: { | ||
color: 'grey' | ||
}, | ||
pageButtonItemGap: -2, | ||
animationDurationUpdate: 300 | ||
}, | ||
tooltip: { | ||
axisPointer: { | ||
lineStyle: { | ||
color: '#cccccc', | ||
width: 1 | ||
}, | ||
crossStyle: { | ||
color: '#cccccc', | ||
width: 1 | ||
} | ||
} | ||
}, | ||
timeline: { | ||
lineStyle: { | ||
color: '#e3e3e3', | ||
width: 2 | ||
}, | ||
itemStyle: { | ||
color: '#d6d6d6', | ||
borderWidth: 1 | ||
}, | ||
controlStyle: { | ||
color: '#bfbfbf', | ||
borderColor: '#bfbfbf', | ||
borderWidth: 1 | ||
}, | ||
checkpointStyle: { | ||
color: '#8f8f8f', | ||
borderColor: '#ffffff' | ||
}, | ||
label: { | ||
color: '#c9c9c9' | ||
}, | ||
emphasis: { | ||
itemStyle: { | ||
color: '#9c9c9c' | ||
}, | ||
controlStyle: { | ||
color: '#bfbfbf', | ||
borderColor: '#bfbfbf', | ||
borderWidth: 1 | ||
}, | ||
label: { | ||
color: '#c9c9c9' | ||
} | ||
} | ||
}, | ||
visualMap: { | ||
color: ['#c41621', '#e39588', '#f5ed98'] | ||
}, | ||
dataZoom: { | ||
handleSize: 'undefined%', | ||
textStyle: {} | ||
}, | ||
markPoint: { | ||
label: { | ||
color: '#f2f2f2' | ||
}, | ||
emphasis: { | ||
label: { | ||
color: '#f2f2f2' | ||
} | ||
} | ||
chart.setOption({ series: tempSeries }); | ||
} | ||
}); | ||
}; | ||
const { config, ratio } = option; | ||
applyEchartsOptions(); | ||
applySeriesColors(); | ||
applySeriesOptions(); | ||
const chart = init(node, 'evidence-light', { renderer: 'canvas' }); | ||
config.animation = false; // disable animation | ||
chart.setOption(config); | ||
let src = chart.getConnectedDataURL({ | ||
@@ -447,2 +106,4 @@ type: 'jpeg', | ||
" />`; | ||
option.config.animation = true; // re-enable animation | ||
}; |
@@ -1,13 +0,76 @@ | ||
import { init, registerMap } from 'echarts'; | ||
import { init, registerMap, connect } from 'echarts'; | ||
import usStateMap from './usStateMap.json'; | ||
export default (node, option) => { | ||
registerMap('US', usStateMap); | ||
registerMap('US', usStateMap, { | ||
Alaska: { | ||
left: -128, | ||
top: 25, | ||
width: 12, | ||
height: 7 | ||
}, | ||
AK: { | ||
left: -128, | ||
top: 25, | ||
width: 12, | ||
height: 7 | ||
}, | ||
Hawaii: { | ||
left: -114, | ||
top: 26, | ||
width: 5 | ||
}, | ||
HI: { | ||
left: -114, | ||
top: 26, | ||
width: 5 | ||
} | ||
}); | ||
let hasLink = option.hasLink; | ||
const chart = init(node, 'none', { renderer: 'svg' }); | ||
let extraHeight = option.extraHeight; | ||
const chart = init(node, 'none', { | ||
renderer: option.renderer ?? 'canvas', | ||
height: `${node.clientWidth * 0.5 + extraHeight}` | ||
}); | ||
// If connectGroup supplied, connect chart to other charts matching that connectGroup | ||
if (option.connectGroup) { | ||
chart.group = option.connectGroup; | ||
connect(option.connectGroup); | ||
} | ||
if (node && node.parentNode) { | ||
// node.parentNode refers to the chart's container | ||
node.style.height = chart.getHeight() + 'px'; | ||
node.parentNode.style.height = chart.getHeight() + 'px'; | ||
} | ||
chart.setOption(option.config); | ||
// Check if echartsOptions are provided and apply them | ||
if (option.echartsOptions) { | ||
chart.setOption(option.echartsOptions); | ||
} | ||
let tempSeries = []; | ||
if (option.seriesOptions) { | ||
const reference_index = option.series.reduce((acc, { evidenceSeriesType }, reference_index) => { | ||
if (evidenceSeriesType === 'reference_line' || evidenceSeriesType === 'reference_area') { | ||
acc.push(reference_index); | ||
} | ||
return acc; | ||
}, []); | ||
for (let i = 0; i < option.series.length; i++) { | ||
if (reference_index.includes(i)) { | ||
tempSeries.push({}); | ||
} else { | ||
tempSeries.push({ ...option.seriesOptions }); | ||
} | ||
} | ||
chart.setOption({ series: tempSeries }); | ||
} | ||
let resizeObserver; | ||
@@ -19,4 +82,11 @@ const containerElement = document.querySelector('div.content > article'); | ||
duration: 500 | ||
} | ||
}, | ||
height: `${chart.getWidth() * 0.5 + extraHeight}` | ||
}); | ||
// After resizing the chart, adjust the container's height | ||
if (node && node.parentNode) { | ||
// node.parentNode refers to the chart's container | ||
node.style.height = chart.getHeight() + 'px'; | ||
node.parentNode.style.height = chart.getHeight() + 'px'; | ||
} | ||
}; | ||
@@ -42,2 +112,6 @@ | ||
chart.setOption(option.config, true, true); | ||
// Check if echartsOptions are provided and apply them | ||
if (option.echartsOptions) { | ||
chart.setOption(option.echartsOptions); | ||
} | ||
}, | ||
@@ -44,0 +118,0 @@ destroy() { |
@@ -13,3 +13,7 @@ import ssf from 'ssf'; | ||
export const getCustomFormats = () => { | ||
return getContext(CUSTOM_FORMATTING_SETTINGS_CONTEXT_KEY)?.getCustomFormats() || []; | ||
try { | ||
return getContext(CUSTOM_FORMATTING_SETTINGS_CONTEXT_KEY)?.getCustomFormats() || []; | ||
} catch (error) { | ||
return []; | ||
} | ||
}; | ||
@@ -31,3 +35,3 @@ | ||
let matchingFormat = [...BUILT_IN_FORMATS, ...customFormats].find( | ||
(format) => format.formatTag?.toLowerCase() === potentialFormatTag?.toLowerCase() | ||
(format) => format.formatTag?.toLowerCase() === potentialFormatTag?.toLowerCase?.() | ||
); | ||
@@ -61,3 +65,3 @@ if (matchingFormat) { | ||
let matchingFormat = [...BUILT_IN_FORMATS, ...customFormats].find( | ||
(format) => format.formatTag?.toLowerCase() === potentialFormatTag?.toLowerCase() | ||
(format) => format.formatTag?.toLowerCase() === potentialFormatTag?.toLowerCase?.() | ||
); | ||
@@ -178,2 +182,7 @@ let newFormat = {}; | ||
typedValue = new Date(standardizeDateString(value)); | ||
} else if (value instanceof Date) { | ||
// "2023-09-06T22:40:43.000Z" minus the Z is interpreted | ||
// as local time | ||
// similar in behavior to standardizeDateString | ||
typedValue = new Date(value.toISOString().slice(0, -1)); | ||
} else if ( | ||
@@ -180,0 +189,0 @@ columnFormat.valueType === 'number' && |
@@ -1,45 +0,59 @@ | ||
import { tidy, summarize, min, max, median } from '@tidyjs/tidy'; | ||
import { tidy, summarize, min, max, median, mean, n, nDistinct, sum } from '@tidyjs/tidy'; | ||
/** | ||
* | ||
* @param {*} data | ||
* @param {*} column | ||
* @returns undefined if not all the defined values are numbers | ||
* @param {Record<string, unknown>[]} data | ||
* @param {string} columnName | ||
* @param {boolean} [isNumeric] | ||
* @returns {{ min?: number, max?: number, median?: number, mean?: number, count?: number, countDistinct?: number, sum?: number, maxDecimals: number, unitType: string }} | ||
*/ | ||
export function getColumnUnitSummary(data, columnName) { | ||
let seriesSummary; | ||
let seriesExtents = tidy( | ||
export function getColumnUnitSummary(data, columnName, isNumeric = true) { | ||
const seriesExtents = tidy( | ||
data, | ||
summarize({ min: min(columnName), max: max(columnName), median: median(columnName) }) | ||
isNumeric | ||
? summarize({ | ||
count: n(columnName), | ||
countDistinct: nDistinct(columnName), | ||
min: min(columnName), | ||
max: max(columnName), | ||
median: median(columnName), | ||
mean: mean(columnName), | ||
sum: sum(columnName) | ||
}) | ||
: summarize({ count: n(columnName), countDistinct: nDistinct(columnName) }) | ||
)[0]; | ||
//TODO try to use summerize spec in tidy | ||
let { maxDecimals, unitType } = summarizeUnits(data.map((row) => row[columnName])); | ||
// TODO try to use summarize spec in tidy | ||
const { maxDecimals, unitType } = summarizeUnits(data.map((row) => row[columnName])); | ||
seriesSummary = { | ||
return { | ||
min: seriesExtents.min, | ||
max: seriesExtents.max, | ||
median: seriesExtents.median, | ||
mean: seriesExtents.mean, | ||
count: seriesExtents.count, | ||
countDistinct: seriesExtents.countDistinct, | ||
sum: seriesExtents.sum, | ||
maxDecimals: maxDecimals, | ||
unitType: unitType | ||
}; | ||
return seriesSummary; | ||
} | ||
/** | ||
* | ||
* @param {Record<string, unknown>[]} data | ||
* @param {string} column | ||
* @returns {[number?, number?]} | ||
*/ | ||
export function getColumnExtentsLegacy(data, column) { | ||
var domainData = tidy(data, summarize({ min: min(column), max: max(column) })); | ||
let minValue = domainData[0].min; | ||
let maxValue = domainData[0].max; | ||
return [minValue, maxValue]; | ||
const domainData = tidy(data, summarize({ min: min(column), max: max(column) }))[0]; | ||
return [domainData.min, domainData.max]; | ||
} | ||
/** | ||
* | ||
* @param {number[]} series | ||
* @returns {{ maxDecimals: number, unitType: string }} | ||
*/ | ||
function summarizeUnits(series) { | ||
let undefinedCount = 0; | ||
let nullCount = 0; | ||
let stringCount = 0; | ||
let numberCount = 0; | ||
let dateCount = 0; | ||
let objectCount = 0; | ||
let maxDecimals = 0; | ||
if (series === undefined || series === null || series.length === 0) { | ||
@@ -51,57 +65,16 @@ return { | ||
} else { | ||
for (let i = 0; i < series.length; i++) { | ||
let nextElement = series[i]; | ||
switch (typeof nextElement) { | ||
case 'undefined': | ||
undefinedCount++; | ||
break; | ||
case 'null': //typically this will be object | ||
nullCount++; | ||
break; | ||
case 'number': { | ||
numberCount++; | ||
let thisDecimalPlaces = nextElement.toString().split('.')[1]?.length; | ||
if (thisDecimalPlaces && thisDecimalPlaces > maxDecimals) { | ||
maxDecimals = thisDecimalPlaces; | ||
} | ||
break; | ||
} | ||
case 'string': | ||
stringCount++; | ||
break; | ||
case 'object': | ||
if (nextElement instanceof Date) { | ||
dateCount++; | ||
} else if (nextElement === null) { | ||
nullCount++; | ||
} else { | ||
objectCount++; | ||
} | ||
break; | ||
case 'date': | ||
dateCount++; | ||
break; | ||
case 'function': | ||
default: | ||
break; | ||
let maxDecimals = 0; | ||
for (const element of series) { | ||
const decimal_places = element?.toString().split('.')[1]?.length; | ||
if (decimal_places > maxDecimals) { | ||
maxDecimals = decimal_places; | ||
} | ||
} | ||
let unitType = undefined; | ||
let emptyValueCount = undefinedCount + nullCount; | ||
if (numberCount + emptyValueCount === series.length) { | ||
unitType = 'number'; | ||
} else if (stringCount + emptyValueCount === series.length) { | ||
unitType = 'string'; | ||
} else if (dateCount + emptyValueCount === series.length) { | ||
unitType = 'date'; | ||
} else if (objectCount + emptyValueCount === series.length) { | ||
unitType = 'object'; | ||
} else { | ||
unitType = 'unknown'; | ||
} | ||
return { | ||
maxDecimals: maxDecimals, | ||
unitType: unitType | ||
unitType: 'number' | ||
}; | ||
} | ||
} |
@@ -1,59 +0,63 @@ | ||
import getColumnEvidenceType from './getColumnEvidenceType.js'; | ||
import { getColumnExtentsLegacy, getColumnUnitSummary } from './getColumnExtents.js'; | ||
import { lookupColumnFormat } from './formatting'; | ||
import { getColumnUnitSummary } from './getColumnExtents.js'; | ||
import { lookupColumnFormat } from './formatting.js'; | ||
import formatTitle from './formatTitle.js'; | ||
import inferColumnTypes from './inferColumnTypes.js'; | ||
import { EvidenceType, TypeFidelity } from './inferColumnTypes.js'; | ||
/** | ||
* @typedef {Object} ColumnSummary | ||
* @property {string} title | ||
* @property {string} type | ||
* @property {Object} evidenceColumnType | ||
* @property {ReturnType<typeof lookupColumnFormat>} format | ||
* @property {ReturnType<typeof getColumnUnitSummary>} columnUnitSummary | ||
*/ | ||
/** | ||
* @function | ||
* @template T | ||
* @param {Record<string, unknown>[]} data | ||
* @param {T} returnType | ||
* @returns {T extends 'object' ? Record<string, ColumnSummary> : (ColumnSummary & { id: string })[]} | ||
*/ | ||
export default function getColumnSummary(data, returnType = 'object') { | ||
var colName; | ||
var colType; | ||
var evidenceColumnType; | ||
var colFormat; | ||
let columnUnitSummary; | ||
let columnSummary = []; | ||
/** @type {Record<string, ColumnSummary>} */ | ||
const columnSummary = {}; | ||
var colExtentsLegacy; | ||
const types = inferColumnTypes(data); | ||
if (returnType === 'object') { | ||
for (const [key] of Object.entries(data[0])) { | ||
colName = key; | ||
evidenceColumnType = getColumnEvidenceType(data, colName); | ||
colType = evidenceColumnType.evidenceType; | ||
columnUnitSummary = getColumnUnitSummary(data, colName); | ||
colFormat = lookupColumnFormat(key, evidenceColumnType, columnUnitSummary); | ||
colExtentsLegacy = getColumnExtentsLegacy(data, colName); | ||
for (const colName of Object.keys(data[0])) { | ||
const evidenceColumnType = types.find( | ||
(item) => item.name?.toLowerCase() === colName?.toLowerCase() | ||
) ?? { | ||
name: colName, | ||
evidenceType: EvidenceType.NUMBER, | ||
typeFidelity: TypeFidelity.INFERRED | ||
}; | ||
const type = evidenceColumnType.evidenceType; | ||
let columnUnitSummary = | ||
evidenceColumnType.evidenceType === 'number' | ||
? getColumnUnitSummary(data, colName, true) | ||
: getColumnUnitSummary(data, colName, false); | ||
let thisCol = { | ||
[colName]: { | ||
title: formatTitle(colName, colFormat), | ||
type: colType, | ||
evidenceColumnType: evidenceColumnType, | ||
format: colFormat, | ||
columnUnitSummary: columnUnitSummary, | ||
extentsLegacy: colExtentsLegacy | ||
} | ||
}; | ||
columnSummary = { ...columnSummary, ...thisCol }; | ||
if (evidenceColumnType.evidenceType !== 'number') { | ||
columnUnitSummary.maxDecimals = 0; | ||
columnUnitSummary.unitType = evidenceColumnType.evidenceType; | ||
} | ||
} else { | ||
for (const [key] of Object.entries(data[0])) { | ||
colName = key; | ||
evidenceColumnType = getColumnEvidenceType(data, colName); | ||
colType = evidenceColumnType.evidenceType; | ||
columnUnitSummary = getColumnUnitSummary(data, colName); | ||
colFormat = lookupColumnFormat(key, evidenceColumnType, columnUnitSummary); | ||
colExtentsLegacy = getColumnExtentsLegacy(data, colName); | ||
const format = lookupColumnFormat(colName, evidenceColumnType, columnUnitSummary); | ||
columnSummary.push({ | ||
id: colName, | ||
title: formatTitle(colName, colFormat), | ||
type: colType, | ||
evidenceColumnType: evidenceColumnType, | ||
format: colFormat, | ||
columnUnitSummary: columnUnitSummary, | ||
extentsLegacy: colExtentsLegacy | ||
}); | ||
} | ||
columnSummary[colName] = { | ||
title: formatTitle(colName, format), | ||
type, | ||
evidenceColumnType, | ||
format, | ||
columnUnitSummary | ||
}; | ||
} | ||
if (returnType !== 'object') { | ||
return Object.entries(columnSummary).map(([key, value]) => ({ id: key, ...value })); | ||
} | ||
return columnSummary; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { tidy, complete, mutate } from '@tidyjs/tidy'; | ||
import { tidy, complete } from '@tidyjs/tidy'; | ||
import getDistinctValues from './getDistinctValues'; | ||
@@ -8,3 +8,3 @@ import { findInterval, vectorSeq } from './helpers/getCompletedData.helpers.js'; | ||
* | ||
* @param {Record<string, unknown>[]} data - The data as an array of objects. | ||
* @param {Record<string, unknown>[]} _data - The data as an array of objects. | ||
* @param {string} x - The property used as x-axis. | ||
@@ -17,7 +17,19 @@ * @param {string} y - The property used as y-axis. | ||
*/ | ||
export default function getCompletedData(data, x, y, series, nullsZero = false, fillX = false) { | ||
export default function getCompletedData(_data, x, y, series, nullsZero = false, fillX = false) { | ||
let xIsDate = false; | ||
const data = _data | ||
.map((d) => | ||
Object.assign({}, d, { | ||
[x]: d[x] instanceof Date ? ((xIsDate = true), d[x].toISOString()) : d[x] | ||
}) | ||
) | ||
.filter((d) => d[x] !== undefined && d[x] !== null); | ||
const groups = Array.from(data).reduce((a, v) => { | ||
if (v[x] instanceof Date) { | ||
v[x] = v[x].toISOString(); | ||
xIsDate = true; | ||
} | ||
if (series) { | ||
if (!a[v[series]]) a[v[series]] = []; | ||
a[v[series]].push(v); | ||
if (!a[v[series] ?? 'null']) a[v[series] ?? 'null'] = []; | ||
a[v[series] ?? 'null'].push(v); | ||
} else { | ||
@@ -34,21 +46,17 @@ if (!a.default) a.default = []; | ||
const xIsDate = data[0]?.[x] instanceof Date; | ||
/** @type {Array<number | string>} */ | ||
let xDistinct; | ||
const exampleX = data[0]?.[x]; | ||
const exampleX = | ||
data.find((item) => item && item[x] !== null && item[x] !== undefined)?.[x] ?? null; | ||
// const exampleX = data[0]?.[x]; | ||
switch (typeof exampleX) { | ||
case 'object': | ||
// If x is not a date; this shouldn't be hit, abort! | ||
if (!(exampleX instanceof Date)) { | ||
if (exampleX === null) { | ||
throw new Error( | ||
`Column '${x}' is entirely null. Column must contain at least one non-null value.` | ||
); | ||
} else { | ||
throw new Error('Unexpected object property, expected string, date, or number'); | ||
} | ||
// Map dates to numeric values | ||
xDistinct = getDistinctValues( | ||
data.map((d) => ({ [x]: d[x].getTime() })), | ||
x | ||
); | ||
// We don't fillX here because date numbers are very large, so a small interval would create a _massive_ array | ||
break; | ||
case 'number': | ||
@@ -91,14 +99,7 @@ // Numbers are the most straightforward | ||
} | ||
if (xIsDate) { | ||
// Ensure that x is actually a date | ||
tidyFuncs.push( | ||
mutate({ | ||
[x]: (val) => new Date(val[x]) | ||
}) | ||
); | ||
} | ||
output.push(tidy(value, ...tidyFuncs)); | ||
} | ||
if (xIsDate) return output.flat().map((r) => ({ ...r, [x]: new Date(r[x]) })); | ||
return output.flat(); | ||
} |
@@ -0,1 +1,12 @@ | ||
/** | ||
* Extracts an array of distinct values from a specified column in a dataset. | ||
* | ||
* This function iterates over the dataset, collecting values from the specified | ||
* column into a Set to ensure uniqueness. It then converts the Set into an array | ||
* of distinct values and returns this array. | ||
* | ||
* @param {Object[]} data - The dataset to process, represented as an array of objects. | ||
* @param {string} column - The name of the column from which to extract distinct values. | ||
* @returns {any[]} An array containing distinct values from the specified column of the dataset. | ||
*/ | ||
export default function getDistinctValues(data, column) { | ||
@@ -2,0 +13,0 @@ const set = new Set(data.map((val) => val[column])); |
@@ -11,11 +11,13 @@ import getDistinctValues from './getDistinctValues.js'; | ||
name, | ||
xMismatch, | ||
xMismatch, // this checks for scenarios where xType is string and xDataType is number. When this is the case, we need to inject strings into the x axis, or else it will cause echarts to think there are duplicate x-axis values (e.g., "4" and 4) | ||
columnSummary, | ||
size = null, | ||
tooltipTitle = null | ||
size = undefined, | ||
tooltipTitle = undefined, | ||
y2 = undefined | ||
) { | ||
function generateTempConfig(seriesData, seriesName, baseConfig) { | ||
function generateTempConfig(seriesData, seriesName, yAxisIndex, baseConfig) { | ||
let tempConfig = { | ||
name: seriesName, | ||
data: seriesData | ||
data: seriesData, | ||
yAxisIndex: yAxisIndex | ||
}; | ||
@@ -34,5 +36,49 @@ tempConfig = { ...baseConfig, ...tempConfig }; | ||
let seriesDistinct; | ||
let yAxisIndex; | ||
// y = single, y2 = empty | ||
// y = single, y2 = single | ||
// y = single, y2 = array | ||
// y = array, y2 = empty | ||
// y = array, y2 = single | ||
// y = array, y2 = array | ||
// y = empty, y2 = empty | ||
// y = empty, y2 = single | ||
// y = empty, y2 = array | ||
// colname, yAxisIndex | ||
function combineVariables(variable1, variable2) { | ||
// Returns an array of arrays, where each individual array is [column_name, yAxisIndex], where yAxisIndex is 0 for y and 1 for y2. | ||
// E.g., [ ['sales', 0 ], ['gross_profit', 1]] - sales on primary axis, gross profit on secondary | ||
const array = []; | ||
// Helper function to check if a value is undefined | ||
function isUndefined(value) { | ||
return typeof value === 'undefined'; | ||
} | ||
// Helper function to add non-undefined values to the array with source indicator | ||
function addValuesToArray(value, source) { | ||
if (!isUndefined(value)) { | ||
if (Array.isArray(value)) { | ||
value.forEach((item) => array.push([item, source])); | ||
} else { | ||
array.push([value, source]); | ||
} | ||
} | ||
} | ||
addValuesToArray(variable1, 0); | ||
addValuesToArray(variable2, 1); | ||
return array; | ||
} | ||
let yList = combineVariables(y, y2); | ||
// 1) Series column with single y column | ||
if (series != null && typeof y !== 'object') { | ||
if (series != null && yList.length === 1) { | ||
seriesDistinct = getDistinctValues(data, series); | ||
@@ -45,5 +91,5 @@ | ||
if (swapXY) { | ||
seriesData = filteredData.map((d) => [d[y], xMismatch ? d[x].toString() : d[x]]); | ||
seriesData = filteredData.map((d) => [d[yList[0][0]], xMismatch ? d[x].toString() : d[x]]); | ||
} else { | ||
seriesData = filteredData.map((d) => [xMismatch ? d[x].toString() : d[x], d[y]]); | ||
seriesData = filteredData.map((d) => [xMismatch ? d[x].toString() : d[x], d[yList[0][0]]]); | ||
} | ||
@@ -66,3 +112,6 @@ | ||
tempConfig = generateTempConfig(seriesData, seriesName, baseConfig); | ||
// Set y-axis index (used for multi-y axis charts): | ||
yAxisIndex = yList[0][1]; | ||
tempConfig = generateTempConfig(seriesData, seriesName, yAxisIndex, baseConfig); | ||
seriesConfig.push(tempConfig); | ||
@@ -73,3 +122,3 @@ } | ||
// 2) Series column with multiple y columns | ||
if (series != null && typeof y === 'object' && y.length > 1) { | ||
if (series != null && yList.length > 1) { | ||
seriesDistinct = getDistinctValues(data, series); | ||
@@ -80,7 +129,13 @@ for (i = 0; i < seriesDistinct.length; i++) { | ||
for (j = 0; j < y.length; j++) { | ||
for (j = 0; j < yList.length; j++) { | ||
if (swapXY) { | ||
seriesData = filteredData.map((d) => [d[y[j]], xMismatch ? d[x].toString() : d[x]]); | ||
seriesData = filteredData.map((d) => [ | ||
d[yList[j][0]], | ||
xMismatch ? d[x].toString() : d[x] | ||
]); | ||
} else { | ||
seriesData = filteredData.map((d) => [xMismatch ? d[x].toString() : d[x], d[y[j]]]); | ||
seriesData = filteredData.map((d) => [ | ||
xMismatch ? d[x].toString() : d[x], | ||
d[yList[j][0]] | ||
]); | ||
} | ||
@@ -101,4 +156,8 @@ | ||
// Set series name: | ||
seriesName = (seriesDistinct[i] ?? 'null') + ' - ' + columnSummary[y[j]].title; | ||
tempConfig = generateTempConfig(seriesData, seriesName, baseConfig); | ||
seriesName = (seriesDistinct[i] ?? 'null') + ' - ' + columnSummary[yList[j][0]].title; | ||
// Set y-axis index (used for multi-y axis charts): | ||
yAxisIndex = yList[j][1]; | ||
tempConfig = generateTempConfig(seriesData, seriesName, yAxisIndex, baseConfig); | ||
seriesConfig.push(tempConfig); | ||
@@ -110,8 +169,8 @@ } | ||
// 3) Multiple y columns without series column | ||
if (series == null && typeof y === 'object' && y.length > 1) { | ||
for (i = 0; i < y.length; i++) { | ||
if (series == null && yList.length > 1) { | ||
for (i = 0; i < yList.length; i++) { | ||
if (swapXY) { | ||
seriesData = data.map((d) => [d[y[i]], xMismatch ? d[x].toString() : d[x]]); | ||
seriesData = data.map((d) => [d[yList[i][0]], xMismatch ? d[x].toString() : d[x]]); | ||
} else { | ||
seriesData = data.map((d) => [xMismatch ? d[x].toString() : d[x], d[y[i]]]); | ||
seriesData = data.map((d) => [xMismatch ? d[x].toString() : d[x], d[yList[i][0]]]); | ||
} | ||
@@ -131,4 +190,8 @@ | ||
seriesName = columnSummary[y[i]].title; | ||
tempConfig = generateTempConfig(seriesData, seriesName, baseConfig); | ||
seriesName = columnSummary[yList[i][0]].title; | ||
// Set y-axis index (used for multi-y axis charts): | ||
yAxisIndex = yList[i][1]; | ||
tempConfig = generateTempConfig(seriesData, seriesName, yAxisIndex, baseConfig); | ||
seriesConfig.push(tempConfig); | ||
@@ -139,7 +202,7 @@ } | ||
// 4) Single y column without series column | ||
if (series == null && typeof y !== 'object') { | ||
if (series == null && yList.length === 1) { | ||
if (swapXY) { | ||
seriesData = data.map((d) => [d[y], xMismatch ? d[x].toString() : d[x]]); | ||
seriesData = data.map((d) => [d[yList[0][0]], xMismatch ? d[x].toString() : d[x]]); | ||
} else { | ||
seriesData = data.map((d) => [xMismatch ? d[x].toString() : d[x], d[y]]); | ||
seriesData = data.map((d) => [xMismatch ? d[x].toString() : d[x], d[yList[0][0]]]); | ||
} | ||
@@ -159,5 +222,8 @@ | ||
seriesName = columnSummary[y].title; | ||
seriesName = columnSummary[yList[0][0]].title; | ||
tempConfig = generateTempConfig(seriesData, seriesName, baseConfig); | ||
// Set y-axis index (used for multi-y axis charts): | ||
yAxisIndex = yList[0][1]; | ||
tempConfig = generateTempConfig(seriesData, seriesName, yAxisIndex, baseConfig); | ||
seriesConfig.push(tempConfig); | ||
@@ -164,0 +230,0 @@ } |
export const CUSTOM_FORMATTING_SETTINGS_CONTEXT_KEY = 'customFormattingSettings'; | ||
export const INPUTS_CONTEXT_KEY = '_inputs'; |
// To-do, replace with import from db-commons | ||
var EvidenceType; | ||
export var EvidenceType; | ||
(function (EvidenceType) { | ||
@@ -11,3 +11,3 @@ EvidenceType['BOOLEAN'] = 'boolean'; | ||
var TypeFidelity; | ||
export var TypeFidelity; | ||
(function (TypeFidelity) { | ||
@@ -23,22 +23,2 @@ TypeFidelity['INFERRED'] = 'inferred'; | ||
return EvidenceType.BOOLEAN; | ||
} else if (typeof columnValue === 'string') { | ||
let result = EvidenceType.STRING; | ||
if (columnValue && columnValue.includes('-')) { | ||
let testDateStr = columnValue; | ||
if (!columnValue.includes(':')) { | ||
testDateStr = columnValue + 'T00:00'; | ||
} | ||
try { | ||
let testDate = new Date(testDateStr); | ||
if (testDate.toLocaleString().length > 0) { | ||
let numCheck = Number.parseInt(testDate.toLocaleString().substring(0, 1)); | ||
if (numCheck != null && !isNaN(numCheck)) { | ||
result = EvidenceType.DATE; | ||
} | ||
} | ||
} catch (err) { | ||
//ignore | ||
} | ||
} | ||
return result; | ||
} else if (columnValue instanceof Date) { | ||
@@ -52,2 +32,5 @@ return EvidenceType.DATE; | ||
export default function inferColumnTypes(rows) { | ||
if (rows?._evidenceColumnTypes) { | ||
return rows._evidenceColumnTypes; | ||
} | ||
if (rows && rows.length > 0) { | ||
@@ -72,3 +55,3 @@ let columns = Object.keys(rows[0]); | ||
} | ||
return undefined; | ||
return []; | ||
} |
@@ -5,8 +5,99 @@ import { dev } from '$app/environment'; | ||
// Persist ShowQueries user choice | ||
export const showQueries = writable( | ||
dev && browser && localStorage.getItem('showQueries') != 'false' | ||
); | ||
showQueries.subscribe((value) => browser && localStorage.setItem('showQueries', value)); | ||
export const pageHasQueries = writable(true); | ||
export const routeHash = writable(''); | ||
function createToastsObject() { | ||
const { subscribe, update } = writable([]); | ||
const timeoutMap = new Map(); | ||
const removeToast = (id) => { | ||
update(($toasts) => $toasts.filter((existing) => existing.id !== id)); | ||
}; | ||
return { | ||
subscribe, | ||
/** | ||
* | ||
* @param {Toast} toast | ||
* @param {number} [timeout] | ||
*/ | ||
add: (toast, timeout = 2000) => { | ||
// Totally safe ids | ||
toast.id = toast.id ?? Math.random().toString(); | ||
update(($toasts) => ($toasts.push(toast), $toasts)); | ||
if (timeout) { | ||
const timeoutId = setTimeout(() => { | ||
removeToast(toast.id); | ||
timeoutMap.delete(toast.id); | ||
}, timeout); | ||
timeoutMap.set(toast.id, timeoutId); | ||
} | ||
}, | ||
dismiss: (toastId) => { | ||
removeToast(toastId); | ||
if (timeoutMap.has(toastId)) { | ||
clearTimeout(timeoutMap.get(toastId)); | ||
timeoutMap.delete[toastId]; | ||
} | ||
} | ||
}; | ||
} | ||
/** @typedef {"error" | "warning" | "success" | "info"} ToastStatus */ | ||
/** @typedef {{ id: string; status?: ToastStatus; title: string; message: string; }} Toast */ | ||
/** @type {import('svelte/store').Readable<Toast[]> & { add: (toast: Toast, timeout: number) => void }} */ | ||
export const toasts = createToastsObject(); | ||
/** | ||
* @template T | ||
* @param {import('svelte/store').Readable<T>} store | ||
* @returns {T} | ||
*/ | ||
const getStoreVal = (store) => { | ||
let v; | ||
store.subscribe((x) => (v = x))(); | ||
return v; | ||
}; | ||
/** | ||
* Implementation of a writable store that also saves it's values to localStorage | ||
* @template T | ||
* @param {string} key localStorage key | ||
* @param {T} init | ||
* @returns {Writable<T>} | ||
*/ | ||
export const localStorageStore = (key, init) => { | ||
const store = writable(browser ? JSON.parse(localStorage.getItem(key)) ?? init : init); | ||
const { subscribe, set } = store; | ||
/** @type {(v: T) => void} */ | ||
const flush = (v) => { | ||
if (browser) { | ||
if (typeof v === 'undefined' || v === null) { | ||
localStorage.removeItem(key); | ||
} else { | ||
localStorage.setItem(key, JSON.stringify(v)); | ||
} | ||
} | ||
}; | ||
flush(getStoreVal(store)); | ||
/** @type {Writable<T>} */ | ||
return { | ||
subscribe, | ||
set: (v) => { | ||
set(v); | ||
flush(v); | ||
}, | ||
update: (cb) => { | ||
const updatedStore = cb(getStoreVal(store)); | ||
set(updatedStore); | ||
flush(updatedStore); | ||
} | ||
}; | ||
}; | ||
// Persist ShowQueries user choice | ||
export const showQueries = localStorageStore('showQueries', dev && browser); |
@@ -5,16 +5,2 @@ import { describe, it, expect } from 'vitest'; | ||
describe('getColumnUnitSummary', () => { | ||
it('should return a valid unit type for mixed type array', () => { | ||
const data = [ | ||
{ column1: 'foo', column2: 3.1 }, | ||
{ column1: 'bar', column2: 'value' }, | ||
{ column1: 'far', column2: 4.1 }, | ||
{ column1: 'far', column2: undefined } | ||
]; | ||
const summary = getColumnUnitSummary(data, 'column2'); | ||
let { unitType, maxDecimals, median } = summary; | ||
expect(unitType).toStrictEqual('unknown'); | ||
expect(median).toBeCloseTo(3.6, 1); | ||
expect(maxDecimals).toStrictEqual(1); | ||
}); | ||
it('should return correct values with real valued nubmer array', () => { | ||
@@ -31,2 +17,6 @@ const data = [ | ||
expect(summary).toStrictEqual({ | ||
count: 5, | ||
countDistinct: 5, | ||
mean: 3.048318, | ||
sum: 15.24159, | ||
min: 1, | ||
@@ -49,2 +39,6 @@ max: 5, | ||
expect(summary).toStrictEqual({ | ||
count: 5, | ||
countDistinct: 4, | ||
mean: 3, | ||
sum: 9, | ||
min: 1, | ||
@@ -57,19 +51,36 @@ max: 5, | ||
}); | ||
it('should return correct values with a string data series', () => { | ||
it('should only return count and countDistinct when the data is not numeric, and other values should be undefined', () => { | ||
const data = [ | ||
{ column1: 'foo', column2: 3 }, | ||
{ column1: 'bar', column2: null }, | ||
{ column1: 'dar', column2: undefined }, | ||
{ column1: 'far', column2: 1 }, | ||
{ column1: 'zar', column2: 5 } | ||
{ column1: 'foo', column2: 'bar', bool: true }, | ||
{ column1: 'dar', column2: 'blah', bool: false }, | ||
{ column1: 'blah', column2: 'blah', bool: true }, | ||
{ column1: 'blah', column2: 'blah', bool: false } | ||
]; | ||
const summary = getColumnUnitSummary(data, 'column1'); | ||
const summary = getColumnUnitSummary(data, 'column2', false); | ||
expect(summary).toStrictEqual({ | ||
min: 'bar', | ||
max: 'zar', | ||
count: 4, | ||
countDistinct: 2, | ||
maxDecimals: 0, | ||
max: undefined, | ||
mean: undefined, | ||
median: undefined, | ||
min: undefined, | ||
sum: undefined, | ||
// Probably undesired behavior, fixed downstream | ||
unitType: 'number' | ||
}); | ||
const summaryBool = getColumnUnitSummary(data, 'bool', false); | ||
expect(summaryBool).toStrictEqual({ | ||
count: 4, | ||
countDistinct: 2, | ||
maxDecimals: 0, | ||
unitType: 'string' | ||
max: undefined, | ||
mean: undefined, | ||
median: undefined, | ||
min: undefined, | ||
sum: undefined, | ||
// Probably undesired behavior, fixed downstream | ||
unitType: 'number' | ||
}); | ||
}); | ||
}); |
@@ -65,1 +65,133 @@ export const MissingYCase = { | ||
}; | ||
export const NullFirstRowCase = { | ||
data: [ | ||
// take out SF 2018 and NY 2016 | ||
{ fed_reserve_district: null, established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'SF', established_date: '2015-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'ATL', established_date: '2015-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'DAL', established_date: '2015-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'KC', established_date: '2015-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'CHI', established_date: '2015-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'SF', established_date: '2016-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'ATL', established_date: '2016-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'DAL', established_date: '2016-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'KC', established_date: '2016-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'CHI', established_date: '2016-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'SF', established_date: '2017-01-01', banks: 1 }, | ||
{ fed_reserve_district: 'ATL', established_date: '2017-01-01', banks: 1 }, | ||
{ fed_reserve_district: 'DAL', established_date: '2017-01-01', banks: 3 }, | ||
{ fed_reserve_district: 'KC', established_date: '2017-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'CHI', established_date: '2017-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'NY', established_date: '2017-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'ATL', established_date: '2018-01-01', banks: 3 }, | ||
{ fed_reserve_district: 'NY', established_date: '2018-01-01', banks: 1 }, | ||
{ fed_reserve_district: 'DAL', established_date: '2018-01-01', banks: 1 }, | ||
{ fed_reserve_district: 'CHI', established_date: '2018-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'KC', established_date: '2018-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'ATL', established_date: '2019-01-01', banks: 4 }, | ||
{ fed_reserve_district: 'NY', established_date: '2019-01-01', banks: 4 }, | ||
{ fed_reserve_district: 'CHI', established_date: '2019-01-01', banks: 2 }, | ||
{ fed_reserve_district: 'SF', established_date: '2019-01-01', banks: 1 }, | ||
{ fed_reserve_district: 'DAL', established_date: '2019-01-01', banks: 2 }, | ||
{ fed_reserve_district: 'KC', established_date: '2019-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'NY', established_date: '2020-01-01', banks: 1 }, | ||
{ fed_reserve_district: 'ATL', established_date: '2020-01-01', banks: 4 }, | ||
{ fed_reserve_district: 'SF', established_date: '2020-01-01', banks: 1 }, | ||
{ fed_reserve_district: 'KC', established_date: '2020-01-01', banks: 1 }, | ||
{ fed_reserve_district: 'CHI', established_date: '2020-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'DAL', established_date: '2020-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'SF', established_date: '2021-01-01', banks: 2 }, | ||
{ fed_reserve_district: 'ATL', established_date: '2021-01-01', banks: 3 }, | ||
{ fed_reserve_district: 'CHI', established_date: '2021-01-01', banks: 3 }, | ||
{ fed_reserve_district: 'DAL', established_date: '2021-01-01', banks: 1 }, | ||
{ fed_reserve_district: 'KC', established_date: '2021-01-01', banks: 0 }, | ||
{ fed_reserve_district: 'NY', established_date: '2021-01-01', banks: 0 } | ||
], | ||
keys: { | ||
x: 'established_date', | ||
y: 'banks', | ||
series: 'fed_reserve_district' | ||
}, | ||
series: { | ||
SF: { interval: 0 }, | ||
ATL: { interval: 0 }, | ||
CHI: { interval: 0 }, | ||
DAL: { interval: 0 }, | ||
KC: { interval: 0 }, | ||
NY: { interval: 0 } | ||
}, | ||
xType: 'string' | ||
}; | ||
export const FullNullCase = { | ||
data: [ | ||
// take out SF 2018 and NY 2016 | ||
{ fed_reserve_district: 'NY', established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'SF', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'ATL', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'DAL', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'KC', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'CHI', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'SF', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'ATL', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'DAL', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'KC', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'CHI', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'SF', established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'ATL', established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'DAL', established_date: null, banks: 3 }, | ||
{ fed_reserve_district: 'KC', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'CHI', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'NY', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'ATL', established_date: null, banks: 3 }, | ||
{ fed_reserve_district: 'NY', established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'DAL', established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'CHI', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'KC', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'ATL', established_date: null, banks: 4 }, | ||
{ fed_reserve_district: 'NY', established_date: null, banks: 4 }, | ||
{ fed_reserve_district: 'CHI', established_date: null, banks: 2 }, | ||
{ fed_reserve_district: 'SF', established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'DAL', established_date: null, banks: 2 }, | ||
{ fed_reserve_district: 'KC', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'NY', established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'ATL', established_date: null, banks: 4 }, | ||
{ fed_reserve_district: 'SF', established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'KC', established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'CHI', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'DAL', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'SF', established_date: null, banks: 2 }, | ||
{ fed_reserve_district: 'ATL', established_date: null, banks: 3 }, | ||
{ fed_reserve_district: 'CHI', established_date: null, banks: 3 }, | ||
{ fed_reserve_district: 'DAL', established_date: null, banks: 1 }, | ||
{ fed_reserve_district: 'KC', established_date: null, banks: 0 }, | ||
{ fed_reserve_district: 'NY', established_date: null, banks: 0 } | ||
], | ||
keys: { | ||
x: 'established_date', | ||
y: 'banks', | ||
series: 'fed_reserve_district' | ||
}, | ||
series: { | ||
SF: { interval: 0 }, | ||
ATL: { interval: 0 }, | ||
CHI: { interval: 0 }, | ||
DAL: { interval: 0 }, | ||
KC: { interval: 0 }, | ||
NY: { interval: 0 } | ||
}, | ||
xType: 'string' | ||
}; |
import { describe, it, expect } from 'vitest'; | ||
import getCompletedData from '../getCompletedData'; | ||
import { genSeries } from './getCompletedData.fixture'; | ||
import { MissingYCase } from './getCompletedData.fixture.manual'; | ||
import { MissingYCase, NullFirstRowCase, FullNullCase } from './getCompletedData.fixture.manual'; | ||
@@ -19,4 +19,2 @@ /** | ||
let series = []; | ||
const simple = false; | ||
@@ -28,3 +26,3 @@ | ||
seriesAlwaysExists: simple ? [true, false] : [true, false], | ||
xType: simple ? ['category'] : ['date', 'number', 'category'], | ||
xType: simple ? ['date'] : ['date', 'number', 'category'], | ||
keys: simple | ||
@@ -34,48 +32,5 @@ ? [undefined, { x: 'someX', y: 'someY', series: 'someSeries' }] | ||
}; | ||
/* | ||
This is responsible for generating a variety of scenarios that the function may encounter | ||
One factor is if the x-axis has values in all positions | ||
One factor is if the y-axis will always have a value, or if it can be null | ||
One factor is if the series field is always set, or if it is sometimes set to null | ||
*/ | ||
for (const xHasGaps of fixturePermutations.xHasGaps) { | ||
for (const yHasNulls of fixturePermutations.yHasNulls) | ||
for (const seriesAlwaysExists of fixturePermutations.seriesAlwaysExists) | ||
for (const xType of fixturePermutations.xType) | ||
for (const keys of fixturePermutations.keys) | ||
series.push({ | ||
description: `(automatic) xType = "${xType}", xHasGaps = ${xHasGaps}, yHasNulls = ${yHasNulls}, seriesAlwaysExists = ${seriesAlwaysExists}, keys = "${JSON.stringify( | ||
keys | ||
)}"`, | ||
xHasGaps, | ||
yHasNulls, | ||
seriesAlwaysExists, | ||
xType, | ||
keys, | ||
manual: false, | ||
data: genSeries({ | ||
xHasGaps, | ||
yHasNulls, | ||
seriesAlwaysExists, | ||
xType, | ||
keys, | ||
minSeriesLen: 2, | ||
maxSeriesLen: 2, | ||
maxSeriesCount: 2, | ||
maxInterval: 1, | ||
maxOffset: 0 | ||
}) | ||
}); | ||
} | ||
series.push({ | ||
description: '(manual) Manual gap values injected', | ||
data: MissingYCase | ||
}); | ||
// Garlic Naan | Goa Chicken | Chicken Curry | ||
/** | ||
* @typedef {Object} SeriesFixture | ||
* @property {import("./getCompletedData.fixture.js").GenSeriesResult} data | ||
* @property {import("./getCompletedData.fixture.js").GenSeriesResult["data"]} data | ||
* @property { 'date' | 'number' } xType | ||
@@ -86,123 +41,332 @@ * @property {boolean} seriesAlwaysExists | ||
* @property {boolean} manual | ||
* @property {import("./getCompletedData.fixture.js").GenSeriesResult["keys"]} keys | ||
* @property {import("./getCompletedData.fixture.js").GenSeriesResult["series"]} series | ||
*/ | ||
describe('getCompletedData', () => { | ||
describe.each(series)( | ||
'$description', | ||
/** | ||
* @param {SeriesFixture} opts | ||
*/ | ||
(opts) => { | ||
const { data, keys } = opts.data; | ||
it('returns no duplicate rows', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, false, false); | ||
for (const row of result) { | ||
expect( | ||
result.filter( | ||
(row2) => | ||
row[keys.x] === row2[keys.x] && | ||
row[keys.y] === row2[keys.y] && | ||
row[keys.series] === row2[keys.series] | ||
).length | ||
).toBe(1); | ||
} | ||
}); | ||
it('replaces nulls with zero if nullsZero is set', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, true, true); | ||
/** | ||
* @param {SeriesFixture} opts | ||
*/ | ||
const testSuite = (opts) => { | ||
const { data, keys, stringify } = opts; | ||
for (const row of data.filter((r) => r[keys.y] === null)) { | ||
expect( | ||
result.find( | ||
(r) => | ||
r[keys.series] === row[keys.series] && | ||
r[keys.x].toString() === row[keys.x].toString() | ||
)?.[keys.y] | ||
).toBeCloseTo(0); | ||
} | ||
}); | ||
it('returns no duplicate rows', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, false, false); | ||
for (const row of result) { | ||
expect( | ||
result.filter( | ||
(row2) => | ||
row[keys.x] === row2[keys.x] && | ||
row[keys.y] === row2[keys.y] && | ||
row[keys.series] === row2[keys.series] | ||
).length | ||
).toBe(1); | ||
} | ||
}); | ||
it('does not fill x-axis values if fillX is not set', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, false, false); | ||
it('replaces nulls with zero if nullsZero is set', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, true, true); | ||
// Expect specific behavior here based on your function's logic | ||
expect( | ||
result.every((r) => | ||
data.some((d) => r[keys.x] === d[keys.x] && r[keys.series] === d[keys.series]) | ||
) | ||
); | ||
}); | ||
for (const row of data.filter((r) => r[keys.y] === null)) { | ||
const targetX = row[keys.x]; | ||
it('returns identical columns to the original data', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, false, false); | ||
const target = result.find( | ||
(r) => r[keys.series] === row[keys.series] && r[keys.x].toString() === targetX.toString() | ||
); | ||
const r = Object.keys(result[0]).sort(stringSortFunc); | ||
const d = Object.keys(data[0]).sort(stringSortFunc); | ||
expect(r).toEqual(d); | ||
}); | ||
expect(target).toBeDefined(); | ||
expect(target[keys.y]).toBeCloseTo(0); | ||
} | ||
}); | ||
// This condition is only applicable to non-date series | ||
if (opts.xType !== 'date') | ||
it('contains series each with identical lengths', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, false, true); | ||
/** @type {any[][]} */ | ||
let groupedSeries = []; | ||
for (const seriesName of Object.keys(opts.data.series)) { | ||
const seriesItems = result.filter((d) => d[keys.series] === seriesName); | ||
groupedSeries.push(seriesItems); | ||
it.each([ | ||
{ fillX: true, nullsZero: true }, | ||
{ fillX: true, nullsZero: false }, | ||
{ fillX: false, nullsZero: false }, | ||
{ fillX: false, nullsZero: true } | ||
])( | ||
`Returns the correct x value type (xType = ${opts.xType}, fillX = $fillX, nullsZero = $nullsZero)`, | ||
({ fillX, nullsZero }) => { | ||
// Verify precondition | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, fillX, nullsZero); | ||
for (const row of result) { | ||
if (stringify) expect(typeof row[keys.x]).toEqual('string'); | ||
else | ||
switch (opts.xType) { | ||
case 'number': | ||
expect(typeof row[keys.x]).toEqual('number'); | ||
break; | ||
case 'date': | ||
expect(row[keys.x]).toBeInstanceOf(Date); | ||
break; | ||
default: | ||
expect(typeof row[keys.x]).toEqual('string'); | ||
break; | ||
} | ||
} | ||
} | ||
); | ||
expect(groupedSeries[0].length, 'Series must have more than one row').toBeGreaterThan(0); | ||
it('does not fill x-axis values if fillX is not set', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, false, false); | ||
for (const s of groupedSeries) { | ||
expect(s.length, 'Series lengths must all be equal').toEqual(groupedSeries[0].length); | ||
} | ||
}); | ||
// Expect specific behavior here based on your function's logic | ||
expect( | ||
result.every((r) => | ||
data.some((d) => r[keys.x] === d[keys.x] && r[keys.series] === d[keys.series]) | ||
) | ||
); | ||
}); | ||
it('returns the original data if series is not defined', () => { | ||
const { x, y } = keys; | ||
const result = getCompletedData(data, x, y, undefined, false, false); | ||
it('returns identical columns to the original data', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, false, false); | ||
expect(data).toEqual(expect.arrayContaining(result)); | ||
}); | ||
const r = Object.keys(result[0]).sort(stringSortFunc); | ||
const d = Object.keys(data[0]).sort(stringSortFunc); | ||
it('fills missing x-axis values with null if fillX is set and not nullsZero', () => { | ||
const { x, y, series } = keys; | ||
expect(r).toEqual(d); | ||
}); | ||
const result = getCompletedData(data, x, y, series, false, true); | ||
// Expect specific behavior here based on your function's logic | ||
for (const seriesName in series) { | ||
for (const row of result.filter((r) => r[series] === seriesName)) { | ||
const inputRow = data.find((d) => d[series] === row[series] && d[x] === row[x]); | ||
if (inputRow) { | ||
// This row already existed | ||
expect(row[y]).toEqual(inputRow[y]); | ||
} else { | ||
// This row was inserted | ||
expect(row[y]).toBe(null); | ||
} | ||
} | ||
// This condition is only applicable to non-date series | ||
// if (opts.xType !== 'date') | ||
it('contains series each with identical lengths', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, false, true); | ||
/** @type {any[][]} */ | ||
let groupedSeries = []; | ||
for (const seriesName of Array.from(new Set(result.map((r) => r[keys.series])))) { | ||
const seriesItems = result.filter((d) => d[keys.series] === seriesName); | ||
groupedSeries.push(seriesItems); | ||
} | ||
expect(groupedSeries.length, 'Must have at least one grouped series').toBeGreaterThan(0); | ||
expect(groupedSeries[0].length, 'Series must have more than one row').toBeGreaterThan(0); | ||
for (const s of groupedSeries) { | ||
expect(s.length, 'Series lengths must all be equal').toEqual(groupedSeries[0].length); | ||
} | ||
}); | ||
it('contains series each with identical x values', () => { | ||
const result = getCompletedData(data, keys.x, keys.y, keys.series, true, true); | ||
/** @type {any[][]} */ | ||
let groupedSeries = []; | ||
for (const seriesName of Array.from(new Set(result.map((r) => r[keys.series])))) { | ||
const seriesItems = result.filter((d) => d[keys.series] === seriesName); | ||
groupedSeries.push(seriesItems); | ||
} | ||
for (const series of groupedSeries) | ||
series.sort((a, b) => a[keys.x].toString().localeCompare(b[keys.x])); // sort by x value | ||
for (let seriesIdx = 1; seriesIdx < groupedSeries.length; seriesIdx++) { | ||
const series = groupedSeries[seriesIdx]; | ||
for (let i = 0; i < groupedSeries[0].length; i++) { | ||
expect(series[i][keys.x]).toEqual(groupedSeries[0][i][keys.x]); | ||
} | ||
} | ||
}); | ||
it('returns the original data if series is not defined', () => { | ||
const { x, y } = keys; | ||
const result = getCompletedData(data, x, y, undefined, false, false); | ||
expect(Array.from(data)).toEqual(expect.arrayContaining(result)); | ||
}); | ||
it('fills missing x-axis values with null if fillX is set and not nullsZero', () => { | ||
const { x, y, series } = keys; | ||
const result = getCompletedData(data, x, y, series, false, true); | ||
// Expect specific behavior here based on your function's logic | ||
for (const seriesName in series) { | ||
for (const row of result.filter((r) => r[series] === seriesName)) { | ||
const inputRow = data.find((d) => d[series] === row[series] && d[x] === row[x]); | ||
if (inputRow) { | ||
expect(row[y]).toEqual(inputRow[y]); | ||
} else { | ||
// This row was inserted | ||
expect(row[y]).toBe(null); | ||
} | ||
}); | ||
} | ||
} | ||
}); | ||
it('fills missing x-axis values with zero if fillX and nullsZero are set', () => { | ||
const { x, y, series } = keys; | ||
it('fills missing x-axis values with zero if fillX and nullsZero are set', () => { | ||
const { x, y, series } = keys; | ||
const result = getCompletedData(data, x, y, series, true, true); | ||
// Expect specific behavior here based on your function's logic | ||
for (const seriesName in series) { | ||
for (const row of result.filter((r) => r[series] === seriesName)) { | ||
const inputRow = data.find((d) => d[series] === row[series] && d[x] === row[x]); | ||
if (inputRow && inputRow[y] !== null) { | ||
// This row already existed | ||
expect(row[y]).toEqual(inputRow[y]); | ||
} else { | ||
// This row was inserted | ||
expect(row[y]).toBe(0); | ||
} | ||
} | ||
const result = getCompletedData(data, x, y, series, true, true); | ||
// Expect specific behavior here based on your function's logic | ||
for (const seriesName in series) { | ||
for (const row of result.filter((r) => r[series] === seriesName)) { | ||
const inputRow = data.find((d) => d[series] === row[series] && d[x] === row[x]); | ||
if (inputRow && inputRow[y] !== null) { | ||
// This row already existed | ||
expect(row[y]).toEqual(inputRow[y]); | ||
} else { | ||
// This row was inserted | ||
expect(row[y]).toBe(0); | ||
} | ||
}); | ||
} | ||
} | ||
); | ||
}); | ||
}; | ||
describe.each(fixturePermutations.xHasGaps.map((v) => ({ xHasGaps: v })))( | ||
'xHasGaps: $xHasGaps', | ||
({ xHasGaps }) => { | ||
describe.each(fixturePermutations.yHasNulls.map((v) => ({ yHasNulls: v })))( | ||
'yHasNulls: $yHasNulls', | ||
({ yHasNulls }) => { | ||
describe.each( | ||
fixturePermutations.seriesAlwaysExists.map((v) => ({ seriesAlwaysExists: v })) | ||
)('seriesAlwaysExists: $seriesAlwaysExists', ({ seriesAlwaysExists }) => { | ||
describe.each(fixturePermutations.keys.map((v) => ({ keys: v })))( | ||
'{ x: $keys.x, y: $keys.y, series: $keys.series}', | ||
({ keys }) => { | ||
describe.each([{ stringify: true }, { stringify: false }])( | ||
'stringify: $stringify', | ||
({ stringify }) => { | ||
describe.each(fixturePermutations.xType.map((v) => ({ xType: v })))( | ||
'xType: $xType', | ||
({ xType }) => { | ||
let mockSeries = genSeries({ | ||
xHasGaps, | ||
yHasNulls, | ||
seriesAlwaysExists, | ||
xType, | ||
keys, | ||
minSeriesLen: 2, | ||
maxSeriesLen: 2, | ||
maxSeriesCount: 2, | ||
maxInterval: 1, | ||
maxOffset: 0 | ||
}); | ||
let values = mockSeries.data.map((d) => ({ | ||
...d, | ||
[mockSeries.keys.x]: stringify | ||
? d[mockSeries.keys.x].toLocaleString() | ||
: d[mockSeries.keys.x] | ||
})); | ||
testSuite({ | ||
xHasGaps, | ||
yHasNulls, | ||
seriesAlwaysExists, | ||
xType, | ||
stringify, | ||
keys: mockSeries.keys, | ||
series: mockSeries.series, | ||
data: values, | ||
description: '' | ||
}); | ||
} | ||
); | ||
} | ||
); | ||
} | ||
); | ||
}); | ||
} | ||
); | ||
} | ||
); | ||
// /* | ||
// This is responsible for generating a variety of scenarios that the function may encounter | ||
// One factor is if the x-axis has values in all positions | ||
// One factor is if the y-axis will always have a value, or if it can be null | ||
// One factor is if the series field is always set, or if it is sometimes set to null | ||
// */ | ||
// for (const xHasGaps of fixturePermutations.xHasGaps) { | ||
// for (const yHasNulls of fixturePermutations.yHasNulls) | ||
// for (const seriesAlwaysExists of fixturePermutations.seriesAlwaysExists) | ||
// for (const xType of fixturePermutations.xType) | ||
// for (const keys of fixturePermutations.keys) { | ||
// for (const stringify of [true, false]) { | ||
// const mockSeries = genSeries({ | ||
// xHasGaps, | ||
// yHasNulls, | ||
// seriesAlwaysExists, | ||
// xType, | ||
// keys, | ||
// minSeriesLen: 2, | ||
// maxSeriesLen: 2, | ||
// maxSeriesCount: 2, | ||
// maxInterval: 1, | ||
// maxOffset: 0 | ||
// }); | ||
// const values = mockSeries.data.map((d) => ({ | ||
// ...d, | ||
// [mockSeries.keys.x]: stringify | ||
// ? d[mockSeries.keys.x].toString() | ||
// : d[mockSeries.keys.x] | ||
// })); | ||
// series.push({ | ||
// description: `(Array) xType = "${xType}", xHasGaps = ${xHasGaps}, yHasNulls = ${yHasNulls}, seriesAlwaysExists = ${seriesAlwaysExists}, keys = "${JSON.stringify( | ||
// keys | ||
// )}"`, | ||
// xHasGaps, | ||
// yHasNulls, | ||
// seriesAlwaysExists, | ||
// xType, | ||
// keys: mockSeries.keys, | ||
// series: mockSeries.series, | ||
// manual: false, | ||
// data: values, | ||
// stringify | ||
// }); | ||
// const queryStore = new QueryStore('', undefined, 'test', { | ||
// disableCache: true, | ||
// initialData: values | ||
// }); | ||
// series.push({ | ||
// description: `(QueryStore) xType = "${xType}", xHasGaps = ${xHasGaps}, yHasNulls = ${yHasNulls}, seriesAlwaysExists = ${seriesAlwaysExists}, keys = "${JSON.stringify( | ||
// keys | ||
// )}"`, | ||
// xHasGaps, | ||
// yHasNulls, | ||
// seriesAlwaysExists, | ||
// xType, | ||
// keys: mockSeries.keys, | ||
// series: mockSeries.series, | ||
// manual: false, | ||
// data: queryStore.value(), | ||
// stringify | ||
// }); | ||
// } | ||
// } | ||
// } | ||
testSuite({ | ||
description: '(Manual) Manual gap values injected', | ||
...MissingYCase | ||
}); | ||
// series.sort((a, b) => a.description.localeCompare(b.description)); | ||
// describe('getCompletedData', () => { | ||
// describe.each(series)( | ||
// '$description', | ||
// /** | ||
// * @param {SeriesFixture} opts | ||
// */ | ||
// ); | ||
// }); | ||
describe('(Manual) First row has null X', () => { | ||
testSuite({ | ||
...NullFirstRowCase | ||
}); | ||
}); | ||
describe('(Manual) All rows have null X', () => { | ||
const { data, keys } = FullNullCase; | ||
it('should throw', () => { | ||
expect(() => getCompletedData(data, keys.x, keys.y, keys.series, false, false)).toThrowError( | ||
`Column '${keys.x}' is entirely null` | ||
); | ||
}); | ||
}); |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
289320
2
47
9270
11
+ Added@steeze-ui/svelte-icon@1.5.0
+ Added@uwdata/mosaic-sql@^0.3.2
+ Added@75lb/deep-merge@1.1.2(transitive)
+ Added@ampproject/remapping@2.3.0(transitive)
+ Added@asamuzakjp/dom-selector@2.0.2(transitive)
+ Added@babel/helper-string-parser@7.25.9(transitive)
+ Added@babel/helper-validator-identifier@7.25.9(transitive)
+ Added@babel/parser@7.26.3(transitive)
+ Added@babel/types@7.26.3(transitive)
+ Added@bcoe/v8-coverage@0.2.3(transitive)
+ Added@brianmd/citty@0.0.1(transitive)
+ Added@clack/core@0.3.5(transitive)
+ Added@clack/prompts@0.7.0(transitive)
+ Added@duckdb/duckdb-wasm@1.28.0(transitive)
+ Added@esbuild/aix-ppc64@0.21.50.24.2(transitive)
+ Added@esbuild/android-arm@0.21.50.24.2(transitive)
+ Added@esbuild/android-arm64@0.21.50.24.2(transitive)
+ Added@esbuild/android-x64@0.21.50.24.2(transitive)
+ Added@esbuild/darwin-arm64@0.21.50.24.2(transitive)
+ Added@esbuild/darwin-x64@0.21.50.24.2(transitive)
+ Added@esbuild/freebsd-arm64@0.21.50.24.2(transitive)
+ Added@esbuild/freebsd-x64@0.21.50.24.2(transitive)
+ Added@esbuild/linux-arm@0.21.50.24.2(transitive)
+ Added@esbuild/linux-arm64@0.21.50.24.2(transitive)
+ Added@esbuild/linux-ia32@0.21.50.24.2(transitive)
+ Added@esbuild/linux-loong64@0.21.50.24.2(transitive)
+ Added@esbuild/linux-mips64el@0.21.50.24.2(transitive)
+ Added@esbuild/linux-ppc64@0.21.50.24.2(transitive)
+ Added@esbuild/linux-riscv64@0.21.50.24.2(transitive)
+ Added@esbuild/linux-s390x@0.21.50.24.2(transitive)
+ Added@esbuild/linux-x64@0.21.50.24.2(transitive)
+ Added@esbuild/netbsd-arm64@0.24.2(transitive)
+ Added@esbuild/netbsd-x64@0.21.50.24.2(transitive)
+ Added@esbuild/openbsd-arm64@0.24.2(transitive)
+ Added@esbuild/openbsd-x64@0.21.50.24.2(transitive)
+ Added@esbuild/sunos-x64@0.21.50.24.2(transitive)
+ Added@esbuild/win32-arm64@0.21.50.24.2(transitive)
+ Added@esbuild/win32-ia32@0.21.50.24.2(transitive)
+ Added@esbuild/win32-x64@0.21.50.24.2(transitive)
+ Added@eslint-community/eslint-utils@4.4.1(transitive)
+ Added@eslint-community/regexpp@4.12.1(transitive)
+ Added@eslint/eslintrc@2.1.4(transitive)
+ Added@eslint/js@8.57.1(transitive)
+ Added@evidence-dev/sdk@0.0.0-6ba0891f(transitive)
+ Added@evidence-dev/universal-sql@0.0.0-6ba0891f(transitive)
+ Added@humanwhocodes/config-array@0.13.0(transitive)
+ Added@humanwhocodes/module-importer@1.0.1(transitive)
+ Added@humanwhocodes/object-schema@2.0.3(transitive)
+ Added@istanbuljs/schema@0.1.3(transitive)
+ Added@jest/schemas@29.6.3(transitive)
+ Added@jridgewell/gen-mapping@0.3.8(transitive)
+ Added@jridgewell/resolve-uri@3.1.2(transitive)
+ Added@jridgewell/set-array@1.2.1(transitive)
+ Added@jridgewell/sourcemap-codec@1.5.0(transitive)
+ Added@jridgewell/trace-mapping@0.3.25(transitive)
+ Added@nodelib/fs.scandir@2.1.5(transitive)
+ Added@nodelib/fs.stat@2.0.5(transitive)
+ Added@nodelib/fs.walk@1.2.8(transitive)
+ Added@polka/url@1.0.0-next.28(transitive)
+ Added@rollup/pluginutils@4.2.1(transitive)
+ Added@rollup/rollup-android-arm-eabi@4.29.1(transitive)
+ Added@rollup/rollup-android-arm64@4.29.1(transitive)
+ Added@rollup/rollup-darwin-arm64@4.29.1(transitive)
+ Added@rollup/rollup-darwin-x64@4.29.1(transitive)
+ Added@rollup/rollup-freebsd-arm64@4.29.1(transitive)
+ Added@rollup/rollup-freebsd-x64@4.29.1(transitive)
+ Added@rollup/rollup-linux-arm-gnueabihf@4.29.1(transitive)
+ Added@rollup/rollup-linux-arm-musleabihf@4.29.1(transitive)
+ Added@rollup/rollup-linux-arm64-gnu@4.29.1(transitive)
+ Added@rollup/rollup-linux-arm64-musl@4.29.1(transitive)
+ Added@rollup/rollup-linux-loongarch64-gnu@4.29.1(transitive)
+ Added@rollup/rollup-linux-powerpc64le-gnu@4.29.1(transitive)
+ Added@rollup/rollup-linux-riscv64-gnu@4.29.1(transitive)
+ Added@rollup/rollup-linux-s390x-gnu@4.29.1(transitive)
+ Added@rollup/rollup-linux-x64-gnu@4.29.1(transitive)
+ Added@rollup/rollup-linux-x64-musl@4.29.1(transitive)
+ Added@rollup/rollup-win32-arm64-msvc@4.29.1(transitive)
+ Added@rollup/rollup-win32-ia32-msvc@4.29.1(transitive)
+ Added@rollup/rollup-win32-x64-msvc@4.29.1(transitive)
+ Added@sinclair/typebox@0.27.8(transitive)
+ Added@steeze-ui/simple-icons@1.10.1(transitive)
+ Added@steeze-ui/svelte-icon@1.5.0(transitive)
+ Added@steeze-ui/tabler-icons@2.1.1(transitive)
+ Added@sveltejs/kit@2.15.1(transitive)
+ Added@sveltejs/vite-plugin-svelte@5.0.3(transitive)
+ Added@sveltejs/vite-plugin-svelte-inspector@4.0.1(transitive)
+ Added@tidyjs/tidy@2.5.2(transitive)
+ Added@types/body-parser@1.19.5(transitive)
+ Added@types/command-line-args@5.2.0(transitive)
+ Added@types/command-line-usage@5.0.2(transitive)
+ Added@types/connect@3.4.38(transitive)
+ Added@types/cookie@0.6.0(transitive)
+ Added@types/estree@1.0.6(transitive)
+ Added@types/express@4.17.21(transitive)
+ Added@types/express-serve-static-core@4.19.6(transitive)
+ Added@types/http-errors@2.0.4(transitive)
+ Added@types/jsdom@21.1.7(transitive)
+ Added@types/lodash@4.17.14(transitive)
+ Added@types/lodash.chunk@4.2.9(transitive)
+ Added@types/lodash.merge@4.6.9(transitive)
+ Added@types/mime@1.3.5(transitive)
+ Added@types/node@20.17.1120.3.0(transitive)
+ Added@types/pad-left@2.1.1(transitive)
+ Added@types/qs@6.9.17(transitive)
+ Added@types/range-parser@1.2.7(transitive)
+ Added@types/send@0.17.4(transitive)
+ Added@types/serve-static@1.15.7(transitive)
+ Added@types/tough-cookie@4.0.5(transitive)
+ Added@types/unist@2.0.11(transitive)
+ Added@typescript-eslint/types@6.21.0(transitive)
+ Added@typescript-eslint/typescript-estree@6.21.0(transitive)
+ Added@typescript-eslint/visitor-keys@6.21.0(transitive)
+ Added@ungap/structured-clone@1.2.1(transitive)
+ Added@uwdata/mosaic-sql@0.3.50.4.0(transitive)
+ Added@vitest/coverage-v8@1.6.0(transitive)
+ Added@vitest/expect@1.6.0(transitive)
+ Added@vitest/runner@1.6.0(transitive)
+ Added@vitest/snapshot@1.6.0(transitive)
+ Added@vitest/spy@1.6.0(transitive)
+ Added@vitest/utils@1.6.0(transitive)
+ Addedaccepts@1.3.8(transitive)
+ Addedacorn@8.14.0(transitive)
+ Addedacorn-jsx@5.3.2(transitive)
+ Addedacorn-typescript@1.4.13(transitive)
+ Addedacorn-walk@8.3.4(transitive)
+ Addedagent-base@7.1.3(transitive)
+ Addedajv@6.12.6(transitive)
+ Addedansi-regex@5.0.16.1.0(transitive)
+ Addedansi-styles@4.3.05.2.0(transitive)
+ Addedanymatch@3.1.3(transitive)
+ Addedapache-arrow@13.0.0(transitive)
+ Addedargparse@2.0.1(transitive)
+ Addedaria-query@5.3.2(transitive)
+ Addedarray-back@3.1.06.2.2(transitive)
+ Addedarray-flatten@1.1.1(transitive)
+ Addedarray-union@2.1.0(transitive)
+ Addedassertion-error@1.1.0(transitive)
+ Addedast-types@0.16.1(transitive)
+ Addedasynckit@0.4.0(transitive)
+ Addedaxobject-query@4.1.0(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbidi-js@1.0.3(transitive)
+ Addedbinary-extensions@2.3.0(transitive)
+ Addedbody-parser@1.20.3(transitive)
+ Addedbrace-expansion@1.1.112.0.1(transitive)
+ Addedbraces@3.0.3(transitive)
+ Addedbytes@3.1.2(transitive)
+ Addedcac@6.7.14(transitive)
+ Addedcall-bind-apply-helpers@1.0.1(transitive)
+ Addedcall-bound@1.0.3(transitive)
+ Addedcallsites@3.1.0(transitive)
+ Addedchai@4.5.0(transitive)
+ Addedchalk@4.1.25.4.1(transitive)
+ Addedchalk-template@0.4.0(transitive)
+ Addedcheck-error@1.0.3(transitive)
+ Addedchokidar@3.6.0(transitive)
+ Addedcli-cursor@5.0.0(transitive)
+ Addedcli-progress@3.12.0(transitive)
+ Addedcli-spinners@2.9.2(transitive)
+ Addedclsx@2.1.1(transitive)
+ Addedcode-red@1.0.4(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcolor-name@1.1.4(transitive)
+ Addedcombined-stream@1.0.8(transitive)
+ Addedcommand-line-args@5.2.1(transitive)
+ Addedcommand-line-usage@7.0.1(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedconfbox@0.1.8(transitive)
+ Addedconsola@3.3.3(transitive)
+ Addedcontent-disposition@0.5.4(transitive)
+ Addedcontent-type@1.0.5(transitive)
+ Addedcookie@0.6.00.7.1(transitive)
+ Addedcookie-signature@1.0.6(transitive)
+ Addedcross-spawn@7.0.6(transitive)
+ Addedcss-tree@2.3.1(transitive)
+ Addedcssesc@3.0.0(transitive)
+ Addedcssstyle@4.1.0(transitive)
+ Addeddata-urls@5.0.0(transitive)
+ Addeddebug@2.6.94.4.0(transitive)
+ Addeddecimal.js@10.4.3(transitive)
+ Addeddeep-eql@4.1.4(transitive)
+ Addeddeep-is@0.1.4(transitive)
+ Addeddeepmerge@4.3.1(transitive)
+ Addeddelayed-stream@1.0.0(transitive)
+ Addeddepd@2.0.0(transitive)
+ Addeddestroy@1.2.0(transitive)
+ Addeddevalue@5.1.1(transitive)
+ Addeddiff-sequences@29.6.3(transitive)
+ Addeddir-glob@3.0.1(transitive)
+ Addeddoctrine@3.0.0(transitive)
+ Addeddunder-proto@1.0.1(transitive)
+ Addedecharts@5.4.3(transitive)
+ Addedee-first@1.1.1(transitive)
+ Addedemoji-regex@10.4.08.0.0(transitive)
+ Addedencodeurl@1.0.22.0.0(transitive)
+ Addedentities@4.5.0(transitive)
+ Addedes-define-property@1.0.1(transitive)
+ Addedes-errors@1.3.0(transitive)
+ Addedes-object-atoms@1.0.0(transitive)
+ Addedesbuild@0.21.50.24.2(transitive)
+ Addedescape-html@1.0.3(transitive)
+ Addedescape-string-regexp@4.0.0(transitive)
+ Addedeslint@8.57.1(transitive)
+ Addedeslint-compat-utils@0.5.1(transitive)
+ Addedeslint-config-prettier@9.1.0(transitive)
+ Addedeslint-plugin-svelte@2.46.1(transitive)
+ Addedeslint-scope@7.2.2(transitive)
+ Addedeslint-visitor-keys@3.4.3(transitive)
+ Addedesm-env@1.2.1(transitive)
+ Addedespree@9.6.1(transitive)
+ Addedesprima@4.0.1(transitive)
+ Addedesquery@1.6.0(transitive)
+ Addedesrap@1.3.2(transitive)
+ Addedesrecurse@4.3.0(transitive)
+ Addedestraverse@5.3.0(transitive)
+ Addedestree-walker@2.0.23.0.3(transitive)
+ Addedesutils@2.0.3(transitive)
+ Addedetag@1.8.1(transitive)
+ Addedexeca@8.0.1(transitive)
+ Addedexpress@4.21.2(transitive)
+ Addedfast-deep-equal@3.1.3(transitive)
+ Addedfast-glob@3.3.2(transitive)
+ Addedfast-json-stable-stringify@2.1.0(transitive)
+ Addedfast-levenshtein@2.0.6(transitive)
+ Addedfastq@1.18.0(transitive)
+ Addedfile-entry-cache@6.0.1(transitive)
+ Addedfill-range@7.1.1(transitive)
+ Addedfinalhandler@1.3.1(transitive)
+ Addedfind-replace@3.0.0(transitive)
+ Addedfind-up@5.0.0(transitive)
+ Addedflat-cache@3.2.0(transitive)
+ Addedflatbuffers@23.5.26(transitive)
+ Addedflatted@3.3.2(transitive)
+ Addedform-data@4.0.1(transitive)
+ Addedforwarded@0.2.0(transitive)
+ Addedfresh@0.5.2(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedfsevents@2.3.3(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedget-east-asian-width@1.3.0(transitive)
+ Addedget-func-name@2.0.2(transitive)
+ Addedget-intrinsic@1.2.7(transitive)
+ Addedget-proto@1.0.1(transitive)
+ Addedget-stream@8.0.1(transitive)
+ Addedglob@7.2.3(transitive)
+ Addedglob-parent@5.1.26.0.2(transitive)
+ Addedglobals@13.24.0(transitive)
+ Addedglobalyzer@0.1.0(transitive)
+ Addedglobby@11.1.0(transitive)
+ Addedglobrex@0.1.2(transitive)
+ Addedgopd@1.2.0(transitive)
+ Addedgraphemer@1.4.0(transitive)
+ Addedhas-flag@4.0.0(transitive)
+ Addedhas-symbols@1.1.0(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedhighlight.js@11.11.1(transitive)
+ Addedhtml-encoding-sniffer@4.0.0(transitive)
+ Addedhtml-escaper@2.0.2(transitive)
+ Addedhttp-errors@2.0.0(transitive)
+ Addedhttp-proxy-agent@7.0.2(transitive)
+ Addedhttps-proxy-agent@7.0.6(transitive)
+ Addedhuman-signals@5.0.0(transitive)
+ Addediconv-lite@0.4.240.6.3(transitive)
+ Addedignore@5.3.2(transitive)
+ Addedimport-fresh@3.3.0(transitive)
+ Addedimport-meta-resolve@4.1.0(transitive)
+ Addedimurmurhash@0.1.4(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedipaddr.js@1.9.1(transitive)
+ Addedis-binary-path@2.1.0(transitive)
+ Addedis-extglob@2.1.1(transitive)
+ Addedis-fullwidth-code-point@3.0.0(transitive)
+ Addedis-glob@4.0.3(transitive)
+ Addedis-interactive@2.0.0(transitive)
+ Addedis-number@7.0.0(transitive)
+ Addedis-path-inside@3.0.3(transitive)
+ Addedis-potential-custom-element-name@1.0.1(transitive)
+ Addedis-reference@3.0.3(transitive)
+ Addedis-stream@3.0.0(transitive)
+ Addedis-unicode-supported@1.3.02.1.0(transitive)
+ Addedisexe@2.0.0(transitive)
+ Addedistanbul-lib-coverage@3.2.2(transitive)
+ Addedistanbul-lib-report@3.0.1(transitive)
+ Addedistanbul-lib-source-maps@5.0.6(transitive)
+ Addedistanbul-reports@3.1.7(transitive)
+ Addedjs-tokens@9.0.1(transitive)
+ Addedjs-yaml@4.1.0(transitive)
+ Addedjsdom@23.2.0(transitive)
+ Addedjson-bignum@0.0.3(transitive)
+ Addedjson-buffer@3.0.1(transitive)
+ Addedjson-schema-traverse@0.4.1(transitive)
+ Addedjson-stable-stringify-without-jsonify@1.0.1(transitive)
+ Addedkeyv@4.5.4(transitive)
+ Addedkleur@4.1.5(transitive)
+ Addedknown-css-properties@0.35.0(transitive)
+ Addedlevn@0.4.1(transitive)
+ Addedlilconfig@2.1.0(transitive)
+ Addedlocal-pkg@0.5.1(transitive)
+ Addedlocate-character@3.0.0(transitive)
+ Addedlocate-path@6.0.0(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedlodash.camelcase@4.3.0(transitive)
+ Addedlodash.chunk@4.2.0(transitive)
+ Addedlodash.merge@4.6.2(transitive)
+ Addedlog-symbols@6.0.0(transitive)
+ Addedloupe@2.3.7(transitive)
+ Addedmagic-string@0.26.70.30.17(transitive)
+ Addedmagicast@0.3.5(transitive)
+ Addedmake-dir@4.0.0(transitive)
+ Addedmath-intrinsics@1.1.0(transitive)
+ Addedmdn-data@2.0.30(transitive)
+ Addedmdsvex@0.11.2(transitive)
+ Addedmedia-typer@0.3.0(transitive)
+ Addedmerge-descriptors@1.0.3(transitive)
+ Addedmerge-stream@2.0.0(transitive)
+ Addedmerge2@1.4.1(transitive)
+ Addedmethods@1.1.2(transitive)
+ Addedmicromatch@4.0.8(transitive)
+ Addedmime@1.6.0(transitive)
+ Addedmime-db@1.52.0(transitive)
+ Addedmime-types@2.1.35(transitive)
+ Addedmimic-fn@4.0.0(transitive)
+ Addedmimic-function@5.0.1(transitive)
+ Addedminimatch@3.1.29.0.3(transitive)
+ Addedmlly@1.7.3(transitive)
+ Addedmri@1.2.0(transitive)
+ Addedmrmime@2.0.0(transitive)
+ Addedms@2.0.02.1.3(transitive)
+ Addednanoid@3.3.85.0.9(transitive)
+ Addednatural-compare@1.4.0(transitive)
+ Addednegotiator@0.6.3(transitive)
+ Addednormalize-path@3.0.0(transitive)
+ Addednpm-run-path@5.3.0(transitive)
+ Addedobject-inspect@1.13.3(transitive)
+ Addedon-finished@2.4.1(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedonetime@6.0.07.0.0(transitive)
+ Addedoptionator@0.9.4(transitive)
+ Addedora@8.1.1(transitive)
+ Addedp-limit@3.1.05.0.0(transitive)
+ Addedp-locate@5.0.0(transitive)
+ Addedpad-left@2.1.0(transitive)
+ Addedparent-module@1.0.1(transitive)
+ Addedparquet-wasm@0.5.0(transitive)
+ Addedparse5@7.2.1(transitive)
+ Addedparseurl@1.3.3(transitive)
+ Addedpath-exists@4.0.0(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpath-key@3.1.14.0.0(transitive)
+ Addedpath-to-regexp@0.1.12(transitive)
+ Addedpath-type@4.0.0(transitive)
+ Addedpathe@1.1.2(transitive)
+ Addedpathval@1.1.1(transitive)
+ Addedperfect-debounce@1.0.0(transitive)
+ Addedperiscopic@3.1.0(transitive)
+ Addedpicocolors@1.1.1(transitive)
+ Addedpicomatch@2.3.1(transitive)
+ Addedpkg-types@1.3.0(transitive)
+ Addedpostcss@8.4.49(transitive)
+ Addedpostcss-load-config@3.1.4(transitive)
+ Addedpostcss-safe-parser@6.0.0(transitive)
+ Addedpostcss-scss@4.0.9(transitive)
+ Addedpostcss-selector-parser@6.1.2(transitive)
+ Addedprelude-ls@1.2.1(transitive)
+ Addedprettier@3.4.2(transitive)
+ Addedprettier-plugin-svelte@3.3.2(transitive)
+ Addedpretty-format@29.7.0(transitive)
+ Addedprism-svelte@0.4.7(transitive)
+ Addedprismjs@1.29.0(transitive)
+ Addedproxy-addr@2.0.7(transitive)
+ Addedpsl@1.15.0(transitive)
+ Addedpunycode@2.3.1(transitive)
+ Addedqs@6.13.0(transitive)
+ Addedquerystringify@2.2.0(transitive)
+ Addedqueue-microtask@1.2.3(transitive)
+ Addedrange-parser@1.2.1(transitive)
+ Addedraw-body@2.5.2(transitive)
+ Addedreact-is@18.3.1(transitive)
+ Addedreaddirp@3.6.0(transitive)
+ Addedrecast@0.23.9(transitive)
+ Addedrepeat-string@1.6.1(transitive)
+ Addedrequire-from-string@2.0.2(transitive)
+ Addedrequires-port@1.0.0(transitive)
+ Addedresolve-from@4.0.0(transitive)
+ Addedrestore-cursor@5.1.0(transitive)
+ Addedreusify@1.0.4(transitive)
+ Addedrimraf@3.0.2(transitive)
+ Addedrollup@4.29.1(transitive)
+ Addedrrweb-cssom@0.6.00.7.1(transitive)
+ Addedrun-parallel@1.2.0(transitive)
+ Addedsade@1.8.1(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedsaxes@6.0.0(transitive)
+ Addedsemver@7.6.3(transitive)
+ Addedsend@0.19.0(transitive)
+ Addedserve-static@1.16.2(transitive)
+ Addedset-cookie-parser@2.7.1(transitive)
+ Addedsetprototypeof@1.2.0(transitive)
+ Addedshebang-command@2.0.0(transitive)
+ Addedshebang-regex@3.0.0(transitive)
+ Addedside-channel@1.1.0(transitive)
+ Addedside-channel-list@1.0.0(transitive)
+ Addedside-channel-map@1.0.1(transitive)
+ Addedside-channel-weakmap@1.0.2(transitive)
+ Addedsiginfo@2.0.0(transitive)
+ Addedsignal-exit@4.1.0(transitive)
+ Addedsirv@3.0.0(transitive)
+ Addedsisteransi@1.0.5(transitive)
+ Addedslash@3.0.0(transitive)
+ Addedsource-map@0.6.1(transitive)
+ Addedsource-map-js@1.2.1(transitive)
+ Addedsourcemap-codec@1.4.8(transitive)
+ Addedstackback@0.0.2(transitive)
+ Addedstatuses@2.0.1(transitive)
+ Addedstd-env@3.8.0(transitive)
+ Addedstdin-discarder@0.2.2(transitive)
+ Addedstream-read-all@3.0.1(transitive)
+ Addedstring-width@4.2.37.2.0(transitive)
+ Addedstrip-ansi@6.0.17.1.0(transitive)
+ Addedstrip-final-newline@3.0.0(transitive)
+ Addedstrip-json-comments@3.1.1(transitive)
+ Addedstrip-literal@2.1.1(transitive)
+ Addedsupports-color@7.2.0(transitive)
+ Addedsvelte@4.2.124.2.195.16.1(transitive)
+ Addedsvelte-eslint-parser@0.43.0(transitive)
+ Addedsvelte-sequential-preprocessor@2.0.2(transitive)
+ Addedsveltekit-autoimport@1.8.1(transitive)
+ Addedsymbol-tree@3.2.4(transitive)
+ Addedtable-layout@3.0.2(transitive)
+ Addedtest-exclude@6.0.0(transitive)
+ Addedtext-table@0.2.0(transitive)
+ Addedtiny-glob@0.2.9(transitive)
+ Addedtiny-invariant@1.3.3(transitive)
+ Addedtinybench@2.9.0(transitive)
+ Addedtinypool@0.8.4(transitive)
+ Addedtinyspy@2.2.1(transitive)
+ Addedto-regex-range@5.0.1(transitive)
+ Addedtoidentifier@1.0.1(transitive)
+ Addedtotalist@3.0.1(transitive)
+ Addedtough-cookie@4.1.4(transitive)
+ Addedtr46@5.0.0(transitive)
+ Addedts-api-utils@1.4.3(transitive)
+ Addedtslib@2.7.02.8.1(transitive)
+ Addedtype-check@0.4.0(transitive)
+ Addedtype-detect@4.1.0(transitive)
+ Addedtype-fest@0.20.2(transitive)
+ Addedtype-is@1.6.18(transitive)
+ Addedtypescript@5.7.2(transitive)
+ Addedtypical@4.0.07.3.0(transitive)
+ Addedufo@1.5.4(transitive)
+ Addedundici-types@6.19.8(transitive)
+ Addedunist-util-stringify-position@2.0.3(transitive)
+ Addeduniversalify@0.2.0(transitive)
+ Addedunpipe@1.0.0(transitive)
+ Addeduri-js@4.4.1(transitive)
+ Addedurl-parse@1.5.10(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
+ Addedutils-merge@1.0.1(transitive)
+ Addedvary@1.1.2(transitive)
+ Addedvfile-message@2.0.4(transitive)
+ Addedvite@5.4.116.0.7(transitive)
+ Addedvite-node@1.6.0(transitive)
+ Addedvitefu@1.0.5(transitive)
+ Addedvitest@1.6.0(transitive)
+ Addedw3c-xmlserializer@5.0.0(transitive)
+ Addedweb-worker@1.3.0(transitive)
+ Addedwebidl-conversions@7.0.0(transitive)
+ Addedwhatwg-encoding@3.1.1(transitive)
+ Addedwhatwg-mimetype@4.0.0(transitive)
+ Addedwhatwg-url@14.1.0(transitive)
+ Addedwhich@2.0.2(transitive)
+ Addedwhy-is-node-running@2.3.0(transitive)
+ Addedword-wrap@1.2.5(transitive)
+ Addedwordwrapjs@5.1.0(transitive)
+ Addedwrappy@1.0.2(transitive)
+ Addedws@8.18.0(transitive)
+ Addedxml-name-validator@5.0.0(transitive)
+ Addedxmlchars@2.2.0(transitive)
+ Addedyaml@1.10.22.7.0(transitive)
+ Addedyocto-queue@0.1.01.1.1(transitive)
+ Addedzimmerframe@1.1.2(transitive)
+ Addedzod@3.24.1(transitive)
+ Addedzrender@5.4.4(transitive)
- Removed@tidyjs/tidy@2.4.4(transitive)
- Removedecharts@5.4.2(transitive)
- Removedsvelte@3.55.0(transitive)
- Removedzrender@5.4.3(transitive)
Updated@tidyjs/tidy@2.5.2
Updatedecharts@5.4.3
Updatedsvelte@4.2.12