gr-plot 0.0.1 → 0.0.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.
data/lib/gr/plot.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'gr'
4
+ require_relative 'plot/version'
4
5
  autoload :GR3, 'gr3'
5
6
 
6
7
  # FIXME: Plot should not depend on Numo::Narrray unless the GR3 module is required.
7
8
  # Note: The Plot class has a linspace function that is independent of Numo..
8
9
  require 'numo/narray'
10
+ require_relative 'plot/histogram'
9
11
 
10
12
  module GR
11
13
  # This class offers a simple, matlab-style API built on top of the GR package.
@@ -31,19 +33,106 @@ module GR
31
33
  # https://github.com/SciRuby/rubyplot
32
34
 
33
35
  # Plot kinds conform to GR.jl
34
- PLOT_KIND = %i[line step scatter stem hist contour contourf hexbin heatmap
35
- nonuniformheatmap wireframe surface plot3 scatter3 imshow
36
- isosurface polar polarhist polarheatmap nonuniformpolarheatmap
37
- trisurf tricont shade volume].freeze
36
+ PLOT_KIND = %i[
37
+ line
38
+ step
39
+ stairs
40
+ scatter
41
+ stem
42
+ bar
43
+ hist
44
+ contour
45
+ contourf
46
+ hexbin
47
+ heatmap
48
+ nonuniformheatmap
49
+ wireframe
50
+ surface
51
+ plot3
52
+ scatter3
53
+ imshow
54
+ isosurface
55
+ polar
56
+ polarhist
57
+ polarheatmap
58
+ nonuniformpolarheatmap
59
+ trisurf
60
+ tricont
61
+ shade
62
+ volume
63
+ ].freeze
38
64
 
39
65
  # Keyword options conform to GR.jl.
40
- KW_ARGS = %i[accelerate algorithm alpha ax backgroundcolor barwidth baseline
41
- clabels clear clim color colormap crange figsize font grid
42
- horizontal isovalue kind label labels levels linewidth location
43
- nbins ratio rotation scale size spec subplot tilt title update
44
- xaxis xflip xform xlabel xlim xlog xrange xticks yaxis yflip
45
- ylabel ylim ylog zflip yrange yticks viewport vp where window
46
- zaxis zlabel zlim zlog zrange zticks].freeze
66
+ KW_ARGS = %i[
67
+ accelerate
68
+ algorithm
69
+ alpha
70
+ ax
71
+ backgroundcolor
72
+ barwidth
73
+ baseline
74
+ borderwidth
75
+ clabels
76
+ clines
77
+ clear
78
+ clim
79
+ color
80
+ colormap
81
+ crange
82
+ dpi
83
+ figsize
84
+ font
85
+ grid
86
+ horizontal
87
+ isovalue
88
+ keepaspect
89
+ kind
90
+ label
91
+ labels
92
+ levels
93
+ linewidth
94
+ location
95
+ markersize
96
+ nbins
97
+ panzoom
98
+ ratio
99
+ rotation
100
+ scale
101
+ size
102
+ spec
103
+ subplot
104
+ theta_direction
105
+ theta_zero_location
106
+ tilt
107
+ title
108
+ update
109
+ viewport
110
+ vp
111
+ where
112
+ window
113
+ xaxis
114
+ xflip
115
+ xform
116
+ xlabel
117
+ xlim
118
+ xlog
119
+ xrange
120
+ xticks
121
+ yaxis
122
+ yflip
123
+ ylabel
124
+ ylim
125
+ ylog
126
+ yrange
127
+ yticks
128
+ zaxis
129
+ zflip
130
+ zlabel
131
+ zlim
132
+ zlog
133
+ zrange
134
+ zticks
135
+ ].freeze
47
136
 
48
137
  FONTS = {
49
138
  times_roman: 101,
@@ -78,12 +167,37 @@ module GR
78
167
  zapfchancery_mediumitalic: 130,
79
168
  zapfdingbats: 131,
80
169
  cmuserif_math: 232, # original: cmuserif-math
81
- dejavusans: 233
170
+ dejavusans: 233,
171
+ stix_two_math: 234
82
172
  }.freeze
83
173
 
174
+ THETA_ZERO_LOCATION = {
175
+ 'E' => 0,
176
+ 'N' => Math::PI / 2,
177
+ 'W' => Math::PI,
178
+ 'S' => 1.5 * Math::PI
179
+ }.freeze
180
+
181
+ COLORS = [
182
+ [0xffffff, 0x000000, 0xff0000, 0x00ff00, 0x0000ff, 0x00ffff, 0xffff00, 0xff00ff],
183
+ [0x282c34, 0xd7dae0, 0xcb4e42, 0x99c27c, 0x85a9fc, 0x5ab6c1, 0xd09a6a, 0xc57bdb],
184
+ [0xfdf6e3, 0x657b83, 0xdc322f, 0x859900, 0x268bd2, 0x2aa198, 0xb58900, 0xd33682],
185
+ [0x002b36, 0x839496, 0xdc322f, 0x859900, 0x268bd2, 0x2aa198, 0xb58900, 0xd33682]
186
+ ].freeze
187
+
188
+ DISTINCT_CMAP = [0, 1, 984, 987, 989, 983, 994, 988].freeze
189
+
84
190
  @last_plot = nil
191
+ @scheme = 0
192
+
85
193
  class << self
86
- attr_accessor :last_plot
194
+ attr_accessor :last_plot, :scheme
195
+
196
+ def usecolorscheme(index)
197
+ raise 'Invalid color scheme' unless index >= 1 && index <= 4
198
+
199
+ @scheme = index
200
+ end
87
201
  end
88
202
 
89
203
  attr_accessor :args, :kvs, :scheme
@@ -106,7 +220,7 @@ module GR
106
220
  kvs[:clear] = true unless kvs.has_key? :clear
107
221
  kvs[:update] = true unless kvs.has_key? :update
108
222
 
109
- @scheme = 0
223
+ @scheme = self.class.scheme
110
224
  @background = 0xffffff
111
225
  # @handle = nil # This variable will be used in gr_meta
112
226
 
@@ -115,32 +229,38 @@ module GR
115
229
 
116
230
  def set_viewport(kind, subplot)
117
231
  mwidth, mheight, width, height = GR.inqdspsize
118
- dpi = width / mwidth * 0.0254
119
- w, h = if kvs[:figsize]
120
- [(0.0254 * width * kvs[:figsize][0] / mwidth),
121
- (0.0254 * height * kvs[:figsize][1] / mheight)]
122
- elsif dpi > 200
123
- kvs[:size].map { |i| i * dpi / 100 }
124
- else
125
- kvs[:size]
126
- end
127
-
128
- vp = subplot.clone
232
+ if kvs.has_key?(:figsize)
233
+ w, h = kvs[:figsize]
234
+ if w < 2 && h < 2
235
+ w = width * w / mwidth
236
+ h = height * h / mheight
237
+ end
238
+ else
239
+ dpi = kvs[:dpi]
240
+ dpi ||= (width / mwidth * 0.0254).round
241
+ if dpi > 200
242
+ w, h = kvs[:size].map { |i| i * dpi / 100.0 }
243
+ else
244
+ w, h = kvs[:size]
245
+ end
246
+ end
247
+
248
+ vp = subplot.clone.map(&:to_f)
129
249
 
130
250
  if w > h
131
- ratio = h / w.to_f
251
+ ratio = w / h.to_f
132
252
  msize = mwidth * w / width
133
- GR.setwsviewport(0, msize, 0, msize * ratio)
134
- GR.setwswindow(0, 1, 0, ratio)
135
- vp[2] *= ratio
136
- vp[3] *= ratio
253
+ GR.setwsviewport(0, msize, 0, msize / ratio)
254
+ GR.setwswindow(0, 1, 0, 1 / ratio)
255
+ vp[2] /= ratio
256
+ vp[3] /= ratio
137
257
  else
138
- ratio = w / h.to_f
258
+ ratio = h / w.to_f
139
259
  msize = mheight * h / height
140
- GR.setwsviewport(0, msize * ratio, 0, msize)
141
- GR.setwswindow(0, ratio, 0, 1)
142
- vp[0] *= ratio
143
- vp[1] *= ratio
260
+ GR.setwsviewport(0, msize / ratio, 0, msize)
261
+ GR.setwswindow(0, 1 / ratio, 0, 1)
262
+ vp[0] /= ratio
263
+ vp[1] /= ratio
144
264
  end
145
265
 
146
266
  if %i[wireframe surface plot3 scatter3 trisurf volume].include?(kind)
@@ -154,7 +274,7 @@ module GR
154
274
  end
155
275
 
156
276
  left_margin = kvs.has_key?(:ylabel) ? 0.05 : 0
157
- right_margin = if %i[contour contourf hexbin heatmap nonuniformheatmap polarheatmap
277
+ right_margin = if %i[contour contourf tricont hexbin heatmap nonuniformheatmap polarheatmap
158
278
  nonuniformpolarheatmap surface trisurf volume].include?(kind)
159
279
  (vp2 - vp1) * 0.1
160
280
  else
@@ -171,9 +291,24 @@ module GR
171
291
  if %i[line step scatter stem].include?(kind) && kvs[:labels]
172
292
  location = kvs[:location] || 1
173
293
  if [11, 12, 13].include?(location)
174
- w, h = legend_size
175
- viewport[1] -= w + 0.1
294
+ w_legend, _h_legend = legend_size
295
+ viewport[1] -= w_legend + 0.1
296
+ end
297
+ end
298
+
299
+ if %i[polar polarhist polarheatmap nonuniformpolarheatmap].include?(kind)
300
+ xmin, xmax, ymin, ymax = viewport
301
+ xcenter = 0.5 * (xmin + xmax)
302
+ ycenter = 0.5 * (ymin + ymax)
303
+ r = 0.45 * [xmax - xmin, ymax - ymin].min
304
+ if kvs.has_key?(:title)
305
+ r *= 0.975
306
+ ycenter -= 0.025 * r
176
307
  end
308
+ viewport[0] = xcenter - r
309
+ viewport[1] = xcenter + r
310
+ viewport[2] = ycenter - r
311
+ viewport[3] = ycenter + r
177
312
  end
178
313
 
179
314
  GR.setviewport(*viewport)
@@ -182,29 +317,21 @@ module GR
182
317
  kvs[:vp] = vp
183
318
  kvs[:ratio] = ratio
184
319
 
185
- if kvs[:backgroundcolor]
186
- GR.savestate
187
- GR.selntran(0)
188
- GR.setfillintstyle(GR::INTSTYLE_SOLID)
189
- GR.setfillcolorind(kvs[:backgroundcolor])
190
- if w > h
191
- GR.fillrect(subplot[0], subplot[1],
192
- ratio * subplot[2], ratio * subplot[3])
193
- else
194
- GR.fillrect(ratio * subplot[0], ratio * subplot[1],
195
- subplot[2], subplot[3])
196
- end
197
- GR.selntran(1)
198
- GR.restorestate
199
- end
320
+ return unless kvs[:backgroundcolor]
200
321
 
201
- if %i[polar polarhist polarheatmap nonuniformpolarheatmap].include? kind
202
- xmin, xmax, ymin, ymax = viewport
203
- xcenter = 0.5 * (xmin + xmax)
204
- ycenter = 0.5 * (ymin + ymax)
205
- r = 0.5 * [xmax - xmin, ymax - ymin].min
206
- GR.setviewport(xcenter - r, xcenter + r, ycenter - r, ycenter + r)
322
+ GR.savestate
323
+ GR.selntran(0)
324
+ GR.setfillintstyle(GR::INTSTYLE_SOLID)
325
+ GR.setfillcolorind(kvs[:backgroundcolor])
326
+ if w > h
327
+ GR.fillrect(subplot[0], subplot[1],
328
+ ratio * subplot[2], ratio * subplot[3])
329
+ else
330
+ GR.fillrect(ratio * subplot[0], ratio * subplot[1],
331
+ subplot[2], subplot[3])
207
332
  end
333
+ GR.selntran(1)
334
+ GR.restorestate
208
335
  end
209
336
 
210
337
  def set_window(kind)
@@ -239,14 +366,13 @@ module GR
239
366
  kvs[:zticks] = [kvs[:zticks], major_count] if kvs[:zticks].is_a? Numeric
240
367
 
241
368
  xmin, xmax = kvs[:xrange]
242
- if %i[heatmap polarheatmap].include?(kind) && kvs.has_key?(:xlim)
369
+ if kind == :heatmap && !kvs.has_key?(:xlim)
243
370
  xmin -= 0.5
244
371
  xmax += 0.5
245
372
  end
246
373
  xtick, majorx = if (scale & GR::OPTION_X_LOG) == 0
247
- if !%i[heatmap polarheatmap].include?(kind) &&
248
- !kvs.has_key?(:xlim) &&
249
- !kvs[:panzoom]
374
+ if !kvs.has_key?(:xlim) && !kvs[:panzoom] &&
375
+ !%i[heatmap polarheatmap nonuniformpolarheatmap].include?(kind)
250
376
  xmin, xmax = GR.adjustlimits(xmin, xmax)
251
377
  end
252
378
  if kvs.has_key?(:xticks)
@@ -261,21 +387,16 @@ module GR
261
387
  kvs[:xaxis] = xtick, xorg, majorx
262
388
 
263
389
  ymin, ymax = kvs[:yrange]
264
- if %i[heatmap polarheatmap].include?(kind) && kvs.has_key?(:ylim)
390
+ if kind == :heatmap && !kvs.has_key?(:ylim)
265
391
  ymin -= 0.5
266
392
  ymax += 0.5
267
393
  end
268
- if kind == :hist
269
- if kvs[:horizontal] && !kvs.has_key?(:xlim)
270
- xmin = (scale & GR::OPTION_X_LOG) == 0 ? 0 : 1
271
- elsif !kvs.has_key?(:ylim)
272
- ymin = (scale & GR::OPTION_Y_LOG) == 0 ? 0 : 1
273
- end
394
+ if kind == :hist && !kvs.has_key?(:ylim)
395
+ ymin = (scale & GR::OPTION_Y_LOG) == 0 ? 0 : 1
274
396
  end
275
397
  ytick, majory = if (scale & GR::OPTION_Y_LOG) == 0
276
- if !%i[heatmap polarheatmap].include?(kind) &&
277
- !kvs.has_key?(:ylim) &&
278
- !kvs[:panzoom]
398
+ if !kvs.has_key?(:ylim) && !kvs[:panzoom] &&
399
+ !%i[heatmap polarheatmap nonuniformpolarheatmap].include?(kind)
279
400
  ymin, ymax = GR.adjustlimits(ymin, ymax)
280
401
  end
281
402
  if kvs.has_key?(:yticks)
@@ -292,7 +413,7 @@ module GR
292
413
  if %i[wireframe surface plot3 scatter3 trisurf volume].include?(kind)
293
414
  zmin, zmax = kvs[:zrange]
294
415
  ztick, majorz = if (scale & GR::OPTION_Z_LOG) == 0
295
- zmin, zmax = GR.adjustlimits(zmin, zmax) if kvs.has_key?(:zlim)
416
+ zmin, zmax = GR.adjustlimits(zmin, zmax) unless kvs.has_key?(:zlim)
296
417
  if kvs.has_key?(:zticks)
297
418
  kvs[:zticks]
298
419
  else
@@ -306,10 +427,12 @@ module GR
306
427
  end
307
428
 
308
429
  kvs[:window] = xmin, xmax, ymin, ymax
309
- if %i[polar polarhist polarheatmap nonuniformpolarheatmap trisurf].include?(kind)
430
+ if %i[polar polarhist polarheatmap nonuniformpolarheatmap].include?(kind)
310
431
  GR.setwindow(-1, 1, -1, 1)
432
+ GR.setclipregion(GR::REGION_ELLIPSE)
311
433
  else
312
434
  GR.setwindow(xmin, xmax, ymin, ymax)
435
+ GR.setclipregion(GR::REGION_RECTANGLE)
313
436
  end
314
437
  if %i[wireframe surface plot3 scatter3 trisurf volume].include?(kind)
315
438
  rotation = kvs[:rotation] || 40
@@ -325,7 +448,6 @@ module GR
325
448
  def draw_axes(kind, pass = 1)
326
449
  viewport = kvs[:viewport]
327
450
  vp = kvs[:vp]
328
- _ratio = kvs[:ratio]
329
451
  xtick, xorg, majorx = kvs[:xaxis]
330
452
  ytick, yorg, majory = kvs[:yaxis]
331
453
  drawgrid = kvs.has_key?(:grid) ? kvs[:grid] : true
@@ -339,27 +461,61 @@ module GR
339
461
  charheight = [0.024 * diag, 0.012].max
340
462
  GR.setcharheight(charheight)
341
463
  ztick, zorg, majorz = kvs[:zaxis]
464
+ rotation = kvs[:rotation] || 40
465
+ tilt = kvs[:tilt] || 60
466
+ zi = tilt >= 0 && tilt <= 90 ? 0 : 1 # Julia: 1-based index -> Ruby: 0-based index
467
+ xlabel = (kvs[:xlabel] || '').to_s
468
+ ylabel = (kvs[:ylabel] || '').to_s
469
+ zlabel = (kvs[:zlabel] || '').to_s
470
+ GR.setcharheight(charheight * 1.5)
471
+ GR.settitles3d(xlabel, ylabel, zlabel)
472
+ GR.setcharheight(charheight)
342
473
  if pass == 1 && drawgrid
343
- GR.grid3d(xtick, 0, ztick, xorg[0], yorg[1], zorg[0], 2, 0, 2)
344
- GR.grid3d(0, ytick, 0, xorg[0], yorg[1], zorg[0], 0, 2, 0)
474
+ if rotation >= 0 && rotation < 90
475
+ GR.grid3d(xtick, 0, ztick, xorg[0], yorg[1], zorg[zi], 2, 0, 2)
476
+ GR.grid3d(0, ytick, 0, xorg[0], yorg[1], zorg[zi], 0, 2, 0)
477
+ elsif rotation >= 90 && rotation < 180
478
+ GR.grid3d(xtick, 0, ztick, xorg[1], yorg[1], zorg[zi], 2, 0, 2)
479
+ GR.grid3d(0, ytick, 0, xorg[1], yorg[1], zorg[zi], 0, 2, 0)
480
+ elsif rotation >= 180 && rotation < 270
481
+ GR.grid3d(xtick, 0, ztick, xorg[1], yorg[0], zorg[zi], 2, 0, 2)
482
+ GR.grid3d(0, ytick, 0, xorg[1], yorg[0], zorg[zi], 0, 2, 0)
483
+ else
484
+ GR.grid3d(xtick, 0, ztick, xorg[0], yorg[0], zorg[0], 2, 0, 2)
485
+ GR.grid3d(0, ytick, 0, xorg[0], yorg[0], zorg[zi], 0, 2, 0)
486
+ end
487
+ elsif rotation >= 0 && rotation < 90
488
+ GR.axes3d(xtick, 0, ztick, xorg[0], yorg[0], zorg[zi], majorx, 0, majorz, -ticksize)
489
+ GR.axes3d(0, ytick, 0, xorg[1], yorg[0], zorg[zi], 0, majory, 0, ticksize)
490
+ elsif rotation >= 90 && rotation < 180
491
+ GR.axes3d(0, 0, ztick, xorg[0], yorg[1], zorg[zi], 0, 0, majorz, -ticksize)
492
+ GR.axes3d(xtick, ytick, 0, xorg[0], yorg[0], zorg[zi], majorx, majory, 0, -ticksize)
493
+ elsif rotation >= 180 && rotation < 270
494
+ GR.axes3d(xtick, 0, ztick, xorg[1], yorg[1], zorg[zi], majorx, 0, majorz, ticksize)
495
+ GR.axes3d(0, ytick, 0, xorg[0], yorg[0], zorg[zi], 0, majory, 0, -ticksize)
345
496
  else
346
- GR.axes3d(xtick, 0, ztick, xorg[0], yorg[0], zorg[0], majorx, 0, majorz, -ticksize)
347
- GR.axes3d(0, ytick, 0, xorg[1], yorg[0], zorg[0], 0, majory, 0, ticksize)
497
+ GR.axes3d(0, 0, ztick, xorg[1], yorg[0], zorg[zi], 0, 0, majorz, -ticksize)
498
+ GR.axes3d(xtick, ytick, 0, xorg[1], yorg[1], zorg[zi], majorx, majory, 0, ticksize)
348
499
  end
349
500
  else
350
501
  charheight = [0.018 * diag, 0.012].max
351
502
  GR.setcharheight(charheight)
352
- if %i[heatmap nonuniformheatmap shade].include?(kind)
503
+ if %i[heatmap nonuniformheatmap shade contourf].include?(kind)
353
504
  ticksize = -ticksize
354
- elsif drawgrid
355
- GR.grid(xtick, ytick, 0, 0, majorx, majory)
505
+ drawgrid = false if kind == :shade
356
506
  end
357
507
  if kvs.has_key?(:xticklabels) || kvs.has_key?(:yticklabels)
508
+ GR.grid(xtick, ytick, 0, 0, majorx, majory) if drawgrid
358
509
  fx = if kvs.has_key?(:xticklabels)
359
510
  GRCommons::Fiddley::Function.new(
360
511
  :void, %i[double double string double]
361
512
  ) do |x, y, _svalue, value|
362
- label = value < 0 ? '' : kvs[:xticklabels][value] || ''
513
+ idx = value.round - 1
514
+ label = if idx >= 0 && idx < kvs[:xticklabels].size
515
+ kvs[:xticklabels][idx]
516
+ else
517
+ ''
518
+ end
363
519
  GR.textext(x, y, label)
364
520
  end
365
521
  else
@@ -373,7 +529,12 @@ module GR
373
529
  GRCommons::Fiddley::Function.new(
374
530
  :void, %i[double double string double]
375
531
  ) do |x, y, _svalue, value|
376
- label = value < 0 ? '' : kvs[:yticklabels][value] || ''
532
+ idx = value.round - 1
533
+ label = if idx >= 0 && idx < kvs[:yticklabels].size
534
+ kvs[:yticklabels][idx]
535
+ else
536
+ ''
537
+ end
377
538
  GR.textext(x, y, label)
378
539
  end
379
540
  else
@@ -385,9 +546,12 @@ module GR
385
546
  end
386
547
  GR.axeslbl(xtick, ytick, xorg[0], yorg[0], majorx, majory, ticksize, fx, fy)
387
548
  else
388
- GR.axes(xtick, ytick, xorg[0], yorg[0], majorx, majory, ticksize)
549
+ x_axis = GR.axis('X', tick: xtick, org: xorg[0], major_count: majorx, tick_size: ticksize)
550
+ y_axis = GR.axis('Y', tick: ytick, org: yorg[0], major_count: majory, tick_size: ticksize)
551
+ options = GR::AXES_SIMPLE_AXES | GR::AXES_TWIN_AXES
552
+ options |= GR::AXES_WITH_GRID if drawgrid
553
+ GR.drawaxes(x_axis, y_axis, options)
389
554
  end
390
- GR.axes(xtick, ytick, xorg[1], yorg[1], -majorx, -majory, -ticksize)
391
555
  end
392
556
 
393
557
  if kvs.has_key?(:title)
@@ -396,30 +560,26 @@ module GR
396
560
  text(0.5 * (viewport[0] + viewport[1]), vp[3], kvs[:title].to_s)
397
561
  GR.restorestate
398
562
  end
399
- if %i[wireframe surface plot3 scatter3 trisurf volume].include?(kind)
400
- xlabel = (kvs[:xlabel] || '').to_s
401
- ylabel = (kvs[:ylabel] || '').to_s
402
- zlabel = (kvs[:zlabel] || '').to_s
403
- GR.titles3d(xlabel, ylabel, zlabel)
404
- else
405
- if kvs.has_key?(:xlabel)
406
- GR.savestate
407
- GR.settextalign(GR::TEXT_HALIGN_CENTER, GR::TEXT_VALIGN_BOTTOM)
408
- text(0.5 * (viewport[0] + viewport[1]), vp[2] + 0.5 * charheight, kvs[:xlabel].to_s)
409
- GR.restorestate
410
- end
411
- if kvs.has_key?(:ylabel)
412
- GR.savestate
413
- GR.settextalign(GR::TEXT_HALIGN_CENTER, GR::TEXT_VALIGN_TOP)
414
- GR.setcharup(-1, 0)
415
- text(vp[0] + 0.5 * charheight, 0.5 * (viewport[2] + viewport[3]), kvs[:ylabel].to_s)
416
- GR.restorestate
417
- end
563
+ return if %i[wireframe surface plot3 scatter3 trisurf volume].include?(kind)
564
+
565
+ if kvs.has_key?(:xlabel)
566
+ GR.savestate
567
+ GR.settextalign(GR::TEXT_HALIGN_CENTER, GR::TEXT_VALIGN_BOTTOM)
568
+ text(0.5 * (viewport[0] + viewport[1]), vp[2] + 0.5 * charheight, kvs[:xlabel].to_s)
569
+ GR.restorestate
418
570
  end
571
+ return unless kvs.has_key?(:ylabel)
572
+
573
+ GR.savestate
574
+ GR.settextalign(GR::TEXT_HALIGN_CENTER, GR::TEXT_VALIGN_TOP)
575
+ GR.setcharup(-1, 0)
576
+ text(vp[0] + 0.5 * charheight, 0.5 * (viewport[2] + viewport[3]), kvs[:ylabel].to_s)
577
+ GR.restorestate
419
578
  end
420
579
 
421
- def draw_polar_axes
580
+ def draw_polar_axes(pass = 1)
422
581
  viewport = kvs[:viewport]
582
+ vp = kvs[:vp]
423
583
  diag = Math.sqrt((viewport[1] - viewport[0])**2 + (viewport[3] - viewport[2])**2)
424
584
  charheight = [0.018 * diag, 0.012].max
425
585
 
@@ -432,41 +592,82 @@ module GR
432
592
  GR.setlinetype(GR::LINETYPE_SOLID)
433
593
 
434
594
  tick = auto_tick(rmin, rmax)
435
- n = ((rmax - rmin) / tick + 0.5).round
436
- (n + 1).times do |i|
437
- r = i.to_f / n
438
- if i.even?
595
+ n = ((rmax - rmin) / tick).truncate
596
+ if n <= 4
597
+ tick /= 2.0
598
+ n *= 2
599
+ end
600
+
601
+ if pass == 1
602
+ GR.selntran(1)
603
+ (n + 1).times do |i|
604
+ r = i.to_f * tick / (rmax - rmin)
605
+ if r > 0 && r < 1
606
+ if i.even?
607
+ GR.setlinecolorind(88)
608
+ GR.drawarc(-r, r, -r, r, 0, 360)
609
+ else
610
+ GR.setlinecolorind(90)
611
+ GR.drawarc(-r, r, -r, r, 0, 360)
612
+ end
613
+ end
614
+ end
615
+ GR.setclip(0)
616
+ GR.setlinecolorind(88)
617
+ GR.drawarc(-1, 1, -1, 1, 0, 360)
618
+
619
+ GR.setclip(1)
620
+ sign = (kvs[:theta_direction] || 1) > 0 ? 1 : -1
621
+ offs = THETA_ZERO_LOCATION[kvs[:theta_zero_location] || 'E']
622
+ 0.step(by: 45, to: 315) do |alpha|
623
+ sinf = Math.sin((alpha * sign) * Math::PI / 180 + offs)
624
+ cosf = Math.cos((alpha * sign) * Math::PI / 180 + offs)
439
625
  GR.setlinecolorind(88)
440
- GR.drawarc(-r, r, -r, r, 0, 359) if i > 0
626
+ GR.polyline([cosf, 0], [sinf, 0])
627
+ GR.settextalign(GR::TEXT_HALIGN_CENTER, GR::TEXT_VALIGN_HALF)
628
+ x, y = GR.wctondc(1.1 * cosf, 1.1 * sinf)
629
+ GR.textext(x, y, "#{alpha}^o")
630
+ end
631
+
632
+ if kvs.has_key?(:title)
633
+ GR.settextalign(GR::TEXT_HALIGN_CENTER, GR::TEXT_VALIGN_TOP)
634
+ text(0.5 * (viewport[0] + viewport[1]), vp[3] - 0.02, kvs[:title].to_s)
635
+ end
636
+ end
637
+
638
+ if pass == 2
639
+ start = (rmin / tick).floor.truncate
640
+ (n + 1).times do |i|
641
+ j = start + i
642
+ next unless j * tick >= rmin
643
+
644
+ r = i.to_f * tick / (rmax - rmin)
645
+ next unless i.even?
646
+
441
647
  GR.settextalign(GR::TEXT_HALIGN_LEFT, GR::TEXT_VALIGN_HALF)
442
648
  x, y = GR.wctondc(0.05, r)
443
- GR.text(x, y, (rmin + i * tick).to_s) # FIXME: round. significant digits.
444
- else
445
- GR.setlinecolorind(90)
446
- GR.drawarc(-r, r, -r, r, 0, 359)
649
+ fmt = GR.getformat(start, rmin, rmax, tick, 2)
650
+ s = GR.ftoa(j * tick, fmt)
651
+ GR.text(x, y, s)
447
652
  end
448
653
  end
449
- 0.step(by: 45, to: 315) do |alpha|
450
- sinf = Math.sin(alpha * Math::PI / 180)
451
- cosf = Math.cos(alpha * Math::PI / 180)
452
- GR.polyline([cosf, 0], [sinf, 0])
453
- GR.settextalign(GR::TEXT_HALIGN_CENTER, GR::TEXT_VALIGN_HALF)
454
- x, y = GR.wctondc(1.1 * cosf, 1.1 * sinf)
455
- GR.textext(x, y, "#{alpha}^o")
456
- end
654
+
457
655
  GR.restorestate
458
656
  end
459
657
 
460
658
  def plot_polar(θ, ρ)
461
659
  window = kvs[:window]
462
- rmax = window[3].to_f
463
- ρ = ρ.map { |i| i / rmax }
660
+ rmin = window[2]
661
+ rmax = window[3]
662
+ sign = (kvs[:theta_direction] || 1) > 0 ? 1 : -1
663
+ offs = THETA_ZERO_LOCATION[kvs[:theta_zero_location] || 'E']
664
+ ρ = ρ.map { |i| (i - rmin) / (rmax - rmin) }
464
665
  n = ρ.length
465
666
  x = []
466
667
  y = []
467
668
  n.times do |i|
468
- x << ρ[i] * Math.cos(θ[i])
469
- y << ρ[i] * Math.sin(θ[i])
669
+ x << ρ[i] * Math.cos(θ[i] * sign + offs)
670
+ y << ρ[i] * Math.sin(θ[i] * sign + offs)
470
671
  end
471
672
  GR.polyline(x, y)
472
673
  end
@@ -479,10 +680,21 @@ module GR
479
680
  if img.is_a? String
480
681
  width, height, data = GR.readimage(img)
481
682
  else
482
- height, width = img.shape
683
+ if narray?(img)
684
+ height, width = img.shape
685
+ data = img
686
+ else
687
+ height = img.length
688
+ width = img[0].length
689
+ data = img.flatten
690
+ end
483
691
  cmin, cmax = kvs[:crange]
484
- data = img.map { |i| normalize_color(i, cmin, cmax) }
485
- data = data.map { |i| (1000 + i * 255).round }
692
+ if narray?(data)
693
+ data = (data - cmin) / (cmax - cmin)
694
+ data = (data * 255 + 1000).round.cast_to(Numo::Int32)
695
+ else
696
+ data = data.map { |i| (1000 + normalize_color(i, cmin, cmax) * 255).round }
697
+ end
486
698
  end
487
699
 
488
700
  if width * (viewport[3] - viewport[2]) < height * (viewport[1] - viewport[0])
@@ -501,12 +713,12 @@ module GR
501
713
 
502
714
  GR.selntran(0)
503
715
  GR.setscale(0)
504
- if kvs.has_key?(:xflip)
716
+ if kvs[:xflip]
505
717
  tmp = xmax
506
718
  xmax = xmin
507
719
  xmin = tmp
508
720
  end
509
- if kvs.has_key?(:yflip)
721
+ if kvs[:yflip]
510
722
  tmp = ymax
511
723
  ymax = ymin
512
724
  ymin = tmp
@@ -552,8 +764,9 @@ module GR
552
764
  nx, ny, nz = v.shape
553
765
  isovalue = ((kvs[:isovalue] || 0.5) - v.min) / (v.max - v.min)
554
766
  rotation = ((kvs[:rotation] || 40) * Math::PI / 180.0)
555
- tilt = ((kvs[:tilt] || 70) * Math::PI / 180.0)
767
+ tilt = ((kvs[:tilt] || 60) * Math::PI / 180.0)
556
768
  r = 2.5
769
+ require 'gr3'
557
770
  GR3.clear
558
771
  mesh = GR3.createisosurfacemesh(values, [2.0 / (nx - 1), 2.0 / (ny - 1), 2.0 / (nz - 1)],
559
772
  [-1, -1, -1],
@@ -582,11 +795,12 @@ module GR
582
795
  GR.inqscale
583
796
  end
584
797
  GR.setscale(options & mask)
585
- h = 0.5 * (zmax - zmin) / (colors - 1)
798
+ h = 0 # 0.5 * (zmax - zmin) / (colors - 1)
586
799
  GR.setwindow(0, 1, zmin, zmax)
800
+ GR.setclipregion(GR::REGION_RECTANGLE)
587
801
  GR.setviewport(viewport[1] + 0.02 + off, viewport[1] + 0.05 + off,
588
802
  viewport[2], viewport[3])
589
- l = linspace(1000, 1255, colors).map(&:round)
803
+ l = linspace(0, 1, colors).map { |i| (1000 + i * 255).round }
590
804
  GR.cellarray(0, 1, zmax + h, zmin - h, 1, colors, l)
591
805
  GR.setlinecolorind(1)
592
806
  diag = Math.sqrt((viewport[1] - viewport[0])**2 + (viewport[3] - viewport[2])**2)
@@ -594,14 +808,24 @@ module GR
594
808
  GR.setcharheight(charheight)
595
809
  if kvs[:scale] & GR::OPTION_Z_LOG == 0
596
810
  ztick = auto_tick(zmin, zmax)
597
- GR.axes(0, ztick, 1, zmin, 0, 1, 0.005)
811
+ y_axis = GR.axis('Y', position: 1, tick: ztick, org: zmin, major_count: 1, tick_size: 0.005)
812
+ GR.drawaxis('Y', y_axis)
598
813
  else
599
814
  GR.setscale(GR::OPTION_Y_LOG)
600
- GR.axes(0, 2, 1, zmin, 0, 1, 0.005)
815
+ y_axis = GR.axis('Y', position: 1, tick: 2, org: zmin, major_count: 1, tick_size: 0.005)
816
+ GR.drawaxis('Y', y_axis)
601
817
  end
602
818
  GR.restorestate
603
819
  end
604
820
 
821
+ def rgb(color)
822
+ [
823
+ ((color >> 16) & 0xff) / 255.0,
824
+ ((color >> 8) & 0xff) / 255.0,
825
+ (color & 0xff) / 255.0
826
+ ]
827
+ end
828
+
605
829
  def plot_data(_figure = true)
606
830
  # GR.init
607
831
 
@@ -619,7 +843,23 @@ module GR
619
843
  GR.clearws if kvs[:clear]
620
844
 
621
845
  if scheme != 0
622
- # Not yet.
846
+ 8.times do |colorind|
847
+ color = COLORS[scheme - 1][colorind]
848
+ r, g, b = rgb(color)
849
+ GR.setcolorrep(colorind, r, g, b)
850
+ GR.setcolorrep(DISTINCT_CMAP[colorind], r, g, b) if scheme != 1
851
+ end
852
+
853
+ r, g, b = rgb(COLORS[scheme - 1][0])
854
+ r2, g2, b2 = rgb(COLORS[scheme - 1][1])
855
+ rdiff = r2 - r
856
+ gdiff = g2 - g
857
+ bdiff = b2 - b
858
+
859
+ 12.times do |colorind|
860
+ f = colorind / 11.0
861
+ GR.setcolorrep(91 - colorind, r + f * rdiff, g + f * gdiff, b + f * bdiff)
862
+ end
623
863
  end
624
864
 
625
865
  if kvs.has_key?(:font)
@@ -670,14 +910,26 @@ module GR
670
910
 
671
911
  when :line
672
912
  mask = GR.uselinespec(spec)
673
- linewidth = kvs[:linewidth]
674
- # Slightly different from Julia,
675
- # Because the implementation of polyline and polymarker is different.
676
- z ||= linewidth # FIXME
677
- GR.polyline(x, y, z, c) if hasline(mask)
678
- GR.polymarker(x, y, z, c) if hasmarker(mask)
679
-
680
- when :step
913
+ if c
914
+ linewidth = kvs[:linewidth] || 1
915
+ z = Array.new(x.length, linewidth)
916
+ GR.polyline(x, y, z, c)
917
+ else
918
+ if hasline(mask)
919
+ linewidth = kvs[:linewidth] || 1
920
+ GR.setlinewidth(linewidth)
921
+ GR.polyline(x, y)
922
+ end
923
+ if hasmarker(mask)
924
+ markersize = kvs[:markersize] || 1
925
+ GR.setmarkersize(markersize)
926
+ borderwidth = kvs[:borderwidth] || 1
927
+ GR.setborderwidth(borderwidth)
928
+ GR.polymarker(x, y)
929
+ end
930
+ end
931
+
932
+ when :step, :stairs
681
933
  mask = GR.uselinespec(spec)
682
934
  if hasline(mask)
683
935
  where = kvs[:where] || 'mid'
@@ -711,43 +963,48 @@ module GR
711
963
 
712
964
  when :scatter
713
965
  GR.setmarkertype(GR::MARKERTYPE_SOLID_CIRCLE)
714
- z = z&.map { |i| i * 0.01 }
715
- c = c&.map { |i| normalize_color(i, *kvs[:crange]) }
716
- GR.polymarker(x, y, z, c)
966
+ if z || c
967
+ if c
968
+ cmin, cmax = kvs[:crange]
969
+ c = c.map { |i| normalize_color(i, cmin, cmax) }
970
+ c = c.map { |i| (1000 + i * 255).round }
971
+ end
972
+ GR.polymarker(x, y, z, c)
973
+ else
974
+ GR.polymarker(x, y)
975
+ end
717
976
 
718
977
  when :stem
719
- GR.setlinecolorind(1)
720
- GR.polyline(kvs[:window][0..1], [0, 0])
721
978
  GR.setmarkertype(GR::MARKERTYPE_SOLID_CIRCLE)
722
979
  GR.uselinespec(spec)
723
980
  x = x.to_a if narray?(x)
724
981
  y = y.to_a if narray?(y)
725
982
  x.zip(y).each do |xi, yi|
726
983
  GR.polyline([xi, xi], [0, yi])
984
+ GR.polymarker([xi], [yi])
985
+ end
986
+ GR.setlinecolorind(1)
987
+ GR.polyline(kvs[:window][0..1], [0, 0])
988
+
989
+ when :bar
990
+ (0...x.length).step(2) do |i|
991
+ GR.setfillcolorind(989)
992
+ GR.setfillintstyle(GR::INTSTYLE_SOLID)
993
+ GR.fillrect(x[i], x[i + 1], y[i], y[i + 1])
994
+ GR.setfillcolorind(1)
995
+ GR.setfillintstyle(GR::INTSTYLE_HOLLOW)
996
+ GR.fillrect(x[i], x[i + 1], y[i], y[i + 1])
727
997
  end
728
- GR.polymarker(x, y)
729
998
 
730
999
  when :hist
731
- if kvs[:horizontal]
732
- xmin = kvs[:window][0]
733
- x.length.times do |i|
734
- GR.setfillcolorind(989)
735
- GR.setfillintstyle(GR::INTSTYLE_SOLID)
736
- GR.fillrect(xmin, x[i], y[i], y[i + 1])
737
- GR.setfillcolorind(1)
738
- GR.setfillintstyle(GR::INTSTYLE_HOLLOW)
739
- GR.fillrect(xmin, x[i], y[i], y[i + 1])
740
- end
741
- else
742
- ymin = kvs[:window][2]
743
- y.length.times do |i|
744
- GR.setfillcolorind(989)
745
- GR.setfillintstyle(GR::INTSTYLE_SOLID)
746
- GR.fillrect(x[i], x[i + 1], ymin, y[i])
747
- GR.setfillcolorind(1)
748
- GR.setfillintstyle(GR::INTSTYLE_HOLLOW)
749
- GR.fillrect(x[i], x[i + 1], ymin, y[i])
750
- end
1000
+ ymin = kvs[:window][2]
1001
+ y.length.times do |i|
1002
+ GR.setfillcolorind(989)
1003
+ GR.setfillintstyle(GR::INTSTYLE_SOLID)
1004
+ GR.fillrect(x[i], x[i + 1], ymin, y[i])
1005
+ GR.setfillcolorind(1)
1006
+ GR.setfillintstyle(GR::INTSTYLE_HOLLOW)
1007
+ GR.fillrect(x[i], x[i + 1], ymin, y[i])
751
1008
  end
752
1009
 
753
1010
  when :polarhist
@@ -762,6 +1019,7 @@ module GR
762
1019
  GR.setfillintstyle(GR::INTSTYLE_HOLLOW)
763
1020
  GR.fillarc(-ρ[i], ρ[i], -ρ[i], ρ[i], θ[i - 1], θ[i])
764
1021
  end
1022
+ draw_polar_axes(2)
765
1023
 
766
1024
  when :polarheatmap, :nonuniformpolarheatmap
767
1025
  w, h = z.shape
@@ -781,9 +1039,10 @@ module GR
781
1039
  θ = x.map { |i| i * 180 / Math::PI }
782
1040
  GR.nonuniformpolarcellarray(θ, ρ, w, h, colors)
783
1041
  end
784
- draw_polar_axes
1042
+ draw_polar_axes(1)
1043
+ draw_polar_axes(2)
785
1044
  kvs[:zrange] = [cmin, cmax]
786
- colorbar
1045
+ colorbar(0.025)
787
1046
 
788
1047
  when :contour, :contourf
789
1048
  zmin, zmax = kvs[:zrange]
@@ -816,7 +1075,12 @@ module GR
816
1075
  when :contour
817
1076
  GR._contour_(x, y, h, z, clabels ? 1 : 1000)
818
1077
  when :contourf
819
- GR._contourf_(x, y, h, z, clabels ? 1 : 0)
1078
+ clines = kvs.has_key?(:clines) ? kvs[:clines] : true
1079
+ GR._contourf_(x, y, h, z, if clines
1080
+ clabels ? 1 : 0
1081
+ else
1082
+ -1
1083
+ end)
820
1084
  end
821
1085
  colorbar(0, h.length)
822
1086
 
@@ -831,12 +1095,11 @@ module GR
831
1095
  when :heatmap, :nonuniformheatmap
832
1096
  case z
833
1097
  when Array
834
- if z.all? { |zi| zi.size = z[0].size }
835
- w = z.size
836
- h = z[0].size
837
- else
838
- raise
839
- end
1098
+ raise unless z.all? { |zi| zi.size == z[0].size }
1099
+
1100
+ w = z.size
1101
+ h = z[0].size
1102
+
840
1103
  when ->(obj) { narray?(obj) }
841
1104
  w, h = z.shape
842
1105
  else
@@ -846,11 +1109,11 @@ module GR
846
1109
  cmin, cmax = kvs[:crange]
847
1110
  levels = kvs[:levels] || 256
848
1111
  data = z.flatten.to_a.map { |i| normalize_color(i, cmin, cmax) } # NArray -> Array
849
- if kind == :heatmap
1112
+ if kind == :heatmap && !ENV['GR_SCALE_FACTOR']
850
1113
  rgba = data.map { |v| to_rgba(v, cmap) }
851
1114
  GR.drawimage(0.5, w + 0.5, h + 0.5, 0.5, w, h, rgba)
852
1115
  else
853
- colors = data.map { |i| (1000 + i * 255).round }
1116
+ colors = data.map { |i| (i.nan? ? 1256 : 1000 + i * 255).round }
854
1117
  GR.nonuniformcellarray(x, y, w, h, colors)
855
1118
  end
856
1119
  colorbar(0, levels)
@@ -887,9 +1150,14 @@ module GR
887
1150
 
888
1151
  when :volume
889
1152
  algorithm = kvs[:algorithm] || 0
1153
+ w, h, ratio = GR.inqvpsize
1154
+ GR.setpicturesizeforvolume((w * ratio).round, (h * ratio).round)
890
1155
  require 'gr3'
891
1156
  GR3.clear
1157
+ ambient, diffuse, specular, specular_power = GR3.getlightparameters
1158
+ GR3.setlightparameters(0.8, 0.2, 0.1, 10.0)
892
1159
  dmin, dmax = GR3.volume(z, algorithm)
1160
+ GR3.setlightparameters(ambient, diffuse, specular, specular_power)
893
1161
  draw_axes(kind, 2)
894
1162
  kvs[:zrange] = [dmin, dmax]
895
1163
  colorbar(0.05)
@@ -924,6 +1192,7 @@ module GR
924
1192
  when :polar
925
1193
  GR.uselinespec(spec)
926
1194
  plot_polar(x, y)
1195
+ draw_polar_axes(2)
927
1196
 
928
1197
  when :trisurf
929
1198
  GR.trisurface(x, y, z)
@@ -934,25 +1203,15 @@ module GR
934
1203
  zmin, zmax = kvs[:zrange]
935
1204
  levels = linspace(zmin, zmax, 20)
936
1205
  GR.tricontour(x, y, z, levels)
1206
+ colorbar
937
1207
 
938
1208
  when :shade
939
1209
  xform = kvs[:xform] || 5
940
- if x.to_a.include? Float::NAN # FIXME: Ruby is different from Julia?
941
- # How to check NArray?
1210
+ if (x.respond_to?(:isnan) && x.isnan.any?) || (x.is_a?(Array) && x.include?(Float::NAN))
942
1211
  GR.shadelines(x, y, xform: xform)
943
1212
  else
944
1213
  GR.shadepoints(x, y, xform: xform)
945
1214
  end
946
-
947
- when :bar
948
- 0.step(x.length - 1, 2) do |i|
949
- GR.setfillcolorind(989)
950
- GR.setfillintstyle(GR::INTSTYLE_SOLID)
951
- GR.fillrect(x[i], x[i + 1], y[i], y[i + 1])
952
- GR.setfillcolorind(1)
953
- GR.setfillintstyle(GR::INTSTYLE_HOLLOW)
954
- GR.fillrect(x[i], x[i + 1], y[i], y[i + 1])
955
- end
956
1215
  end
957
1216
 
958
1217
  GR.restorestate
@@ -960,13 +1219,13 @@ module GR
960
1219
 
961
1220
  draw_legend if %i[line step scatter stem].include?(kind) && kvs.has_key?(:labels)
962
1221
 
963
- if kvs[:update]
964
- GR.updatews
965
- # if GR.isinline()
966
- # restore_context()
967
- # return GR.show()
968
- # end
969
- end
1222
+ return unless kvs[:update]
1223
+
1224
+ GR.updatews
1225
+ # if GR.isinline()
1226
+ # restore_context()
1227
+ # return GR.show()
1228
+ # end
970
1229
 
971
1230
  # flag && restore_context()
972
1231
  end
@@ -1077,6 +1336,8 @@ module GR
1077
1336
 
1078
1337
  # https://gist.github.com/rysk-t/8d1aef0fb67abde1d259#gistcomment-1925021
1079
1338
  def linspace(low, high, num)
1339
+ return [low] if num == 1
1340
+
1080
1341
  [*0..(num - 1)].collect { |i| low + i.to_f * (high - low) / (num - 1) }
1081
1342
  end
1082
1343
 
@@ -1086,6 +1347,7 @@ module GR
1086
1347
  i.is_a?(Array) && (i[0].is_a?(Array) || narray?(i[0]))
1087
1348
  end
1088
1349
  args.map do |xyzc|
1350
+ xyzc = xyzc.dup
1089
1351
  spec = nil
1090
1352
  case xyzc.last
1091
1353
  when String
@@ -1094,20 +1356,28 @@ module GR
1094
1356
  spec = xyzc.pop[:spec]
1095
1357
  end
1096
1358
 
1097
- x, y, z, c = xyzc.map do |i|
1098
- if i.is_a?(Array) || narray?(i) || i.nil?
1099
- i
1100
- elsif i.respond_to?(:to_a)
1101
- # Convert an Array-like class such as Daru::Vector to an Array
1102
- i.to_a
1103
- else # String
1104
- i
1105
- end
1359
+ x, y, z, c = xyzc.map { |i| plot_arg(i) }
1360
+ if xyzc.size == 1 && y.nil?
1361
+ y = x
1362
+ x = linspace(1, y.length, y.length)
1363
+ elsif y.respond_to?(:call)
1364
+ y = x.map { |i| y.call(i) }
1106
1365
  end
1107
1366
  [x, y, z, c, spec]
1108
1367
  end
1109
1368
  end
1110
1369
 
1370
+ def plot_arg(arg)
1371
+ if arg.is_a?(Array) || narray?(arg) || arg.nil? || arg.respond_to?(:call)
1372
+ arg
1373
+ elsif arg.respond_to?(:to_a)
1374
+ # Convert an Array-like class such as Daru::Vector to an Array
1375
+ arg.to_a
1376
+ else
1377
+ arg
1378
+ end
1379
+ end
1380
+
1111
1381
  # Normalize a color c with the range [cmin, cmax]
1112
1382
  # 0 <= normalize_color(c, cmin, cmax) <= 1
1113
1383
  def normalize_color(c, cmin, cmax)
@@ -1148,6 +1418,18 @@ module GR
1148
1418
  [a, b]
1149
1419
  end
1150
1420
 
1421
+ def extrema(a)
1422
+ amin = Float::INFINITY
1423
+ amax = -Float::INFINITY
1424
+ a.each do |el|
1425
+ next if el.nil? || (el.is_a?(Float) && el.nan?)
1426
+
1427
+ amin = el if el < amin
1428
+ amax = el if el > amax
1429
+ end
1430
+ [amin, amax]
1431
+ end
1432
+
1151
1433
  def minmax(kind)
1152
1434
  xmin = ymin = zmin = cmin = Float::INFINITY
1153
1435
  xmax = ymax = zmax = cmax = -Float::INFINITY
@@ -1158,10 +1440,10 @@ module GR
1158
1440
  # duck typing for NArray
1159
1441
  x = x.map { |v| v > 0 ? v : Float::NAN }
1160
1442
  end
1161
- x0, x1 = x.minmax
1443
+ x0, x1 = extrema(x)
1162
1444
  xmin = [x0, xmin].min
1163
1445
  xmax = [x1, xmax].max
1164
- elsif kind == :volume
1446
+ elsif %i[volume isosurface].include?(kind)
1165
1447
  xmin = -1
1166
1448
  xmax = 1
1167
1449
  else
@@ -1172,10 +1454,10 @@ module GR
1172
1454
  if scale & GR::OPTION_Y_LOG != 0
1173
1455
  y = y.map { |v| v > 0 ? v : Float::NAN }
1174
1456
  end
1175
- y0, y1 = y.minmax
1457
+ y0, y1 = extrema(y)
1176
1458
  ymin = [y0, ymin].min
1177
1459
  ymax = [y1, ymax].max
1178
- elsif kind == :volume
1460
+ elsif %i[volume isosurface].include?(kind)
1179
1461
  ymin = -1
1180
1462
  ymax = 1
1181
1463
  else
@@ -1186,18 +1468,24 @@ module GR
1186
1468
  if scale & GR::OPTION_Z_LOG != 0
1187
1469
  z = z.map { |v| v > 0 ? v : Float::NAN }
1188
1470
  end
1189
- z0, z1 = z.minmax
1471
+ z0, z1 = extrema(z)
1190
1472
  zmin = [z0, zmin].min
1191
1473
  zmax = [z1, zmax].max
1474
+ elsif %i[volume isosurface].include?(kind)
1475
+ zmin = -1
1476
+ zmax = 1
1477
+ else
1478
+ zmin = 0
1479
+ zmax = 1
1192
1480
  end
1193
1481
  if c
1194
- c0, c1 = c.minmax
1482
+ c0, c1 = extrema(c)
1195
1483
  cmin = [c0, cmin].min
1196
1484
  cmax = [c1, cmax].max
1197
1485
  elsif z
1198
- z0, z1 = z.minmax
1199
- cmin = [z0, zmin].min
1200
- cmax = [z1, zmax].max
1486
+ c0, c1 = extrema(z)
1487
+ cmin = [c0, cmin].min
1488
+ cmax = [c1, cmax].max
1201
1489
  end
1202
1490
  end
1203
1491
  xmin, xmax = fix_minmax(xmin, xmax)
@@ -1205,9 +1493,9 @@ module GR
1205
1493
  zmin, zmax = fix_minmax(zmin, zmax)
1206
1494
 
1207
1495
  # kvs[:xlim], kvs[:ylim], kvs[:zlim] is supposed to be Array or Range
1208
- kvs[:xrange] = [(kvs[:xlim]&.first || xmin), (kvs[:xlim]&.last || xmax)]
1209
- kvs[:yrange] = [(kvs[:ylim]&.first || ymin), (kvs[:ylim]&.last || ymax)]
1210
- kvs[:zrange] = [(kvs[:zlim]&.first || zmin), (kvs[:zlim]&.last || zmax)]
1496
+ kvs[:xrange] = [kvs[:xlim]&.first || xmin, kvs[:xlim]&.last || xmax]
1497
+ kvs[:yrange] = [kvs[:ylim]&.first || ymin, kvs[:ylim]&.last || ymax]
1498
+ kvs[:zrange] = [kvs[:zlim]&.first || zmin, kvs[:zlim]&.last || zmax]
1211
1499
 
1212
1500
  if kvs.has_key?(:clim)
1213
1501
  c0, c1 = kvs[:clim]
@@ -1228,10 +1516,15 @@ module GR
1228
1516
  def auto_tick(amin, amax)
1229
1517
  scale = 10.0**Math.log10(amax - amin).truncate
1230
1518
  tick_size = [5.0, 2.0, 1.0, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01]
1231
- i = tick_size.find_index do |tsize|
1232
- ((amax - amin) / scale / tsize) > 7 # maximum number of tick marks
1519
+ tick = 1.0
1520
+ tick_size.each_with_index do |tsize, i|
1521
+ n = ((amax - amin) / scale / tsize).truncate
1522
+ if n > 7
1523
+ tick = tick_size[i - 1]
1524
+ break
1525
+ end
1233
1526
  end
1234
- tick_size[i - 1] * scale
1527
+ tick * scale
1235
1528
  end
1236
1529
 
1237
1530
  def legend_size
@@ -1271,6 +1564,11 @@ module GR
1271
1564
  create_plot(:step, *args)
1272
1565
  end
1273
1566
 
1567
+ # (Plot) Draw one or more step or staircase plots.
1568
+ def stairs(*args)
1569
+ create_plot(:stairs, *args)
1570
+ end
1571
+
1274
1572
  # (Plot) Draw one or more scatter plots.
1275
1573
  def scatter(*args)
1276
1574
  create_plot(:scatter, *args)
@@ -1291,46 +1589,88 @@ module GR
1291
1589
  plt.plot_data
1292
1590
  end
1293
1591
 
1592
+ # (Plot) Draw a heatmap.
1294
1593
  # (Plot) Draw a heatmap.
1295
1594
  def heatmap(*args)
1296
- # FIXME
1297
- args, kv = format_xyzc(*args)
1298
- _x, _y, z = args
1299
- ysize, xsize = z.shape
1300
- z = z.reshape(xsize, ysize)
1595
+ kv = args.last.is_a?(Hash) ? args.pop : {}
1596
+ if args.length == 1
1597
+ z = args[0]
1598
+ z = Numo::DFloat.cast(z) if z.is_a?(Array)
1599
+ ysize, xsize = z.shape
1600
+ z = z.reshape(xsize, ysize)
1601
+ x = (1..xsize).to_a
1602
+ y = (1..ysize).to_a
1603
+ elsif args.length == 3
1604
+ x, y, z = args
1605
+ z = Numo::DFloat.cast(z) if z.is_a?(Array)
1606
+ else
1607
+ raise ArgumentError
1608
+ end
1301
1609
  create_plot(:heatmap, kv) do |plt|
1302
- plt.kvs[:xlim] ||= [0.5, xsize + 0.5]
1303
- plt.kvs[:ylim] ||= [0.5, ysize + 0.5]
1304
- plt.args = [[(1..xsize).to_a, (1..ysize).to_a, z, nil, '']]
1610
+ plt.args = [[x, y, z, nil, '']]
1611
+ end
1612
+ end
1613
+
1614
+ # (Plot) Draw a nonuniformheatmap.
1615
+ def nonuniformheatmap(*args)
1616
+ kv = args.last.is_a?(Hash) ? args.pop : {}
1617
+ if args.length == 1
1618
+ z = args[0]
1619
+ z = Numo::DFloat.cast(z) if z.is_a?(Array)
1620
+ ysize, xsize = z.shape
1621
+ z = z.reshape(xsize, ysize)
1622
+ x = (1..xsize).to_a
1623
+ y = (1..ysize).to_a
1624
+ elsif args.length == 3
1625
+ x, y, z = args
1626
+ z = Numo::DFloat.cast(z) if z.is_a?(Array)
1627
+ else
1628
+ raise ArgumentError
1629
+ end
1630
+ create_plot(:nonuniformheatmap, kv) do |plt|
1631
+ plt.args = [[x, y, z, nil, '']]
1305
1632
  end
1306
1633
  end
1307
1634
 
1308
1635
  # (Plot) Draw a polarheatmap.
1309
1636
  def polarheatmap(*args)
1310
- d = args.shift
1311
- # FIXME
1312
- z = Numo::DFloat.cast(d)
1313
- raise 'expected 2-D array' unless z.ndim == 2
1314
-
1315
- create_plot(:polarheatmap, z, *args) do |plt|
1316
- width, height = z.shape
1317
- plt.kvs[:xlim] ||= [0.5, width + 0.5]
1318
- plt.kvs[:ylim] ||= [0.5, height + 0.5]
1319
- plt.args = [[(1..width).to_a, (1..height).to_a, z, nil, '']]
1637
+ kv = args.last.is_a?(Hash) ? args.pop : {}
1638
+ if args.length == 1
1639
+ z = args[0]
1640
+ z = Numo::DFloat.cast(z) if z.is_a?(Array)
1641
+ ysize, xsize = z.shape
1642
+ z = z.reshape(xsize, ysize)
1643
+ x = (1..xsize).to_a
1644
+ y = (1..ysize).to_a
1645
+ elsif args.length == 3
1646
+ x, y, z = args
1647
+ z = Numo::DFloat.cast(z) if z.is_a?(Array)
1648
+ else
1649
+ raise ArgumentError
1650
+ end
1651
+ create_plot(:polarheatmap, kv) do |plt|
1652
+ plt.args = [[x, y, z, nil, '']]
1320
1653
  end
1321
1654
  end
1322
1655
 
1323
1656
  # (Plot) Draw a nonuniformpolarheatmap.
1324
1657
  def nonuniformpolarheatmap(*args)
1325
- # FIXME
1326
- args, kv = format_xyzc(*args)
1327
- _x, _y, z = args
1328
- ysize, xsize = z.shape
1329
- z = z.reshape(xsize, ysize)
1658
+ kv = args.last.is_a?(Hash) ? args.pop : {}
1659
+ if args.length == 1
1660
+ z = args[0]
1661
+ z = Numo::DFloat.cast(z) if z.is_a?(Array)
1662
+ ysize, xsize = z.shape
1663
+ z = z.reshape(xsize, ysize)
1664
+ x = (1..xsize).to_a
1665
+ y = (1..ysize).to_a
1666
+ elsif args.length == 3
1667
+ x, y, z = args
1668
+ z = Numo::DFloat.cast(z) if z.is_a?(Array)
1669
+ else
1670
+ raise ArgumentError
1671
+ end
1330
1672
  create_plot(:nonuniformpolarheatmap, kv) do |plt|
1331
- plt.kvs[:xlim] ||= [0.5, xsize + 0.5]
1332
- plt.kvs[:ylim] ||= [0.5, ysize + 0.5]
1333
- plt.args = [[(1..xsize).to_a, (1..ysize).to_a, z, nil, '']]
1673
+ plt.args = [[x, y, z, nil, '']]
1334
1674
  end
1335
1675
  end
1336
1676
 
@@ -1402,18 +1742,29 @@ module GR
1402
1742
  end
1403
1743
 
1404
1744
  # (Plot) Draw a bar plot.
1405
- def barplot(labels, heights, kv = {})
1406
- labels = labels.map(&:to_s)
1407
- wc, hc = barcoordinates(heights)
1408
- create_plot(:bar, labels, heights, kv) do |plt|
1409
- if kv[:horizontal]
1745
+ def barplot(*args)
1746
+ kv = args.last.is_a?(Hash) ? args.pop : {}
1747
+ if args.length == 2
1748
+ labels, heights = args
1749
+ elsif args.length == 1
1750
+ heights = args[0]
1751
+ labels = (1..heights.length).map(&:to_s)
1752
+ else
1753
+ raise ArgumentError
1754
+ end
1755
+
1756
+ wc, hc = barcoordinates(heights, kv)
1757
+ horizontal = kv.delete(:horizontal)
1758
+
1759
+ create_plot(:bar, kv) do |plt|
1760
+ if horizontal
1410
1761
  plt.args = [[hc, wc, nil, nil, '']]
1411
1762
  plt.kvs[:yticks] = [1, 1]
1412
- plt.kvs[:yticklabels] = labels
1763
+ plt.kvs[:yticklabels] = labels.map(&:to_s)
1413
1764
  else
1414
1765
  plt.args = [[wc, hc, nil, nil, '']]
1415
1766
  plt.kvs[:xticks] = [1, 1]
1416
- plt.kvs[:xticklabels] = labels
1767
+ plt.kvs[:xticklabels] = labels.map(&:to_s)
1417
1768
  end
1418
1769
  end
1419
1770
  end
@@ -1423,17 +1774,13 @@ module GR
1423
1774
  create_plot(:hist, series, kv) do |plt|
1424
1775
  nbins = plt.kvs[:nbins] || 0
1425
1776
  x, y = hist(series, nbins)
1426
- plt.args = if kv[:horizontal]
1427
- [[y, x, nil, nil, '']]
1428
- else
1429
- [[x, y, nil, nil, '']]
1430
- end
1777
+ plt.args = [[x, y, nil, nil, '']]
1431
1778
  end
1432
1779
  end
1433
1780
 
1434
1781
  # (Plot) Draw an image.
1435
1782
  def imshow(img, kv = {})
1436
- img = Numo::DFloat.cast(img) # Umm...
1783
+ img = Numo::DFloat.cast(img) unless img.is_a?(String)
1437
1784
  create_plot(:imshow, img, kv) do |plt|
1438
1785
  plt.args = [[nil, nil, img, nil, '']]
1439
1786
  end
@@ -1524,26 +1871,18 @@ module GR
1524
1871
  end
1525
1872
 
1526
1873
  def hist(x, nbins = 0)
1527
- nbins = (3.3 * Math.log10(x.length)).round + 1 if nbins <= 1
1528
- begin
1529
- require 'histogram/array'
1530
- rescue LoadError => e
1531
- e.message << " Please add gem 'histogram' to your project's Gemfile."
1532
- raise e
1533
- end
1534
- x = x.to_a if narray?(x)
1535
- x, y = x.histogram(nbins, bin_boundary: :min)
1536
- x.push(x[-1] + x[1] - x[0])
1537
- [x, y]
1874
+ GR::Plot::Histogram.hist(x, nbins)
1538
1875
  end
1539
1876
 
1540
- def barcoordinates(heights, barwidth = 0.8, baseline = 0.0)
1877
+ def barcoordinates(heights, kv = {})
1878
+ barwidth = kv[:barwidth] || 0.8
1879
+ baseline = kv[:baseline] || 0.0
1541
1880
  halfw = barwidth / 2.0
1542
1881
  wc = []
1543
1882
  hc = []
1544
1883
  heights.each_with_index do |value, i|
1545
- wc << i - halfw
1546
- wc << i + halfw
1884
+ wc << (i + 1) - halfw
1885
+ wc << (i + 1) + halfw
1547
1886
  hc << baseline
1548
1887
  hc << value
1549
1888
  end