@ironm00n/pyret-lang 0.0.4 → 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.
@@ -4,16 +4,21 @@ provide:
4
4
  from-list,
5
5
  type DataSeries,
6
6
  type ChartWindow,
7
- data StackType,
8
- data TrendlineType,
9
- data PointShape
10
7
  end
11
8
 
12
9
  import global as G
13
10
  import base as B
14
11
  include lists
15
12
  include option
16
- import image-structs as I
13
+ include charts-util
14
+ import charts-util as CU
15
+ provide from CU:
16
+ *,
17
+ type *,
18
+ data *
19
+ end
20
+ import image-structs as IS
21
+ import image as I
17
22
  import internal-image-untyped as IM
18
23
  import sets as S
19
24
  import charts-lib as CL
@@ -22,6 +27,7 @@ import string-dict as SD
22
27
  import valueskeleton as VS
23
28
  import statistics as ST
24
29
  import color as C
30
+ import error as ERR
25
31
  import render-error-display as RED
26
32
 
27
33
  ################################################################################
@@ -48,45 +54,6 @@ end
48
54
  data AxisData:
49
55
  | axis-data(axisTop :: Number, axisBottom :: Number, ticks :: RawArray<Pointer>)
50
56
  end
51
- data StackType:
52
- | absolute
53
- | relative
54
- | percent
55
- | grouped
56
- end
57
-
58
- fun check-positive-degree(v :: Number) -> Boolean block:
59
- when v < 0:
60
- raise("degree: degree must be non-negative")
61
- end
62
- true
63
- end
64
-
65
- data TrendlineType:
66
- | no-trendline
67
- | linear
68
- | exponential
69
- | polynomial(degree :: NumInteger%(check-positive-degree))
70
- end
71
-
72
- fun check-positive-sides(v :: Number) -> Boolean block:
73
- when v < 0:
74
- raise("regular-polygon-shape: number of sides must be non-negative")
75
- end
76
- true
77
- end
78
-
79
- fun check-valid-dent(v :: Number) -> Boolean block:
80
- when (v < 0) or (v > 1):
81
- raise("regular-polygon-shape: dent must be between 0 and 1")
82
- end
83
- true
84
- end
85
-
86
- data PointShape:
87
- | circle-shape
88
- | regular-polygon-shape(sides :: NumInteger%(check-positive-sides), dent :: Number%(check-valid-dent))
89
- end
90
57
 
91
58
  ################################################################################
92
59
  # HELPERS
@@ -147,7 +114,7 @@ fun table2-to-list<A>(table :: RawArray<RawArray<A>>) -> List<List<A>>:
147
114
  end
148
115
 
149
116
  fun get-vs-from-img(s :: String, raw-img :: IM.Image) -> VS.ValueSkeleton:
150
- I.color(190, 190, 190, 0.75)
117
+ IS.color(190, 190, 190, 0.75)
151
118
  ^ IM.text-font(s, 72, _, "", "modern", "normal", "bold", false)
152
119
  ^ IM.overlay-align("center", "bottom", _, raw-img)
153
120
  ^ VS.vs-value
@@ -187,7 +154,7 @@ fun num-to-scientific(base :: Number) -> (Number -> SciNumber) block:
187
154
  Currently only works with bases > 1.
