@graphenedata/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE.md +100 -0
  2. package/THIRD_PARTY_NOTICES.md +12 -0
  3. package/cli.ts +157 -0
  4. package/dist/cli/cli.js +43 -0
  5. package/dist/docs/data_apps/components/charts/annotations.md +673 -0
  6. package/dist/docs/data_apps/components/charts/area-chart.md +202 -0
  7. package/dist/docs/data_apps/components/charts/bar-chart.md +317 -0
  8. package/dist/docs/data_apps/components/charts/box-plot.md +190 -0
  9. package/dist/docs/data_apps/components/charts/bubble-chart.md +151 -0
  10. package/dist/docs/data_apps/components/charts/calendar-heatmap.md +112 -0
  11. package/dist/docs/data_apps/components/charts/custom-echarts.md +308 -0
  12. package/dist/docs/data_apps/components/charts/echarts-options.md +217 -0
  13. package/dist/docs/data_apps/components/charts/funnel-chart.md +106 -0
  14. package/dist/docs/data_apps/components/charts/heatmap.md +180 -0
  15. package/dist/docs/data_apps/components/charts/histogram.md +107 -0
  16. package/dist/docs/data_apps/components/charts/line-chart.md +265 -0
  17. package/dist/docs/data_apps/components/charts/mixed-type-charts.md +240 -0
  18. package/dist/docs/data_apps/components/charts/sankey-diagram.md +301 -0
  19. package/dist/docs/data_apps/components/charts/scatter-plot.md +134 -0
  20. package/dist/docs/data_apps/components/charts/sparkline.md +68 -0
  21. package/dist/docs/data_apps/components/data/big-value.md +153 -0
  22. package/dist/docs/data_apps/components/data/delta.md +89 -0
  23. package/dist/docs/data_apps/components/data/table.md +470 -0
  24. package/dist/docs/data_apps/components/data/value.md +97 -0
  25. package/dist/docs/data_apps/components/inputs/button-group.md +154 -0
  26. package/dist/docs/data_apps/components/inputs/checkbox.md +52 -0
  27. package/dist/docs/data_apps/components/inputs/date-input.md +131 -0
  28. package/dist/docs/data_apps/components/inputs/date-range.md +124 -0
  29. package/dist/docs/data_apps/components/inputs/dimension-grid.md +67 -0
  30. package/dist/docs/data_apps/components/inputs/dropdown.md +199 -0
  31. package/dist/docs/data_apps/components/inputs/index.md +3 -0
  32. package/dist/docs/data_apps/components/inputs/slider.md +126 -0
  33. package/dist/docs/data_apps/components/inputs/text-input.md +86 -0
  34. package/dist/docs/data_apps/components/maps/area-map.md +397 -0
  35. package/dist/docs/data_apps/components/maps/base-map.md +269 -0
  36. package/dist/docs/data_apps/components/maps/bubble-map.md +361 -0
  37. package/dist/docs/data_apps/components/maps/point-map.md +326 -0
  38. package/dist/docs/data_apps/components/maps/us-map.md +167 -0
  39. package/dist/docs/data_apps/components/ui/accordion.md +116 -0
  40. package/dist/docs/data_apps/components/ui/alert.md +37 -0
  41. package/dist/docs/data_apps/components/ui/big-link.md +19 -0
  42. package/dist/docs/data_apps/components/ui/details.md +58 -0
  43. package/dist/docs/data_apps/components/ui/download-data.md +41 -0
  44. package/dist/docs/data_apps/components/ui/embed.md +47 -0
  45. package/dist/docs/data_apps/components/ui/grid.md +45 -0
  46. package/dist/docs/data_apps/components/ui/image.md +61 -0
  47. package/dist/docs/data_apps/components/ui/info.md +47 -0
  48. package/dist/docs/data_apps/components/ui/last-refreshed.md +28 -0
  49. package/dist/docs/data_apps/components/ui/link-button.md +20 -0
  50. package/dist/docs/data_apps/components/ui/link.md +40 -0
  51. package/dist/docs/data_apps/components/ui/modal.md +57 -0
  52. package/dist/docs/data_apps/components/ui/note.md +32 -0
  53. package/dist/docs/data_apps/components/ui/print-format-components.md +85 -0
  54. package/dist/docs/data_apps/components/ui/tabs.md +122 -0
  55. package/dist/docs/graphene.md +129 -0
  56. package/dist/ui/app.css +332 -0
  57. package/dist/ui/assets/favicon.ico +0 -0
  58. package/dist/ui/component-utilities/autoFormatting.js +301 -0
  59. package/dist/ui/component-utilities/builtInFormats.js +482 -0
  60. package/dist/ui/component-utilities/chartContext.js +12 -0
  61. package/dist/ui/component-utilities/chartWindowDebug.js +21 -0
  62. package/dist/ui/component-utilities/checkInputs.js +95 -0
  63. package/dist/ui/component-utilities/convert.js +15 -0
  64. package/dist/ui/component-utilities/dateParsing.js +57 -0
  65. package/dist/ui/component-utilities/dropdownContext.ts +1 -0
  66. package/dist/ui/component-utilities/echarts.js +262 -0
  67. package/dist/ui/component-utilities/echartsThemes.js +453 -0
  68. package/dist/ui/component-utilities/formatTitle.js +24 -0
  69. package/dist/ui/component-utilities/formatting.js +258 -0
  70. package/dist/ui/component-utilities/getColumnExtents.js +79 -0
  71. package/dist/ui/component-utilities/getColumnSummary.js +67 -0
  72. package/dist/ui/component-utilities/getCompletedData.js +114 -0
  73. package/dist/ui/component-utilities/getDistinctCount.js +7 -0
  74. package/dist/ui/component-utilities/getDistinctValues.js +15 -0
  75. package/dist/ui/component-utilities/getSeriesConfig.js +236 -0
  76. package/dist/ui/component-utilities/getSortedData.js +7 -0
  77. package/dist/ui/component-utilities/getStackPercentages.js +43 -0
  78. package/dist/ui/component-utilities/getStackedData.js +17 -0
  79. package/dist/ui/component-utilities/getYAxisIndex.js +15 -0
  80. package/dist/ui/component-utilities/globalContexts.js +1 -0
  81. package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +119 -0
  82. package/dist/ui/component-utilities/inputUtils.ts +25 -0
  83. package/dist/ui/component-utilities/replaceNulls.js +14 -0
  84. package/dist/ui/component-utilities/tableUtils.ts +120 -0
  85. package/dist/ui/component-utilities/themeStores.ts +116 -0
  86. package/dist/ui/components/Area.svelte +174 -0
  87. package/dist/ui/components/AreaChart.svelte +150 -0
  88. package/dist/ui/components/Bar.svelte +326 -0
  89. package/dist/ui/components/BarChart.svelte +194 -0
  90. package/dist/ui/components/BigValue.svelte +69 -0
  91. package/dist/ui/components/Chart.svelte +1070 -0
  92. package/dist/ui/components/Column.svelte +172 -0
  93. package/dist/ui/components/DateRange.svelte +324 -0
  94. package/dist/ui/components/Dropdown.svelte +738 -0
  95. package/dist/ui/components/DropdownOption.svelte +21 -0
  96. package/dist/ui/components/ECharts.svelte +77 -0
  97. package/dist/ui/components/ErrorChart.svelte +54 -0
  98. package/dist/ui/components/GrapheneQuery.svelte +12 -0
  99. package/dist/ui/components/InlineDelta.svelte +150 -0
  100. package/dist/ui/components/Line.svelte +210 -0
  101. package/dist/ui/components/LineChart.svelte +178 -0
  102. package/dist/ui/components/PieChart.svelte +36 -0
  103. package/dist/ui/components/QueryLoad.svelte +82 -0
  104. package/dist/ui/components/Row.svelte +14 -0
  105. package/dist/ui/components/SortIcon.svelte +32 -0
  106. package/dist/ui/components/Table.svelte +19 -0
  107. package/dist/ui/components/TableCell.svelte +75 -0
  108. package/dist/ui/components/TableGroupRow.svelte +136 -0
  109. package/dist/ui/components/TableGroupToggle.svelte +42 -0
  110. package/dist/ui/components/TableHeader.svelte +242 -0
  111. package/dist/ui/components/TableRow.svelte +283 -0
  112. package/dist/ui/components/TableSubtotalRow.svelte +62 -0
  113. package/dist/ui/components/TableTotalRow.svelte +88 -0
  114. package/dist/ui/components/TextInput.svelte +92 -0
  115. package/dist/ui/components/_Table.svelte +516 -0
  116. package/dist/ui/internal/clientCache.ts +43 -0
  117. package/dist/ui/internal/queryEngine.ts +169 -0
  118. package/dist/ui/internal/telemetry.ts +28 -0
  119. package/dist/ui/internal/theme.ts +88 -0
  120. package/dist/ui/layout.svelte +3 -0
  121. package/dist/ui/playwright.config.ts +30 -0
  122. package/dist/ui/web.js +106 -0
  123. package/package.json +71 -0
