@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
|
@@ -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
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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: '#
|
|
31
|
-
positive: '#
|
|
32
|
-
negative: '#
|
|
33
|
-
warning: '#
|
|
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
|
|
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
|
|
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
|
|
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
|
+
}
|