@graphenedata/cli 0.0.14 → 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 (121) hide show
  1. package/LICENSE.md +3 -3
  2. package/README.md +138 -0
  3. package/THIRD_PARTY_NOTICES.md +1 -0
  4. package/bin.js +2 -2
  5. package/dist/cli/bigQuery-I3F46SC6.js +75 -0
  6. package/dist/cli/bigQuery-I3F46SC6.js.map +7 -0
  7. package/dist/cli/chunk-OVWODUTJ.js +12849 -0
  8. package/dist/cli/chunk-OVWODUTJ.js.map +7 -0
  9. package/dist/cli/chunk-QAXEOZ43.js +53 -0
  10. package/dist/cli/chunk-QAXEOZ43.js.map +7 -0
  11. package/dist/cli/cli.js +245 -10290
  12. package/dist/cli/clickhouse-ZN5AN2UL.js +64 -0
  13. package/dist/cli/clickhouse-ZN5AN2UL.js.map +7 -0
  14. package/dist/cli/duckdb-IYBIO5KJ.js +87 -0
  15. package/dist/cli/duckdb-IYBIO5KJ.js.map +7 -0
  16. package/dist/cli/serve2-TNN5EROW.js +447 -0
  17. package/dist/cli/serve2-TNN5EROW.js.map +7 -0
  18. package/dist/cli/snowflake-MOQB5GA4.js +128 -0
  19. package/dist/cli/snowflake-MOQB5GA4.js.map +7 -0
  20. package/dist/index.d.ts +63 -0
  21. package/dist/lang/index.d.ts +63 -0
  22. package/dist/skills/graphene/SKILL.md +235 -0
  23. package/dist/skills/graphene/references/big-value.md +20 -0
  24. package/dist/skills/graphene/references/date-range.md +64 -0
  25. package/dist/skills/graphene/references/dropdown.md +62 -0
  26. package/dist/skills/graphene/references/echarts.md +162 -0
  27. package/dist/skills/graphene/references/gsql.md +393 -0
  28. package/dist/skills/graphene/references/model-gsql.md +72 -0
  29. package/dist/skills/graphene/references/table.md +143 -0
  30. package/dist/skills/graphene/references/text-input.md +29 -0
  31. package/dist/ui/app.css +263 -299
  32. package/dist/ui/component-utilities/dataShaping.ts +484 -0
  33. package/dist/ui/component-utilities/dataSummary.ts +57 -0
  34. package/dist/ui/component-utilities/enrich.ts +763 -0
  35. package/dist/ui/component-utilities/format.ts +177 -0
  36. package/dist/ui/component-utilities/inputUtils.ts +48 -9
  37. package/dist/ui/component-utilities/theme.ts +200 -0
  38. package/dist/ui/component-utilities/themeStores.ts +26 -21
  39. package/dist/ui/component-utilities/types.ts +70 -0
  40. package/dist/ui/components/AreaChart.svelte +57 -105
  41. package/dist/ui/components/BarChart.svelte +71 -129
  42. package/dist/ui/components/BigValue.svelte +24 -40
  43. package/dist/ui/components/Column.svelte +11 -19
  44. package/dist/ui/components/DateRange.svelte +71 -34
  45. package/dist/ui/components/Dropdown.svelte +82 -49
  46. package/dist/ui/components/DropdownOption.svelte +1 -2
  47. package/dist/ui/components/ECharts.svelte +179 -60
  48. package/dist/ui/components/InlineDelta.svelte +51 -32
  49. package/dist/ui/components/LineChart.svelte +54 -125
  50. package/dist/ui/components/PieChart.svelte +27 -37
  51. package/dist/ui/components/QueryLoad.svelte +78 -44
  52. package/dist/ui/components/Row.svelte +2 -1
  53. package/dist/ui/components/ScatterPlot.svelte +52 -0
  54. package/dist/ui/components/Skeleton.svelte +32 -0
  55. package/dist/ui/components/Table.svelte +3 -2
  56. package/dist/ui/components/TableGroupRow.svelte +28 -36
  57. package/dist/ui/components/TableHarness.svelte +32 -0
  58. package/dist/ui/components/TableHeader.svelte +34 -59
  59. package/dist/ui/components/TableRow.svelte +15 -39
  60. package/dist/ui/components/TableSubtotalRow.svelte +26 -21
  61. package/dist/ui/components/TableTotalRow.svelte +27 -37
  62. package/dist/ui/components/TextInput.svelte +17 -14
  63. package/dist/ui/components/Value.svelte +25 -0
  64. package/dist/ui/components/_Table.svelte +80 -76
  65. package/dist/ui/internal/ChartGallery.svelte +527 -0
  66. package/dist/ui/internal/ErrorDisplay.svelte +60 -0
  67. package/dist/ui/internal/LocalApp.svelte +87 -19
  68. package/dist/ui/internal/PageNavGroup.svelte +269 -0
  69. package/dist/ui/internal/Sidebar.svelte +178 -0
  70. package/dist/ui/internal/SidebarToggle.svelte +47 -0
  71. package/dist/ui/internal/StyleGallery.svelte +244 -0
  72. package/dist/ui/internal/clientCache.ts +15 -13
  73. package/dist/ui/internal/pageInputs.svelte.js +292 -0
  74. package/dist/ui/internal/queryEngine.ts +124 -132
  75. package/dist/ui/internal/runSocket.ts +59 -0
  76. package/dist/ui/internal/sidebar.svelte.js +18 -0
  77. package/dist/ui/internal/telemetry.ts +52 -17
  78. package/dist/ui/internal/types.d.ts +7 -0
  79. package/dist/ui/web.js +55 -13
  80. package/package.json +40 -41
  81. package/dist/docs/agent-instructions.md +0 -18
  82. package/dist/docs/base.md +0 -98
  83. package/dist/docs/cli.md +0 -22
  84. package/dist/docs/graphene.md +0 -1462
  85. package/dist/ui/component-utilities/autoFormatting.js +0 -301
  86. package/dist/ui/component-utilities/builtInFormats.js +0 -482
  87. package/dist/ui/component-utilities/chartContext.js +0 -12
  88. package/dist/ui/component-utilities/chartWindowDebug.js +0 -21
  89. package/dist/ui/component-utilities/checkInputs.js +0 -95
  90. package/dist/ui/component-utilities/convert.js +0 -15
  91. package/dist/ui/component-utilities/dateParsing.js +0 -57
  92. package/dist/ui/component-utilities/dropdownContext.ts +0 -1
  93. package/dist/ui/component-utilities/echarts.js +0 -272
  94. package/dist/ui/component-utilities/echartsThemes.js +0 -453
  95. package/dist/ui/component-utilities/formatTitle.js +0 -24
  96. package/dist/ui/component-utilities/formatting.js +0 -250
  97. package/dist/ui/component-utilities/getColumnExtents.js +0 -79
  98. package/dist/ui/component-utilities/getColumnSummary.js +0 -67
  99. package/dist/ui/component-utilities/getCompletedData.js +0 -114
  100. package/dist/ui/component-utilities/getDistinctCount.js +0 -7
  101. package/dist/ui/component-utilities/getDistinctValues.js +0 -15
  102. package/dist/ui/component-utilities/getSeriesConfig.js +0 -237
  103. package/dist/ui/component-utilities/getSortedData.js +0 -7
  104. package/dist/ui/component-utilities/getStackPercentages.js +0 -43
  105. package/dist/ui/component-utilities/getStackedData.js +0 -17
  106. package/dist/ui/component-utilities/getYAxisIndex.js +0 -15
  107. package/dist/ui/component-utilities/globalContexts.js +0 -1
  108. package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +0 -119
  109. package/dist/ui/component-utilities/replaceNulls.js +0 -14
  110. package/dist/ui/component-utilities/tableUtils.ts +0 -120
  111. package/dist/ui/components/Area.svelte +0 -214
  112. package/dist/ui/components/Bar.svelte +0 -350
  113. package/dist/ui/components/Chart.svelte +0 -989
  114. package/dist/ui/components/ErrorChart.svelte +0 -118
  115. package/dist/ui/components/Line.svelte +0 -227
  116. package/dist/ui/internal/NavSidebar.svelte +0 -396
  117. package/dist/ui/internal/PageError.svelte +0 -23
  118. package/dist/ui/internal/checkSocket.ts +0 -48
  119. package/dist/ui/internal/theme.ts +0 -88
  120. package/dist/ui/public/inter-latin-ext.woff2 +0 -0
  121. package/dist/ui/public/inter-latin.woff2 +0 -0
