@graphenedata/cli 0.0.4 → 0.0.6

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 (74) hide show
  1. package/cli.ts +25 -55
  2. package/dist/cli/cli.js +1194 -435
  3. package/dist/docs/graphene.md +1074 -166
  4. package/dist/ui/component-utilities/echarts.js +3 -1
  5. package/dist/ui/component-utilities/inputUtils.ts +11 -0
  6. package/dist/ui/component-utilities/themeStores.ts +35 -7
  7. package/dist/ui/components/Area.svelte +6 -3
  8. package/dist/ui/components/AreaChart.svelte +3 -1
  9. package/dist/ui/components/Bar.svelte +14 -8
  10. package/dist/ui/components/BarChart.svelte +3 -1
  11. package/dist/ui/components/BigValue.svelte +1 -1
  12. package/dist/ui/components/Chart.svelte +57 -101
  13. package/dist/ui/components/Column.svelte +2 -0
  14. package/dist/ui/components/ECharts.svelte +2 -0
  15. package/dist/ui/components/Line.svelte +8 -5
  16. package/dist/ui/components/LineChart.svelte +3 -2
  17. package/dist/ui/components/PieChart.svelte +1 -1
  18. package/dist/ui/components/QueryLoad.svelte +5 -6
  19. package/dist/ui/components/TableRow.svelte +1 -1
  20. package/dist/ui/components/_Table.svelte +2 -0
  21. package/dist/ui/internal/queryEngine.ts +39 -15
  22. package/dist/ui/internal/telemetry.ts +5 -3
  23. package/dist/ui/web.js +28 -12
  24. package/package.json +3 -2
  25. package/dist/docs/data_apps/components/charts/annotations.md +0 -673
  26. package/dist/docs/data_apps/components/charts/area-chart.md +0 -202
  27. package/dist/docs/data_apps/components/charts/bar-chart.md +0 -317
  28. package/dist/docs/data_apps/components/charts/box-plot.md +0 -190
  29. package/dist/docs/data_apps/components/charts/bubble-chart.md +0 -151
  30. package/dist/docs/data_apps/components/charts/calendar-heatmap.md +0 -112
  31. package/dist/docs/data_apps/components/charts/custom-echarts.md +0 -308
  32. package/dist/docs/data_apps/components/charts/echarts-options.md +0 -217
  33. package/dist/docs/data_apps/components/charts/funnel-chart.md +0 -106
  34. package/dist/docs/data_apps/components/charts/heatmap.md +0 -180
  35. package/dist/docs/data_apps/components/charts/histogram.md +0 -107
  36. package/dist/docs/data_apps/components/charts/line-chart.md +0 -265
  37. package/dist/docs/data_apps/components/charts/mixed-type-charts.md +0 -240
  38. package/dist/docs/data_apps/components/charts/sankey-diagram.md +0 -301
  39. package/dist/docs/data_apps/components/charts/scatter-plot.md +0 -134
  40. package/dist/docs/data_apps/components/charts/sparkline.md +0 -68
  41. package/dist/docs/data_apps/components/data/big-value.md +0 -153
  42. package/dist/docs/data_apps/components/data/delta.md +0 -89
  43. package/dist/docs/data_apps/components/data/table.md +0 -470
  44. package/dist/docs/data_apps/components/data/value.md +0 -97
  45. package/dist/docs/data_apps/components/inputs/button-group.md +0 -154
  46. package/dist/docs/data_apps/components/inputs/checkbox.md +0 -52
  47. package/dist/docs/data_apps/components/inputs/date-input.md +0 -131
  48. package/dist/docs/data_apps/components/inputs/date-range.md +0 -124
  49. package/dist/docs/data_apps/components/inputs/dimension-grid.md +0 -67
  50. package/dist/docs/data_apps/components/inputs/dropdown.md +0 -199
  51. package/dist/docs/data_apps/components/inputs/index.md +0 -3
  52. package/dist/docs/data_apps/components/inputs/slider.md +0 -126
  53. package/dist/docs/data_apps/components/inputs/text-input.md +0 -86
  54. package/dist/docs/data_apps/components/maps/area-map.md +0 -397
  55. package/dist/docs/data_apps/components/maps/base-map.md +0 -269
  56. package/dist/docs/data_apps/components/maps/bubble-map.md +0 -361
  57. package/dist/docs/data_apps/components/maps/point-map.md +0 -326
  58. package/dist/docs/data_apps/components/maps/us-map.md +0 -167
  59. package/dist/docs/data_apps/components/ui/accordion.md +0 -116
  60. package/dist/docs/data_apps/components/ui/alert.md +0 -37
  61. package/dist/docs/data_apps/components/ui/big-link.md +0 -19
  62. package/dist/docs/data_apps/components/ui/details.md +0 -58
  63. package/dist/docs/data_apps/components/ui/download-data.md +0 -41
  64. package/dist/docs/data_apps/components/ui/embed.md +0 -47
  65. package/dist/docs/data_apps/components/ui/grid.md +0 -45
  66. package/dist/docs/data_apps/components/ui/image.md +0 -61
  67. package/dist/docs/data_apps/components/ui/info.md +0 -47
  68. package/dist/docs/data_apps/components/ui/last-refreshed.md +0 -28
  69. package/dist/docs/data_apps/components/ui/link-button.md +0 -20
  70. package/dist/docs/data_apps/components/ui/link.md +0 -40
  71. package/dist/docs/data_apps/components/ui/modal.md +0 -57
  72. package/dist/docs/data_apps/components/ui/note.md +0 -32
  73. package/dist/docs/data_apps/components/ui/print-format-components.md +0 -85
  74. package/dist/docs/data_apps/components/ui/tabs.md +0 -122
