@graphenedata/cli 0.0.15 → 0.0.16

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 (117) hide show
  1. package/README.md +138 -0
  2. package/dist/cli/bigQuery-I3F46SC6.js +75 -0
  3. package/dist/cli/bigQuery-I3F46SC6.js.map +7 -0
  4. package/dist/cli/chunk-OVWODUTJ.js +12849 -0
  5. package/dist/cli/chunk-OVWODUTJ.js.map +7 -0
  6. package/dist/cli/chunk-QAXEOZ43.js +53 -0
  7. package/dist/cli/chunk-QAXEOZ43.js.map +7 -0
  8. package/dist/cli/cli.js +234 -11197
  9. package/dist/cli/clickhouse-ZN5AN2UL.js +64 -0
  10. package/dist/cli/clickhouse-ZN5AN2UL.js.map +7 -0
  11. package/dist/cli/duckdb-IYBIO5KJ.js +87 -0
  12. package/dist/cli/duckdb-IYBIO5KJ.js.map +7 -0
  13. package/dist/cli/serve2-TNN5EROW.js +447 -0
  14. package/dist/cli/serve2-TNN5EROW.js.map +7 -0
  15. package/dist/cli/snowflake-MOQB5GA4.js +128 -0
  16. package/dist/cli/snowflake-MOQB5GA4.js.map +7 -0
  17. package/dist/index.d.ts +63 -0
  18. package/dist/lang/index.d.ts +63 -0
  19. package/dist/skills/graphene/SKILL.md +150 -96
  20. package/dist/skills/graphene/references/big-value.md +6 -41
  21. package/dist/skills/graphene/references/date-range.md +64 -0
  22. package/dist/skills/graphene/references/dropdown.md +3 -4
  23. package/dist/skills/graphene/references/echarts.md +162 -0
  24. package/dist/skills/graphene/references/gsql.md +55 -25
  25. package/dist/skills/graphene/references/model-gsql.md +72 -0
  26. package/dist/skills/graphene/references/table.md +13 -14
  27. package/dist/skills/graphene/references/text-input.md +2 -1
  28. package/dist/ui/app.css +239 -340
  29. package/dist/ui/component-utilities/dataShaping.ts +484 -0
  30. package/dist/ui/component-utilities/dataSummary.ts +57 -0
  31. package/dist/ui/component-utilities/enrich.ts +763 -0
  32. package/dist/ui/component-utilities/format.ts +177 -0
  33. package/dist/ui/component-utilities/inputUtils.ts +44 -8
  34. package/dist/ui/component-utilities/theme.ts +200 -0
  35. package/dist/ui/component-utilities/themeStores.ts +21 -8
  36. package/dist/ui/component-utilities/types.ts +70 -0
  37. package/dist/ui/components/AreaChart.svelte +57 -105
  38. package/dist/ui/components/BarChart.svelte +71 -129
  39. package/dist/ui/components/BigValue.svelte +24 -40
  40. package/dist/ui/components/Column.svelte +10 -18
  41. package/dist/ui/components/DateRange.svelte +54 -21
  42. package/dist/ui/components/Dropdown.svelte +47 -26
  43. package/dist/ui/components/DropdownOption.svelte +1 -2
  44. package/dist/ui/components/ECharts.svelte +181 -67
  45. package/dist/ui/components/InlineDelta.svelte +50 -31
  46. package/dist/ui/components/LineChart.svelte +54 -125
  47. package/dist/ui/components/PieChart.svelte +27 -37
  48. package/dist/ui/components/QueryLoad.svelte +77 -45
  49. package/dist/ui/components/Row.svelte +2 -1
  50. package/dist/ui/components/ScatterPlot.svelte +52 -0
  51. package/dist/ui/components/Skeleton.svelte +32 -0
  52. package/dist/ui/components/Table.svelte +3 -2
  53. package/dist/ui/components/TableGroupRow.svelte +28 -36
  54. package/dist/ui/components/TableHarness.svelte +32 -0
  55. package/dist/ui/components/TableHeader.svelte +34 -59
  56. package/dist/ui/components/TableRow.svelte +14 -38
  57. package/dist/ui/components/TableSubtotalRow.svelte +18 -21
  58. package/dist/ui/components/TableTotalRow.svelte +27 -37
  59. package/dist/ui/components/TextInput.svelte +13 -12
  60. package/dist/ui/components/Value.svelte +25 -0
  61. package/dist/ui/components/_Table.svelte +72 -70
  62. package/dist/ui/internal/ChartGallery.svelte +527 -0
  63. package/dist/ui/internal/ErrorDisplay.svelte +22 -97
  64. package/dist/ui/internal/LocalApp.svelte +80 -17
  65. package/dist/ui/internal/PageNavGroup.svelte +269 -0
  66. package/dist/ui/internal/Sidebar.svelte +178 -0
  67. package/dist/ui/internal/SidebarToggle.svelte +47 -0
  68. package/dist/ui/internal/StyleGallery.svelte +244 -0
  69. package/dist/ui/internal/clientCache.ts +2 -2
  70. package/dist/ui/internal/pageInputs.svelte.js +292 -0
  71. package/dist/ui/internal/queryEngine.ts +102 -117
  72. package/dist/ui/internal/runSocket.ts +32 -12
  73. package/dist/ui/internal/sidebar.svelte.js +18 -0
  74. package/dist/ui/internal/telemetry.ts +51 -16
  75. package/dist/ui/internal/types.d.ts +7 -0
  76. package/dist/ui/web.js +28 -11
  77. package/package.json +36 -38
  78. package/dist/skills/graphene/references/area-chart.md +0 -95
  79. package/dist/skills/graphene/references/bar-chart.md +0 -112
  80. package/dist/skills/graphene/references/line-chart.md +0 -108
  81. package/dist/skills/graphene/references/pie-chart.md +0 -29
  82. package/dist/skills/graphene/references/value-formats.md +0 -104
  83. package/dist/ui/component-utilities/autoFormatting.js +0 -280
  84. package/dist/ui/component-utilities/builtInFormats.js +0 -481
  85. package/dist/ui/component-utilities/chartContext.js +0 -12
  86. package/dist/ui/component-utilities/chartWindowDebug.js +0 -21
  87. package/dist/ui/component-utilities/checkInputs.js +0 -84
  88. package/dist/ui/component-utilities/convert.js +0 -15
  89. package/dist/ui/component-utilities/dateParsing.js +0 -56
  90. package/dist/ui/component-utilities/dropdownContext.ts +0 -1
  91. package/dist/ui/component-utilities/echarts.js +0 -252
  92. package/dist/ui/component-utilities/echartsThemes.js +0 -443
  93. package/dist/ui/component-utilities/formatTitle.js +0 -24
  94. package/dist/ui/component-utilities/formatting.js +0 -241
  95. package/dist/ui/component-utilities/getColumnExtents.js +0 -79
  96. package/dist/ui/component-utilities/getColumnSummary.js +0 -62
  97. package/dist/ui/component-utilities/getCompletedData.js +0 -122
  98. package/dist/ui/component-utilities/getDistinctCount.js +0 -7
  99. package/dist/ui/component-utilities/getDistinctValues.js +0 -15
  100. package/dist/ui/component-utilities/getSeriesConfig.js +0 -231
  101. package/dist/ui/component-utilities/getSortedData.js +0 -9
  102. package/dist/ui/component-utilities/getStackPercentages.js +0 -45
  103. package/dist/ui/component-utilities/getStackedData.js +0 -19
  104. package/dist/ui/component-utilities/getYAxisIndex.js +0 -15
  105. package/dist/ui/component-utilities/globalContexts.js +0 -1
  106. package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +0 -119
  107. package/dist/ui/component-utilities/replaceNulls.js +0 -16
  108. package/dist/ui/component-utilities/tableUtils.ts +0 -107
  109. package/dist/ui/component-utilities/tidyWithTypes.js +0 -9
  110. package/dist/ui/components/Area.svelte +0 -214
  111. package/dist/ui/components/Bar.svelte +0 -347
  112. package/dist/ui/components/Chart.svelte +0 -995
  113. package/dist/ui/components/Line.svelte +0 -227
  114. package/dist/ui/internal/NavSidebar.svelte +0 -396
  115. package/dist/ui/internal/theme.ts +0 -60
  116. package/dist/ui/public/inter-latin-ext.woff2 +0 -0
  117. package/dist/ui/public/inter-latin.woff2 +0 -0
