Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@evidence-dev/component-utilities

Package Overview
Dependencies
Maintainers
5
Versions
381
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@evidence-dev/component-utilities - npm Package Compare versions

Comparing version 0.0.0-6af2dedd to 0.0.0-6ba0891f

src/buildQuery.js

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 @@

16

package.json
{
"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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc