@graphenedata/cli 0.0.12 → 0.0.14
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/dist/cli/cli.js +8591 -1214
- package/dist/docs/base.md +98 -0
- package/dist/docs/cli.md +22 -0
- package/dist/docs/graphene.md +10 -10
- package/dist/ui/component-utilities/echarts.js +2 -3
- package/dist/ui/component-utilities/formatting.js +3 -11
- package/dist/ui/component-utilities/getSeriesConfig.js +2 -1
- package/dist/ui/components/Area.svelte +188 -151
- package/dist/ui/components/AreaChart.svelte +43 -79
- package/dist/ui/components/Bar.svelte +273 -255
- package/dist/ui/components/BarChart.svelte +58 -112
- package/dist/ui/components/BigValue.svelte +13 -7
- package/dist/ui/components/Chart.svelte +280 -317
- package/dist/ui/components/Column.svelte +102 -113
- package/dist/ui/components/DateRange.svelte +37 -27
- package/dist/ui/components/Dropdown.svelte +77 -57
- package/dist/ui/components/DropdownOption.svelte +10 -7
- package/dist/ui/components/ECharts.svelte +23 -16
- package/dist/ui/components/ErrorChart.svelte +85 -21
- package/dist/ui/components/GrapheneQuery.svelte +7 -3
- package/dist/ui/components/InlineDelta.svelte +53 -34
- package/dist/ui/components/Line.svelte +192 -178
- package/dist/ui/components/LineChart.svelte +53 -96
- package/dist/ui/components/PieChart.svelte +26 -15
- package/dist/ui/components/QueryLoad.svelte +15 -10
- package/dist/ui/components/SortIcon.svelte +5 -1
- package/dist/ui/components/Table.svelte +15 -9
- package/dist/ui/components/TableCell.svelte +30 -17
- package/dist/ui/components/TableGroupRow.svelte +26 -19
- package/dist/ui/components/TableGroupToggle.svelte +9 -6
- package/dist/ui/components/TableHeader.svelte +37 -27
- package/dist/ui/components/TableRow.svelte +30 -20
- package/dist/ui/components/TableSubtotalRow.svelte +16 -9
- package/dist/ui/components/TableTotalRow.svelte +18 -11
- package/dist/ui/components/TextInput.svelte +23 -20
- package/dist/ui/components/_Table.svelte +303 -260
- package/dist/ui/internal/LocalApp.svelte +40 -0
- package/dist/ui/internal/NavSidebar.svelte +27 -30
- package/dist/ui/internal/PageError.svelte +23 -0
- package/dist/ui/internal/checkSocket.ts +48 -0
- package/dist/ui/internal/queryEngine.ts +9 -2
- package/dist/ui/internal/telemetry.ts +1 -0
- package/dist/ui/web.js +5 -55
- package/package.json +9 -10
- package/cli.ts +0 -156
- package/dist/ui/internal/NavSidebarHMR.svelte +0 -8
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Graphene is a framework for doing data analysis as code.
|
|
2
|
+
|
|
3
|
+
Schema definitions and semantic models are done in `.gsql`, dashboards in `.md`.
|
|
4
|
+
|
|
5
|
+
## GSQL
|
|
6
|
+
GSQL extends ansi sql with a few key features:
|
|
7
|
+
`table` statements define existing tables, adding joins and dimensions/measures
|
|
8
|
+
`table X as (select ...)` define a table as the result of a query
|
|
9
|
+
`extend` add measures and joins to an existing table, usually used with `table X as (select ...)`
|
|
10
|
+
Implicit joins: `from orders select status, user.name` will automatically join users on to orders
|
|
11
|
+
Measure expansion: `from users select id, orders.revenue` automatically expands to `select users.id, sum(orders.amount) ...`
|
|
12
|
+
- NEVER `sum(revenue)` or `group by revenue`
|
|
13
|
+
- OK: `floor(revenue)`, `revenue / cost`
|
|
14
|
+
`group by all` is implied, and does not need to be put in gsql
|
|
15
|
+
|
|
16
|
+
A few common features are not supported in gsql, and must be avoided: window functions, subqueries, CTEs.
|
|
17
|
+
|
|
18
|
+
```sql
|
|
19
|
+
table orders (
|
|
20
|
+
id BIGINT
|
|
21
|
+
user_id BIGINT
|
|
22
|
+
amount FLOAT
|
|
23
|
+
status STRING
|
|
24
|
+
join one users on user_id = users.id -- many orders per user
|
|
25
|
+
is_complete: status = 'Complete' -- dimension (scalar expression)
|
|
26
|
+
revenue: sum(amount) -- measure (agg expression)
|
|
27
|
+
avg_order: revenue / count(*) -- measures can compose
|
|
28
|
+
)
|
|
29
|
+
table users (
|
|
30
|
+
id BIGINT
|
|
31
|
+
name VARCHAR
|
|
32
|
+
join many orders on id = orders.user_id
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Dashboards
|
|
37
|
+
|
|
38
|
+
`.md` files with GSQL in code fences, viz components reference query names.
|
|
39
|
+
|
|
40
|
+
````md
|
|
41
|
+
```gsql monthly_sales
|
|
42
|
+
select date_trunc(created_at, month) as month, revenue from orders
|
|
43
|
+
```
|
|
44
|
+
<LineChart data="monthly_sales" x="month" y="revenue" />
|
|
45
|
+
<BigValue data="monthly_sales" value="revenue" />
|
|
46
|
+
````
|
|
47
|
+
|
|
48
|
+
`data` can be query name or table name. Attributes accept columns, expressions, or GSQL.
|
|
49
|
+
|
|
50
|
+
## Components
|
|
51
|
+
|
|
52
|
+
BarChart: data, x, y, y2, series, title, subtitle, xFmt, yFmt, y2Fmt, colorPalette, type, swapXY, labels, labelFmt, sort, legend, yMin, yMax
|
|
53
|
+
LineChart: data, x, y, y2, series, title, subtitle, xFmt, yFmt, colorPalette, labels, sort, legend, handleMissing, markers, markerShape, lineType, lineWidth
|
|
54
|
+
AreaChart: data, x, y, series, title, subtitle, xFmt, yFmt, colorPalette, type, labels, sort, legend, handleMissing, fillOpacity, line
|
|
55
|
+
PieChart: data, category, value, title, subtitle
|
|
56
|
+
BigValue: data, value, title, fmt, comparison, comparisonFmt, comparisonTitle, downIsGood, sparkline, sparklineType, sparklineColor
|
|
57
|
+
Table: data, rows, title, subtitle, groupBy, groupType, subtotals, totalRow, search, sort, link, rowShading, rowNumbers, compact, headerColor
|
|
58
|
+
Column: id, title, fmt, align, wrap, contentType, totalAgg, redNegatives
|
|
59
|
+
Dropdown: name, data, value, label, defaultValue, multiple, title
|
|
60
|
+
TextInput: name, title, placeholder
|
|
61
|
+
Row: layout container, distributes children horizontally
|
|
62
|
+
|
|
63
|
+
series: column whose values become separate lines/bars (series=country plots one line per country)
|
|
64
|
+
type: stacked (default), grouped, stacked100
|
|
65
|
+
y2: secondary y-axis, y2SeriesType sets its chart type (line/bar/scatter)
|
|
66
|
+
swapXY: horizontal bars
|
|
67
|
+
handleMissing: gap (default), connect, zero
|
|
68
|
+
colorPalette: comma-separated "#hex1, #hex2", applied to series in order
|
|
69
|
+
seriesOrder: control series order "Val1, Val2", pairs with colorPalette
|
|
70
|
+
labels: show value labels on chart
|
|
71
|
+
markers: show dots on line points; markerShape: circle, emptyCircle, rect, triangle, diamond
|
|
72
|
+
lineType: solid, dashed, dotted
|
|
73
|
+
downIsGood: green for negative (for comparison/delta)
|
|
74
|
+
sparkline: date column for mini trend; sparklineType: line, area, bar
|
|
75
|
+
groupBy: group table rows; groupType: accordion (collapsible) or section (merged)
|
|
76
|
+
totalRow: sum row at bottom; subtotals: sum row per group
|
|
77
|
+
link: make table rows clickable, value is URL column
|
|
78
|
+
|
|
79
|
+
Column contentType:
|
|
80
|
+
- delta: arrows+colors. deltaSymbol, downIsGood, chip, neutralMin/Max
|
|
81
|
+
- colorscale: background gradient. colorScale, colorMin/Mid/Max
|
|
82
|
+
- bar: in-cell bar. barColor, negativeBarColor
|
|
83
|
+
- link: clickable. linkLabel, openInNewTab
|
|
84
|
+
- image: show image. height, width
|
|
85
|
+
- sparkline/sparkarea/sparkbar: mini chart from array. sparkX, sparkY, sparkColor
|
|
86
|
+
|
|
87
|
+
Column totalAgg: sum (default), mean, weightedMean, median, min, max, count, countDistinct
|
|
88
|
+
|
|
89
|
+
Inputs: reference as $name in queries. `<Dropdown name=status .../>` -> `where status = $status`
|
|
90
|
+
|
|
91
|
+
## Formatting
|
|
92
|
+
|
|
93
|
+
`fmt` attr accepts Excel codes or built-ins:
|
|
94
|
+
Numbers: num0-num4, num0k, num0m, num0b
|
|
95
|
+
Currency: usd, usd0, usd1k, usd2m (also eur, gbp, etc)
|
|
96
|
+
Percent: pct, pct0, pct1, pct2
|
|
97
|
+
Dates: shortdate, longdate, mdy, dmy, mmm, yyyy
|
|
98
|
+
Excel: "$#,##0.00", "0.0%", "m/d/yy"
|
package/dist/docs/cli.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
## CLI
|
|
2
|
+
|
|
3
|
+
When using Graphene from the CLI, the main command would be:
|
|
4
|
+
|
|
5
|
+
`npm exec graphene check`
|
|
6
|
+
|
|
7
|
+
Use the appropriate package manager if this project doesn't use npm. `check` takes several options
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm exec graphene check # Syntax check entire project
|
|
11
|
+
npm exec graphene check [mdPath] # Check specific markdown file, and take a screenshot
|
|
12
|
+
npm exec graphene check [mdPath] -c [chartTitle] # Check and get a screenshot for one specific chart
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`npm exec graphene run "<sql>"` can be used to run sql directly, without creating a md file.
|
|
16
|
+
`npm exec graphene compile "<sql>"` shows the compiled, dialect-specific SQL.
|
|
17
|
+
|
|
18
|
+
### Best Practices
|
|
19
|
+
|
|
20
|
+
Start simple - Get basic query working, then add complexity.
|
|
21
|
+
Use check often - Catches syntax errors and shows visual output.
|
|
22
|
+
Leverage models - Use modeled joins and stored expressions rather than raw SQL.
|
package/dist/docs/graphene.md
CHANGED
|
@@ -109,7 +109,7 @@ GSQL is comprised of four primary statements:
|
|
|
109
109
|
|
|
110
110
|
```sql
|
|
111
111
|
table orders (
|
|
112
|
-
id BIGINT
|
|
112
|
+
id BIGINT
|
|
113
113
|
user_id BIGINT
|
|
114
114
|
created_at DATETIME
|
|
115
115
|
status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
|
|
@@ -126,7 +126,7 @@ table orders (
|
|
|
126
126
|
)
|
|
127
127
|
|
|
128
128
|
table users (
|
|
129
|
-
id BIGINT
|
|
129
|
+
id BIGINT
|
|
130
130
|
name VARCHAR
|
|
131
131
|
email VARCHAR
|
|
132
132
|
age INTEGER
|
|
@@ -140,7 +140,7 @@ We can break down a table statement into three parts: [base columns](#base-colum
|
|
|
140
140
|
|
|
141
141
|
### Base columns (required)
|
|
142
142
|
|
|
143
|
-
The base column set is simply a reflection of the underlying database table's schema. Similar to `create table` statements in regular SQL DDL, you list each column's name and data type.
|
|
143
|
+
The base column set is simply a reflection of the underlying database table's schema. Similar to `create table` statements in regular SQL DDL, you list each column's name and data type.
|
|
144
144
|
|
|
145
145
|
### Join relationships
|
|
146
146
|
|
|
@@ -231,7 +231,7 @@ table orders (
|
|
|
231
231
|
)
|
|
232
232
|
|
|
233
233
|
table users (
|
|
234
|
-
id BIGINT
|
|
234
|
+
id BIGINT,
|
|
235
235
|
name VARCHAR,
|
|
236
236
|
...
|
|
237
237
|
)
|
|
@@ -270,7 +270,7 @@ table users (
|
|
|
270
270
|
)
|
|
271
271
|
|
|
272
272
|
table countries (
|
|
273
|
-
code VARCHAR
|
|
273
|
+
code VARCHAR
|
|
274
274
|
name VARCHAR
|
|
275
275
|
currency VARCHAR
|
|
276
276
|
free_shipping BOOLEAN
|
|
@@ -300,7 +300,7 @@ Again, using the orders table from before:
|
|
|
300
300
|
|
|
301
301
|
```sql
|
|
302
302
|
table orders (
|
|
303
|
-
id BIGINT
|
|
303
|
+
id BIGINT
|
|
304
304
|
user_id BIGINT
|
|
305
305
|
created_at DATETIME
|
|
306
306
|
status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
|
|
@@ -385,7 +385,7 @@ Here's an example of a fan-out:
|
|
|
385
385
|
|
|
386
386
|
```sql
|
|
387
387
|
table orders (
|
|
388
|
-
id BIGINT
|
|
388
|
+
id BIGINT
|
|
389
389
|
customer_name VARCHAR
|
|
390
390
|
amt_with_shipping FLOAT
|
|
391
391
|
|
|
@@ -393,7 +393,7 @@ table orders (
|
|
|
393
393
|
)
|
|
394
394
|
|
|
395
395
|
table order_items (
|
|
396
|
-
id BIGINT
|
|
396
|
+
id BIGINT
|
|
397
397
|
order_id BIGINT
|
|
398
398
|
product VARCHAR
|
|
399
399
|
price FLOAT
|
|
@@ -583,7 +583,7 @@ You can turn the output of any `select` statement into a table with `table foo a
|
|
|
583
583
|
|
|
584
584
|
```sql
|
|
585
585
|
table orders (
|
|
586
|
-
id BIGINT
|
|
586
|
+
id BIGINT
|
|
587
587
|
user_id BIGINT
|
|
588
588
|
created_at DATETIME
|
|
589
589
|
status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
|
|
@@ -600,7 +600,7 @@ table orders (
|
|
|
600
600
|
)
|
|
601
601
|
|
|
602
602
|
table users (
|
|
603
|
-
id BIGINT
|
|
603
|
+
id BIGINT
|
|
604
604
|
name VARCHAR
|
|
605
605
|
email VARCHAR
|
|
606
606
|
age INTEGER
|
|
@@ -4,7 +4,7 @@ import * as chartWindowDebug from './chartWindowDebug'
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {import("echarts").EChartsOption & {
|
|
7
|
-
*
|
|
7
|
+
* onclick?: (params: any) => void;
|
|
8
8
|
* showAllXAxisLabels?: boolean;
|
|
9
9
|
* theme: 'light' | 'dark';
|
|
10
10
|
* }
|
|
@@ -148,9 +148,8 @@ const echartsAction = (node, options) => {
|
|
|
148
148
|
applySeriesOptions()
|
|
149
149
|
|
|
150
150
|
// Click event handler:
|
|
151
|
-
let dispatch = options.dispatch
|
|
152
151
|
chart.on('click', function (params) {
|
|
153
|
-
|
|
152
|
+
options.onclick?.(params)
|
|
154
153
|
})
|
|
155
154
|
|
|
156
155
|
// watching parent element is necessary for charts within `Fullscreen` components
|
|
@@ -241,18 +241,10 @@ function maybeExtractFormatTag (columnName) {
|
|
|
241
241
|
* Formats a value to whichever format is passed in
|
|
242
242
|
* @param {*} value the value to be formatted
|
|
243
243
|
* @param {string} format string containing an Excel-style format code, or a format name matching a built-in or custom format
|
|
244
|
+
* @param {'number' | 'date' | 'boolean' | 'string'} [valueType] the known column type
|
|
244
245
|
* @returns a formatted value
|
|
245
246
|
*/
|
|
246
|
-
export function fmt (value, format) {
|
|
247
|
-
let formatObj = getFormatObjectFromString(format)
|
|
248
|
-
let valueType = valueTypeFromSample(value)
|
|
249
|
-
formatObj.valueType = valueType
|
|
247
|
+
export function fmt (value, format, valueType = undefined) {
|
|
248
|
+
let formatObj = getFormatObjectFromString(format, valueType)
|
|
250
249
|
return formatValue(value, formatObj)
|
|
251
250
|
}
|
|
252
|
-
|
|
253
|
-
function valueTypeFromSample (value) {
|
|
254
|
-
if (typeof value === 'number') return 'number'
|
|
255
|
-
if (typeof value === 'boolean') return 'boolean'
|
|
256
|
-
if (value instanceof Date) return 'date'
|
|
257
|
-
return 'string'
|
|
258
|
-
}
|
|
@@ -227,8 +227,9 @@ export default function getSeriesConfig (
|
|
|
227
227
|
|
|
228
228
|
// format series config:
|
|
229
229
|
if (seriesLabelFmt) {
|
|
230
|
+
let seriesType = series ? columnSummary[series]?.type : undefined
|
|
230
231
|
seriesConfig.forEach((item) => {
|
|
231
|
-
item.name = fmt(item.name, seriesLabelFmt)
|
|
232
|
+
item.name = fmt(item.name, seriesLabelFmt, seriesType)
|
|
232
233
|
})
|
|
233
234
|
}
|
|
234
235
|
|
|
@@ -1,177 +1,214 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import {
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {getContext} from 'svelte'
|
|
3
|
+
import type {Writable} from 'svelte/store'
|
|
3
4
|
import {propKey, configKey} from '../component-utilities/chartContext.js'
|
|
4
5
|
import getSeriesConfig from '../component-utilities/getSeriesConfig.js'
|
|
5
6
|
import formatTitle from '../component-utilities/formatTitle.js'
|
|
6
7
|
import replaceNulls from '../component-utilities/replaceNulls.js'
|
|
7
8
|
import getCompletedData from '../component-utilities/getCompletedData.js'
|
|
8
|
-
import {
|
|
9
|
-
formatValue,
|
|
10
|
-
getFormatObjectFromString,
|
|
11
|
-
} from '../component-utilities/formatting.js'
|
|
9
|
+
import {formatValue, getFormatObjectFromString} from '../component-utilities/formatting.js'
|
|
12
10
|
import {getThemeStores} from '../component-utilities/themeStores'
|
|
13
11
|
import {parseCommaList} from '../component-utilities/inputUtils.ts'
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export let series = undefined
|
|
22
|
-
const seriesSet = !!series
|
|
23
|
-
export let options = undefined
|
|
24
|
-
export let name = undefined
|
|
25
|
-
export let type = 'stacked'
|
|
26
|
-
export let fillColor = undefined
|
|
27
|
-
$: fillColorStore = resolveColor(fillColor)
|
|
28
|
-
export let lineColor = undefined
|
|
29
|
-
$: lineColorStore = resolveColor(lineColor)
|
|
30
|
-
export let fillOpacity = undefined
|
|
31
|
-
export let line = true
|
|
32
|
-
$: line = line === 'true' || line === true
|
|
33
|
-
export let markers = false
|
|
34
|
-
$: markers = markers === 'true' || markers === true
|
|
35
|
-
export let markerShape = 'circle'
|
|
36
|
-
export let markerSize = 8
|
|
37
|
-
export let handleMissing = 'gap'
|
|
38
|
-
export let step = false
|
|
39
|
-
$: step = step === 'true' || step === true
|
|
40
|
-
export let stepPosition = 'end'
|
|
41
|
-
export let labels = false
|
|
42
|
-
$: labels = labels === 'true' || labels === true
|
|
43
|
-
export let labelSize = 11
|
|
44
|
-
export let labelPosition = 'top'
|
|
45
|
-
export let labelColor = undefined
|
|
46
|
-
$: labelColorStore = resolveColor(labelColor)
|
|
47
|
-
export let labelFmt = undefined
|
|
48
|
-
export let showAllLabels = false
|
|
49
|
-
export let seriesOrder = undefined
|
|
50
|
-
export let seriesLabelFmt = undefined
|
|
51
|
-
|
|
52
|
-
let labelFormat
|
|
53
|
-
let data
|
|
54
|
-
let x
|
|
55
|
-
let swapXY
|
|
56
|
-
let yFormat
|
|
57
|
-
let xType
|
|
58
|
-
let xMismatch
|
|
59
|
-
let columnSummary
|
|
60
|
-
let seriesConfig
|
|
61
|
-
let chartOverrides
|
|
62
|
-
let defaultLabelPosition
|
|
63
|
-
let resolvedY
|
|
64
|
-
let stackName
|
|
65
|
-
|
|
66
|
-
if (labelFmt) labelFormat = getFormatObjectFromString(labelFmt)
|
|
67
|
-
|
|
68
|
-
$: data = $props.data
|
|
69
|
-
$: x = $props.x
|
|
70
|
-
$: swapXY = $props.swapXY
|
|
71
|
-
$: yFormat = $props.yFormat
|
|
72
|
-
$: xType = $props.xType
|
|
73
|
-
$: xMismatch = $props.xMismatch
|
|
74
|
-
$: columnSummary = $props.columnSummary
|
|
75
|
-
$: series = seriesSet ? series : $props.series
|
|
76
|
-
$: resolvedY = ySet ? parseCommaList(y) : $props.y
|
|
77
|
-
$: seriesOrder = parseCommaList(seriesOrder)
|
|
78
|
-
|
|
79
|
-
$: {
|
|
80
|
-
if (!series && (!Array.isArray(resolvedY) || resolvedY.length === 1)) {
|
|
81
|
-
stackName = undefined
|
|
82
|
-
let col = Array.isArray(resolvedY) ? resolvedY[0] : resolvedY
|
|
83
|
-
if (columnSummary?.[col]) name = name ?? formatTitle(col, columnSummary[col].title)
|
|
84
|
-
} else {
|
|
85
|
-
stackName = 'area'
|
|
86
|
-
data = getCompletedData(data, x, resolvedY, series, false, xType === 'value')
|
|
87
|
-
data = replaceNulls(data, resolvedY)
|
|
88
|
-
xType = xType === 'value' ? 'category' : xType
|
|
89
|
-
}
|
|
13
|
+
interface Props {
|
|
14
|
+
y?: any, series?: any, options?: any, name?: any, type?: string, fillColor?: any, lineColor?: any
|
|
15
|
+
fillOpacity?: any, line?: boolean | string, markers?: boolean | string, markerShape?: string
|
|
16
|
+
markerSize?: number, handleMissing?: string, step?: boolean | string, stepPosition?: string
|
|
17
|
+
labels?: boolean | string, labelSize?: number, labelPosition?: string, labelColor?: any
|
|
18
|
+
labelFmt?: any, showAllLabels?: boolean | string, seriesOrder?: any, seriesLabelFmt?: any
|
|
90
19
|
}
|
|
91
20
|
|
|
92
|
-
|
|
21
|
+
const {resolveColor} = getThemeStores()
|
|
22
|
+
const chartProps: Writable<any> = getContext(propKey)
|
|
23
|
+
const config: Writable<any> = getContext(configKey)
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
y = undefined, series = undefined, options = undefined, name = undefined, type = 'stacked',
|
|
27
|
+
fillColor = undefined, lineColor = undefined, fillOpacity = undefined, line = true, markers = false,
|
|
28
|
+
markerShape = 'circle', markerSize = 8, handleMissing = 'gap', step = false, stepPosition = 'end',
|
|
29
|
+
labels = false, labelSize = 11, labelPosition = 'top', labelColor = undefined, labelFmt = undefined,
|
|
30
|
+
showAllLabels = false, seriesOrder = undefined, seriesLabelFmt = undefined,
|
|
31
|
+
}: Props = $props()
|
|
32
|
+
|
|
33
|
+
// Use $derived for values that depend on props
|
|
34
|
+
let ySet = $derived(y ? true : false)
|
|
35
|
+
let seriesSet = $derived(series ? true : false)
|
|
36
|
+
|
|
37
|
+
let fillColorStore = $derived(resolveColor(fillColor))
|
|
38
|
+
let lineColorStore = $derived(resolveColor(lineColor))
|
|
39
|
+
let lineBool = $derived(line === 'true' || line === true)
|
|
40
|
+
let markersBool = $derived(markers === 'true' || markers === true)
|
|
41
|
+
let stepBool = $derived(step === 'true' || step === true)
|
|
42
|
+
let labelsBool = $derived(labels === 'true' || labels === true)
|
|
43
|
+
let labelColorStore = $derived(resolveColor(labelColor))
|
|
44
|
+
|
|
45
|
+
// Format objects derived from props
|
|
46
|
+
let labelFormat = $derived(labelFmt ? getFormatObjectFromString(labelFmt) : undefined)
|
|
93
47
|
|
|
94
48
|
const labelPositions = {above: 'top', below: 'bottom', middle: 'inside'}
|
|
95
49
|
const swapXYLabelPositions = {above: 'right', below: 'left', middle: 'inside'}
|
|
96
50
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
51
|
+
// Derive values from chartProps store instead of using $effect to assign
|
|
52
|
+
let data = $derived($chartProps.data)
|
|
53
|
+
let x = $derived($chartProps.x)
|
|
54
|
+
let swapXY = $derived($chartProps.swapXY)
|
|
55
|
+
let yFormat = $derived($chartProps.yFormat)
|
|
56
|
+
let baseXType = $derived($chartProps.xType)
|
|
57
|
+
let xMismatch = $derived($chartProps.xMismatch)
|
|
58
|
+
let columnSummary = $derived($chartProps.columnSummary)
|
|
59
|
+
let resolvedSeries = $derived(seriesSet ? series : $chartProps.series)
|
|
60
|
+
let resolvedY = $derived(ySet ? parseCommaList(y) : $chartProps.y)
|
|
61
|
+
let resolvedSeriesOrder = $derived(parseCommaList(seriesOrder))
|
|
101
62
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
formatter: (params) =>
|
|
111
|
-
params.value[swapXY ? 0 : 1] === 0
|
|
112
|
-
? ''
|
|
113
|
-
: formatValue(params.value[swapXY ? 0 : 1], labelFormat ?? yFormat),
|
|
114
|
-
fontSize: labelSize,
|
|
115
|
-
color: $labelColorStore,
|
|
116
|
-
position: labelPosition,
|
|
117
|
-
padding: 3,
|
|
118
|
-
},
|
|
119
|
-
labelLayout: {hideOverlap: showAllLabels ? false : true},
|
|
120
|
-
emphasis: {focus: 'series'},
|
|
121
|
-
showSymbol: labels || markers,
|
|
122
|
-
symbol: markerShape,
|
|
123
|
-
symbolSize: labels && !markers ? 0 : markerSize,
|
|
124
|
-
step: step ? stepPosition : false,
|
|
125
|
-
}
|
|
63
|
+
// Compute all the derived state in one $derived.by block to avoid read/write conflicts
|
|
64
|
+
let computedState = $derived.by(() => {
|
|
65
|
+
let isSingleSeries = !resolvedSeries && (!Array.isArray(resolvedY) || resolvedY.length === 1)
|
|
66
|
+
let computedData = data
|
|
67
|
+
let computedXType = baseXType
|
|
68
|
+
let computedName = name
|
|
69
|
+
let computedStackName: string | undefined = undefined
|
|
70
|
+
let computedDefaultLabelPosition = swapXY ? 'right' : 'top'
|
|
126
71
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
72
|
+
if (!data || !columnSummary) {
|
|
73
|
+
return {
|
|
74
|
+
data: computedData,
|
|
75
|
+
xType: computedXType,
|
|
76
|
+
name: computedName,
|
|
77
|
+
stackName: computedStackName,
|
|
78
|
+
defaultLabelPosition: computedDefaultLabelPosition,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isSingleSeries) {
|
|
83
|
+
// Single Series
|
|
84
|
+
let col = Array.isArray(resolvedY) ? resolvedY[0] : resolvedY
|
|
85
|
+
if (col && columnSummary[col]) {
|
|
86
|
+
computedName = computedName ?? formatTitle(col, columnSummary[col].title)
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// Multi Series
|
|
90
|
+
computedStackName = 'area'
|
|
91
|
+
computedData = getCompletedData(computedData, x, resolvedY, resolvedSeries, false, computedXType === 'value')
|
|
92
|
+
computedData = replaceNulls(computedData, resolvedY)
|
|
93
|
+
computedXType = computedXType === 'value' ? 'category' : computedXType
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Handle missing values
|
|
97
|
+
if (handleMissing === 'zero') {
|
|
98
|
+
computedData = replaceNulls(computedData, resolvedY)
|
|
99
|
+
}
|
|
143
100
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
101
|
+
return {
|
|
102
|
+
data: computedData,
|
|
103
|
+
xType: computedXType,
|
|
104
|
+
name: computedName,
|
|
105
|
+
stackName: computedStackName,
|
|
106
|
+
defaultLabelPosition: computedDefaultLabelPosition,
|
|
107
|
+
}
|
|
148
108
|
})
|
|
149
109
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
110
|
+
// Extract computed values for use in template and other derived values
|
|
111
|
+
let processedData = $derived(computedState.data)
|
|
112
|
+
let xType = $derived(computedState.xType)
|
|
113
|
+
let resolvedName = $derived(computedState.name)
|
|
114
|
+
let resolvedStackName = $derived(computedState.stackName)
|
|
115
|
+
let defaultLabelPosition = $derived(computedState.defaultLabelPosition)
|
|
116
|
+
|
|
117
|
+
let resolvedLabelPosition = $derived(
|
|
118
|
+
(swapXY ? swapXYLabelPositions[labelPosition] : labelPositions[labelPosition]) ?? defaultLabelPosition,
|
|
119
|
+
)
|
|
153
120
|
|
|
154
|
-
|
|
121
|
+
let chartOverrides = $derived({
|
|
155
122
|
yAxis: {boundaryGap: ['0%', '1%']},
|
|
156
123
|
xAxis: {boundaryGap: ['4%', '4%'], type: xType},
|
|
157
|
-
}
|
|
124
|
+
})
|
|
158
125
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
126
|
+
$effect(() => {
|
|
127
|
+
// Don't run until we have data
|
|
128
|
+
if (!processedData || !columnSummary) return
|
|
129
|
+
|
|
130
|
+
let baseConfig = {
|
|
131
|
+
type: 'line',
|
|
132
|
+
stack: resolvedStackName,
|
|
133
|
+
areaStyle: {color: $fillColorStore, opacity: fillOpacity},
|
|
134
|
+
connectNulls: handleMissing === 'connect',
|
|
135
|
+
lineStyle: {width: lineBool ? 1 : 0, color: $lineColorStore},
|
|
136
|
+
label: {
|
|
137
|
+
show: labelsBool,
|
|
138
|
+
formatter: (params: any) =>
|
|
139
|
+
params.value[swapXY ? 0 : 1] === 0
|
|
140
|
+
? ''
|
|
141
|
+
: formatValue(params.value[swapXY ? 0 : 1], labelFormat ?? yFormat),
|
|
142
|
+
fontSize: labelSize,
|
|
143
|
+
color: $labelColorStore,
|
|
144
|
+
position: resolvedLabelPosition,
|
|
145
|
+
padding: 3,
|
|
146
|
+
},
|
|
147
|
+
labelLayout: {hideOverlap: showAllLabels ? false : true},
|
|
148
|
+
emphasis: {focus: 'series'},
|
|
149
|
+
showSymbol: labelsBool || markersBool,
|
|
150
|
+
symbol: markerShape,
|
|
151
|
+
symbolSize: labelsBool && !markersBool ? 0 : markerSize,
|
|
152
|
+
step: stepBool ? stepPosition : false,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let seriesConfig = getSeriesConfig(
|
|
156
|
+
processedData,
|
|
157
|
+
x,
|
|
158
|
+
resolvedY,
|
|
159
|
+
resolvedSeries,
|
|
160
|
+
swapXY,
|
|
161
|
+
baseConfig,
|
|
162
|
+
resolvedName,
|
|
163
|
+
xMismatch,
|
|
164
|
+
columnSummary,
|
|
165
|
+
resolvedSeriesOrder,
|
|
166
|
+
undefined,
|
|
167
|
+
undefined,
|
|
168
|
+
undefined,
|
|
169
|
+
seriesLabelFmt,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
config.update((d: any) => {
|
|
173
|
+
// Guard against incomplete config state
|
|
174
|
+
if (!d.series) d.series = []
|
|
175
|
+
if (!d.legend) d.legend = {data: []}
|
|
176
|
+
if (!d.legend.data) d.legend.data = []
|
|
177
|
+
|
|
178
|
+
d.series.push(...seriesConfig)
|
|
179
|
+
d.legend.data.push(...seriesConfig.map((entry: any) => entry.name.toString()))
|
|
180
|
+
return d
|
|
175
181
|
})
|
|
176
182
|
})
|
|
183
|
+
|
|
184
|
+
// Use $effect.pre() instead of beforeUpdate for runes mode
|
|
185
|
+
$effect.pre(() => {
|
|
186
|
+
if (options) {
|
|
187
|
+
config.update((d: any) => ({...d, ...options}))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (chartOverrides) {
|
|
191
|
+
config.update((d: any) => {
|
|
192
|
+
// Guard against incomplete config state
|
|
193
|
+
if (!d.yAxis || !Array.isArray(d.yAxis)) return d
|
|
194
|
+
|
|
195
|
+
d.tooltip = {...d.tooltip, order: 'seriesDesc'}
|
|
196
|
+
if (swapXY) {
|
|
197
|
+
d.yAxis = {...d.yAxis, ...chartOverrides.xAxis}
|
|
198
|
+
d.xAxis = {...d.xAxis, ...chartOverrides.yAxis}
|
|
199
|
+
} else {
|
|
200
|
+
if (d.yAxis[0]) {
|
|
201
|
+
d.yAxis[0] = {...d.yAxis[0], ...chartOverrides.yAxis}
|
|
202
|
+
}
|
|
203
|
+
d.xAxis = {...d.xAxis, ...chartOverrides.xAxis}
|
|
204
|
+
}
|
|
205
|
+
if (type === 'stacked100') {
|
|
206
|
+
if (swapXY) d.xAxis = {...d.xAxis, max: 1}
|
|
207
|
+
else if (d.yAxis[0]) d.yAxis[0] = {...d.yAxis[0], max: 1}
|
|
208
|
+
}
|
|
209
|
+
if (labelsBool) d.axisPointer = {triggerEmphasis: false}
|
|
210
|
+
return d
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
})
|
|
177
214
|
</script>
|