@graphenedata/cli 0.0.15 → 0.0.17
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/README.md +174 -0
- package/dist/cli/bigQuery-OQUNH3VT.js +75 -0
- package/dist/cli/bigQuery-OQUNH3VT.js.map +7 -0
- package/dist/cli/chunk-56K2FF57.js +53 -0
- package/dist/cli/chunk-56K2FF57.js.map +7 -0
- package/dist/cli/chunk-TZTTALAV.js +12868 -0
- package/dist/cli/chunk-TZTTALAV.js.map +7 -0
- package/dist/cli/cli.js +260 -11196
- package/dist/cli/clickhouse-S3BJSKND.js +65 -0
- package/dist/cli/clickhouse-S3BJSKND.js.map +7 -0
- package/dist/cli/duckdb-TKVMONRK.js +87 -0
- package/dist/cli/duckdb-TKVMONRK.js.map +7 -0
- package/dist/cli/serve2-S2LL4D4D.js +448 -0
- package/dist/cli/serve2-S2LL4D4D.js.map +7 -0
- package/dist/cli/snowflake-3VPDEYYP.js +128 -0
- package/dist/cli/snowflake-3VPDEYYP.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 +156 -95
- package/dist/skills/graphene/references/big-value.md +6 -41
- package/dist/skills/graphene/references/date-range.md +64 -0
- package/dist/skills/graphene/references/dropdown.md +3 -4
- package/dist/skills/graphene/references/echarts.md +162 -0
- package/dist/skills/graphene/references/gsql.md +55 -25
- package/dist/skills/graphene/references/model-gsql.md +70 -0
- package/dist/skills/graphene/references/table.md +13 -14
- package/dist/skills/graphene/references/text-input.md +2 -1
- package/dist/ui/app.css +239 -340
- 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 +793 -0
- package/dist/ui/component-utilities/format.ts +177 -0
- package/dist/ui/component-utilities/inputUtils.ts +44 -8
- package/dist/ui/component-utilities/theme.ts +200 -0
- package/dist/ui/component-utilities/themeStores.ts +21 -8
- 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 +10 -18
- package/dist/ui/components/DateRange.svelte +54 -21
- package/dist/ui/components/Dropdown.svelte +47 -26
- package/dist/ui/components/DropdownOption.svelte +1 -2
- package/dist/ui/components/ECharts.svelte +181 -67
- package/dist/ui/components/InlineDelta.svelte +50 -31
- package/dist/ui/components/LineChart.svelte +54 -125
- package/dist/ui/components/PieChart.svelte +27 -37
- package/dist/ui/components/QueryLoad.svelte +77 -45
- 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 +14 -38
- package/dist/ui/components/TableSubtotalRow.svelte +18 -21
- package/dist/ui/components/TableTotalRow.svelte +27 -37
- package/dist/ui/components/TextInput.svelte +13 -12
- package/dist/ui/components/Value.svelte +25 -0
- package/dist/ui/components/_Table.svelte +72 -70
- package/dist/ui/internal/ChartGallery.svelte +527 -0
- package/dist/ui/internal/ErrorDisplay.svelte +22 -97
- package/dist/ui/internal/LocalApp.svelte +84 -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 +2 -2
- package/dist/ui/internal/pageInputs.svelte.js +292 -0
- package/dist/ui/internal/queryEngine.ts +112 -129
- package/dist/ui/internal/runSocket.ts +31 -14
- package/dist/ui/internal/sidebar.svelte.js +18 -0
- package/dist/ui/internal/telemetry.ts +51 -16
- package/dist/ui/internal/types.d.ts +7 -0
- package/dist/ui/web.js +30 -11
- package/package.json +40 -38
- package/dist/skills/graphene/references/area-chart.md +0 -95
- package/dist/skills/graphene/references/bar-chart.md +0 -112
- package/dist/skills/graphene/references/line-chart.md +0 -108
- package/dist/skills/graphene/references/pie-chart.md +0 -29
- package/dist/skills/graphene/references/value-formats.md +0 -104
- package/dist/ui/component-utilities/autoFormatting.js +0 -280
- package/dist/ui/component-utilities/builtInFormats.js +0 -481
- 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 -84
- package/dist/ui/component-utilities/convert.js +0 -15
- package/dist/ui/component-utilities/dateParsing.js +0 -56
- package/dist/ui/component-utilities/dropdownContext.ts +0 -1
- package/dist/ui/component-utilities/echarts.js +0 -252
- package/dist/ui/component-utilities/echartsThemes.js +0 -443
- package/dist/ui/component-utilities/formatTitle.js +0 -24
- package/dist/ui/component-utilities/formatting.js +0 -241
- package/dist/ui/component-utilities/getColumnExtents.js +0 -79
- package/dist/ui/component-utilities/getColumnSummary.js +0 -62
- package/dist/ui/component-utilities/getCompletedData.js +0 -122
- 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 -231
- package/dist/ui/component-utilities/getSortedData.js +0 -9
- package/dist/ui/component-utilities/getStackPercentages.js +0 -45
- package/dist/ui/component-utilities/getStackedData.js +0 -19
- 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 -16
- package/dist/ui/component-utilities/tableUtils.ts +0 -107
- package/dist/ui/component-utilities/tidyWithTypes.js +0 -9
- package/dist/ui/components/Area.svelte +0 -214
- package/dist/ui/components/Bar.svelte +0 -347
- package/dist/ui/components/Chart.svelte +0 -995
- package/dist/ui/components/Line.svelte +0 -227
- package/dist/ui/internal/NavSidebar.svelte +0 -396
- package/dist/ui/internal/theme.ts +0 -60
- package/dist/ui/public/inter-latin-ext.woff2 +0 -0
- package/dist/ui/public/inter-latin.woff2 +0 -0
|
@@ -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,7 +84,7 @@
|
|
|
95
84
|
const renderValue = () => {
|
|
96
85
|
if (numericValue === null) return '–'
|
|
97
86
|
try {
|
|
98
|
-
return
|
|
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
|
-
{
|
|
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} />
|
|
@@ -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 |
|
|
10
|
+
data: string | QueryResult
|
|
7
11
|
height?: number
|
|
8
12
|
fields?: Record<string, string | string[]>
|
|
9
|
-
|
|
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
|
|
15
|
-
let loaded:
|
|
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:
|
|
18
|
-
|
|
19
|
-
loaded = result
|
|
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
|
-
|
|
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
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
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
|
-
<
|
|
42
|
-
|
|
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:
|
|
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>
|