@@ -1,86 +1,205 @@
1
1
  <script lang="ts">
2
- import echarts from '../component-utilities/echarts.js'
3
- import {getThemeStores} from '../component-utilities/themeStores'
2
+ import {init} from 'echarts'
3
+ import {onDestroy, onMount, untrack} from 'svelte'
4
+ import ErrorDisplay from '../internal/ErrorDisplay.svelte'
5
+ import {componentLogger, logExtraProps} from '../internal/telemetry.ts'
6
+ import {enrich, horizontalBarCount} from '../component-utilities/enrich.ts'
7
+ import type {EChartsConfig, NormalConfig, QueryResult} from '../component-utilities/types.ts'
8
+ import '../component-utilities/theme.ts'
9
+ import Skeleton from './Skeleton.svelte'
4
10
 
5
11
  interface Props {
6
- config: any
12
+ config: EChartsConfig
13
+ data: string | QueryResult
7
14
  height?: string | number
8
15
  width?: string | number
9
- data: any
10
- queryID?: any
11
16
  renderer?: 'canvas' | 'svg'
12
- echartsOptions?: any
13
- seriesOptions?: any
14
- seriesColors?: any
15
- connectGroup?: string
16
- xAxisLabelOverflow?: 'truncate' | 'break'
17
- chartTitle?: string
18
- onclick?: (params: any) => void
17
+ componentId?: string
19
18
  }