@@ -12,7 +12,7 @@ import * as chartWindowDebug from './chartWindowDebug'
12
12
  * } EChartsActionOptions
13
13
  */
14
14
 
15
- const ANIMATION_DURATION = 500
15
+ const ANIMATION_DURATION = 0
16
16
  const pendingChartsKey = Symbol.for('graphene.pendingCharts')
17
17
 
18
18
  /** @returns {Set<number> | null} */
@@ -139,6 +139,7 @@ const echartsAction = (node, options) => {
139
139
  // Initial options set:
140
140
  chart.setOption({
141
141
  ...options.config,
142
+ animation: false,
142
143
  animationDuration: ANIMATION_DURATION,
143
144
  animationDurationUpdate: ANIMATION_DURATION,
144
145
  })
@@ -219,6 +220,7 @@ const echartsAction = (node, options) => {
219
220
  chart.setOption(
220
221
  {
221
222
  ...options.config,
223
+ animation: false,
222
224
  animationDuration: ANIMATION_DURATION,
223
225
  animationDurationUpdate: ANIMATION_DURATION,
224
226
  },
@@ -23,3 +23,14 @@ export function serializeValue (value: unknown): string {
23
23
  let str = String(value)
24
24
  return `'${str.replace(/'/g, "''")}'`
25
25
  }
26
+
27
+ // Parse a comma-separated list into an array of trimmed strings.
28
+ // - Strings are split on commas; whitespace trimmed; empty entries removed.
29
+ // - Arrays are normalized by trimming string items and String()-ing non-strings.
30
+ // - null/undefined -> []
31
+ export function parseCommaList (value: unknown): string[] {
32
+ if (value === undefined || value === null) return []
33
+ if (Array.isArray(value)) return value.map(v => typeof v === 'string' ? v.trim() : String(v)).filter(v => v.length > 0)
34
+ if (typeof value === 'string') return value.split(',').map(v => v.trim()).filter(v => v.length > 0)
35
+ return [String(value).trim()].filter(v => v.length > 0)
36
+ }
@@ -12,7 +12,7 @@ type ThemeStores = {
12
12
  activeAppearance: Readable<Appearance>
13
13
  theme: Readable<Theme>
14
14
  resolveColor: <T>(input: T) => Readable<T | string | undefined>
15
- resolveColorsObject: (input: Record<string, unknown> | undefined) => Readable<Record<string, string | undefined> | undefined>
15
+ resolveColorsObject: (input: Record<string, unknown> | string | undefined) => Readable<Record<string, string | undefined> | undefined>
16
16
  resolveColorPalette: (input: unknown) => Readable<string[] | undefined>
17
17
  }
18
18
 
@@ -55,6 +55,14 @@ const isReadable = <T>(value: unknown): value is Readable<T> => {
55
55
  return Boolean(value && typeof value === 'object' && 'subscribe' in (value as any))
56
56
  }
57
57
 
58
+ const parseJson = (value: string): unknown => {
59
+ try {
60
+ return JSON.parse(value)
61
+ } catch {
62
+ return undefined
63
+ }
64
+ }
65
+
58
66
  const normalizeColor = (value: unknown): string | undefined => {
59
67
  if (value == null) return undefined
60
68
  if (Array.isArray(value)) return normalizeColor(value[0])
@@ -64,25 +72,38 @@ const normalizeColor = (value: unknown): string | undefined => {
64
72
  if (!trimmed) return undefined
65
73
  if (trimmed === 'true' || trimmed === 'false') return trimmed
66
74
  if (DEFAULT_THEME.colors[trimmed]) return DEFAULT_THEME.colors[trimmed]
67
- if (trimmed.startsWith('#') || trimmed.startsWith('rgb') || trimmed.startsWith('hsl') || trimmed.startsWith('var(')) return trimmed
68
- if (/^[a-zA-Z0-9_-]+$/.test(trimmed)) return `var(--color-${trimmed})`
75
+ let lower = trimmed.toLowerCase()
76
+ if (trimmed.startsWith('#')) return trimmed
77
+ if (lower.startsWith('rgb') || lower.startsWith('hsl')) return trimmed
69
78
  return trimmed
70
79
  }
71
80
 
72
- const resolveColor = <T>(input: T): Readable<T | string | undefined> => {
81
+ export const resolveColor = <T>(input: T): Readable<T | string | undefined> => {
73
82
  if (isReadable<T | string | undefined>(input)) return input
74
83
  return readable(normalizeColor(input) as T | string | undefined)
75
84
  }
76
85
 
77
- const resolveColorsObject = (input: Record<string, unknown> | undefined): Readable<Record<string, string | undefined> | undefined> => {
86
+ export const resolveColorsObject = (input: Record<string, unknown> | string | undefined): Readable<Record<string, string | undefined> | undefined> => {
78
87
  if (isReadable<Record<string, string | undefined> | undefined>(input)) return input
79
88
  if (!input) return readable(undefined)
80
89
 
81
- let entries = Object.entries(input).map(([key, value]) => [key, normalizeColor(value)] as const)
90
+ let record: Record<string, unknown> | undefined = input as Record<string, unknown>
91
+ if (typeof input === 'string') {
92
+ let parsed = parseJson(input.trim())
93
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
94
+ record = parsed as Record<string, unknown>
95
+ } else {
96
+ return readable(undefined)
97
+ }
98
+ }
99
+
100
+ if (!record) return readable(undefined)
101
+
102
+ let entries = Object.entries(record).map(([key, value]) => [key, normalizeColor(value)] as const)
82
103
  return readable(Object.fromEntries(entries))
83
104
  }
84
105
 
85
- const resolveColorPalette = (input: unknown): Readable<string[] | undefined> => {
106
+ export const resolveColorPalette = (input: unknown): Readable<string[] | undefined> => {
86
107
  if (isReadable<string[] | undefined>(input)) return input
87
108
  if (input == null) return readable(DEFAULT_PALETTE)
88
109
 
@@ -90,6 +111,13 @@ const resolveColorPalette = (input: unknown): Readable<string[] | undefined> =>
90
111
  let key = input.trim()
91
112
  if (!key || key === 'default') return readable(DEFAULT_PALETTE)
92
113
  if (DEFAULT_THEME.colorPalettes[key]) return readable(DEFAULT_THEME.colorPalettes[key])
114
+ if (key.startsWith('[')) {
115
+ let parsed = parseJson(key)
116
+ if (Array.isArray(parsed)) {
117
+ let values = parsed.map(item => normalizeColor(item)).filter(Boolean) as string[]
118
+ return readable(values.length ? values : DEFAULT_PALETTE)
119
+ }
120
+ }
93
121
  if (key.includes(',')) {
94
122
  let values = key.split(',').map(part => normalizeColor(part)).filter(Boolean) as string[]
95
123
  return readable(values.length ? values : DEFAULT_PALETTE)
@@ -10,6 +10,7 @@
10
10
  getFormatObjectFromString,
11
11
  } from '../component-utilities/formatting.js'
12
12
  import {getThemeStores} from '../component-utilities/themeStores'
13
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
13
14
 
14
15
  const {resolveColor} = getThemeStores()
15
16
  const props = getContext(propKey)
@@ -72,12 +73,14 @@
72
73
  $: xMismatch = $props.xMismatch
73
74
  $: columnSummary = $props.columnSummary
74
75
  $: series = seriesSet ? series : $props.series
75
- $: resolvedY = ySet ? y : $props.y
76
+ $: resolvedY = ySet ? parseCommaList(y) : $props.y
77
+ $: seriesOrder = parseCommaList(seriesOrder)
76
78
 
77
79
  $: {
78
- if (!series && typeof resolvedY !== 'object') {
80
+ if (!series && (!Array.isArray(resolvedY) || resolvedY.length === 1)) {
79
81
  stackName = undefined
80
- if (columnSummary?.[resolvedY]) name = name ?? formatTitle(resolvedY, columnSummary[resolvedY].title)
82
+ let col = Array.isArray(resolvedY) ? resolvedY[0] : resolvedY
83
+ if (columnSummary?.[col]) name = name ?? formatTitle(col, columnSummary[col].title)
81
84
  } else {
82
85
  stackName = 'area'
83
86
  data = getCompletedData(data, x, resolvedY, series, false, xType === 'value')
@@ -3,6 +3,7 @@
3
3
  import Area from './Area.svelte'
4
4
  import QueryLoad from './QueryLoad.svelte'
5
5
  import {getThemeStores} from '../component-utilities/themeStores'
6
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
6
7
 
7
8
  const {resolveColor, resolveColorsObject, resolveColorPalette} = getThemeStores()
8
9
 
@@ -83,9 +84,10 @@
83
84
  export let xLabelWrap = undefined
84
85
  </script>
85
86
 
86
- <QueryLoad data={data} fields={[x, y, series]} let:loaded>
87
+ <QueryLoad data={data} fields={{x, y: parseCommaList(y), series}} let:loaded>
87
88
  <Chart
88
89
  data={loaded}
90
+ chartContext={{data, x, y, series}}
89
91
  {x}
90
92
  {y}
91
93
  {xFmt}
@@ -16,6 +16,7 @@
16
16
  getFormatObjectFromString,
17
17
  } from '../component-utilities/formatting.js'
18
18
  import {getThemeStores} from '../component-utilities/themeStores'
19
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
19
20
 
20
21
  const {resolveColor} = getThemeStores()
21
22
 
@@ -77,8 +78,8 @@
77
78
  // Prop check. If local props supplied, use those. Otherwise fall back to global props.
78
79
  $: data = $props.data
79
80
  $: x = $props.x
80
- $: y = ySet ? y : $props.y
81
- $: y2 = y2Set ? y2 : $props.y2
81
+ $: y = ySet ? parseCommaList(y) : $props.y
82
+ $: y2 = y2Set ? parseCommaList(y2) : $props.y2
82
83
  $: yFormat = $props.yFormat
83
84
  $: y2Format = $props.y2Format
84
85
  $: yCount = $props.yCount
@@ -89,14 +90,18 @@
89
90
  $: columnSummary = $props.columnSummary
90
91
  $: sort = $props.sort
91
92
  $: series = seriesSet ? series : $props.series
93
+ $: seriesOrder = parseCommaList(seriesOrder)
92
94
 
93
95
  let stackedData
94
96
  let sortOrder
95
97
  let defaultLabelPosition
96
98
 
97
- $: if (!series && typeof y !== 'object') {
99
+ $: if (!series && (!Array.isArray(y) || y.length === 1)) {
98
100
  // Single Series
99
- name = name ?? formatTitle(y, columnSummary[y].title)
101
+ {
102
+ let col = Array.isArray(y) ? y[0] : y
103
+ name = name ?? formatTitle(col, columnSummary[col].title)
104
+ }
100
105
 
101
106
  if (swapXY && xType !== 'category') {
102
107
  data = getCompletedData(data, x, y, series, true, xType !== 'time')
@@ -112,10 +117,11 @@
112
117
  if (sort === true && xType === 'category') {
113
118
  stackedData = getStackedData(data, x, y)
114
119
 
115
- if (typeof y === 'object') {
120
+ if (Array.isArray(y) && y.length > 1) {
116
121
  stackedData = getSortedData(stackedData, 'stackTotal', false)
117
122
  } else {
118
- stackedData = getSortedData(stackedData, y, false)
123
+ let col = Array.isArray(y) ? y[0] : y
124
+ stackedData = getSortedData(stackedData, col, false)
119
125
  }
120
126
 
121
127
  sortOrder = stackedData.map((d) => d[x])
@@ -233,7 +239,7 @@
233
239
  if (
234
240
  labels === true &&
235
241
  type === 'stacked' &&
236
- (typeof y === 'object') | (series !== undefined) &&
242
+ ((Array.isArray(y) && y.length > 1) || (series !== undefined)) &&
237
243
  stackTotalLabel === true &&
238
244
  series !== x
239
245
  ) {
@@ -309,7 +315,7 @@
309
315
  } else {
310
316
  d.yAxis[0] = {...d.yAxis[0], ...chartOverrides.yAxis}
311
317
  d.xAxis = {...d.xAxis, ...chartOverrides.xAxis}
312
- if (y2) {
318
+ if (y2Count > 0) {
313
319
  d.yAxis[1] = {...d.yAxis[1], show: true}
314
320
  if (['line', 'bar', 'scatter'].includes(y2SeriesType)) {
315
321
  for (let i = 0; i < y2Count; i++) {
@@ -3,6 +3,7 @@
3
3
  import Bar from './Bar.svelte'
4
4
  import QueryLoad from './QueryLoad.svelte'
5
5
  import {getThemeStores} from '../component-utilities/themeStores'
6
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
6
7
 
7
8
  const {resolveColor, resolveColorsObject, resolveColorPalette} = getThemeStores()
8
9
 
@@ -114,9 +115,10 @@
114
115
  export let xLabelWrap = undefined
115
116
  </script>
116
117
 
117
- <QueryLoad data={data} fields={[x, y, y2, series]} let:loaded>
118
+ <QueryLoad data={data} fields={{x, y: parseCommaList(y), y2: parseCommaList(y2), series}} let:loaded>
118
119
  <Chart
119
120
  data={loaded}
121
+ chartContext={{data, x, y, series}}
120
122
  {x}
121
123
  {y}
122
124
  {y2}
@@ -34,7 +34,7 @@
34
34
  }
35
35
  </script>
36
36
 
37
- <QueryLoad {data} fields={[value]} let:loaded>
37
+ <QueryLoad {data} fields={{value}} let:loaded>
38
38
  <div class="big-value">
39
39
  {#if title}<div class="big-value__title">{title}</div>{/if}
40
40
  {#if subtitle}<div class="big-value__subtitle">{subtitle}</div>{/if}
@@ -23,6 +23,8 @@
23
23
  import checkInputs from '../component-utilities/checkInputs.js'
24
24
  import {getThemeStores} from '../component-utilities/themeStores'
25
25
  import {toBoolean} from '../component-utilities/convert'
26
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
27
+ import {logError} from '../internal/telemetry.ts'
26
28
 
27
29
  const {theme, resolveColor, resolveColorsObject, resolveColorPalette} = getThemeStores()
28
30
 
@@ -31,6 +33,7 @@
31
33
  // ---------------------------------------------------------------------------------------
32
34
  // Data and columns:
33
35
  export let data = undefined
36
+ export let chartContext = undefined
34
37
  export let queryID = undefined
35
38
  export let x = undefined
36
39
  export let y = undefined
@@ -248,7 +251,10 @@
248
251
  inputCols = []
249
252
  optCols = []
250
253
  uColName = []
251
- ySet = y ? true : false
254
+ // Normalize list-like inputs first
255
+ y = parseCommaList(y)
256
+ y2 = parseCommaList(y2)
257
+ ySet = y.length > 0
252
258
  xSet = x ? true : false
253
259
 
254
260
  checkInputs(data) // check that dataset exists
@@ -284,7 +290,7 @@
284
290
  }
285
291
  }
286
292
 
287
- y = unusedColumns.length > 1 ? unusedColumns : unusedColumns[0]
293
+ y = unusedColumns // always array; empty handled by required prop checks
288
294
  }
289
295
  // Establish required columns based on chart type:
290
296
  if (bubble) {
@@ -320,40 +326,17 @@
320
326
  }
321
327
 
322
328
  // Fix for stacked100 overwriting y variable. Bandaid fix - not a long-term solution:
323
- if (stacked100 === true && y.includes('_pct') && originalRun === false) {
324
- if (typeof y === 'object') {
325
- for (let i = 0; i < y.length; i++) {
326
- y[i] = y[i].replace('_pct', '')
327
- }
328
- originalRun = false
329
- } else {
330
- y = y.replace('_pct', '')
331
- originalRun = false
332
- }
329
+ if (stacked100 === true && Array.isArray(y) && y.some(col => col.includes('_pct')) && originalRun === false) {
330
+ for (let i = 0; i < y.length; i++) y[i] = y[i].replace('_pct', '')
331
+ originalRun = false
333
332
  }
334
333
 
335
334
  // Check the inputs supplied to the chart:
336
335
  if (x) {
337
336
  inputCols.push(x)
338
337
  }
339
- if (y) {
340
- if (typeof y === 'object') {
341
- for (i = 0; i < y.length; i++) {
342
- inputCols.push(y[i])
343
- }
344
- } else {
345
- inputCols.push(y)
346
- }
347
- }
348
- if (y2) {
349
- if (typeof y2 === 'object') {
350
- for (i = 0; i < y2.length; i++) {
351
- inputCols.push(y2[i])
352
- }
353
- } else {
354
- inputCols.push(y2)
355
- }
356
- }
338
+ if (Array.isArray(y)) for (i = 0; i < y.length; i++) inputCols.push(y[i])
339
+ if (Array.isArray(y2)) for (i = 0; i < y2.length; i++) inputCols.push(y2[i])
357
340
  if (size) {
358
341
  inputCols.push(size)
359
342
  }
@@ -372,18 +355,8 @@
372
355
 
373
356
  if (stacked100 === true) {
374
357
  data = getStackPercentages(data, x, y)
375
-
376
- if (typeof y === 'object') {
377
- for (let i = 0; i < y.length; i++) {
378
- y[i] = y[i] + '_pct'
379
- }
380
- originalRun = false
381
- } else {
382
- y = y + '_pct'
383
- originalRun = false
384
- }
385
-
386
- // Re-run column summary for new columns (not ideal):
358
+ for (let i = 0; i < y.length; i++) y[i] = y[i] + '_pct'
359
+ originalRun = false
387
360
  columnSummary = getColumnSummary(data)
388
361
  }
389
362
 
@@ -426,7 +399,7 @@
426
399
  }
427
400
 
428
401
  // Throw error if attempting to plot secondary y-axis on horizontal chart:
429
- if (swapXY && y2) {
402
+ if (swapXY && y2.length) {
430
403
  throw Error(
431
404
  'Horizontal charts do not support a secondary y-axis. You can either set swapXY=false or remove the y2 prop from your chart.',
432
405
  )
@@ -444,7 +417,10 @@
444
417
  // Sort data based on xType
445
418
  // ---------------------------------------------------------------------------------------
446
419
  if (sort) {
447
- let sortColumn = xDataType === 'category' ? y : x
420
+ let sortColumn = x
421
+ if (xDataType === 'category') {
422
+ sortColumn = Array.isArray(y) ? (y[0] ?? x) : x
423
+ }
448
424
  let sortAscending = xDataType !== 'category'
449
425
  data = getSortedData(data, sortColumn, sortAscending)
450
426
  }
@@ -477,38 +453,16 @@
477
453
  xFormat = columnSummary[x].format
478
454
  }
479
455
 
480
- if (!y) {
456
+ if (y.length === 0) {
481
457
  yFormat = 'str'
482
458
  } else {
483
- if (yFmt) {
484
- if (typeof y === 'object') {
485
- yFormat = getFormatObjectFromString(yFmt, columnSummary[y[0]].format?.valueType)
486
- } else {
487
- yFormat = getFormatObjectFromString(yFmt, columnSummary[y].format?.valueType)
488
- }
489
- } else {
490
- if (typeof y === 'object') {
491
- yFormat = columnSummary[y[0]].format
492
- } else {
493
- yFormat = columnSummary[y].format
494
- }
495
- }
459
+ if (yFmt) yFormat = getFormatObjectFromString(yFmt, columnSummary[y[0]].format?.valueType)
460
+ else yFormat = columnSummary[y[0]].format
496
461
  }
497
462
 
498
- if (y2) {
499
- if (y2Fmt) {
500
- if (typeof y2 === 'object') {
501
- y2Format = getFormatObjectFromString(y2Fmt, columnSummary[y2[0]].format?.valueType)
502
- } else {
503
- y2Format = getFormatObjectFromString(y2Fmt, columnSummary[y2].format?.valueType)
504
- }
505
- } else {
506
- if (typeof y2 === 'object') {
507
- y2Format = columnSummary[y2[0]].format
508
- } else {
509
- y2Format = columnSummary[y2].format
510
- }
511
- }
463
+ if (y2.length) {
464
+ if (y2Fmt) y2Format = getFormatObjectFromString(y2Fmt, columnSummary[y2[0]].format?.valueType)
465
+ else y2Format = columnSummary[y2[0]].format
512
466
  }
513
467
 
514
468
  if (size) {
@@ -521,21 +475,9 @@
521
475
 
522
476
  xUnitSummary = columnSummary[x].columnUnitSummary
523
477
 
524
- if (y) {
525
- if (typeof y === 'object') {
526
- yUnitSummary = columnSummary[y[0]].columnUnitSummary
527
- } else {
528
- yUnitSummary = columnSummary[y].columnUnitSummary
529
- }
530
- }
478
+ if (y.length) yUnitSummary = columnSummary[y[0]].columnUnitSummary
531
479
 
532
- if (y2) {
533
- if (typeof y2 === 'object') {
534
- y2UnitSummary = columnSummary[y2[0]].columnUnitSummary
535
- } else {
536
- y2UnitSummary = columnSummary[y2].columnUnitSummary
537
- }
538
- }
480
+ if (y2.length) y2UnitSummary = columnSummary[y2[0]].columnUnitSummary
539
481
 
540
482
  if (xAxisTitle === 'true') {
541
483
  xAxisTitle = formatTitle(x, xFormat)
@@ -544,13 +486,21 @@
544
486
  }
545
487
 
546
488
  if (yAxisTitle === 'true') {
547
- yAxisTitle = typeof y === 'object' ? '' : formatTitle(y, yFormat)
489
+ if (y.length === 1) {
490
+ yAxisTitle = formatTitle(y[0], yFormat)
491
+ } else {
492
+ yAxisTitle = ''
493
+ }
548
494
  } else if (yAxisTitle === 'false') {
549
495
  yAxisTitle = ''
550
496
  }
551
497
 
552
498
  if (y2AxisTitle === 'true') {
553
- y2AxisTitle = typeof y2 === 'object' ? '' : formatTitle(y2, y2Format)
499
+ if (y2.length === 1) {
500
+ y2AxisTitle = formatTitle(y2[0], y2Format)
501
+ } else {
502
+ y2AxisTitle = ''
503
+ }
554
504
  } else if (y2AxisTitle === 'false') {
555
505
  y2AxisTitle = ''
556
506
  }
@@ -558,18 +508,13 @@
558
508
  // ---------------------------------------------------------------------------------------
559
509
  // Get total series count
560
510
  // ---------------------------------------------------------------------------------------
561
- let yCount = typeof y === 'object' ? y.length : 1
511
+ let yCount = y.length
562
512
  let seriesCount = series ? getDistinctCount(data, series) : 1
563
513
  let ySeriesCount = yCount * seriesCount
564
514
 
565
515
  // y2Count may need to be adjusted to also factor in the series column. For now, we really
566
516
  // only need to know that it's multi-series, so > 1 is sufficient
567
- let y2Count = 0
568
- if (typeof y2 === 'object') {
569
- y2Count = y2.length
570
- } else if (y2) {
571
- y2Count = 1
572
- }
517
+ let y2Count = y2.length
573
518
  let totalSeriesCount = ySeriesCount + y2Count
574
519
 
575
520
  // ---------------------------------------------------------------------------------------
@@ -592,15 +537,13 @@
592
537
  }
593
538
 
594
539
  let minYValue
595
- if (typeof y === 'object') {
540
+ if (y.length) {
596
541
  minYValue = columnSummary[y[0]].columnUnitSummary.min
597
542
  for (let i = 0; i < y.length; i++) {
598
543
  if (columnSummary[y[i]].columnUnitSummary.min < minYValue) {
599
544
  minYValue = columnSummary[y[i]].columnUnitSummary.min
600
545
  }
601
546
  }
602
- } else if (y) {
603
- minYValue = columnSummary[y].columnUnitSummary.min
604
547
  }
605
548
 
606
549
  if (yLog === true && minYValue <= 0 && minYValue !== null) {
@@ -745,7 +688,7 @@
745
688
  }
746
689
  } else {
747
690
  let primaryAxisColor = (() => {
748
- if (!y2) return undefined
691
+ if (!(Array.isArray(y2) && y2.length)) return undefined
749
692
  if ($yAxisColorStore === 'true') return $colorPaletteStore[0]
750
693
  if ($yAxisColorStore === 'false') return undefined
751
694
  return $yAxisColorStore
@@ -855,7 +798,7 @@
855
798
 
856
799
  hasTitle = title ? true : false
857
800
  hasSubtitle = subtitle ? true : false
858
- hasLegend = legend * (series !== null || (typeof y === 'object' && y.length > 1))
801
+ hasLegend = legend * (series !== null || (y.length > 1))
859
802
  hasTopAxisTitle = yAxisTitle !== '' && swapXY
860
803
  hasBottomAxisTitle = xAxisTitle !== '' && !swapXY
861
804
 
@@ -1044,6 +987,18 @@
1044
987
  error = e.message
1045
988
  let setTextRed = '\x1b[31m%s\x1b[0m'
1046
989
  console.error(setTextRed, `Error in ${chartType}: ${e.message}`)
990
+
991
+ // Make an "id" for the chart so its clear to users/agents exactly which caused an error.
992
+ let fieldStr = Object.entries(chartContext || {})
993
+ .filter(([_, val]) => {
994
+ if (Array.isArray(val)) return val.length > 0
995
+ if (typeof val === 'string') return val.trim().length > 0
996
+ return Boolean(val)
997
+ })
998
+ .map(([name, val]) => `${name}="${Array.isArray(val) ? val.join(', ') : val}"`)
999
+ let id = `${title || chartType} (${fieldStr.join(' ')})`
1000
+ logError(e, {id})
1001
+
1047
1002
  props.update((d) => {
1048
1003
  return {...d, error}
1049
1004
  })
@@ -1059,11 +1014,12 @@
1059
1014
  {width}
1060
1015
  {data}
1061
1016
  {queryID}
1017
+ chartTitle={title}
1062
1018
  {echartsOptions}
1063
1019
  {seriesOptions}
1064
1020
  {connectGroup}
1065
1021
  {xAxisLabelOverflow}
1066
- seriesColors={seriesColorsStore}
1022
+ seriesColors={$seriesColorsStore}
1067
1023
  />
1068
1024
  {:else}
1069
1025
  <ErrorChart {error} title={chartType} />
@@ -7,6 +7,7 @@
7
7
  import {propKey, strictBuild} from '../component-utilities/chartContext.js'
8
8
  import {getThemeStores} from '../component-utilities/themeStores'
9
9
  import {toBoolean} from '../component-utilities/convert'
10
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
10
11
 
11
12
  export let id: string
12
13
  export let description: string | undefined = undefined
@@ -64,6 +65,7 @@
64
65
  $: colorScaleStore = resolveColorPalette(colorScale)
65
66
 
66
67
  const props = getContext(propKey)
68
+ $: colorBreakpoints = parseCommaList(colorBreakpoints)
67
69
 
68
70
  const identifier = Symbol('GrapheneColumn')
69
71
 
@@ -16,6 +16,7 @@
16
16
  export let seriesColors: any = undefined
17
17
  export let connectGroup: string | undefined = undefined
18
18
  export let xAxisLabelOverflow: 'truncate' | 'break' | undefined = undefined
19
+ export let chartTitle: string | undefined = undefined
19
20
 
20
21
  const dispatch = createEventDispatcher()
21
22
  const isBrowser = typeof window !== 'undefined'
@@ -33,6 +34,7 @@
33
34
  {:else}
34
35
  <div
35
36
  class="echarts-chart"
37
+ data-chart-title={chartTitle ?? undefined}
36
38
  data-query-id={queryID}
37
39
  style={`height:${toDimension(height, '240px')};width:${toDimension(width, '100%')}`}
38
40
  use:echarts={{
@@ -8,6 +8,7 @@
8
8
  import {formatValue, getFormatObjectFromString} from '../component-utilities/formatting.js'
9
9
  import {getThemeStores} from '../component-utilities/themeStores'
10
10
  import {toBoolean} from '../component-utilities/convert'
11
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
11
12
 
12
13
  const {resolveColor} = getThemeStores()
13
14
  const props = getContext(propKey)
@@ -90,12 +91,14 @@
90
91
  $: xMismatch = $props.xMismatch
91
92
  $: columnSummary = $props.columnSummary
92
93
  $: series = seriesSet ? series : $props.series
93
- $: resolvedY = ySet ? y : $props.y
94
- $: resolvedY2 = y2Set ? y2 : $props.y2
94
+ $: resolvedY = ySet ? parseCommaList(y) : $props.y
95
+ $: resolvedY2 = y2Set ? parseCommaList(y2) : $props.y2
96
+ $: seriesOrder = parseCommaList(seriesOrder)
95
97
 
96
98
  $: {
97
- if (!series && typeof resolvedY !== 'object') {
98
- if (columnSummary?.[resolvedY]) name = name ?? formatTitle(resolvedY, columnSummary[resolvedY].title)
99
+ if (!series && (!Array.isArray(resolvedY) || resolvedY.length === 1)) {
100
+ let col = Array.isArray(resolvedY) ? resolvedY[0] : resolvedY
101
+ if (columnSummary?.[col]) name = name ?? formatTitle(col, columnSummary[col].title)
99
102
  } else {
100
103
  try {
101
104
  data = getCompletedData(data, x, resolvedY, series)
@@ -194,7 +197,7 @@
194
197
  } else {
195
198
  value.yAxis[0] = {...value.yAxis[0], ...chartOverrides.yAxis}
196
199
  value.xAxis = {...value.xAxis, ...chartOverrides.xAxis}
197
- if (resolvedY2) {
200
+ if (y2Count > 0) {
198
201
  value.yAxis[1] = {...value.yAxis[1], show: true}
199
202
  if (['line', 'bar', 'scatter'].includes(y2SeriesType)) {
200
203
  for (let index = 0; index < y2Count; index++) {
@@ -3,6 +3,7 @@
3
3
  import Line from './Line.svelte'
4
4
  import QueryLoad from './QueryLoad.svelte'
5
5
  import {getThemeStores} from '../component-utilities/themeStores'
6
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
6
7
 
7
8
  const {resolveColor, resolveColorsObject, resolveColorPalette} = getThemeStores()
8
9
 
@@ -98,10 +99,10 @@
98
99
  export let xLabelWrap = undefined
99
100
  </script>
100
101
 
101
-
102
- <QueryLoad data={data} fields={[x, y, y2, series]} let:loaded>
102
+ <QueryLoad data={data} fields={{x, y: parseCommaList(y), y2: parseCommaList(y2), series}} let:loaded>
103
103
  <Chart
104
104
  data={loaded}
105
+ chartContext={{data, x, y, series}}
105
106
  {x}
106
107
  {y}
107
108
  {y2}