@graphenedata/cli 0.0.5 → 0.0.7
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 +56 -19
- package/dist/cli/cli.js +1197 -484
- package/dist/docs/graphene.md +184 -171
- 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 +3 -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
|
|
@@ -388,51 +379,48 @@ FROM `bigquery-public-data.thelook_ecommerce.orders` as base
|
|
|
388
379
|
|
|
389
380
|
You don't have to understand this; the point is that GSQL is minimizing the chances that naive users aggregate data incorrectly.
|
|
390
381
|
|
|
382
|
+
#### Percentile shorthand
|
|
383
|
+
|
|
384
|
+
Graphene provides percentile helpers so you rarely have to remember the SQL form for each warehouse. Anywhere you can call an aggregate, you can also write `pXX(column)` where `XX` is a whole number between 0 and 100. If you need precision finer than a whole percentile, append extra digits—everything after the first two digits is treated as decimals. Examples: `p975` → 97.5th percentile, `p9999` → 99.99th percentile. Graphene rewrites these shorthands to the dialect’s native function (`quantile_cont` on DuckDB, `approx_quantiles` on BigQuery, `PERCENTILE_CONT` on Snowflake) and ensures they behave like other aggregates (automatic grouping, structPath handling, etc.).
|
|
385
|
+
|
|
391
386
|
### `table as` statements
|
|
392
387
|
|
|
393
388
|
You can turn the output of any `select` statement into a table with `table foo as (select ...)`. Here's an example of an additional table `user_facts` added to the two tables from earlier:
|
|
394
389
|
|
|
395
390
|
```sql
|
|
396
391
|
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
|
|
392
|
+
id BIGINT primary_key
|
|
393
|
+
user_id BIGINT
|
|
394
|
+
created_at DATETIME
|
|
395
|
+
status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
|
|
396
|
+
amount FLOAT -- Amount paid by customer
|
|
397
|
+
cost FLOAT -- Cost of materials
|
|
398
|
+
|
|
399
|
+
join one users on user_id = users.id
|
|
400
|
+
|
|
401
|
+
revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
|
|
402
|
+
revenue: sum(case when revenue_recognized then amount else 0 end)
|
|
403
|
+
cogs: sum(case when revenue_recognized then cost else 0 end)
|
|
404
|
+
profit: revenue - cogs
|
|
405
|
+
profit_margin: profit / revenue
|
|
412
406
|
)
|
|
413
407
|
|
|
414
408
|
table users (
|
|
415
|
-
id BIGINT primary_key
|
|
416
|
-
name VARCHAR
|
|
417
|
-
email VARCHAR
|
|
418
|
-
age INTEGER
|
|
419
|
-
|
|
420
|
-
join_many orders on id = orders.user_id,
|
|
421
|
-
join_one user_facts on id = user_facts.id,
|
|
409
|
+
id BIGINT primary_key
|
|
410
|
+
name VARCHAR
|
|
411
|
+
email VARCHAR
|
|
412
|
+
age INTEGER
|
|
422
413
|
|
|
423
|
-
|
|
414
|
+
join many orders on id = orders.user_id
|
|
415
|
+
join one user_facts on id = user_facts.id
|
|
424
416
|
|
|
425
|
-
user_facts.ltv
|
|
426
|
-
user_facts.lifetime_orders
|
|
417
|
+
ltv: user_facts.ltv
|
|
418
|
+
lifetime_orders: user_facts.lifetime_orders
|
|
427
419
|
)
|
|
428
420
|
|
|
429
421
|
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
|
|
422
|
+
select id, orders.revenue as ltv, count(orders.id) as lifetime_orders,
|
|
423
|
+
from users group by id
|
|
436
424
|
)
|
|
437
425
|
```
|
|
438
426
|
|
|
@@ -440,6 +428,61 @@ table user_facts as (
|
|
|
440
428
|
- 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
429
|
- 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
430
|
|
|
431
|
+
### `extend` statements
|
|
432
|
+
|
|
433
|
+
`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.
|
|
434
|
+
|
|
435
|
+
For example, if we have a `table as` statement that creates a daily summary of orders:
|
|
436
|
+
|
|
437
|
+
```sql
|
|
438
|
+
table daily_orders as (
|
|
439
|
+
select
|
|
440
|
+
date_trunc(created_at, day) as day,
|
|
441
|
+
count(*) as num_orders,
|
|
442
|
+
sum(amount) as total_revenue
|
|
443
|
+
from orders
|
|
444
|
+
group by 1
|
|
445
|
+
)
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
We can extend this table to add measures or joins:
|
|
449
|
+
|
|
450
|
+
```sql
|
|
451
|
+
extend daily_orders (
|
|
452
|
+
join one calendar on day = calendar.date
|
|
453
|
+
|
|
454
|
+
avg_order_value: total_revenue / num_orders
|
|
455
|
+
)
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Note that you cannot add new base columns with `extend`; you can only add joins and stored expressions.
|
|
459
|
+
|
|
460
|
+
### Working with dates, timestamps, and intervals
|
|
461
|
+
|
|
462
|
+
Graphene understands a handful of common literal formats so you rarely need explicit casts when filtering or doing time math.
|
|
463
|
+
|
|
464
|
+
**Date and timestamp literals**
|
|
465
|
+
|
|
466
|
+
- `YYYY`, `YYYY-MM`, and `YYYY-MM-DD` strings are treated as dates. Leading/trailing spaces are ignored.
|
|
467
|
+
- `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`.
|
|
468
|
+
|
|
469
|
+
```sql
|
|
470
|
+
from users select id
|
|
471
|
+
where created_at >= '2024-01-01' and created_at <= '2024-02-01'
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Interval literals**
|
|
475
|
+
|
|
476
|
+
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).
|
|
477
|
+
|
|
478
|
+
```sql
|
|
479
|
+
from users select
|
|
480
|
+
created_at + '5 minutes' as first_seen_plus_5,
|
|
481
|
+
created_at - '2 days' as first_seen_minus_2
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
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"”.
|
|
485
|
+
|
|
443
486
|
### Other miscellaneous details about GSQL
|
|
444
487
|
|
|
445
488
|
- Trailing commas in `table` statements are optional.
|
|
@@ -449,6 +492,16 @@ table user_facts as (
|
|
|
449
492
|
- Expressions in `group by` are implicitly selected, so `from orders select avg(amount) group by user_id` will return two columns.
|
|
450
493
|
- `count` is a reserved word. Do not alias your columns as `count`.
|
|
451
494
|
- Window functions and set operations are not supported.
|
|
495
|
+
- Subqueries are not supported. However, you can accomplish the same functionality by chaining queries:
|
|
496
|
+
````md
|
|
497
|
+
```sql my_subquery
|
|
498
|
+
select ...
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
```sql my_query
|
|
502
|
+
select ... from my_subquery
|
|
503
|
+
```
|
|
504
|
+
````
|
|
452
505
|
|
|
453
506
|
## Graphene data apps (dashboards)
|
|
454
507
|
|
|
@@ -493,7 +546,7 @@ Use bar or column charts to compare a metric across categories. Bar charts are b
|
|
|
493
546
|
Here's an example:
|
|
494
547
|
|
|
495
548
|
```markdown
|
|
496
|
-
<BarChart
|
|
549
|
+
<BarChart
|
|
497
550
|
title="Sales by Category"
|
|
498
551
|
data=orders_by_category_2021
|
|
499
552
|
x=month
|
|
@@ -522,10 +575,10 @@ Here's an example:
|
|
|
522
575
|
|----------|-------------|----------|---------|---------|
|
|
523
576
|
| data | Query name, wrapped in curly braces | true | query name | - |
|
|
524
577
|
| 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 | - |
|
|
578
|
+
| 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 |
|
|
579
|
+
| 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
580
|
| y2SeriesType | Chart type to apply to the series on the y2 axis | false | `bar`, `line`, `scatter` | `bar` |
|
|
528
|
-
| series | Column or expression to use
|
|
581
|
+
| 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
582
|
| 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
583
|
| type | Grouping method to use for multi-series charts | false | `stacked`, `grouped`, `stacked100` | `stacked` |
|
|
531
584
|
| 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 +661,7 @@ Use a pie chart to show part-to-whole relationships across categories. Best for
|
|
|
608
661
|
Here's an example:
|
|
609
662
|
|
|
610
663
|
```markdown
|
|
611
|
-
<PieChart
|
|
664
|
+
<PieChart
|
|
612
665
|
title="Sales share by category"
|
|
613
666
|
data=orders_by_category_2021
|
|
614
667
|
category=category
|
|
@@ -640,12 +693,12 @@ Use line charts to display how one or more metrics vary over time. Line charts a
|
|
|
640
693
|
Here's an example:
|
|
641
694
|
|
|
642
695
|
```markdown
|
|
643
|
-
<LineChart
|
|
696
|
+
<LineChart
|
|
644
697
|
title="Monthly Sales"
|
|
645
698
|
subtitle="Includes all categories"
|
|
646
699
|
data=orders_by_month
|
|
647
700
|
x=month
|
|
648
|
-
y=sales_usd0k
|
|
701
|
+
y=sales_usd0k
|
|
649
702
|
yAxisTitle="Sales per Month"
|
|
650
703
|
/>
|
|
651
704
|
```
|
|
@@ -670,10 +723,10 @@ Here's an example:
|
|
|
670
723
|
|------|-------------|----------|---------|---------|
|
|
671
724
|
| data | Query name, wrapped in curly braces | true | query name | - |
|
|
672
725
|
| 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 | - |
|
|
726
|
+
| 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 | - |
|
|
727
|
+
| 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
728
|
| y2SeriesType | Chart type to apply to the series on the y2 axis | false | `line`, `bar`, `scatter` | `line` |
|
|
676
|
-
| series | Column or expression to use
|
|
729
|
+
| 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
730
|
| 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
731
|
| handleMissing | Treatment of missing values in the dataset | false | `gap`, `connect`, `zero` | `gap` |
|
|
679
732
|
| 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 +805,7 @@ Use area charts to track how a metric with multiple series changes over time, or
|
|
|
752
805
|
Here's an example:
|
|
753
806
|
|
|
754
807
|
```markdown
|
|
755
|
-
<AreaChart
|
|
808
|
+
<AreaChart
|
|
756
809
|
data=orders_by_month
|
|
757
810
|
x=month
|
|
758
811
|
y=sales
|
|
@@ -779,8 +832,8 @@ Here's an example:
|
|
|
779
832
|
|------|-------------|----------|---------|---------|
|
|
780
833
|
| data | Query name, wrapped in curly braces | true | query name | - |
|
|
781
834
|
| 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
|
|
835
|
+
| 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 |
|
|
836
|
+
| 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
837
|
| 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
838
|
| type | Grouping method to use for multi-series charts | false | `stacked`, `stacked100` | `stacked` |
|
|
786
839
|
| handleMissing | Treatment of missing values in the dataset | false | `gap`, `connect`, `zero` | `gap` for single series, `zero` for multi-series |
|
|
@@ -851,8 +904,8 @@ Use big values to display a large value standalone, and optionally include a com
|
|
|
851
904
|
Here's an example:
|
|
852
905
|
|
|
853
906
|
```markdown
|
|
854
|
-
<BigValue
|
|
855
|
-
data=orders_with_comparisons
|
|
907
|
+
<BigValue
|
|
908
|
+
data=orders_with_comparisons
|
|
856
909
|
value=num_orders
|
|
857
910
|
sparkline=month
|
|
858
911
|
comparison=order_growth
|
|
@@ -1069,7 +1122,7 @@ The user-inputted text would then be referenced in GSQL via `$name_of_input`. Fo
|
|
|
1069
1122
|
```sql
|
|
1070
1123
|
select *
|
|
1071
1124
|
from users
|
|
1072
|
-
where email ilike concat('%', $name_of_input, '%')
|
|
1125
|
+
where email ilike concat('%', $name_of_input, '%')
|
|
1073
1126
|
```
|
|
1074
1127
|
|
|
1075
1128
|
##### All text input attributes
|
|
@@ -1083,44 +1136,6 @@ where email ilike concat('%', $name_of_input, '%')
|
|
|
1083
1136
|
| description | Adds an info icon with description tooltip on hover | false | string | - |
|
|
1084
1137
|
|
|
1085
1138
|
|
|
1086
|
-
#### Date range
|
|
1087
|
-
|
|
1088
|
-
Creates a date picker that can be used to filter a query. Includes a set of preset ranges for quick selection of common date ranges (relative to the supplied end date). To see how to filter a query using an input component, see Filters.
|
|
1089
|
-
|
|
1090
|
-
Here's an example:
|
|
1091
|
-
|
|
1092
|
-
```markdown
|
|
1093
|
-
<DateRange
|
|
1094
|
-
name=date_range_name
|
|
1095
|
-
data=orders_by_day
|
|
1096
|
-
dates=day
|
|
1097
|
-
/>
|
|
1098
|
-
```
|
|
1099
|
-
|
|
1100
|
-
The start and end dates for the user-selected range would then be referenced in GSQL as `$date_range_name_start` and `$date_range_name_end` at the end. For example:
|
|
1101
|
-
|
|
1102
|
-
```sql
|
|
1103
|
-
select *
|
|
1104
|
-
from orders
|
|
1105
|
-
where created_at > $date_range_name_start and < $date_range_name_end
|
|
1106
|
-
```
|
|
1107
|
-
|
|
1108
|
-
##### All date range attributes
|
|
1109
|
-
|
|
1110
|
-
| Attribute | Description | Required | Options | Default |
|
|
1111
|
-
|------|-------------|----------|---------|---------|
|
|
1112
|
-
| name | Name of the DateRange, used to reference the selected values elsewhere as `"$name_start"` or `"$name_end"` | true | string | - |
|
|
1113
|
-
| data | Query name, wrapped in curly braces | false | query name | - |
|
|
1114
|
-
| dates | Column or expression from the query containing date range to span | false | column name, stored expression name, GSQL expression | - |
|
|
1115
|
-
| start | A manually specified start date to use for the range | false | string formatted YYYY-MM-DD | - |
|
|
1116
|
-
| end | A manually specified end date to use for the range | false | string formatted YYYY-MM-DD | - |
|
|
1117
|
-
| title | Title to display in the Date Range component | false | string | - |
|
|
1118
|
-
| presetRanges | Customize "Select a Range" drop down, by including preset range options | false | list of values e.g. `"Last 7 Days, Last 30 Days"`. Allowed values: `Last 7 Days`, `Last 30 Days`, `Last 90 Days`, `Last 365 Days`, `Last 3 Months`, `Last 6 Months`, `Last 12 Months`, `Last Month`, `Last Year`, `Month to Date`, `Month to Today`, `Year to Date`, `Year to Today`, `All Time` | - |
|
|
1119
|
-
| defaultValue | Accepts preset in string format to apply default value in Date Range picker | false | `"Last 7 Days"`, `"Last 30 Days"`, `"Last 90 Days"`, `"Last 365 Days"`, `"Last 3 Months"`, `"Last 6 Months"`, `"Last 12 Months"`, `"Last Month"`, `"Last Year"`, `"Month to Date"`, `"Month to Today"`, `"Year to Date"`, `"Year to Today"`, `"All Time"` | - |
|
|
1120
|
-
| hideDuringPrint | Hide the component when the report is printed | false | `true`, `false` | `true` |
|
|
1121
|
-
| description | Adds an info icon with description tooltip on hover | false | string | - |
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
1139
|
#### Dropdown
|
|
1125
1140
|
|
|
1126
1141
|
Creates a dropdown menu with a list of options that can be selected. The selected option can be used to filter queries or in markdown. To see how to filter a query using a dropdown, see Filters.
|
|
@@ -1132,10 +1147,10 @@ Here's an example:
|
|
|
1132
1147
|
select distinct status from orders
|
|
1133
1148
|
```
|
|
1134
1149
|
|
|
1135
|
-
<Dropdown
|
|
1136
|
-
title="Select Order Status"
|
|
1150
|
+
<Dropdown
|
|
1151
|
+
title="Select Order Status"
|
|
1137
1152
|
name="status_dropdown"
|
|
1138
|
-
data="statuses"
|
|
1153
|
+
data="statuses"
|
|
1139
1154
|
value="status"
|
|
1140
1155
|
defaultValue="Complete"
|
|
1141
1156
|
/>
|
|
@@ -1201,20 +1216,20 @@ The easiest way to format numbers and dates in Graphene is through component att
|
|
|
1201
1216
|
For example, you can use the `fmt` attribute to format values inside a BigValue component:
|
|
1202
1217
|
|
|
1203
1218
|
```markdown
|
|
1204
|
-
<BigValue
|
|
1205
|
-
data=sales_data
|
|
1206
|
-
value=sales
|
|
1207
|
-
fmt="$#,##0"
|
|
1219
|
+
<BigValue
|
|
1220
|
+
data=sales_data
|
|
1221
|
+
value=sales
|
|
1222
|
+
fmt="$#,##0"
|
|
1208
1223
|
/>
|
|
1209
1224
|
```
|
|
1210
1225
|
|
|
1211
1226
|
Within charts, you can format individual columns using `xFmt` and `yFmt`:
|
|
1212
1227
|
|
|
1213
1228
|
```markdown
|
|
1214
|
-
<LineChart
|
|
1215
|
-
data=sales_data
|
|
1216
|
-
x=date
|
|
1217
|
-
y=sales
|
|
1229
|
+
<LineChart
|
|
1230
|
+
data=sales_data
|
|
1231
|
+
x=date
|
|
1232
|
+
y=sales
|
|
1218
1233
|
xFmt="m/d"
|
|
1219
1234
|
yFmt=eur
|
|
1220
1235
|
/>
|
|
@@ -1306,8 +1321,6 @@ These are the available commands:
|
|
|
1306
1321
|
- `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
1322
|
- `npm run graphene compile "<GSQL>"` - Shows how GSQL is translated into the underlying database SQL.
|
|
1308
1323
|
- `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
1324
|
|
|
1312
1325
|
# AGENT INSTRUCTIONS
|
|
1313
1326
|
|
|
@@ -1318,6 +1331,6 @@ Follow these guidelines when working in a Graphene project.
|
|
|
1318
1331
|
- 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
1332
|
- When writing to a .gsql file, check your code with `npm run graphene check`.
|
|
1320
1333
|
- When writing to a Graphene .md file:
|
|
1321
|
-
- Always check your code with `npm run graphene check <mdPath>`.
|
|
1334
|
+
- 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
1335
|
- 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.
|
|
1336
|
+
- 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}}
|