@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
@@ -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,5 +1,5 @@
1
-
2
- export function toBoolean (value: any): boolean {
1
+ export function toBoolean(value: any): boolean | undefined {
2
+ if (value === undefined || value === null) return undefined
3
3
  if (typeof value === 'boolean') return value
4
4
  if (typeof value === 'number') return value !== 0
5
5
  if (typeof value === 'string') {
@@ -10,13 +10,13 @@ export function toBoolean (value: any): boolean {
10
10
  return Boolean(value)
11
11
  }
12
12
 
13
- export function ensureArray<T> (value: T | T[] | undefined | null): T[] {
13
+ export function ensureArray<T>(value: T | T[] | undefined | null): T[] {
14
14
  if (Array.isArray(value)) return value
15
15
  if (value === undefined || value === null) return []
16
16
  return [value]
17
17
  }
18
18
 
19
- export function serializeValue (value: unknown): string {
19
+ export function serializeValue(value: unknown): string {
20
20
  if (value === null || value === undefined) return 'NULL'
21
21
  if (typeof value === 'number' || typeof value === 'bigint') return String(value)
22
22
  if (typeof value === 'boolean') return value ? 'TRUE' : 'FALSE'
@@ -25,12 +25,51 @@ export function serializeValue (value: unknown): string {
25
25
  }
26
26
 
27
27
  // Parse a comma-separated list into an array of trimmed strings.
28
- // - 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.
29
29
  // - Arrays are normalized by trimming string items and String()-ing non-strings.
30
30
  // - null/undefined -> []
31
- export function parseCommaList (value: unknown): string[] {
31
+ export function parseCommaList(value: unknown): string[] {
32
32
  if (value === undefined || value === null) return []
33
- if (Array.isArray(value)) return value.map(v => typeof v === 'string' ? v.trim() : String(v)).filter(v => v.length > 0)
34
- if (typeof value === 'string') return value.split(',').map(v => v.trim()).filter(v => v.length > 0)
35
- return [String(value).trim()].filter(v => v.length > 0)
33
+ if (Array.isArray(value)) return value.map(v => (typeof v === 'string' ? v.trim() : String(v))).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
36
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
+ })
@@ -18,18 +18,7 @@ type ThemeStores = {
18
18
 
19
19
  const THEME_CONTEXT = Symbol('GrapheneThemeStores')
20
20
 
21
- const DEFAULT_PALETTE = [
22
- '#4C78A8',
23
- '#F58518',
24
- '#E45756',
25
- '#72B7B2',
26
- '#54A24B',
27
- '#EECA3B',
28
- '#B279A2',
29
- '#FF9DA6',
30
- '#9D755D',
31
- '#BAB0AC',
32
- ]
21
+ const DEFAULT_PALETTE = ['#4C78A8', '#F58518', '#E45756', '#72B7B2', '#54A24B', '#EECA3B', '#B279A2', '#FF9DA6', '#9D755D', '#BAB0AC']
33
22
 
34
23
  const DEFAULT_THEME: Theme = {
35
24
  colors: {
@@ -38,10 +27,10 @@ const DEFAULT_THEME: Theme = {
38
27
  'base-300': '#e5e7eb',
39
28
  'base-content': '#1f2937',
40
29
  'base-content-muted': '#6b7280',
41
- primary: '#2563eb',
42
- positive: '#16a34a',
43
- negative: '#dc2626',
44
- warning: '#d97706',
30
+ primary: '#3D6B7E',
31
+ positive: '#87A68C',
32
+ negative: '#B87470',
33
+ warning: '#D4A94C',
45
34
  },
46
35
  colorPalettes: {
47
36
  default: DEFAULT_PALETTE,
@@ -103,6 +92,19 @@ export const resolveColorsObject = (input: Record<string, unknown> | string | un
103
92
  return readable(Object.fromEntries(entries))
104
93
  }
105
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
+
106
108
  export const resolveColorPalette = (input: unknown): Readable<string[] | undefined> => {
107
109
  if (isReadable<string[] | undefined>(input)) return input
108
110
  if (input == null) return readable(DEFAULT_PALETTE)
@@ -115,20 +117,23 @@ export const resolveColorPalette = (input: unknown): Readable<string[] | undefin
115
117
  let parsed = parseJson(key)
116
118
  if (Array.isArray(parsed)) {
117
119
  let values = parsed.map(item => normalizeColor(item)).filter(Boolean) as string[]
118
- return readable(values.length ? values : DEFAULT_PALETTE)
120
+ return readable(finalizePalette(values))
119
121
  }
120
122
  }
121
123
  if (key.includes(',')) {
122
- let values = key.split(',').map(part => normalizeColor(part)).filter(Boolean) as string[]
123
- return readable(values.length ? values : DEFAULT_PALETTE)
124
+ let values = key
125
+ .split(',')
126
+ .map(part => normalizeColor(part))
127
+ .filter(Boolean) as string[]
128
+ return readable(finalizePalette(values))
124
129
  }
125
130
  let normalized = normalizeColor(key)
126
- return readable(normalized ? [normalized] : DEFAULT_PALETTE)
131
+ return readable(normalized ? finalizePalette([normalized]) : DEFAULT_PALETTE)
127
132
  }
128
133
 
129
134
  if (Array.isArray(input)) {
130
135
  let values = input.map(item => normalizeColor(item)).filter(Boolean) as string[]
131
- return readable(values.length ? values : DEFAULT_PALETTE)
136
+ return readable(finalizePalette(values))
132
137
  }
133
138
 
134
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
+ }