@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.
Files changed (117) hide show
  1. package/README.md +174 -0
  2. package/dist/cli/bigQuery-OQUNH3VT.js +75 -0
  3. package/dist/cli/bigQuery-OQUNH3VT.js.map +7 -0
  4. package/dist/cli/chunk-56K2FF57.js +53 -0
  5. package/dist/cli/chunk-56K2FF57.js.map +7 -0
  6. package/dist/cli/chunk-TZTTALAV.js +12868 -0
  7. package/dist/cli/chunk-TZTTALAV.js.map +7 -0
  8. package/dist/cli/cli.js +260 -11196
  9. package/dist/cli/clickhouse-S3BJSKND.js +65 -0
  10. package/dist/cli/clickhouse-S3BJSKND.js.map +7 -0
  11. package/dist/cli/duckdb-TKVMONRK.js +87 -0
  12. package/dist/cli/duckdb-TKVMONRK.js.map +7 -0
  13. package/dist/cli/serve2-S2LL4D4D.js +448 -0
  14. package/dist/cli/serve2-S2LL4D4D.js.map +7 -0
  15. package/dist/cli/snowflake-3VPDEYYP.js +128 -0
  16. package/dist/cli/snowflake-3VPDEYYP.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 +156 -95
  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 +70 -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 +793 -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 +84 -19
  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 +112 -129
  72. package/dist/ui/internal/runSocket.ts +31 -14
  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 +30 -11
  77. package/package.json +40 -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