@@ -0,0 +1,24 @@
1
+ import {applyTitleTagReplacement} from './formatting'
2
+
3
+ export default function formatTitle (column, columnFormat) {
4
+ let result = applyTitleTagReplacement(column, columnFormat)
5
+ // Allow some acronyms to remain fully capitalized in titles:
6
+ let acronyms = ['id', 'gdp']
7
+ // Allow some joining words to remain fully lowercased in title:
8
+ let lowercase = ['of', 'the', 'and', 'in', 'on']
9
+ // Set name to proper casing:
10
+ function toTitleCase (str) {
11
+ return str.replace(/\S*/g, function (txt) {
12
+ if (!acronyms.includes(txt) && !lowercase.includes(txt)) {
13
+ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
14
+ } else if (acronyms.includes(txt)) {
15
+ return txt.toUpperCase()
16
+ } else {
17
+ return txt.toLowerCase()
18
+ }
19
+ })
20
+ }
21
+ // Remove all underscores & double quotes before passing to title case function:
22
+ result = toTitleCase(column.replace(/"/g, '').replace(/_/g, ' '))
23
+ return result
24
+ }
@@ -0,0 +1,258 @@
1
+ import ssf from 'ssf'
2
+ import {getContext} from 'svelte'
3
+ import {CUSTOM_FORMATTING_SETTINGS_CONTEXT_KEY} from './globalContexts'
4
+ import {findImplicitAutoFormat, autoFormat, fallbackFormat, isAutoFormat} from './autoFormatting'
5
+ import {BUILT_IN_FORMATS} from './builtInFormats'
6
+ import {standardizeDateString} from './dateParsing'
7
+
8
+ const AXIS_FORMATTING_CONTEXT = 'axis'
9
+ const VALUE_FORMATTING_CONTEXT = 'value'
10
+
11
+ export const getCustomFormats = () => {
12
+ try {
13
+ return getContext(CUSTOM_FORMATTING_SETTINGS_CONTEXT_KEY)?.getCustomFormats() || []
14
+ } catch {
15
+ return []
16
+ }
17
+ }
18
+
19
+ /**
20
+ * @param {*} columnName the name of the column
21
+ * @returns a format object (built-in or custom) based on the column name if it matches the pattern column_${formatTag}, otherwise returns undefined
22
+ */
23
+ export const lookupColumnFormat = (columnName, columnEvidenceType, columnUnitSummary) => {
24
+ let potentialFormatTag = maybeExtractFormatTag(columnName)
25
+
26
+ if (columnEvidenceType.evidenceType === 'string') {
27
+ return undefined
28
+ }
29
+
30
+ if (potentialFormatTag) {
31
+ let customFormats = getCustomFormats()
32
+ let matchingFormat = [...BUILT_IN_FORMATS, ...customFormats].find(
33
+ (format) => format.formatTag?.toLowerCase() === potentialFormatTag?.toLowerCase?.(),
34
+ )
35
+ if (matchingFormat) {
36
+ return matchingFormat
37
+ }
38
+ }
39
+
40
+ let matchingImplicitAutoFormat = findImplicitAutoFormat(
41
+ columnName,
42
+ columnEvidenceType,
43
+ columnUnitSummary,
44
+ )
45
+ if (matchingImplicitAutoFormat) {
46
+ return matchingImplicitAutoFormat
47
+ }
48
+
49
+ return undefined
50
+ }
51
+
52
+ /**
53
+ * Returns an Evidence format object to be used in the applyFormatting function
54
+ * @param {string} formatString string containing an Excel-style format code, or a format name matching a built-in or custom format
55
+ * @param {'number' | 'date' | 'boolean' | 'string'} [valueType] optional - a string representing the data type within the column that will be formatted ('number', 'date', 'boolean', or 'string)
56
+ * @returns a format object based on the formatString matching a built-in or custom format name, or a new custom format object containing an Excel-style format code
57
+ */
58
+ export function getFormatObjectFromString (formatString, valueType = undefined) {
59
+ let potentialFormatTag = formatString
60
+ let customFormats = getCustomFormats()
61
+ let matchingFormat = [...BUILT_IN_FORMATS, ...customFormats].find(
62
+ (format) => format.formatTag?.toLowerCase() === potentialFormatTag?.toLowerCase?.(),
63
+ )
64
+ let newFormat = {}
65
+ if (matchingFormat) {
66
+ return matchingFormat
67
+ } else {
68
+ newFormat = {
69
+ formatTag: 'custom',
70
+ formatCode: potentialFormatTag,
71
+ }
72
+ if (valueType) {
73
+ newFormat.valueType = valueType
74
+ }
75
+ return newFormat
76
+ }
77
+ }
78
+
79
+ export const formatValue = (value, columnFormat = undefined, columnUnitSummary = undefined) => {
80
+ try {
81
+ return applyFormatting(value, columnFormat, columnUnitSummary, VALUE_FORMATTING_CONTEXT)
82
+ } catch (error) {
83
+ //fallback to default
84
+ console.warn(
85
+ `Unexpected error calling applyFormatting(${value}, ${columnFormat}, ${VALUE_FORMATTING_CONTEXT}, ${columnUnitSummary}). Error=${error}`,
86
+ )
87
+ return value
88
+ }
89
+ }
90
+
91
+ export const formatAxisValue = (value, columnFormat = undefined, columnUnitSummary = undefined) => {
92
+ try {
93
+ return applyFormatting(value, columnFormat, columnUnitSummary, AXIS_FORMATTING_CONTEXT)
94
+ } catch {
95
+ //fallback to default
96
+ }
97
+ return value
98
+ }
99
+
100
+ export const applyTitleTagReplacement = (columnName, columnFormatSettings) => {
101
+ let result = columnName
102
+ if (columnName && columnFormatSettings?.formatTag) {
103
+ let lastIndexOfTag = columnName
104
+ .toLowerCase()
105
+ .lastIndexOf(`_${columnFormatSettings.formatTag.toLowerCase()}`)
106
+ let titleTagReplacement = ''
107
+ if (lastIndexOfTag > 0) {
108
+ //explicitly ignore columns starting with _, hence >0 instead of => 0
109
+ if (typeof columnFormatSettings?.titleTagReplacement === 'string') {
110
+ titleTagReplacement = columnFormatSettings.titleTagReplacement
111
+ }
112
+ result = columnName.substring(0, lastIndexOfTag) + titleTagReplacement
113
+ }
114
+ }
115
+ return result
116
+ }
117
+
118
+ export const defaultExample = (valueType) => {
119
+ switch (valueType) {
120
+ case 'number':
121
+ return 1234
122
+ case 'date':
123
+ return '2022-01-03'
124
+ default:
125
+ return undefined
126
+ }
127
+ }
128
+
129
+ export const formatExample = (format) => {
130
+ let normalizedUserInput = format.userInput?.trim()
131
+ let preFormattedValue =
132
+ normalizedUserInput || format.exampleInput || defaultExample(format.valueType)
133
+
134
+ if (preFormattedValue) {
135
+ try {
136
+ let columnUnitSummary = undefined
137
+ if (format.valueType === 'number') {
138
+ let numericValue = Number(preFormattedValue)
139
+ columnUnitSummary = {
140
+ min: numericValue,
141
+ max: numericValue,
142
+ median: numericValue,
143
+ maxDecimals: numericValue.toString().split('.')[1]?.length || 0,
144
+ unitType: 'number',
145
+ }
146
+ }
147
+ return applyFormatting(
148
+ preFormattedValue,
149
+ format,
150
+ columnUnitSummary,
151
+ VALUE_FORMATTING_CONTEXT,
152
+ )
153
+ } catch {
154
+ //return default value
155
+ }
156
+ }
157
+ return ''
158
+ }
159
+
160
+ function applyFormatting (
161
+ value,
162
+ columnFormat = undefined,
163
+ columnUnitSummary = undefined,
164
+ formattingContext = VALUE_FORMATTING_CONTEXT,
165
+ ) {
166
+ if (value === undefined || value === null) {
167
+ return '-'
168
+ }
169
+
170
+ let result = undefined
171
+ if (columnFormat) {
172
+ try {
173
+ let formattingCode = getEffectiveFormattingCode(columnFormat, formattingContext)
174
+ let typedValue
175
+ try {
176
+ if (columnFormat.valueType === 'date' && typeof value === 'string') {
177
+ typedValue = new Date(standardizeDateString(value))
178
+ } else if (value instanceof Date) {
179
+ // "2023-09-06T22:40:43.000Z" minus the Z is interpreted
180
+ // as local time
181
+ // similar in behavior to standardizeDateString
182
+ typedValue = new Date(value.toISOString().slice(0, -1))
183
+ } else if (
184
+ columnFormat.valueType === 'number' &&
185
+ typeof value !== 'number' &&
186
+ !Number.isNaN(value)
187
+ ) {
188
+ typedValue = Number(value)
189
+ } else {
190
+ typedValue = value
191
+ }
192
+ } catch {
193
+ typedValue = value
194
+ }
195
+ if (isAutoFormat(columnFormat, formattingCode)) {
196
+ try {
197
+ result = autoFormat(typedValue, columnFormat, columnUnitSummary)
198
+ } catch (error) {
199
+ console.warn(`Unexpected error applying auto formatting. Error=${error}`)
200
+ }
201
+ } else {
202
+ result = ssf.format(formattingCode, typedValue)
203
+ }
204
+ } catch (error) {
205
+ console.warn(`Unexpected error applying formatting ${error}`)
206
+ }
207
+ }
208
+ if (result === undefined) {
209
+ result = fallbackFormat(value, columnUnitSummary)
210
+ }
211
+ return result
212
+ }
213
+ function getEffectiveFormattingCode (columnFormat, formattingContext = VALUE_FORMATTING_CONTEXT) {
214
+ if (typeof columnFormat === 'string') {
215
+ // This should only be used by end users, not by components.
216
+ return columnFormat
217
+ } else {
218
+ if (formattingContext === AXIS_FORMATTING_CONTEXT && columnFormat?.axisFormatCode) {
219
+ return columnFormat.axisFormatCode
220
+ }
221
+ return columnFormat?.formatCode
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Extracts a possible format tag from a column name based on the column name pattern
227
+ * @returns "column_${formatTag}" will return ${formatTag} or undefined if the columnName doesn't match the pattern
228
+ */
229
+ function maybeExtractFormatTag (columnName) {
230
+ let normalizedColName = columnName.toLowerCase()
231
+ let lastUnderScoreIndex = normalizedColName.lastIndexOf('_')
232
+
233
+ if (lastUnderScoreIndex > 0) {
234
+ return normalizedColName.substr(lastUnderScoreIndex).replace('_', '')
235
+ } else {
236
+ return undefined
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Formats a value to whichever format is passed in
242
+ * @param {*} value the value to be formatted
243
+ * @param {string} format string containing an Excel-style format code, or a format name matching a built-in or custom format
244
+ * @returns a formatted value
245
+ */
246
+ export function fmt (value, format) {
247
+ let formatObj = getFormatObjectFromString(format)
248
+ let valueType = valueTypeFromSample(value)
249
+ formatObj.valueType = valueType
250
+ return formatValue(value, formatObj)
251
+ }
252
+
253
+ function valueTypeFromSample (value) {
254
+ if (typeof value === 'number') return 'number'
255
+ if (typeof value === 'boolean') return 'boolean'
256
+ if (value instanceof Date) return 'date'
257
+ return 'string'
258
+ }
@@ -0,0 +1,79 @@
1
+ import {tidy, summarize, min, max, median, mean, n, nDistinct, sum} from '@tidyjs/tidy'
2
+
3
+ /**
4
+ *
5
+ * @param {Record<string, unknown>[]} data
6
+ * @param {string} columnName
7
+ * @param {boolean} [isNumeric]
8
+ * @returns {{ min?: number, max?: number, median?: number, mean?: number, count?: number, countDistinct?: number, sum?: number, maxDecimals: number, unitType: string }}
9
+ */
10
+ export function getColumnUnitSummary (data, columnName, isNumeric = true) {
11
+ let seriesExtents = tidy(
12
+ data,
13
+ isNumeric
14
+ ? summarize({
15
+ count: n(columnName),
16
+ countDistinct: nDistinct(columnName),
17
+ min: min(columnName),
18
+ max: max(columnName),
19
+ median: median(columnName),
20
+ mean: mean(columnName),
21
+ sum: sum(columnName),
22
+ })
23
+ : summarize({count: n(columnName), countDistinct: nDistinct(columnName)}),
24
+ )[0]
25
+
26
+ // TODO try to use summarize spec in tidy
27
+ let {maxDecimals, unitType} = summarizeUnits(data.map((row) => row[columnName]))
28
+
29
+ return {
30
+ min: seriesExtents.min,
31
+ max: seriesExtents.max,
32
+ median: seriesExtents.median,
33
+ mean: seriesExtents.mean,
34
+ count: seriesExtents.count,
35
+ countDistinct: seriesExtents.countDistinct,
36
+ sum: seriesExtents.sum,
37
+ maxDecimals: maxDecimals,
38
+ unitType: unitType,
39
+ }
40
+ }
41
+
42
+ /**
43
+ *
44
+ * @param {Record<string, unknown>[]} data
45
+ * @param {string} column
46
+ * @returns {[number?, number?]}
47
+ */
48
+ export function getColumnExtentsLegacy (data, column) {
49
+ let domainData = tidy(data, summarize({min: min(column), max: max(column)}))[0]
50
+ return [domainData.min, domainData.max]
51
+ }
52
+
53
+ /**
54
+ *
55
+ * @param {number[]} series
56
+ * @returns {{ maxDecimals: number, unitType: string }}
57
+ */
58
+ function summarizeUnits (series) {
59
+ if (series === undefined || series === null || series.length === 0) {
60
+ return {
61
+ maxDecimals: 0,
62
+ unitType: 'unknown',
63
+ }
64
+ } else {
65
+ let maxDecimals = 0
66
+
67
+ for (let element of series) {
68
+ let decimal_places = element?.toString().split('.')[1]?.length
69
+ if (decimal_places > maxDecimals) {
70
+ maxDecimals = decimal_places
71
+ }
72
+ }
73
+
74
+ return {
75
+ maxDecimals: maxDecimals,
76
+ unitType: 'number',
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,67 @@
1
+ import {getColumnUnitSummary} from './getColumnExtents.js'
2
+ import {lookupColumnFormat} from './formatting.js'
3
+ import formatTitle from './formatTitle.js'
4
+
5
+ const EvidenceType = {
6
+ BOOLEAN: 'boolean',
7
+ NUMBER: 'number',
8
+ STRING: 'string',
9
+ DATE: 'date',
10
+ }
11
+
12
+ /**
13
+ * @typedef {Object} ColumnSummary
14
+ * @property {string} title
15
+ * @property {string} type
16
+ * @property {Object} evidenceColumnType
17
+ * @property {ReturnType<typeof lookupColumnFormat>} format
18
+ * @property {ReturnType<typeof getColumnUnitSummary>} columnUnitSummary
19
+ */
20
+
21
+ /**
22
+ * @function
23
+ * @template T
24
+ * @param {Record<string, unknown>[]} data
25
+ * @param {T} returnType
26
+ * @returns {T extends 'object' ? Record<string, ColumnSummary> : (ColumnSummary & { id: string })[]}
27
+ */
28
+ export default function getColumnSummary (data, returnType = 'object') {
29
+ /** @type {Record<string, ColumnSummary>} */
30
+ let columnSummary = {}
31
+
32
+ let types = Array.isArray(data?._evidenceColumnTypes) ? data._evidenceColumnTypes : []
33
+
34
+ for (let colName of Object.keys(data[0])) {
35
+ let evidenceColumnType = types.find(
36
+ (item) => item.name?.toLowerCase() === colName?.toLowerCase(),
37
+ ) ?? {
38
+ name: colName,
39
+ evidenceType: EvidenceType.STRING,
40
+ }
41
+ let type = evidenceColumnType.evidenceType
42
+ let columnUnitSummary =
43
+ evidenceColumnType.evidenceType === EvidenceType.NUMBER
44
+ ? getColumnUnitSummary(data, colName, true)
45
+ : getColumnUnitSummary(data, colName, false)
46
+
47
+ if (evidenceColumnType.evidenceType !== EvidenceType.NUMBER) {
48
+ columnUnitSummary.maxDecimals = 0
49
+ columnUnitSummary.unitType = evidenceColumnType.evidenceType
50
+ }
51
+ let format = lookupColumnFormat(colName, evidenceColumnType, columnUnitSummary)
52
+
53
+ columnSummary[colName] = {
54
+ title: formatTitle(colName, format),
55
+ type,
56
+ evidenceColumnType,
57
+ format,
58
+ columnUnitSummary,
59
+ }
60
+ }
61
+
62
+ if (returnType !== 'object') {
63
+ return Object.entries(columnSummary).map(([key, value]) => ({id: key, ...value}))
64
+ }
65
+
66
+ return columnSummary
67
+ }
@@ -0,0 +1,114 @@
1
+ import {tidy, complete} from '@tidyjs/tidy'
2
+ import getDistinctValues from './getDistinctValues'
3
+ import {findInterval, vectorSeq} from './helpers/getCompletedData.helpers.js'
4
+
5
+ /**
6
+ * This function fills missing data points in the given data array for a specific series.
7
+ *
8
+ * @param {Record<string, unknown>[]} _data - The data as an array of objects.
9
+ * @param {string} x - The property used as x-axis.
10
+ * @param {string} y - The property used as y-axis.
11
+ * @param {string} series - The specific series in the data to be filled.
12
+ * @param {boolean} [nullsZero=false] - A flag indicating whether nulls should be replaced with zero.
13
+ * @param {boolean} [fillX=false] - A flag indicating whether the x-axis values should be filled (based on the found interval distance).
14
+ * @return {Record<string, unknown>[]} An array containing the filled data objects.
15
+ */
16
+ export default function getCompletedData (_data, x, y, series, nullsZero = false, fillX = false) {
17
+ let xIsDate = false
18
+ let data = _data
19
+ .map((d) =>
20
+ Object.assign({}, d, {
21
+ [x]: d[x] instanceof Date ? ((xIsDate = true), d[x].toISOString()) : d[x],
22
+ }),
23
+ )
24
+ .filter((d) => d[x] !== undefined && d[x] !== null)
25
+ let groups = Array.from(data).reduce((a, v) => {
26
+ if (v[x] instanceof Date) {
27
+ v[x] = v[x].toISOString()
28
+ xIsDate = true
29
+ }
30
+ if (series) {
31
+ if (!a[v[series] ?? 'null']) a[v[series] ?? 'null'] = []
32
+ a[v[series] ?? 'null'].push(v)
33
+ } else {
34
+ if (!a.default) a.default = []
35
+ a.default.push(v)
36
+ }
37
+ return a
38
+ }, {})
39
+
40
+ // Ensures that all permutations of this map exist in the output
41
+ // e.g. can include series and x values to ensure that all series have all x values
42
+ let expandKeys = {}
43
+
44
+ /** @type {Array<number | string>} */
45
+ let xDistinct
46
+
47
+ let exampleX =
48
+ data.find((item) => item && item[x] !== null && item[x] !== undefined)?.[x] ?? null
49
+ // const exampleX = data[0]?.[x];
50
+ switch (typeof exampleX) {
51
+ case 'object':
52
+ if (exampleX === null) {
53
+ throw new Error(
54
+ `Column '${x}' is entirely null. Column must contain at least one non-null value.`,
55
+ )
56
+ } else {
57
+ throw new Error('Unexpected object property, expected string, date, or number')
58
+ }
59
+ case 'number':
60
+ // Numbers are the most straightforward
61
+ xDistinct = getDistinctValues(data, x)
62
+ if (fillX) {
63
+ // Attempt to derive the interval between X values and interpolate missing values in that set (within the bounds of min/max)
64
+ let interval = findInterval(xDistinct)
65
+ expandKeys[x] = vectorSeq(xDistinct, interval)
66
+ }
67
+ break
68
+ case 'string':
69
+ xDistinct = getDistinctValues(data, x)
70
+ expandKeys[x] = xDistinct
71
+ break
72
+ }
73
+
74
+ let output = []
75
+
76
+ for (let value of Object.values(groups)) {
77
+ let nullySpec = series ? {[series]: null} : {}
78
+ if (nullsZero) {
79
+ if (y instanceof Array) {
80
+ for (let i = 0; i < y.length; i++) {
81
+ nullySpec[y[i]] = 0
82
+ }
83
+ } else {
84
+ nullySpec[y] = 0
85
+ }
86
+ } else {
87
+ // Ensure null for consistency
88
+ if (y instanceof Array) {
89
+ for (let i = 0; i < y.length; i++) {
90
+ nullySpec[y[i]] = null
91
+ }
92
+ } else {
93
+ nullySpec[y] = null
94
+ }
95
+ }
96
+
97
+ if (series) {
98
+ expandKeys[series] = series
99
+ }
100
+
101
+ let tidyFuncs = []
102
+ if (Object.keys(expandKeys).length === 0) {
103
+ // empty object, no special configuration
104
+ tidyFuncs.push(complete([x], nullySpec))
105
+ } else {
106
+ tidyFuncs.push(complete(expandKeys, nullySpec))
107
+ }
108
+
109
+ output.push(tidy(value, ...tidyFuncs))
110
+ }
111
+ if (xIsDate) return output.flat().map((r) => ({...r, [x]: new Date(r[x])}))
112
+
113
+ return output.flat()
114
+ }
@@ -0,0 +1,7 @@
1
+ import {tidy, summarize, nDistinct} from '@tidyjs/tidy'
2
+
3
+ export default function getDistinctCount (data, column) {
4
+ let distinctCount = tidy(data, summarize({count: nDistinct(column)}))[0].count
5
+
6
+ return distinctCount
7
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Extracts an array of distinct values from a specified column in a dataset.
3
+ *
4
+ * This function iterates over the dataset, collecting values from the specified
5
+ * column into a Set to ensure uniqueness. It then converts the Set into an array
6
+ * of distinct values and returns this array.
7
+ *
8
+ * @param {Object[]} data - The dataset to process, represented as an array of objects.
9
+ * @param {string} column - The name of the column from which to extract distinct values.
10
+ * @returns {any[]} An array containing distinct values from the specified column of the dataset.
11
+ */
12
+ export default function getDistinctValues (data, column) {
13
+ let set = new Set(data.map((val) => val[column]))
14
+ return Array.from(set)
15
+ }