@@ -1,13 +1,11 @@
1
1
  <script lang="ts">
2
- import {formatValue, getFormatObjectFromString} from '../component-utilities/formatting.js'
2
+ import {formatFromField} from '../component-utilities/format.ts'
3
3
  import {getThemeStores} from '../component-utilities/themeStores'
4
- import {toBoolean} from '../component-utilities/convert'
4
+ import {toBoolean} from '../component-utilities/inputUtils'
5
5
 
6
6
  interface Props {
7
7
  value?: number | string | null
8
- fmt?: string
9
- formatObject?: any
10
- columnUnitSummary?: any
8
+ field?: any
11
9
  downIsGood?: boolean
12
10
  showValue?: boolean
13
11
  showSymbol?: boolean
@@ -22,9 +20,7 @@
22
20
 
23
21
  let {
24
22
  value = undefined,
25
- fmt = undefined,
26
- formatObject = undefined,
27
- columnUnitSummary = undefined,
23
+ field = undefined,
28
24
  downIsGood: downIsGoodProp = false,
29
25
  showValue: showValueProp = true,
30
26
  showSymbol: showSymbolProp = true,
@@ -58,11 +54,6 @@
58
54
  return neutral
59
55
  }
60
56
 
61
- let symbol = $derived((() => {
62
- if (status === 'positive') return '▲'
63
- if (status === 'negative') return '▼'
64
- return '–'
65
- })())
66
57
  let symbolColor = $derived(pickColor(
67
58
  downIsGood ? $theme.colors.negative : $theme.colors.positive,
68
59
  downIsGood ? $theme.colors.positive : $theme.colors.negative,
@@ -75,13 +66,11 @@
75
66
  $theme.colors['base-content-muted'],
76
67
  ))
77
68
 
78
- let chipClass = $derived(pickColor('delta-chip--positive', 'delta-chip--negative', 'delta-chip--neutral'))
79
-
80
- let resolvedFormat = $derived((() => {
81
- if (formatObject) return formatObject
82
- if (fmt) return getFormatObjectFromString(fmt, 'number')
83
- return undefined
84
- })())
69
+ let chipClass = $derived(pickColor(
70
+ downIsGood ? 'delta-chip--negative' : 'delta-chip--positive',
71
+ downIsGood ? 'delta-chip--positive' : 'delta-chip--negative',
72
+ 'delta-chip--neutral',
73
+ ))
85
74
 
86
75
  let deltaClass = $derived((() => {
87
76
  let classes = ['delta']
@@ -95,7 +84,7 @@
95
84
  const renderValue = () => {
96
85
  if (numericValue === null) return '–'
97
86
  try {
98
- return formatValue(numericValue, resolvedFormat, columnUnitSummary)
87
+ return formatFromField(field, numericValue)
99
88
  } catch(error) {
100
89
  console.error('Failed to format delta value', error)
101
90
  return String(numericValue)
@@ -103,19 +92,33 @@
103
92
  }
104
93
  </script>
105
94
 
95
+ {#snippet deltaSymbol()}
96
+ {#if showSymbol}
97
+ <span class="delta-symbol" style={`color:${symbolColor}`} aria-hidden="true">
98
+ {#if status === 'positive'}
99
+ <svg viewBox="0 0 16 16" focusable="false">
100
+ <path d="M8 3 14 13H2Z" fill="currentColor" />
101
+ </svg>
102
+ {:else if status === 'negative'}
103
+ <svg viewBox="0 0 16 16" focusable="false">
104
+ <path d="M2 3h12L8 13Z" fill="currentColor" />
105
+ </svg>
106
+ {:else}
107
+ <span class="delta-symbol__neutral">–</span>
108
+ {/if}
109
+ </span>
110
+ {/if}
111
+ {/snippet}
112
+
106
113
  <span class={deltaClass} style={`text-align:${resolvedAlign}`}>
107
114
  {#if symbolPosition === 'left'}
108
- {#if showSymbol}
109
- <span class="delta-symbol" style={`color:${symbolColor}`}>{symbol}</span>
110
- {/if}
115
+ {@render deltaSymbol()}
111
116
  {/if}
112
117
  {#if showValue}
113
118
  <span class="delta-value" style={`color:${textColor}`}>{renderValue()}</span>
114
119
  {/if}
115
120
  {#if symbolPosition === 'right'}
116
- {#if showSymbol}
117
- <span class="delta-symbol" style={`color:${symbolColor}`}>{symbol}</span>
118
- {/if}
121
+ {@render deltaSymbol()}
119
122
  {/if}
120
123
  {#if text}
121
124
  <span class="delta-text">{text}</span>
@@ -135,6 +138,22 @@
135
138
  }
136
139
 
137
140
  .delta-symbol {
141
+ display: inline-flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ width: 0.75em;
145
+ height: 0.75em;
146
+ line-height: 1;
147
+ flex: 0 0 0.75em;
148
+ }
149
+
150
+ .delta-symbol svg {
151
+ display: block;
152
+ width: 100%;
153
+ height: 100%;
154
+ }
155
+
156
+ .delta-symbol__neutral {
138
157
  font-size: 0.75em;
139
158
  line-height: 1;
140
159
  }
@@ -153,13 +172,13 @@
153
172
  }
154
173
 
155
174
  .delta-chip--positive {
156
- background: rgba(22, 163, 74, 0.1);
157
- border-color: rgba(22, 163, 74, 0.2);
175
+ background: rgba(135, 166, 140, 0.15);
176
+ border-color: rgba(135, 166, 140, 0.3);
158
177
  }
159
178
 
160
179
  .delta-chip--negative {
161
- background: rgba(220, 38, 38, 0.1);
162
- border-color: rgba(220, 38, 38, 0.2);
180
+ background: rgba(184, 116, 112, 0.15);
181
+ border-color: rgba(184, 116, 112, 0.3);
163
182
  }
164
183
 
165
184
  .delta-chip--neutral {
@@ -1,136 +1,65 @@
1
1
  <script lang="ts">
2
- import type {Snippet} from 'svelte'
3
- import Chart from './Chart.svelte'
4
- import Line from './Line.svelte'
5
- import QueryLoad from './QueryLoad.svelte'
6
- import {getThemeStores} from '../component-utilities/themeStores'
2
+ import {untrack} from 'svelte'
3
+ import ECharts from './ECharts.svelte'
4
+ import {componentLogger, logExtraProps} from '../internal/telemetry.ts'
7
5
  import {parseCommaList} from '../component-utilities/inputUtils.ts'
6
+ import type {EChartsConfig, QueryResult, SeriesWithGroupingHint} from '../component-utilities/types.ts'
8
7
 
9
8
  interface Props {
10
- data?: any, x?: any, y?: any, y2?: any, series?: any, xType?: any, yLog?: any, yLogBase?: any
11
- y2SeriesType?: any, yFmt?: any, xFmt?: any, y2Fmt?: any, title?: any, subtitle?: any, legend?: any
12
- xAxisTitle?: any, yAxisTitle?: any, y2AxisTitle?: any, xGridlines?: any, yGridlines?: any
13
- y2Gridlines?: any, xAxisLabels?: any, yAxisLabels?: any, y2AxisLabels?: any, xBaseline?: any
14
- yBaseline?: any, y2Baseline?: any, xTickMarks?: any, yTickMarks?: any, y2TickMarks?: any
15
- yMin?: any, yMax?: any, yScale?: any, y2Min?: any, y2Max?: any, y2Scale?: any, sort?: any
16
- lineColor?: any, lineType?: any, lineWidth?: any, lineOpacity?: any, chartAreaHeight?: any
17
- markers?: any, markerShape?: any, markerSize?: any, handleMissing?: any, step?: any
18
- stepPosition?: any, colorPalette?: string, labels?: any, labelSize?: any, labelPosition?: any
19
- labelColor?: any, labelFmt?: any, yLabelFmt?: any, y2LabelFmt?: any, showAllLabels?: any
20
- yAxisColor?: any, y2AxisColor?: any, echartsOptions?: any, seriesOptions?: any, seriesColors?: any
21
- seriesOrder?: any, connectGroup?: any, seriesLabelFmt?: any, leftPadding?: any, rightPadding?: any
22
- xLabelWrap?: any, children?: Snippet
9
+ data: string | QueryResult
10
+ x: string
11
+ y: string
12
+ y2?: string
13
+ splitBy?: string
14
+ sort?: string
15
+ title?: string
16
+ height?: string | number
17
+ width?: string | number
23
18
  }
24
19
 
25
- const {resolveColor, resolveColorsObject, resolveColorPalette} = getThemeStores()
26
-
27
20
  let {
28
- data = undefined, x = undefined, y = undefined, y2 = undefined, series = undefined, xType = undefined,
29
- yLog = undefined, yLogBase = undefined, y2SeriesType = undefined, yFmt = undefined, xFmt = undefined,
30
- y2Fmt = undefined, title = undefined, subtitle = undefined, legend = undefined, xAxisTitle = undefined,
31
- yAxisTitle = undefined, y2AxisTitle = undefined, xGridlines = undefined, yGridlines = undefined,
32
- y2Gridlines = undefined, xAxisLabels = undefined, yAxisLabels = undefined, y2AxisLabels = undefined,
33
- xBaseline = undefined, yBaseline = undefined, y2Baseline = undefined, xTickMarks = undefined,
34
- yTickMarks = undefined, y2TickMarks = undefined, yMin = undefined, yMax = undefined, yScale = undefined,
35
- y2Min = undefined, y2Max = undefined, y2Scale = undefined, sort = undefined, lineColor = undefined,
36
- lineType = undefined, lineWidth = undefined, lineOpacity = undefined, chartAreaHeight = undefined,
37
- markers = undefined, markerShape = undefined, markerSize = undefined, handleMissing = undefined,
38
- step = undefined, stepPosition = undefined, colorPalette = 'default', labels = undefined,
39
- labelSize = undefined, labelPosition = undefined, labelColor = undefined, labelFmt = undefined,
40
- yLabelFmt = undefined, y2LabelFmt = undefined, showAllLabels = undefined, yAxisColor = undefined,
41
- y2AxisColor = undefined, echartsOptions = undefined, seriesOptions = undefined, seriesColors = undefined,
42
- seriesOrder = undefined, connectGroup = undefined, seriesLabelFmt = undefined, leftPadding = undefined,
43
- rightPadding = undefined, xLabelWrap = undefined, children,
44
- }: Props = $props()
21
+ data,
22
+ x,
23
+ y,
24
+ y2 = undefined,
25
+ splitBy = undefined,
26
+ sort = undefined,
27
+ title = undefined,
28
+ height = undefined,
29
+ width = undefined,
30
+ ...extraProps
31
+ }: Props & Record<string, unknown> = $props()
45
32
 
46
- let lineColorStore = $derived(resolveColor(lineColor))
47
- let colorPaletteStore = $derived(resolveColorPalette(colorPalette))
48
- let labelColorStore = $derived(resolveColor(labelColor))
49
- let yAxisColorStore = $derived(resolveColor(yAxisColor))
50
- let y2AxisColorStore = $derived(resolveColor(y2AxisColor))
51
- let seriesColorsStore = $derived(resolveColorsObject(seriesColors))
33
+ let logger = untrack(() => componentLogger('LineChart', {data: typeof data == 'string' ? data : undefined, x, y}))
34
+ untrack(() => logExtraProps(logger, 'LineChart', extraProps))
52
35
 
53
- let derivedYAxisTitle = $derived(yAxisTitle ?? (y2 ? 'true' : undefined))
54
- let derivedY2AxisTitle = $derived(y2AxisTitle ?? (y2 ? 'true' : undefined))
55
- </script>
36
+ function buildConfig(): EChartsConfig {
37
+ let yFields = parseCommaList(y)
38
+ if (splitBy && yFields.length > 1) throw new Error('LineChart does not support splitBy with multiple y fields')
39
+
40
+ let sortHint = typeof sort === 'string' && sort.trim().length > 0 ? {sort} : {}
41
+ let series: SeriesWithGroupingHint[]
42
+
43
+ if (splitBy) {
44
+ // "tall" data, one template split into one series per splitBy value by enrich()
45
+ series = [{type: 'line' as const, encode: {x, y: yFields[0], splitBy, ...sortHint}}]
46
+ } else {
47
+ // "wide" data, one line per field listed in y
48
+ series = yFields.map(field => ({type: 'line' as const, name: field, encode: {x, y: field, ...sortHint}}))
49
+ }
56
50
 
57
- {#snippet lineChartContent(loaded: any[])}
58
- <Chart
59
- data={loaded}
60
- chartContext={{data, x, y, series}}
61
- {x}
62
- {y}
63
- {y2}
64
- {xFmt}
65
- {yFmt}
66
- {y2Fmt}
67
- {series}
68
- {xType}
69
- {yLog}
70
- {yLogBase}
71
- {legend}
72
- {xAxisTitle}
73
- yAxisTitle={derivedYAxisTitle}
74
- y2AxisTitle={derivedY2AxisTitle}
75
- {xGridlines}
76
- {yGridlines}
77
- {y2Gridlines}
78
- {xAxisLabels}
79
- {yAxisLabels}
80
- {y2AxisLabels}
81
- {xBaseline}
82
- {yBaseline}
83
- {y2Baseline}
84
- {xTickMarks}
85
- {yTickMarks}
86
- {y2TickMarks}
87
- yAxisColor={yAxisColorStore}
88
- y2AxisColor={y2AxisColorStore}
89
- {yMin}
90
- {yMax}
91
- {yScale}
92
- {y2Min}
93
- {y2Max}
94
- {y2Scale}
95
- {title}
96
- {subtitle}
97
- chartType="Line Chart"
98
- {sort}
99
- {chartAreaHeight}
100
- colorPalette={colorPaletteStore}
101
- {echartsOptions}
102
- {seriesOptions}
103
- {connectGroup}
104
- seriesColors={seriesColorsStore}
105
- {leftPadding}
106
- {rightPadding}
107
- {xLabelWrap}
108
- >
109
- <Line
110
- lineColor={lineColorStore}
111
- {lineWidth}
112
- {lineOpacity}
113
- {lineType}
114
- {markers}
115
- {markerShape}
116
- {markerSize}
117
- {handleMissing}
118
- {step}
119
- {stepPosition}
120
- {labels}
121
- {labelSize}
122
- {labelPosition}
123
- labelColor={labelColorStore}
124
- {labelFmt}
125
- {yLabelFmt}
126
- {y2LabelFmt}
127
- {showAllLabels}
128
- {y2SeriesType}
129
- {seriesOrder}
130
- {seriesLabelFmt}
131
- />
132
- {@render children?.()}
133
- </Chart>
134
- {/snippet}
51
+ if (y2) series.push({type: 'line' as const, name: y2, yAxisIndex: 1, encode: {x, y: y2, ...sortHint}})
52
+
53
+ return {
54
+ title: title ? {text: title} : undefined,
55
+ tooltip: {trigger: 'axis'},
56
+ legend: {show: Boolean(splitBy || y2 || yFields.length > 1)},
57
+ xAxis: {},
58
+ yAxis: [{}, ...(y2 ? [{}] : [])],
59
+ series,
60
+ }
61
+ }
62
+
63
+ </script>
135
64
 
136
- <QueryLoad data={data} fields={{x, y: parseCommaList(y), y2: parseCommaList(y2), series}} children={lineChartContent} />
65
+ <ECharts data={data} config={buildConfig()} {height} {width} componentId={logger.id} />
@@ -1,47 +1,37 @@
1
1
  <script lang="ts">
2
+ import {untrack} from 'svelte'
2
3
  import ECharts from './ECharts.svelte'
3
- import QueryLoad from './QueryLoad.svelte'
4
+ import type {EChartsConfig, QueryResult} from '../component-utilities/types.ts'
5
+ import {componentLogger, logExtraProps} from '../internal/telemetry.ts'
4
6
 
5
7
  interface Props {
6
- data: any
7
- category: any
8
- value: any
9
- title?: any
10
- subtitle?: any
11
- printEchartsConfig?: any
12
- echartsOptions?: any
13
- seriesOptions?: any
14
- seriesColors?: any
8
+ data: string | QueryResult
9
+ category: string
10
+ value: string
11
+ title?: string
12
+ height?: string | number
13
+ width?: string | number
15
14
  }
16
15
 
17
16
  let {
18
- data, category, value, title = undefined, subtitle = undefined, printEchartsConfig = undefined,
19
- echartsOptions = undefined, seriesOptions = undefined, seriesColors = undefined,
20
- }: Props = $props()
17
+ data,
18
+ category,
19
+ value,
20
+ title = undefined,
21
+ height = undefined,
22
+ width = undefined,
23
+ ...extraProps
24
+ }: Props & Record<string, unknown> = $props()
21
25
 
22
- // printEchartsConfig is intentionally unused - it's a debug prop that
23
- // users can pass but we don't implement it yet. Using $derived to read
24
- // it reactively and suppress state_referenced_locally warning.
25
- $effect(() => { void printEchartsConfig })
26
- </script>
26
+ let logger = untrack(() => componentLogger('PieChart', {data: typeof data == 'string' ? data : undefined, category, value}))
27
+ untrack(() => logExtraProps(logger, 'PieChart', extraProps))
27
28
 
28
- {#snippet pieChartContent(loaded: any[])}
29
- <ECharts data={loaded} {echartsOptions} {seriesOptions} {seriesColors} config={{
30
- title: {
31
- text: title,
32
- subtext: subtitle,
33
- },
34
- tooltip: {
35
- formatter: '{b}: {c} ({d}%)',
36
- },
37
- series: [
38
- {
39
- type: 'pie',
40
- radius: ['40%', '70%'],
41
- data: [...loaded.map(r => ({name: r[category], value: r[value]}))],
42
- },
43
- ],
44
- }} />
45
- {/snippet}
29
+ function buildConfig(): EChartsConfig {
30
+ return {
31
+ title: title ? {text: title} : undefined,
32
+ series: [{type: 'pie', encode: {itemName: category, value}}],
33
+ }
34
+ }
35
+ </script>
46
36
 
47
- <QueryLoad data={data} fields={{category, value}} children={pieChartContent} />
37
+ <ECharts data={data} config={buildConfig()} {height} {width} componentId={logger.id} />
@@ -1,30 +1,40 @@
1
1
  <script lang="ts">
2
- import {onDestroy, onMount, type Snippet} from 'svelte'
2
+ import {onDestroy, onMount, untrack, type Snippet} from 'svelte'
3
+ import type {GrapheneError} from '../../lang/index.d.ts'
3
4
  import ErrorDisplay from '../internal/ErrorDisplay.svelte'
5
+ import type {QueryResult} from '../component-utilities/types.ts'
6
+ import {componentLogger} from '../internal/telemetry.ts'
7
+ import Skeleton from './Skeleton.svelte'
4
8
 
5
9
  interface Props {
6
- data: string | {rows?: any[]}
10
+ data: string | QueryResult
7
11
  height?: number
8
12
  fields?: Record<string, string | string[]>
9
- children?: Snippet<[any[]]>
13
+ inline?: boolean
14
+ componentId?: string
15
+ children?: Snippet<[QueryResult]>
10
16
  }
11
17
 
12
- let {data, height = 200, fields = {}, children}: Props = $props()
18
+ let {data, height = 200, fields = {}, inline = false, componentId = undefined, children}: Props = $props()
19
+ let logger = untrack(() => componentLogger(componentId || 'QueryLoad', componentId ? {} : {data: typeof data == 'string' ? data : undefined, ...fields}))
13
20
 
14
- let errors: Error[] | null = $state(null)
15
- let loaded: any[] | null = $state(null)
21
+ let error: GrapheneError | null = $state(null)
22
+ let loaded: QueryResult | null = $state(null)
23
+ let tooltipId = `query-error-${Math.random().toString(36).slice(2)}`
16
24
 
17
- let handleResults = (result: any) => {
18
- errors = result.errors || null
19
- loaded = result.rows
25
+ let handleResults = (result: QueryResult) => {
26
+ error = result?.error || null
27
+ loaded = {rows: result?.rows ?? [], fields: result?.fields ?? [], error: result?.error, sql: result?.sql}
28
+ if (result?.error) logger.error(result.error, {...result.error, componentId: logger.id})
20
29
  }
21
30
 
22
31
  onMount(() => {
23
32
  if (typeof data !== 'string') {
24
- loaded = data.rows ?? null
33
+ error = data.error || null
34
+ loaded = {rows: data.rows ?? [], fields: data.fields ?? [], error: data.error, sql: data.sql}
25
35
  } else {
26
36
  let usedFields = Object.fromEntries(Object.entries(fields).filter(e => !!e[1]))
27
- window.$GRAPHENE.query(data, usedFields, handleResults)
37
+ window.$GRAPHENE.query(data, usedFields, handleResults, logger.id)
28
38
  }
29
39
  })
30
40
 
@@ -33,47 +43,28 @@
33
43
  })
34
44
  </script>
35
45
 
36
- {#if errors}
37
- <div style="min-height:{height}px;width:100%;display:grid;align-content:center;padding:8px;box-sizing:border-box">
38
- <ErrorDisplay error={errors[0]} />
39
- </div>
46
+ {#if error}
47
+ {#if inline}
48
+ <span class="inline-error">
49
+ <button class="inline-error__icon" type="button" aria-label="Query failed" aria-describedby={tooltipId}>!</button>
50
+ <span class="inline-error__tooltip" id={tooltipId} role="tooltip">
51
+ <ErrorDisplay {error} />
52
+ </span>
53
+ </span>
54
+ {:else}
55
+ <div style="min-height:{height}px;width:100%;display:grid;align-content:center;padding:8px;box-sizing:border-box">
56
+ <ErrorDisplay {error} />
57
+ </div>
58
+ {/if}
40
59
  {:else if !loaded}
41
- <div class='ql-skeleton' style={`height:${height}px`} role="status" aria-live="polite">
42
- <span class="ql-skeleton__pulse"></span>
43
- </div>
44
- {:else if loaded.length == 0}
60
+ <Skeleton />
61
+ {:else if loaded.rows.length == 0}
45
62
  <div class="empty-chart" role="note">Dataset is empty - query ran successfully, but no data was returned from the database</div>
46
63
  {:else}
47
64
  {@render children?.(loaded)}
48
65
  {/if}
49
66
 
50
67
  <style>
51
- .ql-skeleton {
52
- width: 100%;
53
- position: relative;
54
- overflow: hidden;
55
- background: var(--chart-skeleton-bg, #f3f4f6);
56
- border-radius: 4px;
57
- }
58
-
59
- .ql-skeleton__pulse {
60
- position: absolute;
61
- inset: 0;
62
- transform: translateX(-100%);
63
- background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0) 100%);
64
- animation: ql-pulse 1.4s ease-in-out infinite;
65
- content: '';
66
- }
67
-
68
- @keyframes ql-pulse {
69
- 0% {
70
- transform: translateX(-100%);
71
- }
72
- 100% {
73
- transform: translateX(100%);
74
- }
75
- }
76
-
77
68
  .empty-chart {
78
69
  width: 100%;
79
70
  padding: 12px;
@@ -85,4 +76,45 @@
85
76
  text-align: center;
86
77
  background: rgba(243, 244, 246, 0.6);
87
78
  }
79
+
80
+ .inline-error {
81
+ position: relative;
82
+ display: inline-flex;
83
+ align-items: center;
84
+ vertical-align: middle;
85
+ }
86
+
87
+ .inline-error__icon {
88
+ width: 1.05em;
89
+ height: 1.05em;
90
+ padding: 0;
91
+ display: inline-flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ border: 1px solid var(--graphene-error-border, #ef4444);
95
+ border-radius: 999px;
96
+ background: var(--graphene-error-background, #fef2f2);
97
+ color: var(--graphene-error-content-strong, #b91c1c);
98
+ cursor: help;
99
+ font: inherit;
100
+ font-size: 0.75em;
101
+ font-weight: 700;
102
+ line-height: 1;
103
+ }
104
+
105
+ .inline-error__tooltip {
106
+ display: none;
107
+ position: absolute;
108
+ z-index: 1000;
109
+ top: calc(100% + 8px);
110
+ left: 0;
111
+ width: min(420px, 80vw);
112
+ text-align: left;
113
+ filter: drop-shadow(0 8px 18px rgba(15, 23, 42, 0.18));
114
+ }
115
+
116
+ .inline-error:hover .inline-error__tooltip,
117
+ .inline-error:focus-within .inline-error__tooltip {
118
+ display: block;
119
+ }
88
120
  </style>
@@ -4,11 +4,12 @@
4
4
  div {
5
5
  display: flex;
6
6
  flex-direction: row;
7
- gap: 1rem;
7
+ gap: 1.5rem;
8
8
  }
9
9
 
10
10
  /* Ensure styles apply to slotted children (not scoped) */
11
11
  div > :global(*) {
12
12
  flex: 1 1 0;
13
+ min-width: 0; /* avoids some rendering instability */
13
14
  }
14
15
  </style>
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ import ECharts from './ECharts.svelte'
3
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
4
+ import type {EChartsConfig, QueryResult, SeriesWithGroupingHint} from '../component-utilities/types.ts'
5
+
6
+ interface Props {
7
+ data: string | QueryResult
8
+ x: string
9
+ y: string
10
+ splitBy?: string
11
+ title?: string
12
+ height?: string | number
13
+ width?: string | number
14
+ }
15
+
16
+ let {
17
+ data,
18
+ x,
19
+ y,
20
+ splitBy = undefined,
21
+ title = undefined,
22
+ height = undefined,
23
+ width = undefined,
24
+ }: Props = $props()
25
+
26
+ function buildConfig(): EChartsConfig {
27
+ let yFields = parseCommaList(y)
28
+ if (splitBy && yFields.length > 1) throw new Error('ScatterPlot does not support splitBy with multiple y fields')
29
+
30
+ let series: SeriesWithGroupingHint[]
31
+
32
+ if (splitBy) {
33
+ // "tall" data, one template split into one series per splitBy value by enrich()
34
+ series = [{type: 'scatter' as const, encode: {x, y: yFields[0], splitBy}}]
35
+ } else {
36
+ // "wide" data, one scatter series per field listed in y
37
+ series = yFields.map(field => ({type: 'scatter' as const, name: field, encode: {x, y: field}}))
38
+ }
39
+
40
+ return {
41
+ title: title ? {text: title} : undefined,
42
+ tooltip: {trigger: 'item'},
43
+ legend: {show: Boolean(splitBy || yFields.length > 1)},
44
+ grid: {left: 56, bottom: 52},
45
+ xAxis: {name: x, nameLocation: 'middle', nameGap: 28},
46
+ yAxis: {name: y, nameLocation: 'middle', nameGap: 40},
47
+ series,
48
+ }
49
+ }
50
+ </script>
51
+
52
+ <ECharts data={data} config={buildConfig()} {height} {width} />
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ let {height}: {height: number} = $props()
3
+ </script>
4
+
5
+ <div class='ql-skeleton' style={`height:${height}px`} role="status" aria-live="polite">
6
+ <span class="ql-skeleton__pulse"></span>
7
+ </div>
8
+
9
+ <style>
10
+ .ql-skeleton {
11
+ width: 100%;
12
+ height: 100%;
13
+ position: relative;
14
+ overflow: hidden;
15
+ background: var(--chart-skeleton-bg, #f3f4f6);
16
+ border-radius: 4px;
17
+ }
18
+
19
+ .ql-skeleton__pulse {
20
+ position: absolute;
21
+ inset: 0;
22
+ transform: translateX(-100%);
23
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0) 100%);
24
+ animation: ql-pulse 1.4s ease-in-out infinite;
25
+ content: '';
26
+ }
27
+
28
+ @keyframes ql-pulse {
29
+ 0% { transform: translateX(-100%); }
30
+ 100% { transform: translateX(100%); }
31
+ }
32
+ </style>