@@ -0,0 +1,177 @@
1
+ import type {Field} from './types.ts'
2
+
3
+ const currencySymbols = {usd: '$', eur: '€', gbp: '£', cad: 'C$', aud: 'A$', jpy: '¥'} as const
4
+ const percent = new Intl.NumberFormat('en-US', {maximumFractionDigits: 0})
5
+ const currencyCompact = new Intl.NumberFormat('en-US', {notation: 'compact', maximumFractionDigits: 1})
6
+ const monthYearFormatter = new Intl.DateTimeFormat('en-US', {month: 'long', year: 'numeric'})
7
+ const monthDayYearFormatter = new Intl.DateTimeFormat('en-US', {month: 'short', day: 'numeric', year: 'numeric'})
8
+ const sundayWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] as const
9
+ const mondayWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] as const
10
+ const yearMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] as const
11
+ const titleCaseAcronyms = ['id', 'gdp']
12
+ const titleCaseLowerWords = ['of', 'the', 'and', 'in', 'on']
13
+
14
+ // Formats a raw column name into a readable title.
15
+ export function formatTitle(column: string) {
16
+ let cleaned = column.replace(/"/g, '').replace(/_/g, ' ')
17
+ return cleaned.replace(/\S*/g, token => {
18
+ if (titleCaseAcronyms.includes(token)) return token.toUpperCase()
19
+ if (titleCaseLowerWords.includes(token)) return token.toLowerCase()
20
+ return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase()
21
+ })
22
+ }
23
+
24
+ // ECharts valueFormatter will take different arguments depending on the chart type.
25
+ // For bar/line/area it's just a number
26
+ // for scatter, it's [x,y], for candlestick [open, close, low, high], etc
27
+ export function makeValueFormatter(fields: Field[] = []) {
28
+ return (value: unknown) => {
29
+ if (Array.isArray(value)) return value.map((entry, index) => formatSingleValue(entry, fields[index] || fields[0])).join(', ')
30
+ return formatSingleValue(value, fields[0])
31
+ }
32
+ }
33
+
34
+ // Formats one numeric value with field metadata (units, ratio/pct, compact notation).
35
+ export function formatSingleValue(value: any, field?: Field) {
36
+ let amount = Number(value)
37
+ if (!Number.isFinite(amount)) return String(value ?? '')
38
+
39
+ if (field?.metadata?.ratio) return `${percent.format(amount * 100)}%`
40
+ if (field?.metadata?.pct) return `${percent.format(amount)}%`
41
+
42
+ let unit = field?.metadata?.units?.toLowerCase() as keyof typeof currencySymbols | undefined
43
+ let currencyUnit = unit != null && unit in currencySymbols ? unit : undefined
44
+ if (currencyUnit) {
45
+ let sign = amount < 0 ? '-' : ''
46
+ let formatted = currencyCompact.format(Math.abs(amount)).replace('K', 'k').replace('M', 'm').replace('B', 'b')
47
+ return `${sign}${currencySymbols[currencyUnit]}${formatted}`
48
+ }
49
+
50
+ if (amount === 0) return '0'
51
+ let sign = amount < 0 ? '-' : ''
52
+ let absolute = Math.abs(amount)
53
+
54
+ if (absolute >= 1e12) return `${sign}${compactValue(absolute / 1e12)}T`
55
+ if (absolute >= 1e9) return `${sign}${compactValue(absolute / 1e9)}B`
56
+ if (absolute >= 1e6) return `${sign}${compactValue(absolute / 1e6)}M`
57
+ if (absolute >= 1e3) return `${sign}${compactValue(absolute / 1e3)}k`
58
+ if (absolute >= 1) return `${sign}${compactValue(absolute)}`
59
+ if (absolute >= 1e-3) return `${sign}${compactValue(absolute)}`
60
+ if (absolute >= 1e-6) return `${sign}${compactValue(absolute * 1e3)}m`
61
+ if (absolute >= 1e-9) return `${sign}${compactValue(absolute * 1e6)}u`
62
+ if (absolute >= 1e-12) return `${sign}${compactValue(absolute * 1e9)}n`
63
+ return `${sign}${compactValue(absolute)}`
64
+ }
65
+
66
+ // Creates a formatter function that renders date/timestamp values based on field metadata.timeGrain.
67
+ export function makeTimeFormatter(field?: Field) {
68
+ let timeGrain = String(field?.metadata?.timeGrain || '').toLowerCase()
69
+
70
+ return (input: unknown) => {
71
+ let value = input
72
+ if (value && typeof value === 'object' && 'value' in (value as Record<string, unknown>)) {
73
+ value = (value as Record<string, unknown>).value
74
+ }
75
+
76
+ let date = value instanceof Date ? value : new Date(Number(value))
77
+ if (!Number.isFinite(date.getTime())) return String(value ?? '')
78
+
79
+ let y = date.getFullYear()
80
+ let m = pad2(date.getMonth() + 1)
81
+ let d = pad2(date.getDate())
82
+ let h = pad2(date.getHours())
83
+ let min = pad2(date.getMinutes())
84
+ let s = pad2(date.getSeconds())
85
+
86
+ if (timeGrain === 'year') return String(y)
87
+ if (timeGrain === 'quarter') return `Q${Math.floor(date.getMonth() / 3) + 1} ${y}`
88
+ if (timeGrain === 'month') return monthYearFormatter.format(date)
89
+ if (timeGrain === 'week' || timeGrain === 'day') return monthDayYearFormatter.format(date)
90
+ if (timeGrain === 'hour') return `${y}-${m}-${d} ${h}:00`
91
+ if (timeGrain === 'minute') return `${y}-${m}-${d} ${h}:${min}`
92
+ if (timeGrain === 'second') return `${y}-${m}-${d} ${h}:${min}:${s}`
93
+
94
+ return monthDayYearFormatter.format(date)
95
+ }
96
+ }
97
+
98
+ // Formats one value by selecting the right formatter from the field type.
99
+ export function formatFromField(field: Field | undefined, value: unknown) {
100
+ if (value === null || value === undefined) return '-'
101
+
102
+ let type = String(field?.type || '').toLowerCase()
103
+ if (type === 'number') return formatSingleValue(value, field)
104
+ if (type === 'date' || type === 'timestamp') return makeTimeFormatter(field)(value)
105
+ return String(value)
106
+ }
107
+
108
+ // Formats ordinal time buckets like hour_of_day and day_of_week variants.
109
+ export function formatTimeOrdinal(field: Field | undefined, input: unknown) {
110
+ let value = extractFormatterValue(input)
111
+ let ordinal = String(field?.metadata?.timeOrdinal || '').toLowerCase()
112
+ if (!ordinal) return String(value ?? '')
113
+
114
+ if (ordinal === 'hour_of_day') {
115
+ let hour = Number(value)
116
+ if (!Number.isInteger(hour) || hour < 0 || hour > 23) return String(value ?? '')
117
+ let normalized = hour % 12 || 12
118
+ return `${normalized}${hour < 12 ? 'am' : 'pm'}`
119
+ }
120
+
121
+ if (ordinal === 'dow_1s') {
122
+ let day = Number(value)
123
+ if (!Number.isInteger(day) || day < 1 || day > 7) return String(value ?? '')
124
+ return sundayWeek[day - 1]
125
+ }
126
+
127
+ if (ordinal === 'dow_0s') {
128
+ let day = Number(value)
129
+ if (!Number.isInteger(day) || day < 0 || day > 6) return String(value ?? '')
130
+ return sundayWeek[day]
131
+ }
132
+
133
+ if (ordinal === 'dow_1m') {
134
+ let day = Number(value)
135
+ if (!Number.isInteger(day) || day < 1 || day > 7) return String(value ?? '')
136
+ return mondayWeek[day - 1]
137
+ }
138
+
139
+ if (ordinal === 'month_of_year') {
140
+ let month = Number(value)
141
+ if (!Number.isInteger(month) || month < 1 || month > 12) return String(value ?? '')
142
+ return yearMonths[month - 1]
143
+ }
144
+
145
+ if (ordinal === 'quarter_of_year') {
146
+ let quarter = Number(value)
147
+ if (!Number.isInteger(quarter) || quarter < 1 || quarter > 4) return String(value ?? '')
148
+ return `Q${quarter}`
149
+ }
150
+
151
+ return String(value ?? '')
152
+ }
153
+
154
+ function extractFormatterValue(input: unknown) {
155
+ if (input && typeof input === 'object' && 'value' in (input as Record<string, unknown>)) {
156
+ return (input as Record<string, unknown>).value
157
+ }
158
+ return input
159
+ }
160
+
161
+ function pad2(value: number) {
162
+ return String(value).padStart(2, '0')
163
+ }
164
+
165
+ function compactValue(num: number) {
166
+ let exponent = Math.floor(Math.log10(Math.abs(num)))
167
+ let scale = Math.pow(10, exponent - 1)
168
+ let rounded = Math.round(num / scale) * scale
169
+ if (!Number.isFinite(rounded)) return String(num)
170
+ let magnitude = Math.floor(Math.log10(rounded))
171
+ let decimals = Math.max(0, 1 - magnitude)
172
+ return rounded
173
+ .toFixed(decimals)
174
+ .replace(/\.0+$/, '')
175
+ .replace(/(\.[0-9]*[1-9])0+$/, '$1')
176
+ .replace(/\.$/, '')
177
+ }
@@ -1,4 +1,5 @@
1
- export function toBoolean(value: any): boolean {
1
+ export function toBoolean(value: any): boolean | undefined {
2
+ if (value === undefined || value === null) return undefined
2
3
  if (typeof value === 'boolean') return value
3
4
  if (typeof value === 'number') return value !== 0
4
5
  if (typeof value === 'string') {
@@ -24,16 +25,51 @@ export function serializeValue(value: unknown): string {
24
25
  }
25
26
 
26
27
  // Parse a comma-separated list into an array of trimmed strings.
27
- // - Strings are split on commas; whitespace trimmed; empty entries removed.
28
+ // - Strings are split on top-level commas, ignoring commas inside calls and quoted strings.
28
29
  // - Arrays are normalized by trimming string items and String()-ing non-strings.
29
30
  // - null/undefined -> []
30
31
  export function parseCommaList(value: unknown): string[] {
31
32
  if (value === undefined || value === null) return []
32
33
  if (Array.isArray(value)) return value.map(v => (typeof v === 'string' ? v.trim() : String(v))).filter(v => v.length > 0)
33
- if (typeof value === 'string')
34
- return value
35
- .split(',')
36
- .map(v => v.trim())
37
- .filter(v => v.length > 0)
38
- return [String(value).trim()].filter(v => v.length > 0)
34
+ if (typeof value !== 'string') return [String(value).trim()].filter(v => v.length > 0)
35
+
36
+ let parts: string[] = []
37
+ let current = ''
38
+ let quote: string | undefined
39
+ let depth = 0
40
+
41
+ for (let i = 0; i < value.length; i++) {
42
+ let char = value[i]
43
+
44
+ if (quote) {
45
+ current += char
46
+ if (char === quote) {
47
+ if (quote === "'" && value[i + 1] === "'") current += value[++i]
48
+ else quote = undefined
49
+ }
50
+ continue
51
+ }
52
+
53
+ if (char === "'" || char === '"' || char === '`') {
54
+ quote = char
55
+ current += char
56
+ continue
57
+ }
58
+
59
+ if (char === '(' || char === '[' || char === '{') depth++
60
+ if ((char === ')' || char === ']' || char === '}') && depth > 0) depth--
61
+
62
+ if (char === ',' && depth === 0) {
63
+ let trimmed = current.trim()
64
+ if (trimmed) parts.push(trimmed)
65
+ current = ''
66
+ continue
67
+ }
68
+
69
+ current += char
70
+ }
71
+
72
+ let trimmed = current.trim()
73
+ if (trimmed) parts.push(trimmed)
74
+ return parts
39
75
  }
@@ -0,0 +1,200 @@
1
+ // @ts-expect-error ECharts CJS typings don't expose named exports cleanly, but bundling works.
2
+ import {registerTheme} from 'echarts'
3
+
4
+ // ── Color tokens ────────────────────────────────────────────────────────
5
+ // Palette C · Fjord Dusk
6
+ export const colorPalette = [
7
+ '#3D6B7E', // deep teal — Scandinavian twilight anchor
8
+ '#C87F5A', // warm amber — accent warmth, kiln-fired
9
+ '#87A68C', // sage green — twilight foliage
10
+ '#8E7AA0', // muted mauve — dusk shadow
11
+ '#D4A94C', // amber gold — last light on water
12
+ '#5B8F9E', // fjord blue — mid-tone companion
13
+ '#C4868E', // dusty rose — fading alpine glow
14
+ ]
15
+
16
+ // Rotate a palette by a deterministic page offset so pages don't always start on the same color.
17
+ // We keep this pure and reusable so enrichments can apply it onto config.color.
18
+ export function paletteForPath(pathname?: string) {
19
+ if (import.meta.env.VITE_TEST) return [...colorPalette] // Keep screenshot baselines stable in UI tests.
20
+
21
+ let rawPath = pathname ?? location.pathname
22
+ let key = String(rawPath)
23
+ .split('?')[0]
24
+ .split('#')[0]
25
+ .replace(/\\/g, '/')
26
+ .replace(/^\/+|\/+$/g, '')
27
+ key = key.replace(/\/index$/i, '') || 'index'
28
+
29
+ let hash = 2166136261
30
+ for (let i = 0; i < key.length; i++) {
31
+ hash ^= key.charCodeAt(i)
32
+ hash = Math.imul(hash, 16777619)
33
+ }
34
+
35
+ let offset = (hash >>> 0) % colorPalette.length
36
+ if (offset === 0) return [...colorPalette]
37
+ return [...colorPalette.slice(offset), ...colorPalette.slice(0, offset)]
38
+ }
39
+
40
+ let clr = {
41
+ white: '#ffffff',
42
+ tooltipTxt: '#111827',
43
+ textDark: '#374151',
44
+ textMid: '#6b7280',
45
+ textLight: '#9ca3af',
46
+ lineSubtle: '#d1d5db',
47
+ splitLine: '#dde0e4',
48
+ border: '#e5e7eb',
49
+ seqStart: '#e4eff3',
50
+ statusBad: '#B87470', // muted brick red — in-theme with Fjord Dusk's cool desaturation
51
+ }
52
+
53
+ let axisCommon = {
54
+ axisLine: {lineStyle: {color: clr.border}},
55
+ axisLabel: {color: clr.textLight},
56
+ axisTick: {show: false},
57
+ splitLine: {show: false, lineStyle: {color: clr.splitLine, type: 'dashed'}},
58
+ }
59
+
60
+ registerTheme('graphene-theme', {
61
+ color: colorPalette,
62
+ backgroundColor: 'transparent',
63
+ textStyle: {
64
+ fontFamily: "'Source Sans 3', sans-serif",
65
+ color: clr.textMid,
66
+ fontSize: 13,
67
+ },
68
+ title: {
69
+ left: 'left',
70
+ padding: 0,
71
+ textStyle: {color: clr.textDark, fontSize: 15},
72
+ },
73
+ categoryAxis: {
74
+ ...axisCommon,
75
+ },
76
+ valueAxis: {
77
+ ...axisCommon,
78
+ splitLine: {lineStyle: {color: clr.splitLine}},
79
+ splitNumber: 3,
80
+ },
81
+ timeAxis: {
82
+ ...axisCommon,
83
+ },
84
+ logAxis: {
85
+ ...axisCommon,
86
+ },
87
+ tooltip: {
88
+ backgroundColor: clr.white,
89
+ borderColor: clr.border,
90
+ textStyle: {color: clr.tooltipTxt},
91
+ },
92
+
93
+ visualMap: {
94
+ show: false,
95
+ textStyle: {color: clr.textLight},
96
+ },
97
+ legend: {
98
+ type: 'scroll',
99
+ icon: 'circle',
100
+ itemWidth: 8,
101
+ itemHeight: 8,
102
+ top: 24,
103
+ left: 0,
104
+ textStyle: {color: clr.textMid},
105
+ },
106
+ grid: {
107
+ top: 75,
108
+ left: 40,
109
+ right: 16,
110
+ bottom: 36,
111
+ containLabel: false,
112
+ },
113
+ line: {
114
+ smooth: true,
115
+ symbol: 'emptyCircle',
116
+ symbolSize: 6,
117
+ lineStyle: {width: 2},
118
+ },
119
+ bar: {},
120
+ pie: {
121
+ radius: ['30%', '58%'],
122
+ label: {color: clr.textMid},
123
+ itemStyle: {borderColor: 'var(--color-bg)', borderWidth: 1},
124
+ },
125
+ scatter: {
126
+ symbolSize: 8,
127
+ itemStyle: {opacity: 0.8},
128
+ },
129
+ radar: {
130
+ axisName: {color: clr.textLight},
131
+ splitLine: {lineStyle: {color: clr.border}},
132
+ splitArea: {show: false},
133
+ axisLine: {lineStyle: {color: clr.border}},
134
+ areaStyle: {opacity: 0.15},
135
+ lineStyle: {width: 1.5},
136
+ },
137
+ boxplot: {
138
+ itemStyle: {
139
+ color: clr.seqStart,
140
+ borderColor: colorPalette[0],
141
+ borderWidth: 1.5,
142
+ },
143
+ },
144
+ candlestick: {
145
+ itemStyle: {
146
+ color: colorPalette[2], // up candle — sage green
147
+ color0: clr.statusBad, // down candle — brick red
148
+ borderColor: colorPalette[2],
149
+ borderColor0: clr.statusBad,
150
+ borderWidth: 1.5,
151
+ },
152
+ },
153
+ gauge: {
154
+ progress: {show: true, width: 14, roundCap: true},
155
+ axisLine: {roundCap: true, lineStyle: {width: 14, color: [[1, clr.border]]}},
156
+ axisTick: {show: false},
157
+ splitLine: {show: false},
158
+ axisLabel: {show: false},
159
+ pointer: {show: false},
160
+ detail: {valueAnimation: true, fontSize: 24, color: clr.textDark, offsetCenter: [0, '0%']},
161
+ },
162
+ funnel: {
163
+ left: '10%',
164
+ width: '80%',
165
+ label: {position: 'inside', color: clr.white, fontSize: 12},
166
+ itemStyle: {borderColor: clr.white, borderWidth: 1},
167
+ },
168
+ heatmap: {
169
+ label: {show: true, color: clr.textDark, fontSize: 11},
170
+ },
171
+ graph: {
172
+ lineStyle: {color: clr.lineSubtle, width: 1.5, opacity: 1},
173
+ label: {show: true, color: clr.textDark, fontSize: 11, position: 'right'},
174
+ },
175
+ tree: {
176
+ orient: 'LR',
177
+ symbolSize: 10,
178
+ lineStyle: {color: colorPalette[0], width: 2},
179
+ itemStyle: {color: colorPalette[0], borderColor: colorPalette[0]},
180
+ label: {color: clr.textDark, fontSize: 11, position: 'top', verticalAlign: 'middle', align: 'center'},
181
+ leaves: {label: {position: 'right', verticalAlign: 'middle', align: 'left'}},
182
+ emphasis: {focus: 'descendant'},
183
+ },
184
+ treemap: {
185
+ roam: false,
186
+ breadcrumb: {show: false},
187
+ label: {color: clr.white, fontSize: 12},
188
+ itemStyle: {borderColor: clr.white, borderWidth: 0, gapWidth: 1},
189
+ },
190
+ sunburst: {
191
+ label: {color: clr.textDark, fontSize: 10, rotateLabel: true},
192
+ itemStyle: {borderColor: clr.white, borderWidth: 1},
193
+ },
194
+ sankey: {
195
+ nodeWidth: 8,
196
+ nodeGap: 12,
197
+ lineStyle: {color: 'gradient', opacity: 0.3},
198
+ label: {color: clr.textDark, fontSize: 11},
199
+ },
200
+ })
@@ -27,10 +27,10 @@ const DEFAULT_THEME: Theme = {
27
27
  'base-300': '#e5e7eb',
28
28
  'base-content': '#1f2937',
29
29
  'base-content-muted': '#6b7280',
30
- primary: '#2563eb',
31
- positive: '#16a34a',
32
- negative: '#dc2626',
33
- warning: '#d97706',
30
+ primary: '#3D6B7E',
31
+ positive: '#87A68C',
32
+ negative: '#B87470',
33
+ warning: '#D4A94C',
34
34
  },
35
35
  colorPalettes: {
36
36
  default: DEFAULT_PALETTE,
@@ -92,6 +92,19 @@ export const resolveColorsObject = (input: Record<string, unknown> | string | un
92
92
  return readable(Object.fromEntries(entries))
93
93
  }
94
94
 
95
+ const cssVar = (name: string): string | undefined => {
96
+ if (typeof document === 'undefined' || typeof getComputedStyle === 'undefined') return undefined
97
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || undefined
98
+ }
99
+
100
+ // A single color makes a flat scale; expand to a bg→color gradient so heatmaps work.
101
+ // chroma.scale needs a real color, so resolve the CSS var before building the palette.
102
+ const finalizePalette = (values: string[]): string[] => {
103
+ if (values.length === 0) return DEFAULT_PALETTE
104
+ if (values.length === 1) return [cssVar('--color-bg') ?? DEFAULT_THEME.colors['base-100'], values[0]]
105
+ return values
106
+ }
107
+
95
108
  export const resolveColorPalette = (input: unknown): Readable<string[] | undefined> => {
96
109
  if (isReadable<string[] | undefined>(input)) return input
97
110
  if (input == null) return readable(DEFAULT_PALETTE)
@@ -104,7 +117,7 @@ export const resolveColorPalette = (input: unknown): Readable<string[] | undefin
104
117
  let parsed = parseJson(key)
105
118
  if (Array.isArray(parsed)) {
106
119
  let values = parsed.map(item => normalizeColor(item)).filter(Boolean) as string[]
107
- return readable(values.length ? values : DEFAULT_PALETTE)
120
+ return readable(finalizePalette(values))
108
121
  }
109
122
  }
110
123
  if (key.includes(',')) {
@@ -112,15 +125,15 @@ export const resolveColorPalette = (input: unknown): Readable<string[] | undefin
112
125
  .split(',')
113
126
  .map(part => normalizeColor(part))
114
127
  .filter(Boolean) as string[]
115
- return readable(values.length ? values : DEFAULT_PALETTE)
128
+ return readable(finalizePalette(values))
116
129
  }
117
130
  let normalized = normalizeColor(key)
118
- return readable(normalized ? [normalized] : DEFAULT_PALETTE)
131
+ return readable(normalized ? finalizePalette([normalized]) : DEFAULT_PALETTE)
119
132
  }
120
133
 
121
134
  if (Array.isArray(input)) {
122
135
  let values = input.map(item => normalizeColor(item)).filter(Boolean) as string[]
123
- return readable(values.length ? values : DEFAULT_PALETTE)
136
+ return readable(finalizePalette(values))
124
137
  }
125
138
 
126
139
  return readable(DEFAULT_PALETTE)
@@ -0,0 +1,70 @@
1
+ import type {
2
+ DatasetComponentOption,
3
+ EChartsOption,
4
+ GridComponentOption,
5
+ LegendComponentOption,
6
+ SeriesOption,
7
+ TitleComponentOption,
8
+ TooltipComponentOption,
9
+ XAXisComponentOption,
10
+ YAXisComponentOption,
11
+ } from 'echarts/types/dist/echarts'
12
+
13
+ import type {Field as ApiField, QueryResult as ApiQueryResult} from '../../lang/index.d.ts'
14
+
15
+ type SingleOrArray<T> = T | T[]
16
+ type SeriesEncode = Record<string, unknown>
17
+
18
+ export type Field = ApiField
19
+ export type QueryResult = ApiQueryResult
20
+
21
+ type CommonSeriesFields = {
22
+ type?: string
23
+ name?: string
24
+ color?: string
25
+ stack?: string
26
+ datasetId?: string
27
+ data?: unknown
28
+ links?: unknown
29
+ xAxisIndex?: number
30
+ yAxisIndex?: number
31
+ label?: Record<string, any>
32
+ labelLayout?: Record<string, any> | ((...args: any[]) => any)
33
+ itemStyle?: Record<string, any>
34
+ lineStyle?: Record<string, any>
35
+ areaStyle?: Record<string, any>
36
+ showSymbol?: boolean
37
+ }
38
+
39
+ // ECharts supports a lightweight split hint so configs stay concise.
40
+ // - `encode.splitBy: "field"` splits one template into one series per distinct field value.
41
+ // - `encode.splitBy: ["groupBy", "stackBy"]` is bar-only grouped+stacked shorthand.
42
+ // - with a single split field, use native `series.stack` to choose stacked vs grouped behavior.
43
+ export type SeriesWithGroupingHint = Omit<SeriesOption, 'encode'> &
44
+ CommonSeriesFields & {
45
+ stackPercentage?: boolean
46
+ encode?: SeriesEncode & {
47
+ splitBy?: string | string[]
48
+ sort?: string
49
+ }
50
+ }
51
+
52
+ export type EChartsConfig = Omit<EChartsOption, 'series'> & {
53
+ series?: SingleOrArray<SeriesWithGroupingHint>
54
+ legendSelection?: any
55
+ }
56
+
57
+ type AxisWithField<TAxis> = TAxis & {field?: Field}
58
+
59
+ // Config shape after enrich() normalization runs.
60
+ // We keep this mutable and array-based because enrichments mutate in place.
61
+ export type NormalConfig = Omit<EChartsConfig, 'series' | 'xAxis' | 'yAxis' | 'dataset' | 'grid' | 'legend' | 'title' | 'tooltip'> & {
62
+ series: SeriesWithGroupingHint[]
63
+ xAxis: AxisWithField<XAXisComponentOption>[]
64
+ yAxis: AxisWithField<YAXisComponentOption>[]
65
+ dataset: DatasetComponentOption[]
66
+ grid: GridComponentOption[]
67
+ legend: LegendComponentOption[]
68
+ title: TitleComponentOption[]
69
+ tooltip: TooltipComponentOption[]
70
+ }