20
19
 
21
- const {activeAppearance} = getThemeStores()
22
-
23
20
  let {
24
- config, height = '240px', width = '100%', data, queryID = undefined, renderer = undefined,
25
- echartsOptions = undefined, seriesOptions = undefined, seriesColors = undefined,
26
- connectGroup = undefined, xAxisLabelOverflow = undefined, chartTitle = undefined, onclick = undefined,
27
- }: Props = $props()
21
+ config = {},
22
+ data,
23
+ height = undefined,
24
+ width = '100%',
25
+ renderer = 'svg',
26
+ componentId = undefined,
27
+ ...extraProps
28
+ }: Props & Record<string, unknown> = $props()
29
+
30
+ config ||= {}
31
+
32
+ let queryFieldsForLogger = untrack(() => typeof data == 'string' ? queryFields(config) : {})
33
+ let chartLogger = untrack(() => componentLogger(componentId || 'ECharts', componentId ? {} : {data: typeof data == 'string' ? data : undefined, ...queryFieldsForLogger}))
34
+ let displayId = untrack(() => componentId || chartLogger.id)
35
+ untrack(() => logExtraProps(chartLogger, 'ECharts', extraProps))
36
+
37
+ // not state, because we don't want `$effect` to run when they change
38
+ let node: HTMLDivElement | null = null
39
+ let chart: any
40
+ let resizeObserver: ResizeObserver | null = null
41
+
42
+ // Use `raw` because data can be big, and there's little upside to making it reactive
43
+ let loaded = $state.raw<QueryResult | null>(null)
44
+ let chartError: Error | null = $state(null)
45
+ let mountedComponentId: string | null = $state(displayId)
46
+ let chartTitle: string | undefined = $state(undefined)
47
+ let chartSizeStyle: string = $state(calculateChartSize())
48
+
49
+ function handleResults (res: QueryResult) {
50
+ chartError = null
51
+ loaded = res
52
+ if (res?.error) chartLogger.error(res.error, {...res.error, componentId: displayId})
53
+ }
54
+
55
+ // If `data` is just a string, kick off a query to fetch the data.
56
+ // This maybe could be an effect, but we'd have to ensure we don't double-subscribe.
57
+ onMount(() => {
58
+ resizeObserver = new ResizeObserver(() => chart?.resize())
59
+ if (node) resizeObserver.observe(node)
60
+
61
+ if (typeof data == 'string') {
62
+ try {
63
+ mountedComponentId = window.$GRAPHENE.query(data, queryFieldsForLogger, handleResults, displayId)
64
+ } catch (error) {
65
+ chartError = error instanceof Error ? error : new Error(String(error))
66
+ }
67
+ } else {
68
+ loaded = data
69
+ }
70
+ })
71
+
72
+ onDestroy(() => {
73
+ resizeObserver?.disconnect()
74
+ resizeObserver = null
75
+ window.$GRAPHENE.unsubscribe(handleResults)
76
+ destroyChart()
77
+ })
78
+
79
+ $effect(() => {
80
+ if (chartError) return
81
+
82
+ if (!loaded || loaded.error || loaded.rows.length == 0) {
83
+ destroyChart()
84
+ return
85
+ }
86
+
87
+ if (!chart) {
88
+ chart = init(node, 'graphene-theme', {renderer})
89
+ chart.on('legendselectchanged', renderChart)
90
+ }
91
+
92
+ try {
93
+ window.$GRAPHENE?.renderStart?.(`chart:${chart.id}`)
94
+ renderChart()
95
+ chartError = null
96
+ window.$GRAPHENE?.renderComplete?.(`chart:${chart.id}`)
97
+ } catch (error) {
98
+ console.error('Chart failed to render', error)
99
+ chartError = error instanceof Error ? error : new Error(String(error))
100
+ chartLogger.error(chartError, {componentId: displayId})
101
+ window.$GRAPHENE?.renderComplete?.(`chart:${chart.id}`)
102
+ destroyChart()
103
+ }
104
+ })
105
+
106
+ // Build a fresh enriched option each render so legend-driven stack rounding
107
+ // always reflects the currently visible series.
108
+ function renderChart() {
109
+ if (!chart || !loaded) return
110
+
111
+ // clone config, since enriching mutates the config, and mutating a prop is weird
112
+ // structuredClone doesn't like proxies, so use state.snapshot
113
+ let cloned = structuredClone($state.snapshot(config)) as EChartsConfig
114
+ let rows = loaded.rows
115
+ let fields = loaded.fields || []
116
+ cloned.legendSelection = chart.getOption()?.legend?.[0]?.selected
117
+ let enriched = enrich(cloned, rows, fields)
118
+
119
+ chartTitle = enriched.title.find(t => t?.text)?.text
120
+ chartSizeStyle = calculateChartSize(enriched, rows, fields)
121
+ chart.setOption({...enriched, animation: false, animationDuration: 0, animationDurationUpdate: 0}, true)
122
+ }
28
123
 