188
155
  ```
189
156
  when base <= 1:
190
- raise("Num-to-scientific: Only defined on bases > 1")
157
+ raise(ERR.message-exception("Num-to-scientific: Only defined on bases > 1"))
191
158
  end
192
159
 
193
160
  fun convert(n :: Number):
@@ -222,7 +189,7 @@ fun string-to-stacktype(s :: String) -> StackType:
222
189
  | string-equal(s, 'absolute') then: absolute
223
190
  | string-equal(s, 'percent') then: percent
224
191
  | string-equal(s, 'relative') then: relative
225
- | otherwise: raise('type must be absolute, relative, percent, or none')
192
+ | otherwise: raise(ERR.message-exception('type must be absolute, relative, percent, or none'))
226
193
  end
227
194
  end
228
195
 
@@ -289,46 +256,46 @@ type BoxData = {
289
256
  }
290
257
 
291
258
  fun get-box-data(label :: String, lst :: List<Number>) -> BoxData:
292
- n = lst.length()
293
- shadow lst = lst.sort()
294
- median = ST.median(lst)
295
- {first-quartile; third-quartile} = if num-modulo(n, 2) == 0:
296
- splitted = lst.split-at(n / 2)
297
- {ST.median(splitted.prefix); ST.median(splitted.suffix)}
298
- else:
299
- splitted = lst.split-at((n - 1) / 2)
300
- {ST.median(splitted.prefix); ST.median(splitted.suffix.rest)}
301
- end
302
- iqr = third-quartile - first-quartile
303
- high-outliers = for filter(shadow n from lst):
304
- n > (third-quartile + (1.5 * iqr))
305
- end
306
- low-outliers = for filter(shadow n from lst):
307
- n < (first-quartile - (1.5 * iqr))
308
- end
309
- min-val = lst.first
310
- max-val = lst.last()
311
- low-whisker = lst.get(low-outliers.length())
312
- high-whisker = lst.get(n - high-outliers.length() - 1)
313
- {
314
- label: label,
315
- max-val: max-val,
316
- min-val: min-val,
317
- first-quartile: first-quartile,
318
- median: median,
319
- third-quartile: third-quartile,
320
- high-whisker: high-whisker,
321
- low-whisker: low-whisker,
322
- high-outliers: builtins.raw-array-from-list(high-outliers),
323
- low-outliers: builtins.raw-array-from-list(low-outliers)
324
- }
259
+ n = lst.length()
260
+ shadow lst = lst.sort()
261
+ median = ST.median(lst)
262
+ {first-quartile; third-quartile} = if num-modulo(n, 2) == 0:
263
+ splitted = lst.split-at(n / 2)
264
+ {ST.median(splitted.prefix); ST.median(splitted.suffix)}
265
+ else:
266
+ splitted = lst.split-at((n - 1) / 2)
267
+ {ST.median(splitted.prefix); ST.median(splitted.suffix.rest)}
268
+ end
269
+ iqr = third-quartile - first-quartile
270
+ high-outliers = for filter(shadow n from lst):
271
+ n > (third-quartile + (1.5 * iqr))
272
+ end
273
+ low-outliers = for filter(shadow n from lst):
274
+ n < (first-quartile - (1.5 * iqr))
275
+ end
276
+ min-val = lst.first
277
+ max-val = lst.last()
278
+ low-whisker = lst.get(low-outliers.length())
279
+ high-whisker = lst.get(n - high-outliers.length() - 1)
280
+ {
281
+ label: label,
282
+ max-val: max-val,
283
+ min-val: min-val,
284
+ first-quartile: first-quartile,
285
+ median: median,
286
+ third-quartile: third-quartile,
287
+ high-whisker: high-whisker,
288
+ low-whisker: low-whisker,
289
+ high-outliers: builtins.raw-array-from-list(high-outliers),
290
+ low-outliers: builtins.raw-array-from-list(low-outliers)
291
+ }
325
292
  end
326
293
 
327
294
  ################################################################################
328
295
  # METHODS
329
296
  ################################################################################
330
297
 
331
- color-method = method(self, color :: I.Color):
298
+ color-method = method(self, color :: IS.Color):
332
299
  self.constr()(self.obj.{color: some(color)})
333
300
  end
334
301
 
@@ -337,30 +304,30 @@ color-list-method = method(self, colors :: CL.LoC):
337
304
  | empty => self.constr()(self.obj.{colors: none})
338
305
  | link(_, _) =>
339
306
  block:
340
- each({(c :: I.Color): c}, colors)
307
+ each({(c :: IS.Color): c}, colors)
341
308
  self.constr()(self.obj.{colors: some(colors ^ builtins.raw-array-from-list)})
342
309
  end
343
310
  end
344
311
  end
345
312
 
346
- pointer-color-method = method(self, color :: I.Color):
313
+ pointer-color-method = method(self, color :: IS.Color):
347
314
  self.constr()(self.obj.{pointer-color: some(color)})
348
315
  end
349
316
 
350
- interval-color-method = method(self, color :: I.Color):
317
+ interval-color-method = method(self, color :: IS.Color):
351
318
  self.constr()(self.obj.{default-interval-color: some(color)})
352
319
  end
353
320
 
354
321
  line-width-method = method(self, lineWidth :: Number) block:
355
322
  when lineWidth < 0:
356
- raise("line-width: Line Width must be non-negative")
323
+ raise(ERR.message-exception("line-width: Line Width must be non-negative"))
357
324
  end
358
325
  self.constr()(self.obj.{lineWidth: lineWidth})
359
326
  end
360
327
 
361
328
  style-method = method(self, style :: String) block:
362
329
  when not(string-equal(style, "sticks")) and not(string-equal(style, "bars")) and not(string-equal(style, "boxes")):
363
- raise("style: must be either sticks, bars, or boxes")
330
+ raise(ERR.message-exception("style: must be either sticks, bars, or boxes"))
364
331
  end
365
332
  self.constr()(self.obj.{style: style})
366
333
  end
@@ -374,7 +341,7 @@ end
374
341
  labels-method = method(self, labels :: CL.LoS):
375
342
  block:
376
343
  when self.obj!ps.length() <> labels.length():
377
- raise('plot: xs and labels should have the same length')
344
+ raise(ERR.message-exception('plot: xs and labels should have the same length'))
378
345
  end
379
346
  self.constr()(self.obj.{ps: map2({(arr, label): raw-array-set(arr, 2, label)}, self.obj!ps, labels)})
380
347
  end
@@ -383,7 +350,7 @@ end
383
350
  image-labels-method = method(self, images :: CL.LoI):
384
351
  block:
385
352
  when self.obj!ps.length() <> images.length():
386
- raise('plot: xs and images should have the same length')
353
+ raise(ERR.message-exception('plot: xs and images should have the same length'))
387
354
  end
388
355
  self.constr()(self.obj.{ps: map2({(arr, image): raw-array-set(arr, 3, image)}, self.obj!ps, images)})
389
356
  end
@@ -391,11 +358,11 @@ end
391
358
 
392
359
  explode-method = method(self, offsets :: CL.LoN) block:
393
360
  when raw-array-length(self.obj!tab) <> offsets.length():
394
- raise('exploding-pie-chart: labels and offsets should have the same length')
361
+ raise(ERR.message-exception('exploding-pie-chart: labels and offsets should have the same length'))
395
362
  end
396
363
  for each(offset from offsets):
397
364
  when (offset < 0) or (offset > 1):
398
- raise('exploding-pie-chart: offset must be between 0 and 1')
365
+ raise(ERR.message-exception('exploding-pie-chart: offset must be between 0 and 1'))
399
366
  end
400
367
  end
401
368
  self.constr()(self.obj.{tab: raw-array-from-list(map2({(arr, offset): raw-array-set(arr, 2, offset)}, raw-array-to-list(self.obj!tab), offsets))})
@@ -403,17 +370,17 @@ end
403
370
 
404
371
  histogram-label-method = method(self, labels :: CL.LoS) block:
405
372
  when raw-array-length(self.obj!tab) <> labels.length():
406
- raise('histogram: xs and labels should have the same length')
373
+ raise(ERR.message-exception('histogram: xs and labels should have the same length'))
407
374
  end
408
375
  self.constr()(self.obj.{tab: raw-array-from-list(map2({(arr, label): raw-array-set(arr, 0, label)}, raw-array-to-list(self.obj!tab), labels))})
409
376
  end
410
377
 
411
378
  box-labels-method = method(self, labels :: CL.LoS) block:
412
379
  when labels.length() <> self.obj!values.length():
413
- raise('labeled-box-plot: labels and values should have the same length')
380
+ raise(ERR.message-exception('labeled-box-plot: labels and values should have the same length'))
414
381
  end
415
382
  when labels.length() == 0:
416
- raise('labeled-box-plot: expect at least one box')
383
+ raise(ERR.message-exception('labeled-box-plot: expect at least one box'))
417
384
  end
418
385
  self.constr()(self.obj.{tab: map2(get-box-data, labels, self.obj!values) ^ builtins.raw-array-from-list,})
419
386
  end
@@ -423,7 +390,7 @@ threeD-method = method(self, threeD :: Boolean):
423
390
  end
424
391
 
425
392
  piehole-method = method(self, piehole :: Number):
426
- if (piehole < 0) or (piehole > 1): raise("piehole: Value must be between 0 and 1")
393
+ if (piehole < 0) or (piehole > 1): raise(ERR.message-exception("piehole: Value must be between 0 and 1"))
427
394
  else: self.constr()(self.obj.{piehole: piehole})
428
395
  end
429
396
  end
@@ -434,7 +401,7 @@ end
434
401
 
435
402
  collapse-threshold-method = method(self, collapseThreshold :: Number) block:
436
403
  when (collapseThreshold < 0) or (collapseThreshold > 1):
437
- raise("collapse-threshold: Threshold must be between 0 and 1")
404
+ raise(ERR.message-exception("collapse-threshold: Threshold must be between 0 and 1"))
438
405
  end
439
406
  self.constr()(self.obj.{collapseThreshold: collapseThreshold})
440
407
  end
@@ -442,26 +409,26 @@ end
442
409
  trendline-type-method = method(self, trendlineType :: TrendlineType):
443
410
  cases (TrendlineType) trendlineType:
444
411
  | no-trendline => self.constr()(self.obj.{trendlineType: none})
445
- | linear => self.constr()(self.obj.{trendlineType: some("poly"), trendlineDegree: 1})
446
- | exponential => self.constr()(self.obj.{trendlineType: some("exp")})
447
- | polynomial(degree) => self.constr()(self.obj.{trendlineType: some("poly"), trendlineDegree: degree})
412
+ | tl-linear => self.constr()(self.obj.{trendlineType: some("poly"), trendlineDegree: 1})
413
+ | tl-exponential => self.constr()(self.obj.{trendlineType: some("exp")})
414
+ | tl-polynomial(degree) => self.constr()(self.obj.{trendlineType: some("poly"), trendlineDegree: degree})
448
415
  end
449
416
 
450
417
  end
451
418
 
452
- trendline-color-method = method(self, color :: I.Color):
419
+ trendline-color-method = method(self, color :: IS.Color):
453
420
  self.constr()(self.obj.{trendlineColor: some(color)})
454
421
  end
455
422
 
456
423
  trendline-width-method = method(self, lineWidth :: Number) block:
457
424
  when lineWidth < 0:
458
- raise("trendline-width: Trendline Width must be non-negative")
425
+ raise(ERR.message-exception("trendline-width: Trendline Width must be non-negative"))
459
426
  end
460
427
  self.constr()(self.obj.{trendlineWidth: lineWidth})
461
428
  end
462
429
 
463
430
  trendline-opacity-method = method(self, opacity :: Number):
464
- if (opacity < 0) or (opacity > 1): raise("Trendline opacity: Value must be between 0 and 1")
431
+ if (opacity < 0) or (opacity > 1): raise(ERR.message-exception("Trendline opacity: Value must be between 0 and 1"))
465
432
  else: self.constr()(self.obj.{trendlineOpacity: opacity})
466
433
  end
467
434
  end
@@ -472,7 +439,7 @@ end
472
439
 
473
440
  dashed-line-style-method = method(self, dashed-line-style :: CL.LoNi) block:
474
441
  when any({(n): n < 0}, dashed-line-style):
475
- raise("Dashed Line Style: Values must be non-negative")
442
+ raise(ERR.message-exception("Dashed Line Style: Values must be non-negative"))
476
443
  end
477
444
  self.constr()(self.obj.{dashedLine: true, dashlineStyle: raw-array-from-list(dashed-line-style)})
478
445
  end
@@ -488,18 +455,18 @@ select-multiple-method = method(self, multiple :: Boolean):
488
455
  self.constr()(self.obj.{multiple: multiple})
489
456
  end
490
457
 
491
- background-color-method = method(self, color :: I.Color):
458
+ background-color-method = method(self, color :: IS.Color):
492
459
  self.constr()(self.obj.{backgroundColor: some(color)})
493
460
  end
494
461
 
495
462
  background-border-method = method(self, border-size :: Number) block:
496
463
  when border-size < 0:
497
- raise("border-size: Border Size must be non-negative")
464
+ raise(ERR.message-exception("border-size: Border Size must be non-negative"))
498
465
  end
499
466
  self.constr()(self.obj.{borderSize: border-size})
500
467
  end
501
468
 
502
- border-color-method = method(self, border-color :: I.Color):
469
+ border-color-method = method(self, border-color :: IS.Color):
503
470
  self.constr()(self.obj.{borderColor: some(border-color)})
504
471
  end
505
472
 
@@ -515,24 +482,24 @@ show-grid-lines-method = method(self, is-showing :: Boolean):
515
482
  self.constr()(self.obj.{show-grid-lines: is-showing})
516
483
  end
517
484
 
518
- gridlines-color-method = method(self, color :: I.Color):
485
+ gridlines-color-method = method(self, color :: IS.Color):
519
486
  self.constr()(self.obj.{show-grid-lines: true, gridlineColor: some(color)})
520
487
  end
521
488
 
522
- minor-gridlines-color-method = method(self, color :: I.Color):
489
+ minor-gridlines-color-method = method(self, color :: IS.Color):
523
490
  self.constr()(self.obj.{show-minor-grid-lines: true, minorGridlineColor: some(color)})
524
491
  end
525
492
 
526
493
  gridlines-min-spacing-method = method(self, minspacing :: Number) block:
527
494
  when minspacing < 0:
528
- raise("gridlines-minspacing: Min spacing must be non-negative")
495
+ raise(ERR.message-exception("gridlines-minspacing: Min spacing must be non-negative"))
529
496
  end
530
497
  self.constr()(self.obj.{gridlineMinspacing: some(minspacing)})
531
498
  end
532
499
 
533
500
  minor-gridlines-min-spacing-method = method(self, minspacing :: Number) block:
534
501
  when minspacing < 0:
535
- raise("minor-gridlines-minspacing: Min spacing must be non-negative")
502
+ raise(ERR.message-exception("minor-gridlines-minspacing: Min spacing must be non-negative"))
536
503
  end
537
504
  self.constr()(self.obj.{show-minor-grid-lines: true, minorGridlineMinspacing: minspacing})
538
505
  end
@@ -545,6 +512,14 @@ y-axis-method = method(self, y-axis :: String):
545
512
  self.constr()(self.obj.{y-axis: y-axis})
546
513
  end
547
514
 
515
+ x-axis-type-method = method(self, x-axis-type :: AxisType):
516
+ self.constr()(self.obj.{x-axis-type: x-axis-type})
517
+ end
518
+
519
+ y-axis-type-method = method(self, y-axis-type :: AxisType):
520
+ self.constr()(self.obj.{y-axis-type: y-axis-type})
521
+ end
522
+
548
523
  x-min-method = method(self, x-min :: Number):
549
524
  self.constr()(self.obj.{x-min: some(x-min)})
550
525
  end
@@ -648,10 +623,10 @@ axis-pointer-method = method(self,
648
623
 
649
624
  # Edge Case Error Checking
650
625
  when not(distinctTVLen == TVLen):
651
- raise('add-pointers: pointers cannot overlap')
626
+ raise(ERR.message-exception('add-pointers: pointers cannot overlap'))
652
627
  end
653
628
  when not(TVLen == TLLen):
654
- raise('add-pointers: pointers values and names should have the same length')
629
+ raise(ERR.message-exception('add-pointers: pointers values and names should have the same length'))
655
630
  end
656
631
 
657
632
  ticks = fold2({(acc, e1, e2): link(pointer(e1, e2), acc)}, empty, tickLabels, tickValues)
@@ -697,7 +672,7 @@ end
697
672
  format-axis-data-method = method(self, format-func :: (Number -> String)):
698
673
  cases (Option) self.obj!axisdata:
699
674
  | none =>
700
- raise("Axis properties initialized improperly. Please report as a bug!")
675
+ raise(ERR.message-exception("Axis properties initialized improperly. Please report as a bug!"))
701
676
  | some(ad) =>
702
677
  ad-tick-list = ad.ticks ^ raw-array-to-list
703
678
  new-ticks = map({(p): pointer(format-func(p.value), p.value)}, ad-tick-list) ^ builtins.raw-array-from-list
@@ -756,7 +731,7 @@ stacking-type-method = method(self, stack-type :: StackType):
756
731
  {max-positive-height; max-negative-height} =
757
732
  multi-prep-axis(grouped, value-lists)
758
733
  new-self.make-axis(max-positive-height, max-negative-height)
759
- | otherwise: raise('stacking-type: type must be absolute, relative, percent, or grouped')
734
+ | otherwise: raise(ERR.message-exception('stacking-type: type must be absolute, relative, percent, or grouped'))
760
735
  end
761
736
  end
762
737
 
@@ -766,10 +741,10 @@ annotations-method = method(self,
766
741
  expected-length = raw-array-length(self.obj.annotations)
767
742
  given-length = annotations.length()
768
743
  when given-length <> expected-length:
769
- raise("annotations: input dimensions mismatch. Expected length "
770
- + num-to-string(expected-length)
771
- + ", received "
772
- + num-to-string(given-length))
744
+ raise(ERR.message-exception("annotations: input dimensions mismatch. Expected length "
745
+ + num-to-string(expected-length)
746
+ + ", received "
747
+ + num-to-string(given-length)))
773
748
  end
774
749
 
775
750
  block:
@@ -786,12 +761,12 @@ annotations-method = method(self,
786
761
  shadow expected-length = raw-array-length(expected)
787
762
  shadow given-length = given.length()
788
763
  when given-length <> expected-length:
789
- raise("annotations: length mismatch on row "
790
- + num-to-string(index)
791
- + ". Expected "
792
- + num-to-string(expected-length)
793
- + ", received "
794
- + num-to-string(given-length))
764
+ raise(ERR.message-exception("annotations: length mismatch on row "
765
+ + num-to-string(index)
766
+ + ". Expected "
767
+ + num-to-string(expected-length)
768
+ + ", received "
769
+ + num-to-string(given-length)))
795
770
  end
796
771
  end
797
772
  end
@@ -808,10 +783,10 @@ intervals-method = method(self, intervals :: CL.LoLoLoN) block:
808
783
  expected-length = raw-array-length(self.obj.intervals)
809
784
  given-length = intervals.length()
810
785
  when given-length <> expected-length:
811
- raise("intervals: input dimensions mismatch. Expected length "
812
- + num-to-string(expected-length)
813
- + ", received "
814
- + num-to-string(given-length))
786
+ raise(ERR.message-exception("intervals: input dimensions mismatch. Expected length "
787
+ + num-to-string(expected-length)
788
+ + ", received "
789
+ + num-to-string(given-length)))
815
790
  end
816
791
  block:
817
792
  each({(l :: List<List<Number>>):
@@ -823,12 +798,12 @@ intervals-method = method(self, intervals :: CL.LoLoLoN) block:
823
798
  shadow expected-length = raw-array-length(expected)
824
799
  shadow given-length = given.length()
825
800
  when given-length <> expected-length:
826
- raise("intervals: length mismatch on row "
827
- + num-to-string(index)
828
- + ". Expected "
829
- + num-to-string(expected-length)
830
- + ", received "
831
- + num-to-string(given-length))
801
+ raise(ERR.message-exception("intervals: length mismatch on row "
802
+ + num-to-string(index)
803
+ + ". Expected "
804
+ + num-to-string(expected-length)
805
+ + ", received "
806
+ + num-to-string(given-length)))
832
807
  end
833
808
  end
834
809
  end
@@ -854,10 +829,10 @@ error-bars-method = method(self, errors :: CL.LoLoLoN) block:
854
829
  expected-length = raw-array-length(self.obj.intervals)
855
830
  given-length = errors.length()
856
831
  when given-length <> expected-length:
857
- raise("error-bars: input dimensions mismatch. Expected length "
858
- + num-to-string(expected-length)
859
- + ", received "
860
- + num-to-string(given-length))
832
+ raise(ERR.message-exception("error-bars: input dimensions mismatch. Expected length "
833
+ + num-to-string(expected-length)
834
+ + ", received "
835
+ + num-to-string(given-length)))
861
836
  end
862
837
  block:
863
838
  each({(l :: List<List<Number>>):
@@ -871,26 +846,26 @@ error-bars-method = method(self, errors :: CL.LoLoLoN) block:
871
846
  shadow given-length = given.length()
872
847
  row-str = num-to-string(index)
873
848
  when given-length <> expected-length:
874
- raise("error-bars: length mismatch on row " + row-str
875
- + ". Expected "
876
- + num-to-string(expected-length)
877
- + ", received "
878
- + num-to-string(given-length))
849
+ raise(ERR.message-exception("error-bars: length mismatch on row " + row-str
850
+ + ". Expected "
851
+ + num-to-string(expected-length)
852
+ + ", received "
853
+ + num-to-string(given-length)))
879
854
  end
880
855
  for each2(pair from given, column from range(0, given.length())):
881
856
  block:
882
857
  col-str = num-to-string(column)
883
858
  when pair.length() <> 2:
884
- raise("error-bars: on row " + row-str + " column " + col-str
885
- + ", 2 intervals must be given.")
859
+ raise(ERR.message-exception("error-bars: on row " + row-str + " column " + col-str
860
+ + ", 2 intervals must be given."))
886
861
  end
887
862
  when pair.get(0) > 0:
888
- raise("error-bars: on row " + row-str + " column " + col-str
889
- + ", first pair must be non-positive.")
863
+ raise(ERR.message-exception("error-bars: on row " + row-str + " column " + col-str
864
+ + ", first pair must be non-positive."))
890
865
  end
891
866
  when pair.get(1) < 0:
892
- raise("error-bars: on row " + row-str + " column " + col-str
893
- + ", second pair must be non-negative.")
867
+ raise(ERR.message-exception("error-bars: on row " + row-str + " column " + col-str
868
+ + ", second pair must be non-negative."))
894
869
  end
895
870
  end
896
871
  end
@@ -916,10 +891,10 @@ single-error-bars-method = method(self, errors :: CL.LoLoN) block:
916
891
  expected-length = raw-array-length(self.obj.intervals)
917
892
  given-length = errors.length()
918
893
  when given-length <> expected-length:
919
- raise("error-bars: input dimensions mismatch. Expected length "
920
- + num-to-string(expected-length)
921
- + ", received "
922
- + num-to-string(given-length))
894
+ raise(ERR.message-exception("error-bars: input dimensions mismatch. Expected length "
895
+ + num-to-string(expected-length)
896
+ + ", received "
897
+ + num-to-string(given-length)))
923
898
  end
924
899
  block:
925
900
  each({(l :: List<Number>):
@@ -931,17 +906,17 @@ single-error-bars-method = method(self, errors :: CL.LoLoN) block:
931
906
  block:
932
907
  row-str = num-to-string(index)
933
908
  when given.length() <> 2:
934
- raise("error-bars: on row " + row-str
935
- + ", 2 intervals must be given (received "
936
- + num-to-string(given.length()) + ").")
909
+ raise(ERR.message-exception("error-bars: on row " + row-str
910
+ + ", 2 intervals must be given (received "
911
+ + num-to-string(given.length()) + ")."))
937
912
  end
938
913
  when given.get(0) > 0:
939
- raise("error-bars: on row " + row-str
940
- + ", first pair must be non-positive.")
914
+ raise(ERR.message-exception("error-bars: on row " + row-str
915
+ + ", first pair must be non-positive."))
941
916
  end
942
917
  when given.get(1) < 0:
943
- raise("error-bars: on row " + row-str
944
- + ", second pair must be non-negative.")
918
+ raise(ERR.message-exception("error-bars: on row " + row-str
919
+ + ", second pair must be non-negative."))
945
920
  end
946
921
  end
947
922
  end
@@ -968,6 +943,13 @@ horizontal-method = method(self, b :: Boolean):
968
943
  self.constr()(self.obj.{horizontal: b})
969
944
  end
970
945
 
946
+ bandwidth-method = method(self, width :: Number) block:
947
+ when (width <= 0) or (width > 1):
948
+ raise(ERR.message-exception("bandwidth: must be greater than zero and at most 1, given " + num-to-string(width)))
949
+ end
950
+ self.constr()(self.obj.{bandwidth: width})
951
+ end
952
+
971
953
  ################################################################################
972
954
  # BOUNDING BOX
973
955
  ################################################################################
@@ -1053,7 +1035,7 @@ type BoxChartSeries = {
1053
1035
  tab :: TableIntern,
1054
1036
  height :: Number,
1055
1037
  horizontal :: Boolean,
1056
- color :: Option<I.Color>,
1038
+ color :: Option<IS.Color>,
1057
1039
  }
1058
1040
 
1059
1041
  default-box-plot-series = {
@@ -1064,7 +1046,7 @@ default-box-plot-series = {
1064
1046
 
1065
1047
  type PieChartSeries = {
1066
1048
  tab :: TableIntern,
1067
- colors :: Option<RawArray<I.Color>>,
1049
+ colors :: Option<RawArray<IS.Color>>,
1068
1050
  threeD :: Boolean,
1069
1051
  piehole :: Number,
1070
1052
  startingAngle :: Number,
@@ -1082,14 +1064,15 @@ default-pie-chart-series = {
1082
1064
  type BarChartSeries = {
1083
1065
  tab :: TableIntern,
1084
1066
  axisdata :: Option<AxisData>,
1085
- color :: Option<I.Color>,
1086
- colors :: Option<RawArray<I.Color>>,
1067
+ color :: Option<IS.Color>,
1068
+ colors :: Option<RawArray<IS.Color>>,
1087
1069
  pointers :: Option<RawArray<Pointer>>,
1088
- pointer-color :: Option<I.Color>,
1070
+ pointer-color :: Option<IS.Color>,
1089
1071
  horizontal :: Boolean,
1072
+ bandwidth :: Number,
1090
1073
  annotations :: RawArray<RawArray<Option<String>>>,
1091
1074
  intervals :: RawArray<RawArray<RawArray<Number>>>,
1092
- default-interval-color :: Option<I.Color>,
1075
+ default-interval-color :: Option<IS.Color>,
1093
1076
  }
1094
1077
 
1095
1078
  default-bar-chart-series = {
@@ -1098,7 +1081,8 @@ default-bar-chart-series = {
1098
1081
  pointers: none,
1099
1082
  pointer-color: none,
1100
1083
  axisdata: none,
1101
- horizontal: false,
1084
+ horizontal: false,
1085
+ bandwidth: 0.8,
1102
1086
  default-interval-color: none,
1103
1087
  }
1104
1088
 
@@ -1107,13 +1091,14 @@ type MultiBarChartSeries = {
1107
1091
  axisdata :: Option<AxisData>,
1108
1092
  legends :: RawArray<String>,
1109
1093
  is-stacked :: String,
1110
- colors :: Option<RawArray<I.Color>>,
1094
+ colors :: Option<RawArray<IS.Color>>,
1111
1095
  pointers :: Option<RawArray<Pointer>>,
1112
- pointer-color :: Option<I.Color>,
1096
+ pointer-color :: Option<IS.Color>,
1113
1097
  horizontal :: Boolean,
1098
+ bandwidth :: Number,
1114
1099
  annotations :: RawArray<RawArray<Option<String>>>,
1115
1100
  intervals :: RawArray<RawArray<RawArray<Number>>>,
1116
- default-interval-color :: Option<I.Color>
1101
+ default-interval-color :: Option<IS.Color>
1117
1102
  }
1118
1103
 
1119
1104
  default-multi-bar-chart-series = {
@@ -1122,7 +1107,8 @@ default-multi-bar-chart-series = {
1122
1107
  pointers: none,
1123
1108
  pointer-color: none,
1124
1109
  axisdata: none,
1125
- horizontal: false,
1110
+ horizontal: false,
1111
+ bandwidth: 0.8,
1126
1112
  default-interval-color: none
1127
1113
  }
1128
1114
 
@@ -1131,7 +1117,7 @@ type HistogramSeries = {
1131
1117
  bin-width :: Option<Number>,
1132
1118
  max-num-bins :: Option<Number>,
1133
1119
  min-num-bins :: Option<Number>,
1134
- color :: Option<I.Color>,
1120
+ color :: Option<IS.Color>,
1135
1121
  }
1136
1122
 
1137
1123
  default-histogram-series = {
@@ -1143,12 +1129,12 @@ default-histogram-series = {
1143
1129
 
1144
1130
  type LinePlotSeries = {
1145
1131
  ps :: List<Posn>,
1146
- color :: Option<I.Color>,
1132
+ color :: Option<IS.Color>,
1147
1133
  legend :: String,
1148
1134
  curved :: String,
1149
1135
  lineWidth :: Number,
1150
1136
  trendlineType :: Option<String>,
1151
- trendlineColor :: Option<I.Color>,
1137
+ trendlineColor :: Option<IS.Color>,
1152
1138
  trendlineWidth :: Number,
1153
1139
  trendlineOpacity :: Number,
1154
1140
  trendlineDegree :: NumInteger,
@@ -1193,12 +1179,12 @@ type ScatterPoint = {
1193
1179
 
1194
1180
  type ScatterPlotSeries = {
1195
1181
  ps :: List<ScatterPoint>,
1196
- color :: Option<I.Color>,
1182
+ color :: Option<IS.Color>,
1197
1183
  legend :: String,
1198
1184
  point-size :: Number,
1199
1185
  useImageSizes :: Boolean,
1200
1186
  trendlineType :: Option<String>,
1201
- trendlineColor :: Option<I.Color>,
1187
+ trendlineColor :: Option<IS.Color>,
1202
1188
  trendlineWidth :: Number,
1203
1189
  trendlineOpacity :: Number,
1204
1190
  trendlineDegree :: NumInteger,
@@ -1234,7 +1220,7 @@ type DotPoint = {
1234
1220
 
1235
1221
  type DotPlotSeries = {
1236
1222
  ps :: List<DotPoint>,
1237
- color :: Option<I.Color>,
1223
+ color :: Option<IS.Color>,
1238
1224
  legend :: String,
1239
1225
  point-size :: Number,
1240
1226
  useImageSizes :: Boolean,
@@ -1254,7 +1240,7 @@ type CategoricalDotPoint ={
1254
1240
 
1255
1241
  type CategoricalDotPlotSeries = {
1256
1242
  ps :: List<CategoricalDotPoint>,
1257
- color :: Option<I.Color>,
1243
+ color :: Option<IS.Color>,
1258
1244
  legend :: String
1259
1245
  }
1260
1246
 
@@ -1272,18 +1258,18 @@ type IntervalPoint = {
1272
1258
  }
1273
1259
  type IntervalChartSeries = {
1274
1260
  axisdata :: Option<AxisData>,
1275
- color :: Option<I.Color>,
1261
+ color :: Option<IS.Color>,
1276
1262
  pointers :: Option<RawArray<Pointer>>,
1277
- pointer-color :: Option<I.Color>,
1263
+ pointer-color :: Option<IS.Color>,
1278
1264
  point-size :: Number,
1279
1265
  lineWidth :: Number,
1280
1266
  stick-width :: Number,
1281
1267
  style :: String,
1282
1268
  horizontal :: Boolean,
1283
- default-interval-color :: Option<I.Color>,
1269
+ default-interval-color :: Option<IS.Color>,
1284
1270
  legend :: String,
1285
1271
  trendlineType :: Option<String>,
1286
- trendlineColor :: Option<I.Color>,
1272
+ trendlineColor :: Option<IS.Color>,
1287
1273
  trendlineWidth :: Number,
1288
1274
  trendlineOpacity :: Number,
1289
1275
  trendlineDegree :: NumInteger,
@@ -1322,7 +1308,7 @@ default-interval-chart-series = {
1322
1308
 
1323
1309
  type FunctionPlotSeries = {
1324
1310
  f :: PlottableFunction,
1325
- color :: Option<I.Color>,
1311
+ color :: Option<IS.Color>,
1326
1312
  legend :: String,
1327
1313
  horizontal :: Boolean,
1328
1314
  }
@@ -1339,9 +1325,9 @@ type ChartWindowObject = {
1339
1325
  title :: String,
1340
1326
  width :: Number,
1341
1327
  height :: Number,
1342
- backgroundColor :: Option<I.Color>,
1328
+ backgroundColor :: Option<IS.Color>,
1343
1329
  borderSize :: Number,
1344
- borderColor :: Option<I.Color>,
1330
+ borderColor :: Option<IS.Color>,
1345
1331
  render :: ( -> IM.Image)
1346
1332
  }
1347
1333
 
@@ -1352,18 +1338,20 @@ default-chart-window-object :: ChartWindowObject = {
1352
1338
  backgroundColor: none,
1353
1339
  borderSize: 0,
1354
1340
  borderColor: none,
1355
- method render(self): raise('unimplemented') end,
1341
+ method render(self): raise(ERR.message-exception('unimplemented')) end,
1356
1342
  }
1357
1343
 
1358
1344
  type BoxChartWindowObject = {
1359
1345
  title :: String,
1360
1346
  width :: Number,
1361
1347
  height :: Number,
1362
- backgroundColor :: Option<I.Color>,
1348
+ backgroundColor :: Option<IS.Color>,
1363
1349
  borderSize :: Number,
1364
- borderColor :: Option<I.Color>,
1350
+ borderColor :: Option<IS.Color>,
1365
1351
  x-axis :: String,
1366
1352
  y-axis :: String,
1353
+ x-axis-type :: AxisType,
1354
+ y-axis-type :: AxisType,
1367
1355
  min :: Option<Number>,
1368
1356
  max :: Option<Number>,
1369
1357
  render :: ( -> IM.Image),
@@ -1372,6 +1360,8 @@ type BoxChartWindowObject = {
1372
1360
  default-box-plot-chart-window-object :: BoxChartWindowObject = default-chart-window-object.{
1373
1361
  x-axis: '',
1374
1362
  y-axis: '',
1363
+ x-axis-type: at-linear,
1364
+ y-axis-type: at-linear,
1375
1365
  min: none,
1376
1366
  max: none,
1377
1367
  }
@@ -1380,9 +1370,9 @@ type PieChartWindowObject = {
1380
1370
  title :: String,
1381
1371
  width :: Number,
1382
1372
  height :: Number,
1383
- backgroundColor :: Option<I.Color>,
1373
+ backgroundColor :: Option<IS.Color>,
1384
1374
  borderSize :: Number,
1385
- borderColor :: Option<I.Color>,
1375
+ borderColor :: Option<IS.Color>,
1386
1376
  render :: ( -> IM.Image),
1387
1377
  }
1388
1378
 
@@ -1392,29 +1382,39 @@ type DotChartWindowObject = {
1392
1382
  title :: String,
1393
1383
  width :: Number,
1394
1384
  height :: Number,
1395
- backgroundColor :: Option<I.Color>,
1385
+ backgroundColor :: Option<IS.Color>,
1396
1386
  borderSize :: Number,
1397
- borderColor :: Option<I.Color>,
1387
+ borderColor :: Option<IS.Color>,
1398
1388
  render :: ( -> IM.Image),
1399
1389
  x-axis :: String,
1400
- y-axis :: String
1390
+ y-axis :: String,
1391
+ x-axis-type :: AxisType,
1392
+ y-axis-type :: AxisType,
1393
+ x-min :: Option<Number>,
1394
+ x-max :: Option<Number>,
1401
1395
  }
1402
1396
 
1403
1397
  default-dot-chart-window-object :: DotChartWindowObject = default-chart-window-object.{
1404
1398
  x-axis: '',
1405
- y-axis: ''
1399
+ y-axis: '',
1400
+ x-axis-type: at-linear,
1401
+ y-axis-type: at-linear,
1402
+ x-min: none,
1403
+ x-max: none,
1406
1404
  }
1407
1405
 
1408
1406
  type BarChartWindowObject = {
1409
1407
  title :: String,
1410
1408
  width :: Number,
1411
1409
  height :: Number,
1412
- backgroundColor :: Option<I.Color>,
1410
+ backgroundColor :: Option<IS.Color>,
1413
1411
  borderSize :: Number,
1414
- borderColor :: Option<I.Color>,
1412
+ borderColor :: Option<IS.Color>,
1415
1413
  render :: ( -> IM.Image),
1416
1414
  x-axis :: String,
1417
1415
  y-axis :: String,
1416
+ x-axis-type :: AxisType,
1417
+ y-axis-type :: AxisType,
1418
1418
  y-min :: Option<Number>,
1419
1419
  y-max :: Option<Number>,
1420
1420
  }
@@ -1422,6 +1422,8 @@ type BarChartWindowObject = {
1422
1422
  default-bar-chart-window-object :: BarChartWindowObject = default-chart-window-object.{
1423
1423
  x-axis: '',
1424
1424
  y-axis: '',
1425
+ x-axis-type: at-linear,
1426
+ y-axis-type: at-linear,
1425
1427
  y-min: none,
1426
1428
  y-max: none,
1427
1429
  }
@@ -1430,12 +1432,14 @@ type IntervalChartWindowObject = {
1430
1432
  title :: String,
1431
1433
  width :: Number,
1432
1434
  height :: Number,
1433
- backgroundColor :: Option<I.Color>,
1435
+ backgroundColor :: Option<IS.Color>,
1434
1436
  borderSize :: Number,
1435
- borderColor :: Option<I.Color>,
1437
+ borderColor :: Option<IS.Color>,
1436
1438
  render :: ( -> IM.Image),
1437
1439
  x-axis :: String,
1438
1440
  y-axis :: String,
1441
+ x-axis-type :: AxisType,
1442
+ y-axis-type :: AxisType,
1439
1443
  y-min :: Option<Number>,
1440
1444
  y-max :: Option<Number>,
1441
1445
  }
@@ -1443,6 +1447,8 @@ type IntervalChartWindowObject = {
1443
1447
  default-interval-chart-window-object :: IntervalChartWindowObject = default-chart-window-object.{
1444
1448
  x-axis: '',
1445
1449
  y-axis: '',
1450
+ x-axis-type: at-linear,
1451
+ y-axis-type: at-linear,
1446
1452
  y-min: none,
1447
1453
  y-max: none,
1448
1454
  }
@@ -1451,12 +1457,14 @@ type HistogramChartWindowObject = {
1451
1457
  title :: String,
1452
1458
  width :: Number,
1453
1459
  height :: Number,
1454
- backgroundColor :: Option<I.Color>,
1460
+ backgroundColor :: Option<IS.Color>,
1455
1461
  borderSize :: Number,
1456
- borderColor :: Option<I.Color>,
1462
+ borderColor :: Option<IS.Color>,
1457
1463
  render :: ( -> IM.Image),
1458
1464
  x-axis :: String,
1459
1465
  y-axis :: String,
1466
+ x-axis-type :: AxisType,
1467
+ y-axis-type :: AxisType,
1460
1468
  x-min :: Option<Number>,
1461
1469
  x-max :: Option<Number>,
1462
1470
  y-max :: Option<Number>,
@@ -1466,6 +1474,8 @@ default-histogram-chart-window-object :: HistogramChartWindowObject =
1466
1474
  default-chart-window-object.{
1467
1475
  x-axis: '',
1468
1476
  y-axis: '',
1477
+ x-axis-type: at-linear,
1478
+ y-axis-type: at-linear,
1469
1479
  x-min: none,
1470
1480
  x-max: none,
1471
1481
  y-max: none,
@@ -1475,18 +1485,20 @@ type PlotChartWindowObject = {
1475
1485
  title :: String,
1476
1486
  width :: Number,
1477
1487
  height :: Number,
1478
- backgroundColor :: Option<I.Color>,
1488
+ backgroundColor :: Option<IS.Color>,
1479
1489
  borderSize :: Number,
1480
- borderColor :: Option<I.Color>,
1490
+ borderColor :: Option<IS.Color>,
1481
1491
  render :: ( -> IM.Image),
1482
1492
  show-grid-lines :: Boolean,
1483
1493
  show-minor-grid-lines :: Boolean,
1484
- gridlineColor :: Option<I.Color>,
1494
+ gridlineColor :: Option<IS.Color>,
1485
1495
  gridlineMinspacing :: Option<Number>,
1486
- minorGridlineColor :: Option<I.Color>,
1496
+ minorGridlineColor :: Option<IS.Color>,
1487
1497
  minorGridlineMinspacing :: Number,
1488
1498
  x-axis :: String,
1489
1499
  y-axis :: String,
1500
+ x-axis-type :: AxisType,
1501
+ y-axis-type :: AxisType,
1490
1502
  x-min :: Option<Number>,
1491
1503
  x-max :: Option<Number>,
1492
1504
  y-min :: Option<Number>,
@@ -1498,6 +1510,8 @@ type PlotChartWindowObject = {
1498
1510
  default-plot-chart-window-object :: PlotChartWindowObject = default-chart-window-object.{
1499
1511
  x-axis: '',
1500
1512
  y-axis: '',
1513
+ x-axis-type: at-linear,
1514
+ y-axis-type: at-linear,
1501
1515
  show-grid-lines: true,
1502
1516
  show-minor-grid-lines: false,
1503
1517
  x-min: none,
@@ -1535,7 +1549,7 @@ data DataSeries:
1535
1549
  image-labels: image-labels-method,
1536
1550
  method point-size(self, point-size :: Number) block:
1537
1551
  when point-size < 0:
1538
- raise("point-size: Point Size must be non-negative")
1552
+ raise(ERR.message-exception("point-size: Point Size must be non-negative"))
1539
1553
  end
1540
1554
  self.constr()(self.obj.{point-size: point-size})
1541
1555
  end,
@@ -1557,7 +1571,7 @@ data DataSeries:
1557
1571
  point-shape: pointshape-method,
1558
1572
  method point-size(self, point-size :: Number) block:
1559
1573
  when point-size <= 0:
1560
- raise("point-size: Point Size must be positive")
1574
+ raise(ERR.message-exception("point-size: Point Size must be positive"))
1561
1575
  end
1562
1576
  self.constr()(self.obj.{point-size: point-size})
1563
1577
  end,
@@ -1574,7 +1588,7 @@ data DataSeries:
1574
1588
  labels: labels-method,
1575
1589
  method point-size(self, point-size :: Number) block:
1576
1590
  when point-size < 0:
1577
- raise("point-size: Point Size must be non-negative")
1591
+ raise(ERR.message-exception("point-size: Point Size must be non-negative"))
1578
1592
  end
1579
1593
  self.constr()(self.obj.{point-size: point-size})
1580
1594
  end,
@@ -1608,6 +1622,7 @@ data DataSeries:
1608
1622
  is-single: true,
1609
1623
  color: color-method,
1610
1624
  colors: color-list-method,
1625
+ bandwidth: bandwidth-method,
1611
1626
  sort: default-sort-method,
1612
1627
  sort-by: sort-method,
1613
1628
  sort-by-label: label-sort-method,
@@ -1640,7 +1655,7 @@ data DataSeries:
1640
1655
  trendline-type: trendline-type-method,
1641
1656
  method point-size(self, point-size :: Number) block:
1642
1657
  when point-size < 0:
1643
- raise("point-size: Point Size must be non-negative")
1658
+ raise(ERR.message-exception("point-size: Point Size must be non-negative"))
1644
1659
  end
1645
1660
  self.constr()(self.obj.{point-size: point-size})
1646
1661
  end,
@@ -1653,6 +1668,7 @@ data DataSeries:
1653
1668
  | multi-bar-chart-series(obj :: MultiBarChartSeries) with:
1654
1669
  is-single: true,
1655
1670
  colors: color-list-method,
1671
+ bandwidth: bandwidth-method,
1656
1672
  sort: super-default-multi-sort-method,
1657
1673
  sort-by: default-multi-sort-method,
1658
1674
  sort-by-data: multi-sort-method,
@@ -1710,7 +1726,7 @@ end
1710
1726
 
1711
1727
  fun check-chart-window(p :: ChartWindowObject) -> Nothing:
1712
1728
  if (p.width <= 0) or (p.height <= 0):
1713
- raise('render: width and height must be positive')
1729
+ raise(ERR.message-exception('render: width and height must be positive'))
1714
1730
  else:
1715
1731
  nothing
1716
1732
  end
@@ -1723,28 +1739,40 @@ data ChartWindow:
1723
1739
  constr: {(): dot-chart-window},
1724
1740
  x-axis: x-axis-method,
1725
1741
  y-axis: y-axis-method,
1742
+ x-min: x-min-method,
1743
+ x-max: x-max-method,
1744
+ x-axis-type: x-axis-type-method,
1745
+ y-axis-type: y-axis-type-method,
1726
1746
  | box-plot-chart-window(obj :: BoxChartWindowObject) with:
1727
1747
  constr: {(): box-plot-chart-window},
1728
1748
  x-axis: x-axis-method,
1729
1749
  y-axis: y-axis-method,
1750
+ x-axis-type: x-axis-type-method,
1751
+ y-axis-type: y-axis-type-method,
1730
1752
  min: min-method,
1731
1753
  max: max-method,
1732
1754
  | bar-chart-window(obj :: BarChartWindowObject) with:
1733
1755
  constr: {(): bar-chart-window},
1734
1756
  x-axis: x-axis-method,
1735
1757
  y-axis: y-axis-method,
1758
+ x-axis-type: x-axis-type-method,
1759
+ y-axis-type: y-axis-type-method,
1736
1760
  y-min: y-min-method,
1737
1761
  y-max: y-max-method,
1738
1762
  | interval-chart-window(obj :: IntervalChartWindowObject) with:
1739
1763
  constr: {(): interval-chart-window},
1740
1764
  x-axis: x-axis-method,
1741
1765
  y-axis: y-axis-method,
1766
+ x-axis-type: x-axis-type-method,
1767
+ y-axis-type: y-axis-type-method,
1742
1768
  y-min: y-min-method,
1743
1769
  y-max: y-max-method,
1744
1770
  | histogram-chart-window(obj :: HistogramChartWindowObject) with:
1745
1771
  constr: {(): histogram-chart-window},
1746
1772
  x-axis: x-axis-method,
1747
1773
  y-axis: y-axis-method,
1774
+ x-axis-type: x-axis-type-method,
1775
+ y-axis-type: y-axis-type-method,
1748
1776
  x-min: x-min-method,
1749
1777
  x-max: x-max-method,
1750
1778
  y-max: y-max-method,
@@ -1758,6 +1786,8 @@ data ChartWindow:
1758
1786
  # minor-gridlines-minspacing: minor-gridlines-min-spacing-method,
1759
1787
  x-axis: x-axis-method,
1760
1788
  y-axis: y-axis-method,
1789
+ x-axis-type: x-axis-type-method,
1790
+ y-axis-type: y-axis-type-method,
1761
1791
  x-min: x-min-method,
1762
1792
  x-max: x-max-method,
1763
1793
  y-min: y-min-method,
@@ -1765,7 +1795,7 @@ data ChartWindow:
1765
1795
  select-multiple: select-multiple-method,
1766
1796
  method num-samples(self, num-samples :: Number) block:
1767
1797
  when (num-samples <= 0) or (num-samples > 100000) or not(num-is-integer(num-samples)):
1768
- raise('num-samples: value must be an ineger between 1 and 100000')
1798
+ raise(ERR.message-exception('num-samples: value must be an ineger between 1 and 100000'))
1769
1799
  end
1770
1800
  plot-chart-window(self.obj.{num-samples: num-samples})
1771
1801
  end,
@@ -1781,6 +1811,10 @@ sharing:
1781
1811
  _ = check-chart-window(self.obj)
1782
1812
  self.obj.{interact: false}.render()
1783
1813
  end,
1814
+ method get-spec(self):
1815
+ _ = check-chart-window(self.obj)
1816
+ self.obj.{interact: false, vega: true}.render()
1817
+ end,
1784
1818
  method title(self, title :: String):
1785
1819
  self.constr()(self.obj.{title: title})
1786
1820
  end,
@@ -1807,7 +1841,7 @@ end
1807
1841
 
1808
1842
  fun line-plot-from-list(xs :: CL.LoN, ys :: CL.LoN) -> DataSeries block:
1809
1843
  when xs.length() <> ys.length():
1810
- raise('line-plot: xs and ys should have the same length')
1844
+ raise(ERR.message-exception('line-plot: xs and ys should have the same length'))
1811
1845
  end
1812
1846
  xs.each(check-num)
1813
1847
  ys.each(check-num)
@@ -1818,10 +1852,10 @@ end
1818
1852
 
1819
1853
  fun labeled-line-plot-from-list(labels :: CL.LoS, xs :: CL.LoN, ys :: CL.LoN) -> DataSeries block:
1820
1854
  when xs.length() <> ys.length():
1821
- raise('labeled-line-plot: xs and ys should have the same length')
1855
+ raise(ERR.message-exception('labeled-line-plot: xs and ys should have the same length'))
1822
1856
  end
1823
1857
  when xs.length() <> labels.length():
1824
- raise('labeled-line-plot: xs and labels should have the same length')
1858
+ raise(ERR.message-exception('labeled-line-plot: xs and labels should have the same length'))
1825
1859
  end
1826
1860
  xs.each(check-num)
1827
1861
  ys.each(check-num)
@@ -1832,10 +1866,10 @@ end
1832
1866
 
1833
1867
  fun image-line-plot-from-list(images :: CL.LoI, xs :: CL.LoN, ys :: CL.LoN) -> DataSeries block:
1834
1868
  when xs.length() <> ys.length():
1835
- raise('image-line-plot: xs and ys should have the same length')
1869
+ raise(ERR.message-exception('image-line-plot: xs and ys should have the same length'))
1836
1870
  end
1837
1871
  when xs.length() <> images.length():
1838
- raise('image-line-plot: xs and images should have the same length')
1872
+ raise(ERR.message-exception('image-line-plot: xs and images should have the same length'))
1839
1873
  end
1840
1874
  xs.each(check-num)
1841
1875
  ys.each(check-num)
@@ -1849,7 +1883,7 @@ fun get-scatter-point(x :: Number, y :: Number, label :: String, optimg :: Optio
1849
1883
  end
1850
1884
  fun scatter-plot-from-list(xs :: CL.LoN, ys :: CL.LoN) -> DataSeries block:
1851
1885
  when xs.length() <> ys.length():
1852
- raise('scatter-plot: xs and ys should have the same length')
1886
+ raise(ERR.message-exception('scatter-plot: xs and ys should have the same length'))
1853
1887
  end
1854
1888
  xs.each(check-num)
1855
1889
  ys.each(check-num)
@@ -1863,10 +1897,10 @@ fun labeled-scatter-plot-from-list(
1863
1897
  xs :: CL.LoN,
1864
1898
  ys :: CL.LoN) -> DataSeries block:
1865
1899
  when xs.length() <> ys.length():
1866
- raise('labeled-scatter-plot: xs and ys should have the same length')
1900
+ raise(ERR.message-exception('labeled-scatter-plot: xs and ys should have the same length'))
1867
1901
  end
1868
1902
  when xs.length() <> labels.length():
1869
- raise('labeled-scatter-plot: xs and labels should have the same length')
1903
+ raise(ERR.message-exception('labeled-scatter-plot: xs and labels should have the same length'))
1870
1904
  end
1871
1905
  xs.each(check-num)
1872
1906
  ys.each(check-num)
@@ -1881,10 +1915,10 @@ fun image-scatter-plot-from-list(
1881
1915
  xs :: CL.LoN,
1882
1916
  ys :: CL.LoN) -> DataSeries block:
1883
1917
  when xs.length() <> ys.length():
1884
- raise('labeled-scatter-plot: xs and ys should have the same length')
1918
+ raise(ERR.message-exception('labeled-scatter-plot: xs and ys should have the same length'))
1885
1919
  end
1886
1920
  when xs.length() <> images.length():
1887
- raise('labeled-scatter-plot: xs and images should have the same length')
1921
+ raise(ERR.message-exception('labeled-scatter-plot: xs and images should have the same length'))
1888
1922
  end
1889
1923
  xs.each(check-num)
1890
1924
  ys.each(check-num)
@@ -1917,10 +1951,10 @@ fun image-bar-chart-from-list(
1917
1951
 
1918
1952
  # Edge Case Error Checking
1919
1953
  when value-length == 0:
1920
- raise("bar-chart: can't have empty data")
1954
+ raise(ERR.message-exception("bar-chart: can't have empty data"))
1921
1955
  end
1922
1956
  when label-length <> value-length:
1923
- raise('bar-chart: labels and values should have the same length')
1957
+ raise(ERR.message-exception('bar-chart: labels and values should have the same length'))
1924
1958
  end
1925
1959
 
1926
1960
  {max-positive-height; max-negative-height} = prep-axis(rational-values)
@@ -1946,22 +1980,22 @@ fun exploding-pie-chart-from-list(
1946
1980
  value-length = values.length()
1947
1981
  for each(value from values):
1948
1982
  when value < 0:
1949
- raise('exploding-pie-chart: values must be non-negative')
1983
+ raise(ERR.message-exception('exploding-pie-chart: values must be non-negative'))
1950
1984
  end
1951
1985
  end
1952
1986
  when label-length <> value-length:
1953
- raise('exploding-pie-chart: labels and values should have the same length')
1987
+ raise(ERR.message-exception('exploding-pie-chart: labels and values should have the same length'))
1954
1988
  end
1955
1989
  offset-length = offsets.length()
1956
1990
  when label-length <> offset-length:
1957
- raise('exploding-pie-chart: labels and offsets should have the same length')
1991
+ raise(ERR.message-exception('exploding-pie-chart: labels and offsets should have the same length'))
1958
1992
  end
1959
1993
  when label-length == 0:
1960
- raise('exploding-pie-chart: need at least one data')
1994
+ raise(ERR.message-exception('exploding-pie-chart: need at least one data'))
1961
1995
  end
1962
1996
  for each(offset from offsets):
1963
1997
  when (offset < 0) or (offset > 1):
1964
- raise('exploding-pie-chart: offset must be between 0 and 1')
1998
+ raise(ERR.message-exception('exploding-pie-chart: offset must be between 0 and 1'))
1965
1999
  end
1966
2000
  end
1967
2001
  values.each(check-num)
@@ -1981,14 +2015,14 @@ fun pie-chart-from-list(labels :: CL.LoS, values :: CL.LoN) -> DataSeries block:
1981
2015
  value-length = values.length()
1982
2016
  for each(value from values):
1983
2017
  when value < 0:
1984
- raise('pie-chart: values must be non-negative')
2018
+ raise(ERR.message-exception('pie-chart: values must be non-negative'))
1985
2019
  end
1986
2020
  end
1987
2021
  when label-length <> value-length:
1988
- raise('pie-chart: labels and values should have the same length')
2022
+ raise(ERR.message-exception('pie-chart: labels and values should have the same length'))
1989
2023
  end
1990
2024
  when label-length == 0:
1991
- raise('pie-chart: need at least one data')
2025
+ raise(ERR.message-exception('pie-chart: need at least one data'))
1992
2026
  end
1993
2027
  values.each(check-num)
1994
2028
  labels.each(check-string)
@@ -2006,14 +2040,14 @@ fun image-pie-chart-from-list(images :: CL.LoI, labels :: CL.LoS, values :: CL.L
2006
2040
  value-length = values.length()
2007
2041
  for each(value from values):
2008
2042
  when value < 0:
2009
- raise('pie-chart: values must be non-negative')
2043
+ raise(ERR.message-exception('pie-chart: values must be non-negative'))
2010
2044
  end
2011
2045
  end
2012
2046
  when label-length <> value-length:
2013
- raise('pie-chart: labels and values should have the same length')
2047
+ raise(ERR.message-exception('pie-chart: labels and values should have the same length'))
2014
2048
  end
2015
2049
  when label-length == 0:
2016
- raise('pie-chart: need at least one data')
2050
+ raise(ERR.message-exception('pie-chart: need at least one data'))
2017
2051
  end
2018
2052
  images.each(check-image)
2019
2053
  values.each(check-num)
@@ -2039,10 +2073,10 @@ fun bar-chart-from-list(labels :: CL.LoS, values :: CL.LoN) -> DataSeries block:
2039
2073
 
2040
2074
  # Edge Case Error Checking
2041
2075
  when value-length == 0:
2042
- raise("bar-chart: can't have empty data")
2076
+ raise(ERR.message-exception("bar-chart: can't have empty data"))
2043
2077
  end
2044
2078
  when label-length <> value-length:
2045
- raise('bar-chart: labels and values should have the same length')
2079
+ raise(ERR.message-exception('bar-chart: labels and values should have the same length'))
2046
2080
  end
2047
2081
 
2048
2082
  {max-positive-height; max-negative-height} = prep-axis(rational-values)
@@ -2068,7 +2102,7 @@ fun num-dot-chart-from-list(x-values :: CL.LoN) -> DataSeries block:
2068
2102
  ```
