@graphenedata/cli 0.0.5 → 0.0.6
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/cli.ts +20 -14
- package/dist/cli/cli.js +936 -413
- package/dist/docs/graphene.md +180 -133
- package/dist/ui/component-utilities/inputUtils.ts +11 -0
- package/dist/ui/components/Area.svelte +6 -3
- package/dist/ui/components/AreaChart.svelte +2 -1
- package/dist/ui/components/Bar.svelte +14 -8
- package/dist/ui/components/BarChart.svelte +3 -2
- package/dist/ui/components/Chart.svelte +48 -101
- package/dist/ui/components/Column.svelte +2 -0
- package/dist/ui/components/Line.svelte +8 -5
- package/dist/ui/components/LineChart.svelte +3 -3
- package/dist/ui/components/QueryLoad.svelte +1 -1
- package/dist/ui/internal/queryEngine.ts +29 -8
- package/dist/ui/web.js +3 -2
- package/package.json +2 -2
package/dist/docs/graphene.md
CHANGED
|
@@ -21,6 +21,10 @@ Graphene also has a CLI that lets you check syntax, run queries, serve data apps
|
|
|
21
21
|
- [Using stored expressions in queries](#using-stored-expressions-in-queries)
|
|
22
22
|
- [Safe aggregation in fan-outs](#safe-aggregation-in-fan-outs)
|
|
23
23
|
- [`table as` statements](#table-as-statements)
|
|
24
|
+
- [`extend` statements](#extend-statements)
|
|
25
|
+
- [Working with dates, timestamps, and intervals](#working-with-dates-timestamps-and-intervals)
|
|
26
|
+
- [Date and timestamp literals](#date-and-timestamp-literals)
|
|
27
|
+
- [Interval literals](#interval-literals)
|
|
24
28
|
- [Other miscellaneous details about GSQL](#other-miscellaneous-details-about-gsql)
|
|
25
29
|
- [Graphene data apps (dashboards)](#graphene-data-apps-dashboards)
|
|
26
30
|
- [Visualization components](#visualization-components)
|
|
@@ -90,40 +94,30 @@ GSQL is comprised of `table` statements that declare tables and `select` stateme
|
|
|
90
94
|
|
|
91
95
|
```sql
|
|
92
96
|
table orders (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
/* Scalar expressions */
|
|
108
|
-
|
|
109
|
-
status in ('Processing', 'Shipped', 'Complete') as revenue_recognized,
|
|
110
|
-
|
|
111
|
-
/* Agg expressions */
|
|
112
|
-
|
|
113
|
-
sum(case when revenue_recognized then amount else 0 end) as revenue,
|
|
114
|
-
sum(case when revenue_recognized then cost else 0 end) as cogs,
|
|
115
|
-
revenue - cogs as profit,
|
|
116
|
-
profit / revenue as profit_margin
|
|
97
|
+
id BIGINT primary_key
|
|
98
|
+
user_id BIGINT
|
|
99
|
+
created_at DATETIME
|
|
100
|
+
status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
|
|
101
|
+
amount FLOAT -- Amount paid by customer
|
|
102
|
+
cost FLOAT -- Cost of materials
|
|
103
|
+
|
|
104
|
+
join one users on user_id = users.id
|
|
105
|
+
|
|
106
|
+
revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
|
|
107
|
+
revenue: sum(case when revenue_recognized then amount else 0 end)
|
|
108
|
+
cogs: sum(case when revenue_recognized then cost else 0 end)
|
|
109
|
+
profit: revenue - cogs
|
|
110
|
+
profit_margin: profit / revenue
|
|
117
111
|
)
|
|
118
112
|
|
|
119
113
|
table users (
|
|
120
|
-
id BIGINT primary_key
|
|
121
|
-
name VARCHAR
|
|
122
|
-
email VARCHAR
|
|
123
|
-
age INTEGER
|
|
124
|
-
country_code VARCHAR
|
|
114
|
+
id BIGINT primary_key
|
|
115
|
+
name VARCHAR
|
|
116
|
+
email VARCHAR
|
|
117
|
+
age INTEGER
|
|
118
|
+
country_code VARCHAR
|
|
125
119
|
|
|
126
|
-
|
|
120
|
+
join many orders on id = orders.user_id
|
|
127
121
|
)
|
|
128
122
|
```
|
|
129
123
|
|
|
@@ -154,19 +148,19 @@ Sometimes there are multiple valid ways to join two tables together. You can mod
|
|
|
154
148
|
```sql
|
|
155
149
|
table projects (
|
|
156
150
|
...
|
|
157
|
-
owner_id BIGINT
|
|
158
|
-
viewer_id BIGINT
|
|
151
|
+
owner_id BIGINT
|
|
152
|
+
viewer_id BIGINT
|
|
159
153
|
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
join one users as project_owner on owner_id = project_owner.id
|
|
155
|
+
join one users as project_viewer on viewer_id = project_viewer.id
|
|
162
156
|
)
|
|
163
157
|
|
|
164
158
|
table users (
|
|
165
159
|
...
|
|
166
|
-
id BIGINT
|
|
160
|
+
id BIGINT
|
|
167
161
|
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
join many projects as projects_as_owner on id = projects_as_owner.owner_id
|
|
163
|
+
join many projects as projects_as_viewer on id = projects_as_viewer.viewer_id
|
|
170
164
|
)
|
|
171
165
|
```
|
|
172
166
|
|
|
@@ -179,7 +173,7 @@ table users (
|
|
|
179
173
|
|
|
180
174
|
**Stored expressions** are GSQL expressions (ie. any arbitrary combination of functions, operators, and column references) that you want to make reusable to queries. Stored expressions are great for canonizing metrics, segments, and other important business definitions.
|
|
181
175
|
|
|
182
|
-
A stored expression must be given a name via `
|
|
176
|
+
A stored expression must be given a name via `name: expression`. It can then be referenced by name in queries that use the parent table. See [Using stored expressions in queries](#using-stored-expressions-in-queries) below for how to use stored expressions in queries.
|
|
183
177
|
|
|
184
178
|
Like expressions in regular SQL, expressions in GSQL are either scalar or aggregative. In BI parlance, these would be called dimensions and measures, respectively.
|
|
185
179
|
|
|
@@ -190,15 +184,13 @@ table orders (
|
|
|
190
184
|
...
|
|
191
185
|
|
|
192
186
|
/* Scalar expressions */
|
|
193
|
-
|
|
194
|
-
status in ('Processing', 'Shipped', 'Complete') as revenue_recognized,
|
|
187
|
+
revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
|
|
195
188
|
|
|
196
189
|
/* Agg expressions */
|
|
197
|
-
|
|
198
|
-
sum(case when revenue_recognized then
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
profit / revenue as profit_margin
|
|
190
|
+
revenue: sum(case when revenue_recognized then amount else 0 end)
|
|
191
|
+
cogs: sum(case when revenue_recognized then cost else 0 end)
|
|
192
|
+
profit: revenue - cogs -- even though there are no agg functions here, this is still aggregative as it references other aggregative expressions
|
|
193
|
+
profit_margin: profit / revenue
|
|
202
194
|
)
|
|
203
195
|
```
|
|
204
196
|
|
|
@@ -221,7 +213,7 @@ If you recall the model from before:
|
|
|
221
213
|
table orders (
|
|
222
214
|
...
|
|
223
215
|
user_id BIGINT,
|
|
224
|
-
|
|
216
|
+
join one users on user_id = users.id
|
|
225
217
|
)
|
|
226
218
|
|
|
227
219
|
table users (
|
|
@@ -253,23 +245,23 @@ Sometimes you need to access columns or stored expressions in a table that is tw
|
|
|
253
245
|
table orders (
|
|
254
246
|
...
|
|
255
247
|
|
|
256
|
-
|
|
248
|
+
join one users on user_id = users.id
|
|
257
249
|
)
|
|
258
250
|
|
|
259
251
|
table users (
|
|
260
252
|
...
|
|
261
253
|
|
|
262
|
-
|
|
263
|
-
|
|
254
|
+
join many orders on id = orders.user_id
|
|
255
|
+
join one country on country_code = countries.code
|
|
264
256
|
)
|
|
265
257
|
|
|
266
258
|
table countries (
|
|
267
|
-
code VARCHAR primary_key
|
|
268
|
-
name VARCHAR
|
|
269
|
-
currency VARCHAR
|
|
270
|
-
free_shipping BOOLEAN
|
|
259
|
+
code VARCHAR primary_key
|
|
260
|
+
name VARCHAR
|
|
261
|
+
currency VARCHAR
|
|
262
|
+
free_shipping BOOLEAN
|
|
271
263
|
|
|
272
|
-
|
|
264
|
+
join many users on code = users.country_code
|
|
273
265
|
)
|
|
274
266
|
```
|
|
275
267
|
|
|
@@ -294,21 +286,20 @@ Again, using the orders table from before:
|
|
|
294
286
|
|
|
295
287
|
```sql
|
|
296
288
|
table orders (
|
|
297
|
-
id BIGINT primary_key
|
|
298
|
-
user_id BIGINT
|
|
299
|
-
created_at DATETIME
|
|
300
|
-
status STRING
|
|
301
|
-
amount FLOAT
|
|
302
|
-
cost FLOAT
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
status in ('Processing', 'Shipped', 'Complete')
|
|
307
|
-
|
|
308
|
-
sum(case when revenue_recognized then
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
profit / revenue as profit_margin
|
|
289
|
+
id BIGINT primary_key
|
|
290
|
+
user_id BIGINT
|
|
291
|
+
created_at DATETIME
|
|
292
|
+
status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
|
|
293
|
+
amount FLOAT -- Amount paid by customer
|
|
294
|
+
cost FLOAT -- Cost of materials
|
|
295
|
+
|
|
296
|
+
join one users on user_id = users.id
|
|
297
|
+
|
|
298
|
+
revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
|
|
299
|
+
revenue: sum(case when revenue_recognized then amount else 0 end)
|
|
300
|
+
cogs: sum(case when revenue_recognized then cost else 0 end)
|
|
301
|
+
profit: revenue - cogs
|
|
302
|
+
profit_margin: profit / revenue
|
|
312
303
|
)
|
|
313
304
|
```
|
|
314
305
|
|
|
@@ -330,7 +321,7 @@ select
|
|
|
330
321
|
status in ('Processing', 'Shipped', 'Complete') as revenue_recognized,
|
|
331
322
|
count(*)
|
|
332
323
|
from orders
|
|
333
|
-
group by 1
|
|
324
|
+
group by 1
|
|
334
325
|
```
|
|
335
326
|
|
|
336
327
|
You can see that invoking a stored expression is like using a macro: the definition for the stored expression is effectively expanded in-line by Graphene when it runs the query.
|
|
@@ -371,7 +362,7 @@ GSQL aims to solve this problem. With the additional information provided via `j
|
|
|
371
362
|
The query `select avg(users.age) from orders` will be rewritten to the following SQL when Graphene queries the underlying database (this is for BigQuery, specifically):
|
|
372
363
|
|
|
373
364
|
```sql
|
|
374
|
-
SELECT
|
|
365
|
+
SELECT
|
|
375
366
|
(CAST((
|
|
376
367
|
(
|
|
377
368
|
SUM(DISTINCT
|
|
@@ -394,45 +385,38 @@ You can turn the output of any `select` statement into a table with `table foo a
|
|
|
394
385
|
|
|
395
386
|
```sql
|
|
396
387
|
table orders (
|
|
397
|
-
id BIGINT primary_key
|
|
398
|
-
user_id BIGINT
|
|
399
|
-
created_at DATETIME
|
|
400
|
-
status STRING
|
|
401
|
-
amount FLOAT
|
|
402
|
-
cost FLOAT
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
status in ('Processing', 'Shipped', 'Complete')
|
|
407
|
-
|
|
408
|
-
sum(case when revenue_recognized then
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
profit / revenue as profit_margin
|
|
388
|
+
id BIGINT primary_key
|
|
389
|
+
user_id BIGINT
|
|
390
|
+
created_at DATETIME
|
|
391
|
+
status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
|
|
392
|
+
amount FLOAT -- Amount paid by customer
|
|
393
|
+
cost FLOAT -- Cost of materials
|
|
394
|
+
|
|
395
|
+
join one users on user_id = users.id
|
|
396
|
+
|
|
397
|
+
revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
|
|
398
|
+
revenue: sum(case when revenue_recognized then amount else 0 end)
|
|
399
|
+
cogs: sum(case when revenue_recognized then cost else 0 end)
|
|
400
|
+
profit: revenue - cogs
|
|
401
|
+
profit_margin: profit / revenue
|
|
412
402
|
)
|
|
413
403
|
|
|
414
404
|
table users (
|
|
415
|
-
id BIGINT primary_key
|
|
416
|
-
name VARCHAR
|
|
417
|
-
email VARCHAR
|
|
418
|
-
age INTEGER
|
|
405
|
+
id BIGINT primary_key
|
|
406
|
+
name VARCHAR
|
|
407
|
+
email VARCHAR
|
|
408
|
+
age INTEGER
|
|
419
409
|
|
|
420
|
-
|
|
421
|
-
|
|
410
|
+
join many orders on id = orders.user_id
|
|
411
|
+
join one user_facts on id = user_facts.id
|
|
422
412
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
user_facts.ltv as ltv,
|
|
426
|
-
user_facts.lifetime_orders as lifetime_orders
|
|
413
|
+
ltv: user_facts.ltv
|
|
414
|
+
lifetime_orders: user_facts.lifetime_orders
|
|
427
415
|
)
|
|
428
416
|
|
|
429
417
|
table user_facts as (
|
|
430
|
-
select
|
|
431
|
-
|
|
432
|
-
orders.revenue as ltv,
|
|
433
|
-
count(orders.id) as lifetime_orders,
|
|
434
|
-
from users
|
|
435
|
-
group by id
|
|
418
|
+
select id, orders.revenue as ltv, count(orders.id) as lifetime_orders,
|
|
419
|
+
from users group by id
|
|
436
420
|
)
|
|
437
421
|
```
|
|
438
422
|
|
|
@@ -440,6 +424,61 @@ table user_facts as (
|
|
|
440
424
|
- You cannot yet declare join relationships or stored expressions directly in a `table as` statement. Other tables can declare join relationships to it, though, as shown above.
|
|
441
425
|
- In the example above, the `ltv` and `lifetime_orders` columns from `user_facts` are "hoisted" back into `users` so that they appear as if they are columns from `users`. This is simply a design choice which allows query writers to never need to know about `user_facts`.
|
|
442
426
|
|
|
427
|
+
### `extend` statements
|
|
428
|
+
|
|
429
|
+
`extend` statements allow you to add join relationships or stored expressions to an existing table. This is especially useful for tables created via `table as` statements, which do not support defining these properties directly.
|
|
430
|
+
|
|
431
|
+
For example, if we have a `table as` statement that creates a daily summary of orders:
|
|
432
|
+
|
|
433
|
+
```sql
|
|
434
|
+
table daily_orders as (
|
|
435
|
+
select
|
|
436
|
+
date_trunc(created_at, day) as day,
|
|
437
|
+
count(*) as num_orders,
|
|
438
|
+
sum(amount) as total_revenue
|
|
439
|
+
from orders
|
|
440
|
+
group by 1
|
|
441
|
+
)
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
We can extend this table to add measures or joins:
|
|
445
|
+
|
|
446
|
+
```sql
|
|
447
|
+
extend daily_orders (
|
|
448
|
+
join one calendar on day = calendar.date
|
|
449
|
+
|
|
450
|
+
avg_order_value: total_revenue / num_orders
|
|
451
|
+
)
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Note that you cannot add new base columns with `extend`; you can only add joins and stored expressions.
|
|
455
|
+
|
|
456
|
+
### Working with dates, timestamps, and intervals
|
|
457
|
+
|
|
458
|
+
Graphene understands a handful of common literal formats so you rarely need explicit casts when filtering or doing time math.
|
|
459
|
+
|
|
460
|
+
**Date and timestamp literals**
|
|
461
|
+
|
|
462
|
+
- `YYYY`, `YYYY-MM`, and `YYYY-MM-DD` strings are treated as dates. Leading/trailing spaces are ignored.
|
|
463
|
+
- `YYYY-MM-DD HH[:MM[:SS]]` (with either a space or `T` between the date and time) is treated as a timestamp. Missing minutes or seconds default to `00`.
|
|
464
|
+
|
|
465
|
+
```sql
|
|
466
|
+
from users select id
|
|
467
|
+
where created_at >= '2024-01-01' and created_at <= '2024-02-01'
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Interval literals**
|
|
471
|
+
|
|
472
|
+
To add or subtract time, provide a quantity followed by a unit inside a string literal. Supported units include `second`, `minute`, `hour`, `day`, `week`, `month`, `quarter`, and `year` (plural forms or shorthands like `secs`, `mins`, `hrs` also work).
|
|
473
|
+
|
|
474
|
+
```sql
|
|
475
|
+
from users select
|
|
476
|
+
created_at + '5 minutes' as first_seen_plus_5,
|
|
477
|
+
created_at - '2 days' as first_seen_minus_2
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Interval literals accept decimals (`'1.5 hours'`) and negative values (`'-7 days'`). Invalid strings produce a diagnostic such as “Could not parse interval literal: "many moons"”.
|
|
481
|
+
|
|
443
482
|
### Other miscellaneous details about GSQL
|
|
444
483
|
|
|
445
484
|
- Trailing commas in `table` statements are optional.
|
|
@@ -449,6 +488,16 @@ table user_facts as (
|
|
|
449
488
|
- Expressions in `group by` are implicitly selected, so `from orders select avg(amount) group by user_id` will return two columns.
|
|
450
489
|
- `count` is a reserved word. Do not alias your columns as `count`.
|
|
451
490
|
- Window functions and set operations are not supported.
|
|
491
|
+
- Subqueries are not supported. However, you can accomplish the same functionality by chaining queries:
|
|
492
|
+
````md
|
|
493
|
+
```sql my_subquery
|
|
494
|
+
select ...
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
```sql my_query
|
|
498
|
+
select ... from my_subquery
|
|
499
|
+
```
|
|
500
|
+
````
|
|
452
501
|
|
|
453
502
|
## Graphene data apps (dashboards)
|
|
454
503
|
|
|
@@ -493,7 +542,7 @@ Use bar or column charts to compare a metric across categories. Bar charts are b
|
|
|
493
542
|
Here's an example:
|
|
494
543
|
|
|
495
544
|
```markdown
|
|
496
|
-
<BarChart
|
|
545
|
+
<BarChart
|
|
497
546
|
title="Sales by Category"
|
|
498
547
|
data=orders_by_category_2021
|
|
499
548
|
x=month
|
|
@@ -522,10 +571,10 @@ Here's an example:
|
|
|
522
571
|
|----------|-------------|----------|---------|---------|
|
|
523
572
|
| data | Query name, wrapped in curly braces | true | query name | - |
|
|
524
573
|
| x | Column or expression to use for the x-axis of the chart | false | column name, stored expression name, GSQL expression | First column |
|
|
525
|
-
| y | Column(s) or expression(s) to use for the y-axis of the chart | false | column name, stored expression name, GSQL expression, list of any combination of these | Any non-assigned numeric columns |
|
|
526
|
-
| y2 | Column(s) or expression(s) to include on a secondary y-axis | false | column name, stored expression name, GSQL expression, list of any combination of these | - |
|
|
574
|
+
| y | Column(s) or expression(s) to use for the y-axis of the chart. Each will create its own series. Consider a split axis with `y2` if there is a difference of scale or unit of measure between the series. | false | column name, stored expression name, GSQL expression, list of any combination of these | Any non-assigned numeric columns |
|
|
575
|
+
| y2 | Column(s) or expression(s) to include on a secondary y-axis. | false | column name, stored expression name, GSQL expression, list of any combination of these | - |
|
|
527
576
|
| y2SeriesType | Chart type to apply to the series on the y2 axis | false | `bar`, `line`, `scatter` | `bar` |
|
|
528
|
-
| series | Column or expression to use
|
|
577
|
+
| series | Column or expression to use to define the series (groups) in a multi-series chart. Use when values of a particular column dictate the multiple series to plot, eg. `country` would create a series for every distinct country in the column. | false | column name, stored expression name, GSQL expression | - |
|
|
529
578
|
| sort | Whether to apply default sort to your data. Default sort is x ascending for number and date x-axes, and y descending for category x-axes | false | `true`, `false` | `true` |
|
|
530
579
|
| type | Grouping method to use for multi-series charts | false | `stacked`, `grouped`, `stacked100` | `stacked` |
|
|
531
580
|
| stackName | Name for an individual stack. If separate Bar components are used with different stackNames, the chart will show multiple stacks | false | string | - |
|
|
@@ -608,7 +657,7 @@ Use a pie chart to show part-to-whole relationships across categories. Best for
|
|
|
608
657
|
Here's an example:
|
|
609
658
|
|
|
610
659
|
```markdown
|
|
611
|
-
<PieChart
|
|
660
|
+
<PieChart
|
|
612
661
|
title="Sales share by category"
|
|
613
662
|
data=orders_by_category_2021
|
|
614
663
|
category=category
|
|
@@ -640,12 +689,12 @@ Use line charts to display how one or more metrics vary over time. Line charts a
|
|
|
640
689
|
Here's an example:
|
|
641
690
|
|
|
642
691
|
```markdown
|
|
643
|
-
<LineChart
|
|
692
|
+
<LineChart
|
|
644
693
|
title="Monthly Sales"
|
|
645
694
|
subtitle="Includes all categories"
|
|
646
695
|
data=orders_by_month
|
|
647
696
|
x=month
|
|
648
|
-
y=sales_usd0k
|
|
697
|
+
y=sales_usd0k
|
|
649
698
|
yAxisTitle="Sales per Month"
|
|
650
699
|
/>
|
|
651
700
|
```
|
|
@@ -670,10 +719,10 @@ Here's an example:
|
|
|
670
719
|
|------|-------------|----------|---------|---------|
|
|
671
720
|
| data | Query name, wrapped in curly braces | true | query name | - |
|
|
672
721
|
| x | Column or expression to use for the x-axis of the chart | true | column name, stored expression name, GSQL expression | - |
|
|
673
|
-
| y | Column(s) or expression(s) to use for the y-axis of the chart | true | column name, stored expression name, GSQL expression, list of any combination of these | - |
|
|
674
|
-
| y2 | Column(s) or expression(s) to include on a secondary y-axis | false | column name, stored expression name, GSQL expression, list of any combination of these | - |
|
|
722
|
+
| y | Column(s) or expression(s) to use for the y-axis of the chart. Each will create its own series. Consider a split axis with `y2` if there is a difference of scale or unit of measure between the series. | true | column name, stored expression name, GSQL expression, list of any combination of these | - |
|
|
723
|
+
| y2 | Column(s) or expression(s) to include on a secondary y-axis. | false | column name, stored expression name, GSQL expression, list of any combination of these | - |
|
|
675
724
|
| y2SeriesType | Chart type to apply to the series on the y2 axis | false | `line`, `bar`, `scatter` | `line` |
|
|
676
|
-
| series | Column or expression to use
|
|
725
|
+
| series | Column or expression to use to define the series (groups) in a multi-series chart. Use when values of a particular column dictate the multiple series to plot, eg. `country` would create a series for every distinct country in the column. | false | column name, stored expression name, GSQL expression | - |
|
|
677
726
|
| sort | Whether to apply default sort to your data. Default is x ascending for number and date x-axes, and y descending for category x-axes | false | `true`, `false` | `true` |
|
|
678
727
|
| handleMissing | Treatment of missing values in the dataset | false | `gap`, `connect`, `zero` | `gap` |
|
|
679
728
|
| emptySet | Sets behaviour for empty datasets. Can throw an error, a warning, or allow empty. When set to 'error', empty datasets will block builds in `build:strict`. Note this only applies to initial page load - empty datasets caused by input component changes (dropdowns, etc.) are allowed. | false | `error`, `warn`, `pass` | `error` |
|
|
@@ -752,7 +801,7 @@ Use area charts to track how a metric with multiple series changes over time, or
|
|
|
752
801
|
Here's an example:
|
|
753
802
|
|
|
754
803
|
```markdown
|
|
755
|
-
<AreaChart
|
|
804
|
+
<AreaChart
|
|
756
805
|
data=orders_by_month
|
|
757
806
|
x=month
|
|
758
807
|
y=sales
|
|
@@ -779,8 +828,8 @@ Here's an example:
|
|
|
779
828
|
|------|-------------|----------|---------|---------|
|
|
780
829
|
| data | Query name, wrapped in curly braces | true | query name | - |
|
|
781
830
|
| x | Column or expression to use for the x-axis of the chart | true | column name, stored expression name, GSQL expression | First column |
|
|
782
|
-
| y | Column(s) or expression(s) to use for the y-axis of the chart | true | column name, stored expression name, GSQL expression, list of any combination of these | Any non-assigned numeric columns |
|
|
783
|
-
| series | Column or expression to use
|
|
831
|
+
| y | Column(s) or expression(s) to use for the y-axis of the chart. Each will create its own series. Consider a split axis with `y2` if there is a difference of scale or unit of measure between the series. | true | column name, stored expression name, GSQL expression, list of any combination of these | Any non-assigned numeric columns |
|
|
832
|
+
| series | Column or expression to use to define the series (groups) in a multi-series chart. Use when values of a particular column dictate the multiple series to plot, eg. `country` would create a series for every distinct country in the column. | false | column name, stored expression name, GSQL expression | - |
|
|
784
833
|
| sort | Whether to apply default sort to your data. Default sort is x ascending for number and date x-axes, and y descending for category x-axes | false | `true`, `false` | `true` |
|
|
785
834
|
| type | Grouping method to use for multi-series charts | false | `stacked`, `stacked100` | `stacked` |
|
|
786
835
|
| handleMissing | Treatment of missing values in the dataset | false | `gap`, `connect`, `zero` | `gap` for single series, `zero` for multi-series |
|
|
@@ -851,8 +900,8 @@ Use big values to display a large value standalone, and optionally include a com
|
|
|
851
900
|
Here's an example:
|
|
852
901
|
|
|
853
902
|
```markdown
|
|
854
|
-
<BigValue
|
|
855
|
-
data=orders_with_comparisons
|
|
903
|
+
<BigValue
|
|
904
|
+
data=orders_with_comparisons
|
|
856
905
|
value=num_orders
|
|
857
906
|
sparkline=month
|
|
858
907
|
comparison=order_growth
|
|
@@ -1069,7 +1118,7 @@ The user-inputted text would then be referenced in GSQL via `$name_of_input`. Fo
|
|
|
1069
1118
|
```sql
|
|
1070
1119
|
select *
|
|
1071
1120
|
from users
|
|
1072
|
-
where email ilike concat('%', $name_of_input, '%')
|
|
1121
|
+
where email ilike concat('%', $name_of_input, '%')
|
|
1073
1122
|
```
|
|
1074
1123
|
|
|
1075
1124
|
##### All text input attributes
|
|
@@ -1132,10 +1181,10 @@ Here's an example:
|
|
|
1132
1181
|
select distinct status from orders
|
|
1133
1182
|
```
|
|
1134
1183
|
|
|
1135
|
-
<Dropdown
|
|
1136
|
-
title="Select Order Status"
|
|
1184
|
+
<Dropdown
|
|
1185
|
+
title="Select Order Status"
|
|
1137
1186
|
name="status_dropdown"
|
|
1138
|
-
data="statuses"
|
|
1187
|
+
data="statuses"
|
|
1139
1188
|
value="status"
|
|
1140
1189
|
defaultValue="Complete"
|
|
1141
1190
|
/>
|
|
@@ -1201,20 +1250,20 @@ The easiest way to format numbers and dates in Graphene is through component att
|
|
|
1201
1250
|
For example, you can use the `fmt` attribute to format values inside a BigValue component:
|
|
1202
1251
|
|
|
1203
1252
|
```markdown
|
|
1204
|
-
<BigValue
|
|
1205
|
-
data=sales_data
|
|
1206
|
-
value=sales
|
|
1207
|
-
fmt="$#,##0"
|
|
1253
|
+
<BigValue
|
|
1254
|
+
data=sales_data
|
|
1255
|
+
value=sales
|
|
1256
|
+
fmt="$#,##0"
|
|
1208
1257
|
/>
|
|
1209
1258
|
```
|
|
1210
1259
|
|
|
1211
1260
|
Within charts, you can format individual columns using `xFmt` and `yFmt`:
|
|
1212
1261
|
|
|
1213
1262
|
```markdown
|
|
1214
|
-
<LineChart
|
|
1215
|
-
data=sales_data
|
|
1216
|
-
x=date
|
|
1217
|
-
y=sales
|
|
1263
|
+
<LineChart
|
|
1264
|
+
data=sales_data
|
|
1265
|
+
x=date
|
|
1266
|
+
y=sales
|
|
1218
1267
|
xFmt="m/d"
|
|
1219
1268
|
yFmt=eur
|
|
1220
1269
|
/>
|
|
@@ -1306,8 +1355,6 @@ These are the available commands:
|
|
|
1306
1355
|
- `npm run graphene check <mdPath> --chart "<chartTitle>"` - Same as above, except if the runtime check is successful, only takes a screenshot of the specified chart. `<chartTitle>` must match (case sensitive) the `title` attribute on the chart component. `-c` can be used as shorthand for `--chart`.
|
|
1307
1356
|
- `npm run graphene compile "<GSQL>"` - Shows how GSQL is translated into the underlying database SQL.
|
|
1308
1357
|
- `npm run graphene run "<GSQL>"` - Runs a GSQL query. The tables and semantics defined in all .gsql files in the project are available for the query to use.
|
|
1309
|
-
- `npm run graphene serve` - Starts (or restarts) the dev server, which allows the user to view their Graphene app on localhost.
|
|
1310
|
-
- `npm run graphene stop` - Stops the dev server.
|
|
1311
1358
|
|
|
1312
1359
|
# AGENT INSTRUCTIONS
|
|
1313
1360
|
|
|
@@ -1318,6 +1365,6 @@ Follow these guidelines when working in a Graphene project.
|
|
|
1318
1365
|
- Do not try to search the web for Graphene-specific info; you will not find anything. All the documentation is here in graphene.md.
|
|
1319
1366
|
- When writing to a .gsql file, check your code with `npm run graphene check`.
|
|
1320
1367
|
- When writing to a Graphene .md file:
|
|
1321
|
-
- Always check your code with `npm run graphene check <mdPath>`.
|
|
1368
|
+
- Always check your code with `npm run graphene check <mdPath>`. Run the command with full permissions because the screenshot may not work in a sandbox.
|
|
1322
1369
|
- Then do a visual check by either a) looking at the screenshot that `npm run graphene check <mdPath>` creates, or b) using your browser tool to open the .md file at `localhost:<port>/mdPath` (without the .md extension; default port 4000).
|
|
1323
|
-
- Critique what you see: Are all the data values formatted in a way that is easy to read? Does the shape of the visualized data require an adjustment to scale, axis min/max, etc.? Are any visualizations missing data altogether? Is that visualization type really the best way to paint the picture? Etc.
|
|
1370
|
+
- Critique what you see: Are all the data values formatted in a way that is easy to read? Does the shape of the visualized data require an adjustment to scale, axis min/max, etc.? Are any visualizations missing data altogether? Is that visualization type really the best way to paint the picture? Etc.
|
|
@@ -23,3 +23,14 @@ export function serializeValue (value: unknown): string {
|
|
|
23
23
|
let str = String(value)
|
|
24
24
|
return `'${str.replace(/'/g, "''")}'`
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
// Parse a comma-separated list into an array of trimmed strings.
|
|
28
|
+
// - Strings are split on commas; whitespace trimmed; empty entries removed.
|
|
29
|
+
// - Arrays are normalized by trimming string items and String()-ing non-strings.
|
|
30
|
+
// - null/undefined -> []
|
|
31
|
+
export function parseCommaList (value: unknown): string[] {
|
|
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)
|
|
36
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
getFormatObjectFromString,
|
|
11
11
|
} from '../component-utilities/formatting.js'
|
|
12
12
|
import {getThemeStores} from '../component-utilities/themeStores'
|
|
13
|
+
import {parseCommaList} from '../component-utilities/inputUtils.ts'
|
|
13
14
|
|
|
14
15
|
const {resolveColor} = getThemeStores()
|
|
15
16
|
const props = getContext(propKey)
|
|
@@ -72,12 +73,14 @@
|
|
|
72
73
|
$: xMismatch = $props.xMismatch
|
|
73
74
|
$: columnSummary = $props.columnSummary
|
|
74
75
|
$: series = seriesSet ? series : $props.series
|
|
75
|
-
$: resolvedY = ySet ? y : $props.y
|
|
76
|
+
$: resolvedY = ySet ? parseCommaList(y) : $props.y
|
|
77
|
+
$: seriesOrder = parseCommaList(seriesOrder)
|
|
76
78
|
|
|
77
79
|
$: {
|
|
78
|
-
if (!series &&
|
|
80
|
+
if (!series && (!Array.isArray(resolvedY) || resolvedY.length === 1)) {
|
|
79
81
|
stackName = undefined
|
|
80
|
-
|
|
82
|
+
let col = Array.isArray(resolvedY) ? resolvedY[0] : resolvedY
|
|
83
|
+
if (columnSummary?.[col]) name = name ?? formatTitle(col, columnSummary[col].title)
|
|
81
84
|
} else {
|
|
82
85
|
stackName = 'area'
|
|
83
86
|
data = getCompletedData(data, x, resolvedY, series, false, xType === 'value')
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import Area from './Area.svelte'
|
|
4
4
|
import QueryLoad from './QueryLoad.svelte'
|
|
5
5
|
import {getThemeStores} from '../component-utilities/themeStores'
|
|
6
|
+
import {parseCommaList} from '../component-utilities/inputUtils.ts'
|
|
6
7
|
|
|
7
8
|
const {resolveColor, resolveColorsObject, resolveColorPalette} = getThemeStores()
|
|
8
9
|
|
|
@@ -83,7 +84,7 @@
|
|
|
83
84
|
export let xLabelWrap = undefined
|
|
84
85
|
</script>
|
|
85
86
|
|
|
86
|
-
<QueryLoad data={data} fields={{x, y, series}} let:loaded>
|
|
87
|
+
<QueryLoad data={data} fields={{x, y: parseCommaList(y), series}} let:loaded>
|
|
87
88
|
<Chart
|
|
88
89
|
data={loaded}
|
|
89
90
|
chartContext={{data, x, y, series}}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
getFormatObjectFromString,
|
|
17
17
|
} from '../component-utilities/formatting.js'
|
|
18
18
|
import {getThemeStores} from '../component-utilities/themeStores'
|
|
19
|
+
import {parseCommaList} from '../component-utilities/inputUtils.ts'
|
|
19
20
|
|
|
20
21
|
const {resolveColor} = getThemeStores()
|
|
21
22
|
|
|
@@ -77,8 +78,8 @@
|
|
|
77
78
|
// Prop check. If local props supplied, use those. Otherwise fall back to global props.
|
|
78
79
|
$: data = $props.data
|
|
79
80
|
$: x = $props.x
|
|
80
|
-
$: y = ySet ? y : $props.y
|
|
81
|
-
$: y2 = y2Set ? y2 : $props.y2
|
|
81
|
+
$: y = ySet ? parseCommaList(y) : $props.y
|
|
82
|
+
$: y2 = y2Set ? parseCommaList(y2) : $props.y2
|
|
82
83
|
$: yFormat = $props.yFormat
|
|
83
84
|
$: y2Format = $props.y2Format
|
|
84
85
|
$: yCount = $props.yCount
|
|
@@ -89,14 +90,18 @@
|
|
|
89
90
|
$: columnSummary = $props.columnSummary
|
|
90
91
|
$: sort = $props.sort
|
|
91
92
|
$: series = seriesSet ? series : $props.series
|
|
93
|
+
$: seriesOrder = parseCommaList(seriesOrder)
|
|
92
94
|
|
|
93
95
|
let stackedData
|
|
94
96
|
let sortOrder
|
|
95
97
|
let defaultLabelPosition
|
|
96
98
|
|
|
97
|
-
$: if (!series &&
|
|
99
|
+
$: if (!series && (!Array.isArray(y) || y.length === 1)) {
|
|
98
100
|
// Single Series
|
|
99
|
-
|
|
101
|
+
{
|
|
102
|
+
let col = Array.isArray(y) ? y[0] : y
|
|
103
|
+
name = name ?? formatTitle(col, columnSummary[col].title)
|
|
104
|
+
}
|
|
100
105
|
|
|
101
106
|
if (swapXY && xType !== 'category') {
|
|
102
107
|
data = getCompletedData(data, x, y, series, true, xType !== 'time')
|
|
@@ -112,10 +117,11 @@
|
|
|
112
117
|
if (sort === true && xType === 'category') {
|
|
113
118
|
stackedData = getStackedData(data, x, y)
|
|
114
119
|
|
|
115
|
-
if (
|
|
120
|
+
if (Array.isArray(y) && y.length > 1) {
|
|
116
121
|
stackedData = getSortedData(stackedData, 'stackTotal', false)
|
|
117
122
|
} else {
|
|
118
|
-
|
|
123
|
+
let col = Array.isArray(y) ? y[0] : y
|
|
124
|
+
stackedData = getSortedData(stackedData, col, false)
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
sortOrder = stackedData.map((d) => d[x])
|
|
@@ -233,7 +239,7 @@
|
|
|
233
239
|
if (
|
|
234
240
|
labels === true &&
|
|
235
241
|
type === 'stacked' &&
|
|
236
|
-
(
|
|
242
|
+
((Array.isArray(y) && y.length > 1) || (series !== undefined)) &&
|
|
237
243
|
stackTotalLabel === true &&
|
|
238
244
|
series !== x
|
|
239
245
|
) {
|
|
@@ -309,7 +315,7 @@
|
|
|
309
315
|
} else {
|
|
310
316
|
d.yAxis[0] = {...d.yAxis[0], ...chartOverrides.yAxis}
|
|
311
317
|
d.xAxis = {...d.xAxis, ...chartOverrides.xAxis}
|
|
312
|
-
if (
|
|
318
|
+
if (y2Count > 0) {
|
|
313
319
|
d.yAxis[1] = {...d.yAxis[1], show: true}
|
|
314
320
|
if (['line', 'bar', 'scatter'].includes(y2SeriesType)) {
|
|
315
321
|
for (let i = 0; i < y2Count; i++) {
|