vg_helper 0.1.0 → 0.1.2
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.
- checksums.yaml +4 -4
- data/README.md +660 -15
- data/lib/vg_helper/version.rb +1 -1
- data/lib/vg_helper.rb +184 -104
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4dbf940d1eca83928ba5bbb9881af79c29744be7b4287ff2cc23dc6d055a5a87
|
|
4
|
+
data.tar.gz: b4c473be9618bbba21f1e3bf3d297295d5206317925b95fbb6b88471f011d819
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e82f4ac7b4123e4324114cc7ec897b15e6737d9e2c7c4018b4909e86a61e9bc4576d38222259028c0dab108e95e212c6535e7c1233316170f6e71754b4fb1b76
|
|
7
|
+
data.tar.gz: a63d810f05411f24ffc0eef693c7c06a3de2a50562c7fcb638e86d283295b487dbeae24b267d678044e5fa04a8ef725296e3fafdd43921ae7b92b51e7be47714
|
data/README.md
CHANGED
|
@@ -1,34 +1,674 @@
|
|
|
1
|
-
#
|
|
1
|
+
# VGHelper
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
VGHelper is a small Ruby helper library for creating interactive
|
|
4
|
+
[Vega-Lite](https://vega.github.io/vega-lite/) charts from
|
|
5
|
+
`Rover::DataFrame`.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
It provides convenient helpers for time-series and nominal x-axis charts,
|
|
8
|
+
with support for multiple categories, secondary y-axes, reference lines,
|
|
9
|
+
interactive range controls, brush statistics, and offline HTML output.
|
|
10
|
+
|
|
11
|
+
One of the main goals of VGHelper is to make Vega-Lite charts usable
|
|
12
|
+
even in **offline or proxy-restricted environments**.
|
|
13
|
+
|
|
14
|
+
VGHelper bundles the JavaScript runtimes required by Vega, Vega-Lite,
|
|
15
|
+
and Vega-Embed, so HTML generated by VGHelper does not need to load
|
|
16
|
+
these libraries from an external CDN.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- Time-series charts (`draw_xdate`)
|
|
21
|
+
- Nominal / categorical x-axis charts (`draw_xnominal`)
|
|
22
|
+
- Multiple series and multiple category columns
|
|
23
|
+
- Line charts with optional point markers
|
|
24
|
+
- Secondary y-axis with bar charts
|
|
25
|
+
- Stacked bars using a secondary category
|
|
26
|
+
- Horizontal reference lines
|
|
27
|
+
- Vertical reference lines
|
|
28
|
+
- Configurable colors for reference lines
|
|
29
|
+
- Interactive x-axis range controls
|
|
30
|
+
- Interactive y-axis range controls
|
|
31
|
+
- Category visibility checkboxes
|
|
32
|
+
- Brush selection
|
|
33
|
+
- Statistics for the selected / displayed range
|
|
34
|
+
- count
|
|
35
|
+
- mean
|
|
36
|
+
- standard deviation
|
|
37
|
+
- maximum
|
|
38
|
+
- minimum
|
|
39
|
+
- Statistics based on total stacked-bar height for the secondary axis
|
|
40
|
+
- Daily and exact-time modes for temporal data
|
|
41
|
+
- Reversible left/right y-axis orientation
|
|
42
|
+
- Self-contained offline HTML output
|
|
43
|
+
- No CDN access required for generated HTML
|
|
44
|
+
|
|
45
|
+
---
|
|
6
46
|
|
|
7
47
|
## Installation
|
|
8
48
|
|
|
9
|
-
|
|
49
|
+
Add this line to your application's Gemfile:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
gem "vg_helper"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
and run:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
bundle install
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or install it directly:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
gem install vg_helper
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
require "vg_helper"
|
|
71
|
+
require "rover"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Quick Start
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
require "vg_helper"
|
|
80
|
+
require "rover"
|
|
81
|
+
|
|
82
|
+
df = Rover::DataFrame.new(
|
|
83
|
+
{
|
|
84
|
+
"datetime" => [
|
|
85
|
+
"2026-01-01 10:00:00",
|
|
86
|
+
"2026-01-02 10:00:00",
|
|
87
|
+
"2026-01-03 10:00:00",
|
|
88
|
+
"2026-01-04 10:00:00"
|
|
89
|
+
],
|
|
90
|
+
"category" => [
|
|
91
|
+
"A", "A", "A", "A"
|
|
92
|
+
],
|
|
93
|
+
"value" => [
|
|
94
|
+
10, 15, 12, 18
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
chart = VGHelper.draw_xdate(
|
|
100
|
+
df,
|
|
101
|
+
"datetime",
|
|
102
|
+
"value",
|
|
103
|
+
"category"
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Jupyter Notebook / IRuby
|
|
108
|
+
|
|
109
|
+
In Jupyter Notebook with IRuby, place the chart object at the end of
|
|
110
|
+
the cell to display it directly:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
chart
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A complete cell can simply end with:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
chart = VGHelper.draw_xdate(
|
|
120
|
+
df,
|
|
121
|
+
"datetime",
|
|
122
|
+
"value",
|
|
123
|
+
"category"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
chart
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The same applies to charts created with `draw_xnominal`.
|
|
130
|
+
|
|
131
|
+
### Offline HTML output
|
|
132
|
+
|
|
133
|
+
To write the chart to an HTML file instead, use `write_htmls`:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
VGHelper.write_htmls(chart, "chart.html")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`write_htmls` uses the bundled `vega.min.js`, `vega-lite.min.js`, and
|
|
140
|
+
`vega-embed.min.js` files. The generated HTML does not load these
|
|
141
|
+
libraries from a CDN and can be viewed without a network connection.
|
|
142
|
+
|
|
143
|
+
Open `chart.html` in a browser to view the interactive chart.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
# `draw_xdate`
|
|
148
|
+
|
|
149
|
+
`draw_xdate` creates a chart with a temporal x-axis.
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
VGHelper.draw_xdate(
|
|
153
|
+
df,
|
|
154
|
+
x_col,
|
|
155
|
+
y_col,
|
|
156
|
+
category_col,
|
|
157
|
+
...
|
|
158
|
+
)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Basic example:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
chart = VGHelper.draw_xdate(
|
|
165
|
+
df,
|
|
166
|
+
"datetime",
|
|
167
|
+
"temperature",
|
|
168
|
+
"station"
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The fourth argument specifies the column used to separate the data
|
|
173
|
+
into series.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Multiple category columns
|
|
178
|
+
|
|
179
|
+
An array of columns can be supplied as `category_col`.
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
chart = VGHelper.draw_xdate(
|
|
183
|
+
df,
|
|
184
|
+
"datetime",
|
|
185
|
+
"temperature",
|
|
186
|
+
["pref", "city"]
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
VGHelper combines the values internally and treats each combination
|
|
191
|
+
as an independent series.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Time modes
|
|
196
|
+
|
|
197
|
+
`draw_xdate` supports two time modes.
|
|
198
|
+
|
|
199
|
+
### Daily mode
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
time_mode: :day
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Dates are normalized to the beginning of each calendar day.
|
|
206
|
+
|
|
207
|
+
This is useful when the time of day is irrelevant and the x-axis
|
|
208
|
+
should represent daily observations.
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
chart = VGHelper.draw_xdate(
|
|
212
|
+
df,
|
|
213
|
+
"datetime",
|
|
214
|
+
"value",
|
|
215
|
+
"category",
|
|
216
|
+
time_mode: :day
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Exact-time mode
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
time_mode: :exact
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The time of day is preserved.
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
chart = VGHelper.draw_xdate(
|
|
230
|
+
df,
|
|
231
|
+
"datetime",
|
|
232
|
+
"value",
|
|
233
|
+
"category",
|
|
234
|
+
time_mode: :exact
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Use this for hourly, minute-level, or other intraday data.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
# `draw_xnominal`
|
|
243
|
+
|
|
244
|
+
`draw_xnominal` creates a chart whose x-axis consists of discrete
|
|
245
|
+
ordered values.
|
|
246
|
+
|
|
247
|
+
For example:
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
df = Rover::DataFrame.new(
|
|
251
|
+
{
|
|
252
|
+
"step" => [
|
|
253
|
+
"STEP_001",
|
|
254
|
+
"STEP_002",
|
|
255
|
+
"STEP_003",
|
|
256
|
+
"STEP_004"
|
|
257
|
+
],
|
|
258
|
+
"value" => [
|
|
259
|
+
10, 13, 18, 16
|
|
260
|
+
],
|
|
261
|
+
"category" => [
|
|
262
|
+
"A", "A", "A", "A"
|
|
263
|
+
]
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
chart = VGHelper.draw_xnominal(
|
|
268
|
+
df,
|
|
269
|
+
"step",
|
|
270
|
+
"value",
|
|
271
|
+
"category"
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The original order of x values is preserved.
|
|
276
|
+
|
|
277
|
+
This makes `draw_xnominal` useful for data such as:
|
|
278
|
+
|
|
279
|
+
```text
|
|
280
|
+
STEP_001
|
|
281
|
+
STEP_002
|
|
282
|
+
STEP_003
|
|
283
|
+
...
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
where the x values are categories but their sequence is meaningful.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Limiting the initial x range
|
|
291
|
+
|
|
292
|
+
For large nominal datasets:
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
initialxpoints: 100
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
controls the number of x positions initially displayed.
|
|
299
|
+
|
|
300
|
+
For example:
|
|
301
|
+
|
|
302
|
+
```ruby
|
|
303
|
+
chart = VGHelper.draw_xnominal(
|
|
304
|
+
df,
|
|
305
|
+
"step",
|
|
306
|
+
"value",
|
|
307
|
+
"category",
|
|
308
|
+
initialxpoints: 100
|
|
309
|
+
)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
# Interactive controls
|
|
315
|
+
|
|
316
|
+
## X-axis controls
|
|
317
|
+
|
|
318
|
+
The x-axis range controls can be enabled or disabled.
|
|
319
|
+
|
|
320
|
+
```ruby
|
|
321
|
+
xslide: true
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
or:
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
xslide: false
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
For `draw_xdate`, the controls modify the temporal range.
|
|
331
|
+
|
|
332
|
+
For `draw_xnominal`, they modify the range of displayed x positions.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Y-axis controls
|
|
337
|
+
|
|
338
|
+
Y-axis controls can be enabled with:
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
yslide: true
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
and disabled with:
|
|
345
|
+
|
|
346
|
+
```ruby
|
|
347
|
+
yslide: false
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The control type can also be selected:
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
yrangechanger: "number"
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
or:
|
|
10
357
|
|
|
11
|
-
|
|
358
|
+
```ruby
|
|
359
|
+
yrangechanger: "range"
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Explicit y ranges can be supplied where appropriate:
|
|
363
|
+
|
|
364
|
+
```ruby
|
|
365
|
+
miny: 0,
|
|
366
|
+
maxy: 100
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Category checkboxes
|
|
372
|
+
|
|
373
|
+
Series visibility can be controlled interactively.
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
category_check: true
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
This adds a checkbox for each category.
|
|
380
|
+
|
|
381
|
+
To hide these controls:
|
|
382
|
+
|
|
383
|
+
```ruby
|
|
384
|
+
category_check: false
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
# Brush selection and statistics
|
|
390
|
+
|
|
391
|
+
Both chart helpers support interactive x-axis brush selection.
|
|
392
|
+
|
|
393
|
+
Drag across part of the chart to select a range.
|
|
394
|
+
|
|
395
|
+
VGHelper displays statistics for the selected data:
|
|
396
|
+
|
|
397
|
+
```text
|
|
398
|
+
n
|
|
399
|
+
avg
|
|
400
|
+
std
|
|
401
|
+
max
|
|
402
|
+
min
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
For example:
|
|
406
|
+
|
|
407
|
+
```text
|
|
408
|
+
temperature n: 32 avg: 21.45 std: 2.18 max: 25.60 min: 17.90
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
When no smaller brush range is selected, the statistics correspond
|
|
412
|
+
to the currently displayed range.
|
|
413
|
+
|
|
414
|
+
Category visibility settings are also reflected in the statistics.
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
# Secondary y-axis
|
|
419
|
+
|
|
420
|
+
A second `Rover::DataFrame` can be displayed as bars using a
|
|
421
|
+
secondary y-axis.
|
|
422
|
+
|
|
423
|
+
```ruby
|
|
424
|
+
chart = VGHelper.draw_xdate(
|
|
425
|
+
df,
|
|
426
|
+
"datetime",
|
|
427
|
+
"temperature",
|
|
428
|
+
"station",
|
|
429
|
+
|
|
430
|
+
df2: volume_df,
|
|
431
|
+
x2_col: "datetime",
|
|
432
|
+
y2_col: "volume"
|
|
433
|
+
)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
This produces a main chart together with bars using an independent
|
|
437
|
+
y scale.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Stacked secondary-axis bars
|
|
442
|
+
|
|
443
|
+
A category can also be assigned to the secondary data.
|
|
444
|
+
|
|
445
|
+
```ruby
|
|
446
|
+
chart = VGHelper.draw_xdate(
|
|
447
|
+
df,
|
|
448
|
+
"datetime",
|
|
449
|
+
"temperature",
|
|
450
|
+
"station",
|
|
451
|
+
|
|
452
|
+
df2: volume_df,
|
|
453
|
+
x2_col: "datetime",
|
|
454
|
+
y2_col: "volume",
|
|
455
|
+
y2_category_col: "product"
|
|
456
|
+
)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Rows sharing the same x position are displayed as stacked bars.
|
|
460
|
+
|
|
461
|
+
For statistical calculations, VGHelper first calculates the total
|
|
462
|
+
height of each stacked bar.
|
|
463
|
+
|
|
464
|
+
For example, if one x position contains:
|
|
465
|
+
|
|
466
|
+
```text
|
|
467
|
+
Product A : 10
|
|
468
|
+
Product B : 15
|
|
469
|
+
Product C : 5
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
it is treated as one observation:
|
|
473
|
+
|
|
474
|
+
```text
|
|
475
|
+
30
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
The mean, standard deviation, maximum, minimum, and count displayed
|
|
479
|
+
for the secondary axis are then calculated from these stacked totals,
|
|
480
|
+
rather than from the individual bar segments.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
# Axis orientation
|
|
485
|
+
|
|
486
|
+
By default, the main y-axis is displayed on the left and the
|
|
487
|
+
secondary y-axis on the right.
|
|
488
|
+
|
|
489
|
+
The orientation can be reversed:
|
|
490
|
+
|
|
491
|
+
```ruby
|
|
492
|
+
reverse_orient: true
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
This places the main axis on the right and the secondary axis
|
|
496
|
+
on the left.
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
# Horizontal reference lines
|
|
501
|
+
|
|
502
|
+
Horizontal reference lines can be added with `horizontallines`.
|
|
12
503
|
|
|
13
|
-
```
|
|
14
|
-
|
|
504
|
+
```ruby
|
|
505
|
+
horizontallines: [
|
|
506
|
+
[20, "Target"],
|
|
507
|
+
[30, "Upper Limit"]
|
|
508
|
+
]
|
|
15
509
|
```
|
|
16
510
|
|
|
17
|
-
|
|
511
|
+
Colors can be specified separately:
|
|
18
512
|
|
|
19
|
-
```
|
|
20
|
-
|
|
513
|
+
```ruby
|
|
514
|
+
horizontalcolors: [
|
|
515
|
+
"#0000ff",
|
|
516
|
+
"#ff0000"
|
|
517
|
+
]
|
|
21
518
|
```
|
|
22
519
|
|
|
23
|
-
|
|
520
|
+
Reference lines are useful for targets, limits, thresholds,
|
|
521
|
+
specifications, and other fixed values.
|
|
24
522
|
|
|
25
|
-
|
|
523
|
+
---
|
|
26
524
|
|
|
27
|
-
|
|
525
|
+
# Vertical reference lines
|
|
28
526
|
|
|
29
|
-
|
|
527
|
+
Vertical reference lines can also be added.
|
|
30
528
|
|
|
31
|
-
|
|
529
|
+
For a date chart:
|
|
530
|
+
|
|
531
|
+
```ruby
|
|
532
|
+
verticallines: [
|
|
533
|
+
["2026-01-10", "Close"],
|
|
534
|
+
["2026-01-20", "Restart"]
|
|
535
|
+
]
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
For a nominal chart:
|
|
539
|
+
|
|
540
|
+
```ruby
|
|
541
|
+
verticallines: [
|
|
542
|
+
["STEP_020", "Event A"],
|
|
543
|
+
["STEP_050", "Event B"]
|
|
544
|
+
]
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
Colors can be specified with:
|
|
548
|
+
|
|
549
|
+
```ruby
|
|
550
|
+
verticalcolors: [
|
|
551
|
+
"#0000ff",
|
|
552
|
+
"#ff0000"
|
|
553
|
+
]
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Vertical lines can be used to indicate important x positions.
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
# Custom marks
|
|
561
|
+
|
|
562
|
+
The Vega-Lite mark definition can be customized with `markkind`.
|
|
563
|
+
|
|
564
|
+
For example:
|
|
565
|
+
|
|
566
|
+
```ruby
|
|
567
|
+
markkind: {
|
|
568
|
+
type: "line",
|
|
569
|
+
point: {
|
|
570
|
+
filled: true,
|
|
571
|
+
size: 40
|
|
572
|
+
},
|
|
573
|
+
tooltip: true
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
This allows VGHelper to expose Vega-Lite's mark configuration while
|
|
578
|
+
providing convenient defaults.
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
# Offline HTML output
|
|
583
|
+
|
|
584
|
+
VGHelper is designed to work without access to external CDNs.
|
|
585
|
+
|
|
586
|
+
The gem bundles local copies of:
|
|
587
|
+
|
|
588
|
+
```text
|
|
589
|
+
vega.min.js
|
|
590
|
+
vega-lite.min.js
|
|
591
|
+
vega-embed.min.js
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
`write_htmls` embeds these JavaScript files directly into the output
|
|
595
|
+
HTML.
|
|
596
|
+
|
|
597
|
+
```ruby
|
|
598
|
+
VGHelper.write_htmls(
|
|
599
|
+
chart,
|
|
600
|
+
"chart.html"
|
|
601
|
+
)
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
The resulting file can therefore be opened without accessing
|
|
605
|
+
jsDelivr, unpkg, or another CDN.
|
|
606
|
+
|
|
607
|
+
This can be particularly useful in:
|
|
608
|
+
|
|
609
|
+
- corporate proxy environments
|
|
610
|
+
- restricted networks
|
|
611
|
+
- isolated analysis environments
|
|
612
|
+
- offline machines
|
|
613
|
+
|
|
614
|
+
Multiple charts can also be written to the same HTML file:
|
|
615
|
+
|
|
616
|
+
```ruby
|
|
617
|
+
VGHelper.write_htmls(
|
|
618
|
+
[chart1, chart2, chart3],
|
|
619
|
+
"charts.html"
|
|
620
|
+
)
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
# TODO
|
|
627
|
+
|
|
628
|
+
VGHelper is still under development.
|
|
629
|
+
|
|
630
|
+
Planned features include:
|
|
631
|
+
|
|
632
|
+
- [ ] Export charts as images
|
|
633
|
+
- [ ] Improve customization of Vega-Lite parameters
|
|
634
|
+
- [ ] Improve documentation and examples
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
# Dependencies
|
|
639
|
+
|
|
640
|
+
VGHelper currently uses:
|
|
641
|
+
|
|
642
|
+
- Ruby
|
|
643
|
+
- `vega`
|
|
644
|
+
- `rover`
|
|
645
|
+
|
|
646
|
+
Vega, Vega-Lite, and Vega-Embed JavaScript runtimes are bundled
|
|
647
|
+
for offline HTML output.
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
# Development
|
|
652
|
+
|
|
653
|
+
After checking out the repository:
|
|
654
|
+
|
|
655
|
+
```sh
|
|
656
|
+
bundle install
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
Run the test suite with:
|
|
660
|
+
|
|
661
|
+
```sh
|
|
662
|
+
bundle exec rspec
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
To build the gem locally:
|
|
666
|
+
|
|
667
|
+
```sh
|
|
668
|
+
gem build vg_helper.gemspec
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
---
|
|
32
672
|
|
|
33
673
|
## Contributing
|
|
34
674
|
|
|
@@ -37,3 +677,8 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
|
|
|
37
677
|
## License
|
|
38
678
|
|
|
39
679
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
680
|
+
|
|
681
|
+
The bundled Vega, Vega-Lite, and Vega-Embed JavaScript files remain
|
|
682
|
+
subject to their respective licenses.
|
|
683
|
+
|
|
684
|
+
Please see the relevant license files for details.
|
data/lib/vg_helper/version.rb
CHANGED
data/lib/vg_helper.rb
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
require "vega"
|
|
2
2
|
require "rover"
|
|
3
|
+
require "json"
|
|
3
4
|
|
|
4
5
|
# パラメータを任意に足せる/変更できるようにするには?
|
|
5
|
-
# 第2軸の設定は?
|
|
6
|
-
# 垂直線も入れたい
|
|
7
|
-
# 日付軸の水平線 点線未対応
|
|
8
6
|
|
|
9
7
|
# Use Rover::DataFrame, Not Daru
|
|
10
8
|
# Usage: VGHelper::draw_xdate(df, "datetime", "value", ["category1", "category2"])
|
|
11
9
|
|
|
10
|
+
# TODO:
|
|
11
|
+
# With a secondary y-axis, the horizontal-line layer creates another
|
|
12
|
+
# y-axis overlapping the main y-axis because y scales are independent.
|
|
13
|
+
# Currently both axes intentionally use the same style.
|
|
14
|
+
# Refactor main + horizontal layers into a nested layer in a future release.
|
|
15
|
+
|
|
12
16
|
module VGHelper
|
|
13
17
|
|
|
14
18
|
# functionalize
|
|
@@ -51,6 +55,60 @@ module VGHelper
|
|
|
51
55
|
Time.local(t.year, t.month, t.day).to_i * 1000
|
|
52
56
|
end
|
|
53
57
|
|
|
58
|
+
def mark_preset(kind)
|
|
59
|
+
case kind
|
|
60
|
+
when :line
|
|
61
|
+
{
|
|
62
|
+
type: "line",
|
|
63
|
+
tooltip: true,
|
|
64
|
+
clip: true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
when :line_dot
|
|
68
|
+
{
|
|
69
|
+
type: "line",
|
|
70
|
+
point: {
|
|
71
|
+
filled: true,
|
|
72
|
+
size: 40
|
|
73
|
+
},
|
|
74
|
+
tooltip: true,
|
|
75
|
+
clip: true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
when :small_dot
|
|
79
|
+
{
|
|
80
|
+
type: "point",
|
|
81
|
+
filled: true,
|
|
82
|
+
size: 25,
|
|
83
|
+
opacity: 0.75,
|
|
84
|
+
tooltip: true,
|
|
85
|
+
clip: true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
when :large_dot
|
|
89
|
+
{
|
|
90
|
+
type: "point",
|
|
91
|
+
filled: true,
|
|
92
|
+
size: 100,
|
|
93
|
+
opacity: 0.75,
|
|
94
|
+
tooltip: true,
|
|
95
|
+
clip: true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
when :bar
|
|
99
|
+
{
|
|
100
|
+
type: "bar",
|
|
101
|
+
opacity: 0.75,
|
|
102
|
+
tooltip: true,
|
|
103
|
+
clip: true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
else
|
|
107
|
+
raise ArgumentError,
|
|
108
|
+
"unknown mark preset: #{kind.inspect}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
54
112
|
def draw_xdate(df, x_col, y_col, category_col,
|
|
55
113
|
df2: nil, x2_col: nil, y2_col: nil, y2_category_col: nil,
|
|
56
114
|
horizontallines: [], horizontalcolors: nil,
|
|
@@ -78,11 +136,11 @@ module VGHelper
|
|
|
78
136
|
values1 = df.dup
|
|
79
137
|
|
|
80
138
|
if category_col.is_a?(Array)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
139
|
+
values1["multicategory"] =
|
|
140
|
+
values1[category_col].to_a.map { _1.values.join("_") }
|
|
141
|
+
category_col = "multicategory"
|
|
84
142
|
else
|
|
85
|
-
|
|
143
|
+
category_col = category_col.to_s
|
|
86
144
|
end
|
|
87
145
|
############################
|
|
88
146
|
# Default Settings
|
|
@@ -90,21 +148,36 @@ module VGHelper
|
|
|
90
148
|
|
|
91
149
|
categories ||= values1[category_col].to_a.uniq
|
|
92
150
|
|
|
93
|
-
markkind ||= {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
151
|
+
#markkind ||= {
|
|
152
|
+
# type: "line",
|
|
153
|
+
# point: { filled: true, size: 40 },
|
|
154
|
+
# tooltip: true,
|
|
155
|
+
# clip: true
|
|
156
|
+
#}
|
|
157
|
+
|
|
158
|
+
markkind =
|
|
159
|
+
case markkind
|
|
160
|
+
when nil
|
|
161
|
+
mark_preset(:line_dot)
|
|
162
|
+
when Symbol
|
|
163
|
+
mark_preset(markkind)
|
|
164
|
+
when Hash
|
|
165
|
+
markkind
|
|
166
|
+
else
|
|
167
|
+
raise ArgumentError,
|
|
168
|
+
"markkind must be a Symbol or Hash"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
|
|
99
172
|
title ||= y_col
|
|
100
173
|
|
|
101
174
|
colorset ||= (["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
|
|
102
175
|
"#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf",
|
|
103
176
|
"#393b79", "#637939", "#8c6d31", "#843c39", "#7b4173"] * 10)[0...(categories.length)]
|
|
104
177
|
horizontalcolors ||= (["#0000ff", "#ff0000"] * 6)[0...(horizontallines.length)]
|
|
105
|
-
colorset += horizontalcolors
|
|
178
|
+
# colorset += horizontalcolors
|
|
106
179
|
verticalcolors ||= (["#0000ff", "#ff0000"] * 6)[0...(verticallines.length)]
|
|
107
|
-
colorset += verticalcolors
|
|
180
|
+
# colorset += verticalcolors
|
|
108
181
|
|
|
109
182
|
x1_msec = values1[x_col].to_a.map { |v| udate(v) }
|
|
110
183
|
|
|
@@ -235,6 +308,12 @@ module VGHelper
|
|
|
235
308
|
# x_encoding[:timeUnit] = "utcyearmonthdate"
|
|
236
309
|
#end
|
|
237
310
|
|
|
311
|
+
main_y_axis = {
|
|
312
|
+
orient: reverse_orient ? :right : :left,
|
|
313
|
+
titleFontSize: 16,
|
|
314
|
+
labelFontSize: 14
|
|
315
|
+
}
|
|
316
|
+
|
|
238
317
|
# 水平線データの構築
|
|
239
318
|
horiz_layer = []
|
|
240
319
|
if horizontallines && !horizontallines.empty?
|
|
@@ -265,7 +344,8 @@ module VGHelper
|
|
|
265
344
|
field: y_col,
|
|
266
345
|
type: "quantitative",
|
|
267
346
|
scale: { domain: [{expr: "min_y"}, {expr: "max_y"}] },
|
|
268
|
-
axis:
|
|
347
|
+
axis: main_y_axis
|
|
348
|
+
#axis: nil
|
|
269
349
|
},
|
|
270
350
|
color: {
|
|
271
351
|
field: category_col,
|
|
@@ -275,7 +355,8 @@ module VGHelper
|
|
|
275
355
|
range: horizontalcolors
|
|
276
356
|
}
|
|
277
357
|
}
|
|
278
|
-
}
|
|
358
|
+
},
|
|
359
|
+
|
|
279
360
|
}]
|
|
280
361
|
end
|
|
281
362
|
|
|
@@ -314,14 +395,6 @@ module VGHelper
|
|
|
314
395
|
# filtering parameter settings
|
|
315
396
|
############################
|
|
316
397
|
|
|
317
|
-
# categ_params = categories.map do |c|
|
|
318
|
-
# {
|
|
319
|
-
# name: "show_#{c}",
|
|
320
|
-
# value: true,
|
|
321
|
-
# bind: { input: "checkbox", name: c }
|
|
322
|
-
# }
|
|
323
|
-
# end
|
|
324
|
-
|
|
325
398
|
params = []
|
|
326
399
|
|
|
327
400
|
if xslide
|
|
@@ -393,9 +466,9 @@ module VGHelper
|
|
|
393
466
|
]
|
|
394
467
|
end
|
|
395
468
|
|
|
396
|
-
categ_params = categories.map do |c|
|
|
469
|
+
categ_params = categories.map.with_index do |c, i|
|
|
397
470
|
ctp = {
|
|
398
|
-
name: "show_#{
|
|
471
|
+
name: "show_#{i}",
|
|
399
472
|
value: true
|
|
400
473
|
}
|
|
401
474
|
|
|
@@ -411,7 +484,9 @@ module VGHelper
|
|
|
411
484
|
|
|
412
485
|
params += categ_params
|
|
413
486
|
|
|
414
|
-
categ_transforms = categories.map {|c|
|
|
487
|
+
categ_transforms = categories.map.with_index {|c, i|
|
|
488
|
+
"(show_#{i} && datum[#{category_col.to_s.to_json}] == #{c.to_s.to_json})" }
|
|
489
|
+
.join(" || ")
|
|
415
490
|
|
|
416
491
|
# 第二縦軸
|
|
417
492
|
bar_layer = []
|
|
@@ -478,11 +553,12 @@ module VGHelper
|
|
|
478
553
|
field: y_col,
|
|
479
554
|
type: "quantitative",
|
|
480
555
|
title: y_col,
|
|
481
|
-
axis:
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
556
|
+
axis: main_y_axis,
|
|
557
|
+
# axis: {
|
|
558
|
+
# orient: reverse_orient ? :right : :left,
|
|
559
|
+
# titleFontSize: 16,
|
|
560
|
+
# labelFontSize: 14
|
|
561
|
+
#},
|
|
486
562
|
scale: { domain: [{expr: "min_y"}, {expr: "max_y"}] }
|
|
487
563
|
},
|
|
488
564
|
color: {
|
|
@@ -655,12 +731,17 @@ module VGHelper
|
|
|
655
731
|
.width(width)
|
|
656
732
|
.height(height)
|
|
657
733
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
734
|
+
resolve_scale = {
|
|
735
|
+
color: "independent",
|
|
736
|
+
x: "shared"
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
resolve_scale[:y] = "independent" unless v2_array.empty?
|
|
740
|
+
|
|
741
|
+
builder = builder.resolve(
|
|
742
|
+
scale: resolve_scale,
|
|
743
|
+
axis: { x: "shared" }
|
|
662
744
|
)
|
|
663
|
-
end
|
|
664
745
|
|
|
665
746
|
builder
|
|
666
747
|
|
|
@@ -721,15 +802,28 @@ module VGHelper
|
|
|
721
802
|
# Mark settings
|
|
722
803
|
############################
|
|
723
804
|
|
|
724
|
-
markkind = {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
}.merge(markkind || {})
|
|
805
|
+
# markkind = {
|
|
806
|
+
# type: "line",
|
|
807
|
+
# tooltip: true,
|
|
808
|
+
# point: {
|
|
809
|
+
# filled: false,
|
|
810
|
+
# size: 20
|
|
811
|
+
# },
|
|
812
|
+
# clip: true
|
|
813
|
+
# }.merge(markkind || {})
|
|
814
|
+
|
|
815
|
+
markkind =
|
|
816
|
+
case markkind
|
|
817
|
+
when nil
|
|
818
|
+
mark_preset(:line_dot)
|
|
819
|
+
when Symbol
|
|
820
|
+
mark_preset(markkind)
|
|
821
|
+
when Hash
|
|
822
|
+
markkind
|
|
823
|
+
else
|
|
824
|
+
raise ArgumentError,
|
|
825
|
+
"markkind must be a Symbol or Hash"
|
|
826
|
+
end
|
|
733
827
|
|
|
734
828
|
############################
|
|
735
829
|
# Main dataframe
|
|
@@ -1036,9 +1130,9 @@ module VGHelper
|
|
|
1036
1130
|
|
|
1037
1131
|
if category_check
|
|
1038
1132
|
category_params =
|
|
1039
|
-
categories.map do |c|
|
|
1133
|
+
categories.map.with_index do |c, i|
|
|
1040
1134
|
{
|
|
1041
|
-
name: "show_#{
|
|
1135
|
+
name: "show_#{i}",
|
|
1042
1136
|
value: true,
|
|
1043
1137
|
bind: {
|
|
1044
1138
|
input: "checkbox",
|
|
@@ -1050,8 +1144,8 @@ module VGHelper
|
|
|
1050
1144
|
params += category_params
|
|
1051
1145
|
|
|
1052
1146
|
categ_filter_str =
|
|
1053
|
-
categories.map do |c|
|
|
1054
|
-
"(show_#{
|
|
1147
|
+
categories.map.with_index do |c, i|
|
|
1148
|
+
"(show_#{i} && datum[#{category_col_str.to_s.to_json}] == #{c.to_s.to_json})"
|
|
1055
1149
|
end.join(" || ")
|
|
1056
1150
|
|
|
1057
1151
|
category_transforms = [
|
|
@@ -1062,6 +1156,16 @@ module VGHelper
|
|
|
1062
1156
|
]
|
|
1063
1157
|
end
|
|
1064
1158
|
|
|
1159
|
+
############################
|
|
1160
|
+
# Y Axises
|
|
1161
|
+
############################
|
|
1162
|
+
|
|
1163
|
+
main_y_axis = {
|
|
1164
|
+
orient: reverse_orient ? :right : :left,
|
|
1165
|
+
titleFontSize: 16,
|
|
1166
|
+
labelFontSize: 14
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1065
1169
|
############################
|
|
1066
1170
|
# Common X encoding
|
|
1067
1171
|
############################
|
|
@@ -1176,21 +1280,12 @@ module VGHelper
|
|
|
1176
1280
|
|
|
1177
1281
|
encoding: {
|
|
1178
1282
|
y: {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
scale: {
|
|
1186
|
-
domain:
|
|
1187
|
-
ydomain
|
|
1188
|
-
},
|
|
1189
|
-
|
|
1190
|
-
axis:
|
|
1191
|
-
nil
|
|
1283
|
+
field: y_col_str,
|
|
1284
|
+
type: "quantitative",
|
|
1285
|
+
scale: { domain: ydomain },
|
|
1286
|
+
axis: main_y_axis
|
|
1287
|
+
#axis: nil
|
|
1192
1288
|
},
|
|
1193
|
-
|
|
1194
1289
|
color: {
|
|
1195
1290
|
field:
|
|
1196
1291
|
category_col_str,
|
|
@@ -1300,27 +1395,15 @@ module VGHelper
|
|
|
1300
1395
|
field:
|
|
1301
1396
|
y_col_str,
|
|
1302
1397
|
|
|
1303
|
-
type:
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
titleFontSize:
|
|
1314
|
-
16,
|
|
1315
|
-
|
|
1316
|
-
labelFontSize:
|
|
1317
|
-
14
|
|
1318
|
-
},
|
|
1319
|
-
|
|
1320
|
-
scale: {
|
|
1321
|
-
domain:
|
|
1322
|
-
ydomain
|
|
1323
|
-
}
|
|
1398
|
+
type: "quantitative",
|
|
1399
|
+
# axis: {
|
|
1400
|
+
# orient: reverse_orient ? :right : :left,
|
|
1401
|
+
# title: y_col_str,
|
|
1402
|
+
# titleFontSize: 16,
|
|
1403
|
+
# labelFontSize: 14,
|
|
1404
|
+
# },
|
|
1405
|
+
axis: main_y_axis,
|
|
1406
|
+
scale: { domain: ydomain }
|
|
1324
1407
|
},
|
|
1325
1408
|
|
|
1326
1409
|
color: {
|
|
@@ -1539,29 +1622,26 @@ module VGHelper
|
|
|
1539
1622
|
############################
|
|
1540
1623
|
# Build
|
|
1541
1624
|
############################
|
|
1542
|
-
|
|
1625
|
+
|
|
1543
1626
|
builder =
|
|
1544
1627
|
Vega.lite
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1628
|
+
.data(values: all_data)
|
|
1629
|
+
.params(params)
|
|
1630
|
+
.encoding(x: x_encoding)
|
|
1631
|
+
.layer(layers)
|
|
1632
|
+
.width(width)
|
|
1633
|
+
.height(height)
|
|
1634
|
+
.title(title)
|
|
1635
|
+
|
|
1636
|
+
resolve_scale = {
|
|
1637
|
+
color: "independent"
|
|
1638
|
+
}
|
|
1552
1639
|
|
|
1553
|
-
|
|
1554
|
-
builder =
|
|
1555
|
-
builder.resolve(
|
|
1556
|
-
scale: {
|
|
1557
|
-
y:
|
|
1558
|
-
"independent",
|
|
1640
|
+
resolve_scale[:y] = "independent" unless v2_array.empty?
|
|
1559
1641
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
}
|
|
1642
|
+
builder = builder.resolve(
|
|
1643
|
+
scale: resolve_scale
|
|
1563
1644
|
)
|
|
1564
|
-
end
|
|
1565
1645
|
|
|
1566
1646
|
builder
|
|
1567
1647
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vg_helper
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- showata
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rover-df
|