29
- const isBrowser = typeof window !== 'undefined'
124
+ function destroyChart() {
125
+ if (!chart) return
126
+ chart.off('legendselectchanged', renderChart)
127
+ chart.dispose()
128
+ chart = null
129
+ }
130
+
131
+ function queryFields(config: EChartsConfig) {
132
+ let fields: Record<string, string[]> = {}
133
+ let series = Array.isArray(config.series) ? config.series : [config.series]
134
+ let entries = series.flatMap(s => Object.entries(s?.encode || {}))
135
+
136
+ for (let [attr, col] of entries) {
137
+ let value = queryableEncodeValue(attr, col)
138
+ if (!value) continue
139
+ fields[attr] ||= []
140
+ if (!fields[attr].includes(value)) fields[attr].push(value)
141
+ }
142
+
143
+ return fields
144
+ }
145
+
146
+ function queryableEncodeValue(attr: string, value: unknown) {
147
+ if (typeof value !== 'string') return undefined
148
+ let trimmed = value.trim()
149
+ if (!trimmed) return undefined
150
+
151
+ // sort supports "column" or "column asc|desc". We only need the field in SELECT.
152
+ if (attr === 'sort') return trimmed.split(/\s+/)[0]
153
+ return trimmed
154
+ }
155
+
156
+ function calculateChartSize(config?: NormalConfig, rows: Record<string, any>[] = [], fields: any[] = []) {
157
+ let threshold = 8 // over this many bars, start to grow
158
+ let resolvedHeight: string | number = height ?? '320px'
159
+ let barSeries = config?.series.find(s => s.type == 'bar')
160
+ let categoricalY = config?.yAxis[0]?.type == 'category'
30
161
 
31
- const toDimension = (dimension: string | number | undefined, fallback: string) => {
32
- if (typeof dimension === 'number') return `${dimension}px`
33
- if (!dimension) return fallback
34
- return dimension
162
+ if (config && barSeries && categoricalY) {
163
+ let distinctX = horizontalBarCount(config, rows, fields)
164
+ if (distinctX > threshold) resolvedHeight = 320 * Math.max(1, distinctX / threshold)
165
+ }
166
+
167
+ return `height:${toDim(resolvedHeight)};width:${toDim(width ?? '100%')};`
168
+ }
169
+
170
+ function toDim(dim: string | number) {
171
+ let t = typeof dim
172
+ if (t == 'number' || (t == 'string' && (dim as string).match(/^\d+$/))) return `${dim}px`
173
+ return dim
35
174
  }
175
+
36
176
  </script>
37
177
 
38
- <div class="echarts-container">
39
- {#if !isBrowser}
40
- <div class="echarts-loading" style={`height:${toDimension(height, '240px')}`}>Loading…</div>
41
- {:else}
42
- <div
43
- class="echarts-chart"
44
- data-chart-title={chartTitle ?? undefined}
45
- data-query-id={queryID}
46
- style={`height:${toDimension(height, '240px')};width:${toDimension(width, '100%')}`}
47
- use:echarts={{
48
- config,
49
- data,
50
- echartsOptions,
51
- seriesOptions,
52
- onclick,
53
- renderer,
54
- connectGroup,
55
- xAxisLabelOverflow,
56
- seriesColors,
57
- theme: $activeAppearance,
58
- }}
59
- ></div>
178
+ <div class="echarts" bind:this={node} style={chartSizeStyle} data-component-id={mountedComponentId} data-chart-title={chartTitle}>
179
+ {#if loaded?.error || chartError}
180
+ <ErrorDisplay error={loaded?.error || chartError} />
181
+ {:else if !loaded}
182
+ <Skeleton />
183
+ {:else if loaded.rows.length == 0}
184
+ <div class="empty-chart" role="note">Dataset is empty - query ran successfully, but no data was returned from the database</div>
60
185
  {/if}
61
186
  </div>
62
187
 
63
188
  <style>
64
- .echarts-container {
189
+ .echarts {
65
190
  position: relative;
66
- margin: 8px 0;
67
191
  }
68
192
 
69
- .echarts-chart {
193
+ .empty-chart {
70
194
  width: 100%;
71
- overflow: visible;
72
- user-select: none;
73
- }
74
-
75
- .echarts-loading {
76
- width: 100%;
77
- display: flex;
78
- align-items: center;
79
- justify-content: center;
80
- border: 1px solid rgba(209, 213, 219, 0.8);
195
+ height: 100%;
196
+ padding: 12px;
197
+ margin: 8px 0;
198
+ border: 1px dashed rgba(107, 114, 128, 0.6);
81
199
  border-radius: 4px;
82
- background: rgba(249, 250, 251, 0.6);
83
- color: rgba(107, 114, 128, 0.95);
84
200
  font-size: 12px;
201
+ color: rgba(75, 85, 99, 0.9);
202
+ text-align: center;
203
+ background: rgba(243, 244, 246, 0.6);
85
204
  }
86
205
  </style>
@@ -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,27 +84,41 @@
95
84
  const renderValue = () => {
96
85
  if (numericValue === null) return '–'
97
86
  try {
98
- return formatValue(numericValue, resolvedFormat, columnUnitSummary)
99
- } catch (error) {
87
+ return formatFromField(field, numericValue)
88
+ } catch(error) {
100
89
  console.error('Failed to format delta value', error)
101
90
  return String(numericValue)
102
91
  }
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} />