@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.
- package/LICENSE.md +3 -3
- package/README.md +138 -0
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/bin.js +2 -2
- package/dist/cli/bigQuery-I3F46SC6.js +75 -0
- package/dist/cli/bigQuery-I3F46SC6.js.map +7 -0
- package/dist/cli/chunk-OVWODUTJ.js +12849 -0
- package/dist/cli/chunk-OVWODUTJ.js.map +7 -0
- package/dist/cli/chunk-QAXEOZ43.js +53 -0
- package/dist/cli/chunk-QAXEOZ43.js.map +7 -0
- package/dist/cli/cli.js +245 -10290
- package/dist/cli/clickhouse-ZN5AN2UL.js +64 -0
- package/dist/cli/clickhouse-ZN5AN2UL.js.map +7 -0
- package/dist/cli/duckdb-IYBIO5KJ.js +87 -0
- package/dist/cli/duckdb-IYBIO5KJ.js.map +7 -0
- package/dist/cli/serve2-TNN5EROW.js +447 -0
- package/dist/cli/serve2-TNN5EROW.js.map +7 -0
- package/dist/cli/snowflake-MOQB5GA4.js +128 -0
- package/dist/cli/snowflake-MOQB5GA4.js.map +7 -0
- package/dist/index.d.ts +63 -0
- package/dist/lang/index.d.ts +63 -0
- package/dist/skills/graphene/SKILL.md +235 -0
- package/dist/skills/graphene/references/big-value.md +20 -0
- package/dist/skills/graphene/references/date-range.md +64 -0
- package/dist/skills/graphene/references/dropdown.md +62 -0
- package/dist/skills/graphene/references/echarts.md +162 -0
- package/dist/skills/graphene/references/gsql.md +393 -0
- package/dist/skills/graphene/references/model-gsql.md +72 -0
- package/dist/skills/graphene/references/table.md +143 -0
- package/dist/skills/graphene/references/text-input.md +29 -0
- package/dist/ui/app.css +263 -299
- package/dist/ui/component-utilities/dataShaping.ts +484 -0
- package/dist/ui/component-utilities/dataSummary.ts +57 -0
- package/dist/ui/component-utilities/enrich.ts +763 -0
- package/dist/ui/component-utilities/format.ts +177 -0
- package/dist/ui/component-utilities/inputUtils.ts +48 -9
- package/dist/ui/component-utilities/theme.ts +200 -0
- package/dist/ui/component-utilities/themeStores.ts +26 -21
- package/dist/ui/component-utilities/types.ts +70 -0
- package/dist/ui/components/AreaChart.svelte +57 -105
- package/dist/ui/components/BarChart.svelte +71 -129
- package/dist/ui/components/BigValue.svelte +24 -40
- package/dist/ui/components/Column.svelte +11 -19
- package/dist/ui/components/DateRange.svelte +71 -34
- package/dist/ui/components/Dropdown.svelte +82 -49
- package/dist/ui/components/DropdownOption.svelte +1 -2
- package/dist/ui/components/ECharts.svelte +179 -60
- package/dist/ui/components/InlineDelta.svelte +51 -32
- package/dist/ui/components/LineChart.svelte +54 -125
- package/dist/ui/components/PieChart.svelte +27 -37
- package/dist/ui/components/QueryLoad.svelte +78 -44
- package/dist/ui/components/Row.svelte +2 -1
- package/dist/ui/components/ScatterPlot.svelte +52 -0
- package/dist/ui/components/Skeleton.svelte +32 -0
- package/dist/ui/components/Table.svelte +3 -2
- package/dist/ui/components/TableGroupRow.svelte +28 -36
- package/dist/ui/components/TableHarness.svelte +32 -0
- package/dist/ui/components/TableHeader.svelte +34 -59
- package/dist/ui/components/TableRow.svelte +15 -39
- package/dist/ui/components/TableSubtotalRow.svelte +26 -21
- package/dist/ui/components/TableTotalRow.svelte +27 -37
- package/dist/ui/components/TextInput.svelte +17 -14
- package/dist/ui/components/Value.svelte +25 -0
- package/dist/ui/components/_Table.svelte +80 -76
- package/dist/ui/internal/ChartGallery.svelte +527 -0
- package/dist/ui/internal/ErrorDisplay.svelte +60 -0
- package/dist/ui/internal/LocalApp.svelte +87 -19
- package/dist/ui/internal/PageNavGroup.svelte +269 -0
- package/dist/ui/internal/Sidebar.svelte +178 -0
- package/dist/ui/internal/SidebarToggle.svelte +47 -0
- package/dist/ui/internal/StyleGallery.svelte +244 -0
- package/dist/ui/internal/clientCache.ts +15 -13
- package/dist/ui/internal/pageInputs.svelte.js +292 -0
- package/dist/ui/internal/queryEngine.ts +124 -132
- package/dist/ui/internal/runSocket.ts +59 -0
- package/dist/ui/internal/sidebar.svelte.js +18 -0
- package/dist/ui/internal/telemetry.ts +52 -17
- package/dist/ui/internal/types.d.ts +7 -0
- package/dist/ui/web.js +55 -13
- package/package.json +40 -41
- package/dist/docs/agent-instructions.md +0 -18
- package/dist/docs/base.md +0 -98
- package/dist/docs/cli.md +0 -22
- package/dist/docs/graphene.md +0 -1462
- package/dist/ui/component-utilities/autoFormatting.js +0 -301
- package/dist/ui/component-utilities/builtInFormats.js +0 -482
- package/dist/ui/component-utilities/chartContext.js +0 -12
- package/dist/ui/component-utilities/chartWindowDebug.js +0 -21
- package/dist/ui/component-utilities/checkInputs.js +0 -95
- package/dist/ui/component-utilities/convert.js +0 -15
- package/dist/ui/component-utilities/dateParsing.js +0 -57
- package/dist/ui/component-utilities/dropdownContext.ts +0 -1
- package/dist/ui/component-utilities/echarts.js +0 -272
- package/dist/ui/component-utilities/echartsThemes.js +0 -453
- package/dist/ui/component-utilities/formatTitle.js +0 -24
- package/dist/ui/component-utilities/formatting.js +0 -250
- package/dist/ui/component-utilities/getColumnExtents.js +0 -79
- package/dist/ui/component-utilities/getColumnSummary.js +0 -67
- package/dist/ui/component-utilities/getCompletedData.js +0 -114
- package/dist/ui/component-utilities/getDistinctCount.js +0 -7
- package/dist/ui/component-utilities/getDistinctValues.js +0 -15
- package/dist/ui/component-utilities/getSeriesConfig.js +0 -237
- package/dist/ui/component-utilities/getSortedData.js +0 -7
- package/dist/ui/component-utilities/getStackPercentages.js +0 -43
- package/dist/ui/component-utilities/getStackedData.js +0 -17
- package/dist/ui/component-utilities/getYAxisIndex.js +0 -15
- package/dist/ui/component-utilities/globalContexts.js +0 -1
- package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +0 -119
- package/dist/ui/component-utilities/replaceNulls.js +0 -14
- package/dist/ui/component-utilities/tableUtils.ts +0 -120
- package/dist/ui/components/Area.svelte +0 -214
- package/dist/ui/components/Bar.svelte +0 -350
- package/dist/ui/components/Chart.svelte +0 -989
- package/dist/ui/components/ErrorChart.svelte +0 -118
- package/dist/ui/components/Line.svelte +0 -227
- package/dist/ui/internal/NavSidebar.svelte +0 -396
- package/dist/ui/internal/PageError.svelte +0 -23
- package/dist/ui/internal/checkSocket.ts +0 -48
- package/dist/ui/internal/theme.ts +0 -88
- package/dist/ui/public/inter-latin-ext.woff2 +0 -0
- package/dist/ui/public/inter-latin.woff2 +0 -0
|
@@ -1,86 +1,205 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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:
|
|
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
|
-
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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-
|
|
39
|
-
{#if
|
|
40
|
-
<
|
|
41
|
-
{:else}
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
189
|
+
.echarts {
|
|
65
190
|
position: relative;
|
|
66
|
-
margin: 8px 0;
|
|
67
191
|
}
|
|
68
192
|
|
|
69
|
-
.
|
|
193
|
+
.empty-chart {
|
|
70
194
|
width: 100%;
|
|
71
|
-
|
|
72
|
-
|
|
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 {
|
|
2
|
+
import {formatFromField} from '../component-utilities/format.ts'
|
|
3
3
|
import {getThemeStores} from '../component-utilities/themeStores'
|
|
4
|
-
import {toBoolean} from '../component-utilities/
|
|
4
|
+
import {toBoolean} from '../component-utilities/inputUtils'
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
value?: number | string | null
|
|
8
|
-
|
|
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
|
-
|
|
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(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
99
|
-
} catch
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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(
|
|
157
|
-
border-color: rgba(
|
|
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(
|
|
162
|
-
border-color: rgba(
|
|
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
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
47
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
<
|
|
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
|
|
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:
|
|
7
|
-
category:
|
|
8
|
-
value:
|
|
9
|
-
title?:
|
|
10
|
-
|
|
11
|
-
|
|
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,
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
<
|
|
37
|
+
<ECharts data={data} config={buildConfig()} {height} {width} componentId={logger.id} />
|