2069
2103
  x-values.each(check-num)
2070
2104
  when x-values.length() == 0:
2071
- raise("num-dot-chart: can't have empty data")
2105
+ raise(ERR.message-exception("num-dot-chart: can't have empty data"))
2072
2106
  end
2073
2107
  default-dot-plot-series.{
2074
2108
  ps: map3(get-dot-point, x-values, x-values.map({(_): ''}), x-values.map({(_): none})),
@@ -2082,11 +2116,11 @@ fun image-num-dot-chart-from-list(images :: CL.LoI, x-values :: CL.LoN) -> DataS
2082
2116
  ```
2083
2117
  x-values.each(check-num)
2084
2118
  when x-values.length() == 0:
2085
- raise("num-dot-chart: can't have empty data")
2119
+ raise(ERR.message-exception("num-dot-chart: can't have empty data"))
2086
2120
  end
2087
2121
  images.each(check-image)
2088
2122
  when images.length() <> x-values.length():
2089
- raise("num-dot-chart: the lists of numbers and images must have the same length")
2123
+ raise(ERR.message-exception("num-dot-chart: the lists of numbers and images must have the same length"))
2090
2124
  end
2091
2125
  default-dot-plot-series.{
2092
2126
  ps: map3(get-dot-point, x-values, x-values.map({(_): ''}), images.map(some)),
@@ -2100,11 +2134,11 @@ fun labeled-num-dot-chart-from-list(labels :: CL.LoS, x-values :: CL.LoN) -> Dat
2100
2134
  ```
2101
2135
  x-values.each(check-num)
2102
2136
  when x-values.length() == 0:
2103
- raise("num-dot-chart: can't have empty data")
2137
+ raise(ERR.message-exception("num-dot-chart: can't have empty data"))
2104
2138
  end
2105
2139
  labels.each(check-string)
2106
2140
  when labels.length() <> x-values.length():
2107
- raise("num-dot-chart: the lists of numbers and labels must have the same length")
2141
+ raise(ERR.message-exception("num-dot-chart: the lists of numbers and labels must have the same length"))
2108
2142
  end
2109
2143
  default-dot-plot-series.{
2110
2144
  ps: map3(get-dot-point, x-values, labels, x-values.map({(_): none})),
@@ -2118,7 +2152,7 @@ fun dot-chart-from-list(input-labels :: CL.LoS) -> DataSeries block:
2118
2152
 
2119
2153
  # Edge Case Error Checking
2120
2154
  when input-labels.length() == 0:
2121
- raise("dot-chart: can't have empty data")
2155
+ raise(ERR.message-exception("dot-chart: can't have empty data"))
2122
2156
  end
2123
2157
 
2124
2158
  # Type Checking
@@ -2153,16 +2187,16 @@ fun grouped-bar-chart-from-list(
2153
2187
 
2154
2188
  # Edge Case Error Checking
2155
2189
  when value-length == 0:
2156
- raise("grouped-bar-chart: can't have empty data")
2190
+ raise(ERR.message-exception("grouped-bar-chart: can't have empty data"))
2157
2191
  end
2158
2192
  when legend-length == 0:
2159
- raise("grouped-bar-chart: can't have empty legends")
2193
+ raise(ERR.message-exception("grouped-bar-chart: can't have empty legends"))
2160
2194
  end
2161
2195
  when label-length <> value-length:
2162
- raise('grouped-bar-chart: labels and values should have the same length')
2196
+ raise(ERR.message-exception('grouped-bar-chart: labels and values should have the same length'))
2163
2197
  end
2164
2198
  when any({(group): legend-length <> group.length()}, value-lists):
2165
- raise('grouped-bar-chart: labels and legends should have the same length')
2199
+ raise(ERR.message-exception('grouped-bar-chart: labels and legends should have the same length'))
2166
2200
  end
2167
2201
 
2168
2202
  # Typechecking each input
@@ -2202,16 +2236,16 @@ fun stacked-bar-chart-from-list(
2202
2236
 
2203
2237
  # Edge Case Error Checking
2204
2238
  when value-length == 0:
2205
- raise("stacked-bar-chart: can't have empty data")
2239
+ raise(ERR.message-exception("stacked-bar-chart: can't have empty data"))
2206
2240
  end
2207
2241
  when legend-length == 0:
2208
- raise("stacked-bar-chart: can't have empty legends")
2242
+ raise(ERR.message-exception("stacked-bar-chart: can't have empty legends"))
2209
2243
  end
2210
2244
  when label-length <> value-length:
2211
- raise('stacked-bar-chart: labels and values should have the same length')
2245
+ raise(ERR.message-exception('stacked-bar-chart: labels and values should have the same length'))
2212
2246
  end
2213
2247
  when any({(stack): legend-length <> stack.length()}, value-lists):
2214
- raise('stacked-bar-chart: labels and legends should have the same length')
2248
+ raise(ERR.message-exception('stacked-bar-chart: labels and legends should have the same length'))
2215
2249
  end
2216
2250
 
2217
2251
  # Typechecking the input
@@ -2262,16 +2296,16 @@ fun labeled-interval-chart-from-list(
2262
2296
  ys-length = ys.length()
2263
2297
  deltas-length = deltas.length()
2264
2298
  when xs-length <> ys-length:
2265
- raise('interval-chart: xs and ys should have the same length')
2299
+ raise(ERR.message-exception('interval-chart: xs and ys should have the same length'))
2266
2300
  end
2267
2301
  when xs-length <> deltas-length:
2268
- raise('interval-chart: deltas should have the same length as xs and ys')
2302
+ raise(ERR.message-exception('interval-chart: deltas should have the same length as xs and ys'))
2269
2303
  end
2270
2304
  when xs-length <> labels-length:
2271
- raise('interval-chart: labels should have the same length as xs and ys')
2305
+ raise(ERR.message-exception('interval-chart: labels should have the same length as xs and ys'))
2272
2306
  end
2273
2307
  when xs-length == 0:
2274
- raise('interval-chart: need at least one datum')
2308
+ raise(ERR.message-exception('interval-chart: need at least one datum'))
2275
2309
  end
2276
2310
  xs.each(check-num)
2277
2311
  ys.each(check-num)
@@ -2303,16 +2337,16 @@ fun labeled-box-plot-from-list(
2303
2337
  label-length = labels.length()
2304
2338
  value-length = values.length()
2305
2339
  when label-length <> value-length:
2306
- raise('labeled-box-plot: labels and values should have the same length')
2340
+ raise(ERR.message-exception('labeled-box-plot: labels and values should have the same length'))
2307
2341
  end
2308
2342
  when label-length == 0:
2309
- raise('labeled-box-plot: expect at least one box')
2343
+ raise(ERR.message-exception('labeled-box-plot: expect at least one box'))
2310
2344
  end
2311
2345
  values.each(_.each(check-num))
2312
2346
  values.each(
2313
2347
  lam(lst):
2314
2348
  when lst.length() <= 1:
2315
- raise('labeled-box-plot: the list length should be at least 2')
2349
+ raise(ERR.message-exception('labeled-box-plot: the list length should be at least 2'))
2316
2350
  end
2317
2351
  end)
2318
2352
  labels.each(check-string)
@@ -2363,7 +2397,7 @@ fun labeled-histogram-from-list(labels :: CL.LoS, values :: CL.LoN) -> DataSerie
2363
2397
  label-length = labels.length()
2364
2398
  value-length = values.length()
2365
2399
  when label-length <> value-length:
2366
- raise('labeled-histogram: labels and values should have the same length')
2400
+ raise(ERR.message-exception('labeled-histogram: labels and values should have the same length'))
2367
2401
  end
2368
2402
  values.each(check-num)
2369
2403
  labels.each(check-string)
@@ -2396,7 +2430,7 @@ fun check-render-x-axis(self) -> Nothing:
2396
2430
  cases (Option) self.x-max:
2397
2431
  | some(x-max) =>
2398
2432
  if x-min >= x-max:
2399
- raise("render: x-min must be strictly less than x-max")
2433
+ raise(ERR.message-exception("render: x-min must be strictly less than x-max"))
2400
2434
  else:
2401
2435
  nothing
2402
2436
  end
@@ -2412,7 +2446,7 @@ fun check-render-y-axis(self) -> Nothing:
2412
2446
  cases (Option) self.y-max:
2413
2447
  | some(y-max) =>
2414
2448
  if y-min >= y-max:
2415
- raise("render: y-min must be strictly less than y-max")
2449
+ raise(ERR.message-exception("render: y-min must be strictly less than y-max"))
2416
2450
  else:
2417
2451
  nothing
2418
2452
  end
@@ -2422,6 +2456,16 @@ fun check-render-y-axis(self) -> Nothing:
2422
2456
  end
2423
2457
  end
2424
2458
 
2459
+ fun check-data-range(min, max, vals) -> Nothing:
2460
+ fun too-small(v): v.value < min.or-else(v.value) end
2461
+ fun too-big(v): v.value > max.or-else(v.value) end
2462
+ if vals.any(too-small) or vals.any(too-big):
2463
+ raise(ERR.message-exception("render: All values must be between specified x-min and x-max bounds"))
2464
+ else:
2465
+ nothing
2466
+ end
2467
+ end
2468
+
2425
2469
  fun render-chart(s :: DataSeries) -> ChartWindow:
2426
2470
  doc: 'Render it!'
2427
2471
  cases (DataSeries) s:
@@ -2432,6 +2476,8 @@ fun render-chart(s :: DataSeries) -> ChartWindow:
2432
2476
  | dot-plot-series(obj) =>
2433
2477
  default-dot-chart-window-object.{
2434
2478
  method render(self) block:
2479
+ _ = check-render-x-axis(self)
2480
+ _ = check-data-range(self.x-min, self.x-max, obj.ps)
2435
2481
  CL.dot-chart(self, obj)
2436
2482
  end
2437
2483
  } ^ dot-chart-window
@@ -2740,18 +2786,18 @@ end
2740
2786
  fun render-charts(lst :: List<DataSeries>) -> ChartWindow block:
2741
2787
  doc: "Draw 'em all"
2742
2788
  cases (Option) find(_.is-single, lst):
2743
- | some(v) => raise(
2744
- [sprintf: "render-charts: can't draw ", v,
2745
- " with `render-charts`. Use `render-chart` instead."])
2789
+ | some(v) => raise(ERR.message-exception(
2790
+ [sprintf: "render-charts: can't draw ", v,
2791
+ " with `render-charts`. Use `render-chart` instead."]))
2746
2792
  | else => nothing
2747
2793
  end
2748
2794
  cases (List<DataSeries>) lst:
2749
- | empty => raise('render-charts: need at least one series to plot')
2795
+ | empty => raise(ERR.message-exception('render-charts: need at least one series to plot'))
2750
2796
  | else => nothing
2751
2797
  end
2752
2798
  when lst.any({(ds): ds.obj.horizontal}) and not(lst.all({(ds): ds.obj.horizontal})):
2753
- raise("render-charts: Cannot render a mix of horizontal and vertical charts. "
2754
- + "Make sure all charts have the same direction.")
2799
+ raise(ERR.message-exception("render-charts: Cannot render a mix of horizontal and vertical charts. "
2800
+ + "Make sure all charts have the same direction."))
2755
2801
  end
2756
2802
 
2757
2803
  partitioned = partition(is-function-plot-series, lst)
@@ -2801,51 +2847,24 @@ fun render-charts(lst :: List<DataSeries>) -> ChartWindow block:
2801
2847
 
2802
2848
  # shadow self = self.{y-min: y-min, y-max: y-max}
2803
2849
 
2804
- fun helper(shadow self, shadow function-plots-data :: Option) -> IM.Image:
2805
- shadow function-plots-data = cases (Option) function-plots-data:
2806
- | none => function-plots
2807
- .map(generate-xy(_, self.x-min.value, self.x-max.value, self.num-samples))
2808
- | some(shadow function-plots-data) => function-plots-data
2809
- end
2810
-
2811
- # scatters-arr = for map(p from scatter-plots + function-plots-data):
2812
- # ps-to-arr(p.{ps: p.ps.filter(in-bound-xy(_, self))})
2813
- # end ^ reverse ^ builtins.raw-array-from-list
2814
-
2815
- # lines-arr = for map(p from line-plots):
2816
- # ps-to-arr(p.{ps: line-plot-edge-cut(p.ps, self)})
2817
- # end ^ reverse ^ builtins.raw-array-from-list
2818
-
2819
- # intervals-arr = for map(p from interval-plots):
2820
- # ps-to-arr(p.{ps: p.ps.filter(in-bound-xy(_, self))})
2821
- # end ^ reverse ^ builtins.raw-array-from-list
2822
-
2823
- ret = CL.plot(self, {
2850
+ CL.plot(self, {
2824
2851
  scatters: builtins.raw-array-from-list(scatter-plots),
2825
2852
  lines: builtins.raw-array-from-list(line-plots),
2826
2853
  intervals: builtins.raw-array-from-list(interval-plots),
2827
2854
  functions: builtins.raw-array-from-list(function-plots)
2828
2855
  })
2829
- # NOTE(Ben): THIS IS A POLYFILL FOR NOW,
2830
- # until I remove the Either callback hooking mechanism altogether.
2831
- if (IM.is-image(ret)): ret
2832
- else:
2833
- cases (E.Either<Any, IM.Image>) ret:
2834
- | left(new-self) => helper(new-self, none)
2835
- | right(image) => image
2836
- end
2837
- end
2838
- end
2839
- helper(self, some(empty))
2840
2856
  end
2841
2857
  } ^ plot-chart-window
2842
2858
  where:
2843
- p1 = from-list.function-plot(lam(x): x * x end).color(I.red)
2844
- p2 = from-list.line-plot([list: 1, 2, 3, 4], [list: 1, 4, 9, 16]).color(I.green)
2859
+ xs = [list: 1, 3, 5, 8, 20]
2860
+ ys = [list: 5, 3, 8, 2, 10]
2861
+ p1 = from-list.function-plot(lam(x): x * x end).color(IS.red)
2862
+ p2 = from-list.line-plot([list: 1, 2, 3, 4], [list: 1, 4, 9, 16]).color(IS.green)
2845
2863
  p3 = from-list.histogram([list: 1, 2, 3, 4])
2846
2864
  p4 = from-list.line-plot(
2847
2865
  [list: -1, 1, 2, 3, 11, 8, 9],
2848
2866
  [list: 10, -1, 11, 9, 9, 3, 2])
2867
+ p5 = from-list.scatter-plot(ys, xs)
2849
2868
  render-charts([list: p1, p2, p3]) raises ''
2850
2869
  render-charts([list: p1, p2])
2851
2870
  .title('quadratic function and a scatter plot')
@@ -2860,6 +2879,12 @@ where:
2860
2879
  .y-min(0)
2861
2880
  .y-max(10)
2862
2881
  .get-image() does-not-raise
2882
+ render-chart(p5) does-not-raise
2883
+ img = render-chart(p5).get-image()
2884
+ img satisfies I.is-image
2885
+ I.image-pinhole-x(img) is I.image-width(img) / 2
2886
+ I.image-pinhole-y(img) is I.image-height(img) / 2
2887
+ render-chart(p5).get-spec() satisfies is-string
2863
2888
  end
2864
2889
 
2865
2890
  from-list = {