gr-plot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/gr/plot.rb ADDED
@@ -0,0 +1,1553 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gr'
4
+ autoload :GR3, 'gr3'
5
+
6
+ # FIXME: Plot should not depend on Numo::Narrray unless the GR3 module is required.
7
+ # Note: The Plot class has a linspace function that is independent of Numo..
8
+ require 'numo/narray'
9
+
10
+ module GR
11
+ # This class offers a simple, matlab-style API built on top of the GR package.
12
+ # The class name Plot may be changed in the future.
13
+ class Plot
14
+ # Why is the Plot class NOT object-oriented?
15
+ #
16
+ # Because the code here is mainly ported from GR.jl.
17
+ # https://github.com/jheinen/GR.jl/blob/master/src/jlgr.jl
18
+ #
19
+ # The Python implementation is also Julia compliant.
20
+ # https://github.com/sciapp/python-gr
21
+ #
22
+ # Julia is not an object-oriented language (at least in 2019).
23
+ # So, you will see many if branches here.
24
+ # This is not the Ruby code style. But it WORKS.
25
+ #
26
+ # I want to thank Josef Heinen(@jheinen), the creator of GR.jl
27
+ # and Florian Rhiem(@FlorianRhiem), the creator of python-gr.
28
+ #
29
+ # If you are interested in an object-oriented implementation,
30
+ # See rubyplot.
31
+ # https://github.com/SciRuby/rubyplot
32
+
33
+ # 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
38
+
39
+ # 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
47
+
48
+ FONTS = {
49
+ times_roman: 101,
50
+ times_italic: 102,
51
+ times_bold: 103,
52
+ times_bolditalic: 104,
53
+ helvetica_regular: 105,
54
+ helvetica_oblique: 106,
55
+ helvetica_bold: 107,
56
+ helvetica_boldoblique: 108,
57
+ courier_regular: 109,
58
+ courier_oblique: 110,
59
+ courier_bold: 111,
60
+ courier_boldoblique: 112,
61
+ symbol: 113,
62
+ bookman_light: 114,
63
+ bookman_lightitalic: 115,
64
+ bookman_demi: 116,
65
+ bookman_demiitalic: 117,
66
+ newcenturyschlbk_roman: 118,
67
+ newcenturyschlbk_italic: 119,
68
+ newcenturyschlbk_bold: 120,
69
+ newcenturyschlbk_bolditalic: 121,
70
+ avantgarde_book: 122,
71
+ avantgarde_bookoblique: 123,
72
+ avantgarde_demi: 124,
73
+ avantgarde_demioblique: 125,
74
+ palatino_roman: 126,
75
+ palatino_italic: 127,
76
+ palatino_bold: 128,
77
+ palatino_bolditalic: 129,
78
+ zapfchancery_mediumitalic: 130,
79
+ zapfdingbats: 131,
80
+ cmuserif_math: 232, # original: cmuserif-math
81
+ dejavusans: 233
82
+ }.freeze
83
+
84
+ @last_plot = nil
85
+ class << self
86
+ attr_accessor :last_plot
87
+ end
88
+
89
+ attr_accessor :args, :kvs, :scheme
90
+
91
+ def initialize(*raw_args)
92
+ # Keywords are cloned to avoid disruptive changes
93
+ @kvs = raw_args.last.is_a?(Hash) ? raw_args.pop.clone : {}
94
+ @args = plot_args(raw_args) # method name is the same as Julia/Python
95
+
96
+ # Check keyword options.
97
+ kvs.each_key { |k| warn "Unknown keyword: #{k}" unless KW_ARGS.include? k }
98
+
99
+ # label(singular form) is a original keyword arg which GR.jl does not have.
100
+ kvs[:labels] ||= [kvs[:label]] if kvs.has_key? :label
101
+
102
+ # Don't use ||= here, because we need to tell `false` from `nil`
103
+ kvs[:size] = [600, 450] unless kvs.has_key? :size
104
+ kvs[:ax] = false unless kvs.has_key? :ax
105
+ kvs[:subplot] = [0, 1, 0, 1] unless kvs.has_key? :subplot
106
+ kvs[:clear] = true unless kvs.has_key? :clear
107
+ kvs[:update] = true unless kvs.has_key? :update
108
+
109
+ @scheme = 0
110
+ @background = 0xffffff
111
+ # @handle = nil # This variable will be used in gr_meta
112
+
113
+ self.class.last_plot = self
114
+ end
115
+
116
+ def set_viewport(kind, subplot)
117
+ 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
129
+
130
+ if w > h
131
+ ratio = h / w.to_f
132
+ 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
137
+ else
138
+ ratio = w / h.to_f
139
+ 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
144
+ end
145
+
146
+ if %i[wireframe surface plot3 scatter3 trisurf volume].include?(kind)
147
+ extent = [vp[1] - vp[0], vp[3] - vp[2]].min
148
+ vp1 = 0.5 * (vp[0] + vp[1] - extent)
149
+ vp2 = 0.5 * (vp[0] + vp[1] + extent)
150
+ vp3 = 0.5 * (vp[2] + vp[3] - extent)
151
+ vp4 = 0.5 * (vp[2] + vp[3] + extent)
152
+ else
153
+ vp1, vp2, vp3, vp4 = vp
154
+ end
155
+
156
+ left_margin = kvs.has_key?(:ylabel) ? 0.05 : 0
157
+ right_margin = if %i[contour contourf hexbin heatmap nonuniformheatmap polarheatmap
158
+ nonuniformpolarheatmap surface trisurf volume].include?(kind)
159
+ (vp2 - vp1) * 0.1
160
+ else
161
+ 0
162
+ end
163
+ bottom_margin = kvs.has_key?(:xlabel) ? 0.05 : 0
164
+ top_margin = kvs.has_key?(:title) ? 0.075 : 0
165
+
166
+ viewport = [vp1 + (0.075 + left_margin) * (vp2 - vp1),
167
+ vp1 + (0.95 - right_margin) * (vp2 - vp1),
168
+ vp3 + (0.075 + bottom_margin) * (vp4 - vp3),
169
+ vp3 + (0.975 - top_margin) * (vp4 - vp3)]
170
+
171
+ if %i[line step scatter stem].include?(kind) && kvs[:labels]
172
+ location = kvs[:location] || 1
173
+ if [11, 12, 13].include?(location)
174
+ w, h = legend_size
175
+ viewport[1] -= w + 0.1
176
+ end
177
+ end
178
+
179
+ GR.setviewport(*viewport)
180
+
181
+ kvs[:viewport] = viewport
182
+ kvs[:vp] = vp
183
+ kvs[:ratio] = ratio
184
+
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
200
+
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)
207
+ end
208
+ end
209
+
210
+ def set_window(kind)
211
+ scale = 0
212
+ unless %i[polar polarhist polarheatmap nonuniformpolarheatmap].include?(kind)
213
+ scale |= GR::OPTION_X_LOG if kvs[:xlog]
214
+ scale |= GR::OPTION_Y_LOG if kvs[:ylog]
215
+ scale |= GR::OPTION_Z_LOG if kvs[:zlog]
216
+ scale |= GR::OPTION_FLIP_X if kvs[:xflip]
217
+ scale |= GR::OPTION_FLIP_Y if kvs[:yflip]
218
+ scale |= GR::OPTION_FLIP_Z if kvs[:zflip]
219
+ end
220
+ kvs[:scale] = scale
221
+
222
+ if kvs.has_key?(:panzoom)
223
+ xmin, xmax, ymin, ymax = GR.panzoom(*kvs[:panzoom])
224
+ kvs[:xrange] = [xmin, xmax]
225
+ kvs[:yrange] = [ymin, ymax]
226
+ else
227
+ minmax(kind)
228
+ end
229
+
230
+ major_count = if %i[wireframe surface plot3 scatter3 polar polarhist
231
+ polarheatmap nonuniformpolarheatmap trisurf volume].include?(kind)
232
+ 2
233
+ else
234
+ 5
235
+ end
236
+
237
+ kvs[:xticks] = [kvs[:xticks], major_count] if kvs[:xticks].is_a? Numeric
238
+ kvs[:yticks] = [kvs[:yticks], major_count] if kvs[:yticks].is_a? Numeric
239
+ kvs[:zticks] = [kvs[:zticks], major_count] if kvs[:zticks].is_a? Numeric
240
+
241
+ xmin, xmax = kvs[:xrange]
242
+ if %i[heatmap polarheatmap].include?(kind) && kvs.has_key?(:xlim)
243
+ xmin -= 0.5
244
+ xmax += 0.5
245
+ end
246
+ xtick, majorx = if (scale & GR::OPTION_X_LOG) == 0
247
+ if !%i[heatmap polarheatmap].include?(kind) &&
248
+ !kvs.has_key?(:xlim) &&
249
+ !kvs[:panzoom]
250
+ xmin, xmax = GR.adjustlimits(xmin, xmax)
251
+ end
252
+ if kvs.has_key?(:xticks)
253
+ kvs[:xticks]
254
+ else
255
+ [auto_tick(xmin, xmax) / major_count, major_count]
256
+ end
257
+ else
258
+ [1, 1]
259
+ end
260
+ xorg = (scale & GR::OPTION_FLIP_X) == 0 ? [xmin, xmax] : [xmax, xmin]
261
+ kvs[:xaxis] = xtick, xorg, majorx
262
+
263
+ ymin, ymax = kvs[:yrange]
264
+ if %i[heatmap polarheatmap].include?(kind) && kvs.has_key?(:ylim)
265
+ ymin -= 0.5
266
+ ymax += 0.5
267
+ 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
274
+ end
275
+ ytick, majory = if (scale & GR::OPTION_Y_LOG) == 0
276
+ if !%i[heatmap polarheatmap].include?(kind) &&
277
+ !kvs.has_key?(:ylim) &&
278
+ !kvs[:panzoom]
279
+ ymin, ymax = GR.adjustlimits(ymin, ymax)
280
+ end
281
+ if kvs.has_key?(:yticks)
282
+ kvs[:yticks]
283
+ else
284
+ [auto_tick(ymin, ymax) / major_count, major_count]
285
+ end
286
+ else
287
+ [1, 1]
288
+ end
289
+ yorg = (scale & GR::OPTION_FLIP_Y) == 0 ? [ymin, ymax] : [ymax, ymin]
290
+ kvs[:yaxis] = ytick, yorg, majory
291
+
292
+ if %i[wireframe surface plot3 scatter3 trisurf volume].include?(kind)
293
+ zmin, zmax = kvs[:zrange]
294
+ ztick, majorz = if (scale & GR::OPTION_Z_LOG) == 0
295
+ zmin, zmax = GR.adjustlimits(zmin, zmax) if kvs.has_key?(:zlim)
296
+ if kvs.has_key?(:zticks)
297
+ kvs[:zticks]
298
+ else
299
+ [auto_tick(zmin, zmax) / major_count, major_count]
300
+ end
301
+ else
302
+ [1, 1]
303
+ end
304
+ zorg = (scale & GR::OPTION_FLIP_Z) == 0 ? [zmin, zmax] : [zmax, zmin]
305
+ kvs[:zaxis] = ztick, zorg, majorz
306
+ end
307
+
308
+ kvs[:window] = xmin, xmax, ymin, ymax
309
+ if %i[polar polarhist polarheatmap nonuniformpolarheatmap trisurf].include?(kind)
310
+ GR.setwindow(-1, 1, -1, 1)
311
+ else
312
+ GR.setwindow(xmin, xmax, ymin, ymax)
313
+ end
314
+ if %i[wireframe surface plot3 scatter3 trisurf volume].include?(kind)
315
+ rotation = kvs[:rotation] || 40
316
+ tilt = kvs[:tilt] || 60
317
+ GR.setwindow3d(xmin, xmax, ymin, ymax, zmin, zmax)
318
+ GR.setspace3d(-rotation, tilt, 30, 0)
319
+ end
320
+
321
+ kvs[:scale] = scale
322
+ GR.setscale(scale)
323
+ end
324
+
325
+ def draw_axes(kind, pass = 1)
326
+ viewport = kvs[:viewport]
327
+ vp = kvs[:vp]
328
+ _ratio = kvs[:ratio]
329
+ xtick, xorg, majorx = kvs[:xaxis]
330
+ ytick, yorg, majory = kvs[:yaxis]
331
+ drawgrid = kvs.has_key?(:grid) ? kvs[:grid] : true
332
+ xtick = 10 if kvs[:scale] & GR::OPTION_X_LOG != 0
333
+ ytick = 10 if kvs[:scale] & GR::OPTION_Y_LOG != 0
334
+ GR.setlinecolorind(1)
335
+ diag = Math.sqrt((viewport[1] - viewport[0])**2 + (viewport[3] - viewport[2])**2)
336
+ GR.setlinewidth(1)
337
+ ticksize = 0.0075 * diag
338
+ if %i[wireframe surface plot3 scatter3 trisurf volume].include?(kind)
339
+ charheight = [0.024 * diag, 0.012].max
340
+ GR.setcharheight(charheight)
341
+ ztick, zorg, majorz = kvs[:zaxis]
342
+ 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)
345
+ 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)
348
+ end
349
+ else
350
+ charheight = [0.018 * diag, 0.012].max
351
+ GR.setcharheight(charheight)
352
+ if %i[heatmap nonuniformheatmap shade].include?(kind)
353
+ ticksize = -ticksize
354
+ elsif drawgrid
355
+ GR.grid(xtick, ytick, 0, 0, majorx, majory)
356
+ end
357
+ if kvs.has_key?(:xticklabels) || kvs.has_key?(:yticklabels)
358
+ fx = if kvs.has_key?(:xticklabels)
359
+ GRCommons::Fiddley::Function.new(
360
+ :void, %i[double double string double]
361
+ ) do |x, y, _svalue, value|
362
+ label = value < 0 ? '' : kvs[:xticklabels][value] || ''
363
+ GR.textext(x, y, label)
364
+ end
365
+ else
366
+ GRCommons::Fiddley::Function.new(
367
+ :void, %i[double double string double]
368
+ ) do |x, y, _svalue, value|
369
+ GR.textext(x, y, value.to_s)
370
+ end
371
+ end
372
+ fy = if kvs.has_key?(:yticklabels)
373
+ GRCommons::Fiddley::Function.new(
374
+ :void, %i[double double string double]
375
+ ) do |x, y, _svalue, value|
376
+ label = value < 0 ? '' : kvs[:yticklabels][value] || ''
377
+ GR.textext(x, y, label)
378
+ end
379
+ else
380
+ GRCommons::Fiddley::Function.new(
381
+ :void, %i[double double string double]
382
+ ) do |x, y, _svalue, value|
383
+ GR.textext(x, y, value.to_s)
384
+ end
385
+ end
386
+ GR.axeslbl(xtick, ytick, xorg[0], yorg[0], majorx, majory, ticksize, fx, fy)
387
+ else
388
+ GR.axes(xtick, ytick, xorg[0], yorg[0], majorx, majory, ticksize)
389
+ end
390
+ GR.axes(xtick, ytick, xorg[1], yorg[1], -majorx, -majory, -ticksize)
391
+ end
392
+
393
+ if kvs.has_key?(:title)
394
+ GR.savestate
395
+ GR.settextalign(GR::TEXT_HALIGN_CENTER, GR::TEXT_VALIGN_TOP)
396
+ text(0.5 * (viewport[0] + viewport[1]), vp[3], kvs[:title].to_s)
397
+ GR.restorestate
398
+ 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
418
+ end
419
+ end
420
+
421
+ def draw_polar_axes
422
+ viewport = kvs[:viewport]
423
+ diag = Math.sqrt((viewport[1] - viewport[0])**2 + (viewport[3] - viewport[2])**2)
424
+ charheight = [0.018 * diag, 0.012].max
425
+
426
+ window = kvs[:window]
427
+ rmin = window[2]
428
+ rmax = window[3]
429
+
430
+ GR.savestate
431
+ GR.setcharheight(charheight)
432
+ GR.setlinetype(GR::LINETYPE_SOLID)
433
+
434
+ 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?
439
+ GR.setlinecolorind(88)
440
+ GR.drawarc(-r, r, -r, r, 0, 359) if i > 0
441
+ GR.settextalign(GR::TEXT_HALIGN_LEFT, GR::TEXT_VALIGN_HALF)
442
+ 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)
447
+ end
448
+ 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
457
+ GR.restorestate
458
+ end
459
+
460
+ def plot_polar(θ, ρ)
461
+ window = kvs[:window]
462
+ rmax = window[3].to_f
463
+ ρ = ρ.map { |i| i / rmax }
464
+ n = ρ.length
465
+ x = []
466
+ y = []
467
+ n.times do |i|
468
+ x << ρ[i] * Math.cos(θ[i])
469
+ y << ρ[i] * Math.sin(θ[i])
470
+ end
471
+ GR.polyline(x, y)
472
+ end
473
+
474
+ def plot_img(img)
475
+ viewport = kvs[:vp].clone
476
+ viewport[3] -= 0.05 if kvs.has_key?(:title)
477
+ vp = kvs[:vp]
478
+
479
+ if img.is_a? String
480
+ width, height, data = GR.readimage(img)
481
+ else
482
+ height, width = img.shape
483
+ cmin, cmax = kvs[:crange]
484
+ data = img.map { |i| normalize_color(i, cmin, cmax) }
485
+ data = data.map { |i| (1000 + i * 255).round }
486
+ end
487
+
488
+ if width * (viewport[3] - viewport[2]) < height * (viewport[1] - viewport[0])
489
+ w = width.to_f / height * (viewport[3] - viewport[2])
490
+ xmin = [0.5 * (viewport[0] + viewport[1] - w), viewport[0]].max
491
+ xmax = [0.5 * (viewport[0] + viewport[1] + w), viewport[1]].min
492
+ ymin = viewport[2]
493
+ ymax = viewport[3]
494
+ else
495
+ h = height.to_f / width * (viewport[1] - viewport[0])
496
+ xmin = viewport[0]
497
+ xmax = viewport[1]
498
+ ymin = [0.5 * (viewport[3] + viewport[2] - h), viewport[2]].max
499
+ ymax = [0.5 * (viewport[3] + viewport[2] + h), viewport[3]].min
500
+ end
501
+
502
+ GR.selntran(0)
503
+ GR.setscale(0)
504
+ if kvs.has_key?(:xflip)
505
+ tmp = xmax
506
+ xmax = xmin
507
+ xmin = tmp
508
+ end
509
+ if kvs.has_key?(:yflip)
510
+ tmp = ymax
511
+ ymax = ymin
512
+ ymin = tmp
513
+ end
514
+ if img.is_a? String
515
+ GR.drawimage(xmin, xmax, ymin, ymax, width, height, data)
516
+ else
517
+ GR.cellarray(xmin, xmax, ymin, ymax, width, height, data)
518
+ end
519
+
520
+ if kvs.has_key?(:title)
521
+ GR.savestate
522
+ GR.settextalign(GR::TEXT_HALIGN_CENTER, GR::TEXT_VALIGN_TOP)
523
+ text(0.5 * (viewport[0] + viewport[1]), vp[3], kvs[:title].to_s)
524
+ GR.restorestate
525
+ end
526
+ GR.selntran(1)
527
+ end
528
+
529
+ def plot_iso(v)
530
+ viewport = kvs[:viewport]
531
+
532
+ if viewport[3] - viewport[2] < viewport[1] - viewport[0]
533
+ width = viewport[3] - viewport[2]
534
+ centerx = 0.5 * (viewport[0] + viewport[1])
535
+ xmin = [centerx - 0.5 * width, viewport[0]].max
536
+ xmax = [centerx + 0.5 * width, viewport[1]].min
537
+ ymin = viewport[2]
538
+ ymax = viewport[3]
539
+ else
540
+ height = viewport[1] - viewport[0]
541
+ centery = 0.5 * (viewport[2] + viewport[3])
542
+ xmin = viewport[0]
543
+ xmax = viewport[1]
544
+ ymin = [centery - 0.5 * height, viewport[2]].max
545
+ ymax = [centery + 0.5 * height, viewport[3]].min
546
+ end
547
+
548
+ GR.selntran(0)
549
+ v = Numo::DFloat.cast(v) if v.is_a? Array
550
+ values = ((v - v.min) / (v.max - v.min) * (2**16 - 1)).round
551
+ values = Numo::UInt16.cast(values)
552
+ nx, ny, nz = v.shape
553
+ isovalue = ((kvs[:isovalue] || 0.5) - v.min) / (v.max - v.min)
554
+ rotation = ((kvs[:rotation] || 40) * Math::PI / 180.0)
555
+ tilt = ((kvs[:tilt] || 70) * Math::PI / 180.0)
556
+ r = 2.5
557
+ GR3.clear
558
+ mesh = GR3.createisosurfacemesh(values, [2.0 / (nx - 1), 2.0 / (ny - 1), 2.0 / (nz - 1)],
559
+ [-1, -1, -1],
560
+ (isovalue * (2**16 - 1)).round)
561
+ color = kvs[:color] || [0.0, 0.5, 0.8]
562
+ GR3.setbackgroundcolor(1, 1, 1, 0)
563
+ GR3.drawmesh(mesh, 1, [0, 0, 0], [0, 0, 1], [0, 1, 0], color, [1, 1, 1])
564
+ GR3.cameralookat(r * Math.sin(tilt) * Math.sin(rotation),
565
+ r * Math.cos(tilt), r * Math.sin(tilt) * Math.cos(rotation),
566
+ 0, 0, 0, 0, 1, 0)
567
+ GR3.drawimage(xmin, xmax, ymin, ymax, 500, 500, GR3::DRAWABLE_GKS)
568
+ GR3.deletemesh(mesh)
569
+ GR.selntran(1)
570
+ end
571
+
572
+ def colorbar(off = 0, colors = 256)
573
+ GR.savestate
574
+ viewport = kvs[:viewport]
575
+ zmin, zmax = kvs[:zrange]
576
+ mask = (GR::OPTION_Z_LOG | GR::OPTION_FLIP_Y | GR::OPTION_FLIP_Z)
577
+ options = if kvs.has_key?(:zflip)
578
+ (GR.inqscale | GR::OPTION_FLIP_Y)
579
+ elsif kvs.has_key?(:yflip)
580
+ GR.inqscale & ~GR::OPTION_FLIP_Y
581
+ else
582
+ GR.inqscale
583
+ end
584
+ GR.setscale(options & mask)
585
+ h = 0.5 * (zmax - zmin) / (colors - 1)
586
+ GR.setwindow(0, 1, zmin, zmax)
587
+ GR.setviewport(viewport[1] + 0.02 + off, viewport[1] + 0.05 + off,
588
+ viewport[2], viewport[3])
589
+ l = linspace(1000, 1255, colors).map(&:round)
590
+ GR.cellarray(0, 1, zmax + h, zmin - h, 1, colors, l)
591
+ GR.setlinecolorind(1)
592
+ diag = Math.sqrt((viewport[1] - viewport[0])**2 + (viewport[3] - viewport[2])**2)
593
+ charheight = [0.016 * diag, 0.012].max
594
+ GR.setcharheight(charheight)
595
+ if kvs[:scale] & GR::OPTION_Z_LOG == 0
596
+ ztick = auto_tick(zmin, zmax)
597
+ GR.axes(0, ztick, 1, zmin, 0, 1, 0.005)
598
+ else
599
+ GR.setscale(GR::OPTION_Y_LOG)
600
+ GR.axes(0, 2, 1, zmin, 0, 1, 0.005)
601
+ end
602
+ GR.restorestate
603
+ end
604
+
605
+ def plot_data(_figure = true)
606
+ # GR.init
607
+
608
+ # target = GR.displayname
609
+ # if flag && target != None
610
+ # if target == "js" || target == "meta"
611
+ # send_meta(0)
612
+ # else
613
+ # send_serialized(target)
614
+ # end
615
+ # return
616
+ # end
617
+
618
+ kind = kvs[:kind] || :line
619
+ GR.clearws if kvs[:clear]
620
+
621
+ if scheme != 0
622
+ # Not yet.
623
+ end
624
+
625
+ if kvs.has_key?(:font)
626
+ name = kvs[:font]
627
+ # 'Cmuserif-Math' => :cmuserif_math
628
+ sym_name = name.to_s.gsub('-', '_').downcase.to_sym
629
+ if FONTS.include?(sym_name)
630
+ font = FONTS[sym_name]
631
+ GR.settextfontprec(font, font > 200 ? 3 : 0)
632
+ else
633
+ font = GR.loadfont(name)
634
+ if font >= 0
635
+ GR.settextfontprec(font, 3)
636
+ else
637
+ warn "Unknown font name: #{name}"
638
+ end
639
+ end
640
+ else
641
+ # The following fonts are the default in GR.jl
642
+ # Japanese, Chinese, Korean, etc. cannot be displayed.
643
+
644
+ # GR.settextfontprec(232, 3) # CM Serif Roman
645
+ end
646
+
647
+ set_viewport(kind, kvs[:subplot])
648
+ unless kvs[:ax]
649
+ set_window(kind)
650
+ if %i[polar polarhist].include?(kind)
651
+ draw_polar_axes
652
+ elsif !%i[imshow isosurface polarheatmap nonuniformpolarheatmap].include?(kind)
653
+ draw_axes(kind)
654
+ end
655
+ end
656
+
657
+ if kvs.has_key?(:colormap)
658
+ GR.setcolormap(kvs[:colormap])
659
+ else
660
+ GR.setcolormap(GR::COLORMAP_VIRIDIS)
661
+ end
662
+
663
+ GR.uselinespec(' ')
664
+ args.each do |x, y, z, c, spec|
665
+ spec ||= kvs[:spec] ||= ''
666
+ GR.savestate
667
+ GR.settransparency(kvs[:alpha]) if kvs.has_key?(:alpha)
668
+
669
+ case kind
670
+
671
+ when :line
672
+ 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
681
+ mask = GR.uselinespec(spec)
682
+ if hasline(mask)
683
+ where = kvs[:where] || 'mid'
684
+ n = x.length
685
+ xs = [x[0]]
686
+ case where
687
+ when 'pre'
688
+ ys = [y[0]]
689
+ (n - 1).times do |i|
690
+ xs << x[i] << x[i + 1]
691
+ ys << y[i + 1] << y[i + 1]
692
+ end
693
+ when 'post'
694
+ ys = [y[0]]
695
+ (n - 1).times do |i|
696
+ xs << x[i + 1] << x[i + 1]
697
+ ys << y[i] << y[i + 1]
698
+ end
699
+ else
700
+ ys = []
701
+ (n - 1).times do |i|
702
+ xs << 0.5 * (x[i] + x[i + 1]) << 0.5 * (x[i] + x[i + 1])
703
+ ys << y[i] << y[i]
704
+ end
705
+ xs << x[n - 1]
706
+ ys << y[n - 1] << y[n - 1]
707
+ end
708
+ GR.polyline(xs, ys)
709
+ end
710
+ GR.polymarker(x, y) if hasmarker(mask)
711
+
712
+ when :scatter
713
+ 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)
717
+
718
+ when :stem
719
+ GR.setlinecolorind(1)
720
+ GR.polyline(kvs[:window][0..1], [0, 0])
721
+ GR.setmarkertype(GR::MARKERTYPE_SOLID_CIRCLE)
722
+ GR.uselinespec(spec)
723
+ x = x.to_a if narray?(x)
724
+ y = y.to_a if narray?(y)
725
+ x.zip(y).each do |xi, yi|
726
+ GR.polyline([xi, xi], [0, yi])
727
+ end
728
+ GR.polymarker(x, y)
729
+
730
+ 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
751
+ end
752
+
753
+ when :polarhist
754
+ ymax = kvs[:window][3].to_f
755
+ ρ = y.map { |i| i / ymax }
756
+ θ = x.map { |i| i * 180 / Math::PI }
757
+ (1...ρ.length).each do |i|
758
+ GR.setfillcolorind(989)
759
+ GR.setfillintstyle(GR::INTSTYLE_SOLID)
760
+ GR.fillarc(-ρ[i], ρ[i], -ρ[i], ρ[i], θ[i - 1], θ[i])
761
+ GR.setfillcolorind(1)
762
+ GR.setfillintstyle(GR::INTSTYLE_HOLLOW)
763
+ GR.fillarc(-ρ[i], ρ[i], -ρ[i], ρ[i], θ[i - 1], θ[i])
764
+ end
765
+
766
+ when :polarheatmap, :nonuniformpolarheatmap
767
+ w, h = z.shape
768
+ cmap = colormap
769
+ cmin, cmax = kvs[:zrange]
770
+ data = z.map { |i| normalize_color(i, cmin, cmax) }
771
+ data.reverse(axis: 0) if kvs[:xflip]
772
+ data.reverse(axis: 1) if kvs[:yflip]
773
+ colors = data * 255 + 1000
774
+ colors = colors.transpose # Julia is column major
775
+ case kind
776
+ when :polarheatmap
777
+ GR.polarcellarray(0, 0, 0, 360, 0, 1, w, h, colors)
778
+ when :nonuniformpolarheatmap
779
+ ymax = y.max.to_f
780
+ ρ = y.map { |i| i / ymax }
781
+ θ = x.map { |i| i * 180 / Math::PI }
782
+ GR.nonuniformpolarcellarray(θ, ρ, w, h, colors)
783
+ end
784
+ draw_polar_axes
785
+ kvs[:zrange] = [cmin, cmax]
786
+ colorbar
787
+
788
+ when :contour, :contourf
789
+ zmin, zmax = kvs[:zrange]
790
+ if narray?(z) && z.ndim == 2
791
+ a, b = z.shape
792
+ x = (1..b).to_a
793
+ y = (1..a).to_a
794
+ zmin, zmax = z.minmax
795
+ elsif equal_length(x, y, z)
796
+ x, y, z = GR.gridit(x, y, z, 200, 200)
797
+ zmin, zmax = z.compact.minmax # compact : removed nil
798
+ end
799
+
800
+ # kvs[:zlim] is supposed to be Array or Range
801
+ if kvs.has_key?(:zlim)
802
+ zmin = kvs[:zlim].first if kvs[:zlim].first
803
+ zmax = kvs[:zlim].last if kvs[:zlim].last
804
+ end
805
+ GR.setprojectiontype(0)
806
+ GR.setspace(zmin, zmax, 0, 90)
807
+ levels = kvs[:levels] || 0
808
+ clabels = kvs[:clabels] || false
809
+ if levels.is_a? Integer
810
+ hmin, hmax = GR.adjustrange(zmin, zmax)
811
+ h = linspace(hmin, hmax, levels == 0 ? 21 : levels + 1)
812
+ else
813
+ h = levels
814
+ end
815
+ case kind
816
+ when :contour
817
+ GR._contour_(x, y, h, z, clabels ? 1 : 1000)
818
+ when :contourf
819
+ GR._contourf_(x, y, h, z, clabels ? 1 : 0)
820
+ end
821
+ colorbar(0, h.length)
822
+
823
+ when :hexbin
824
+ nbins = kvs[:nbins] || 40
825
+ cntmax = GR._hexbin_(x, y, nbins)
826
+ if cntmax > 0
827
+ kvs[:zrange] = [0, cntmax]
828
+ colorbar
829
+ end
830
+
831
+ when :heatmap, :nonuniformheatmap
832
+ case z
833
+ 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
840
+ when ->(obj) { narray?(obj) }
841
+ w, h = z.shape
842
+ else
843
+ raise
844
+ end
845
+ cmap = colormap
846
+ cmin, cmax = kvs[:crange]
847
+ levels = kvs[:levels] || 256
848
+ data = z.flatten.to_a.map { |i| normalize_color(i, cmin, cmax) } # NArray -> Array
849
+ if kind == :heatmap
850
+ rgba = data.map { |v| to_rgba(v, cmap) }
851
+ GR.drawimage(0.5, w + 0.5, h + 0.5, 0.5, w, h, rgba)
852
+ else
853
+ colors = data.map { |i| (1000 + i * 255).round }
854
+ GR.nonuniformcellarray(x, y, w, h, colors)
855
+ end
856
+ colorbar(0, levels)
857
+
858
+ when :wireframe
859
+ if narray?(z) && z.ndim == 2
860
+ a, b = z.shape
861
+ x = (1..b).to_a
862
+ y = (1..a).to_a
863
+ elsif equal_length(x, y, z)
864
+ x, y, z = GR.gridit(x, y, z, 50, 50)
865
+ end
866
+ GR.setfillcolorind(0)
867
+ GR._surface_(x, y, z, GR::OPTION_FILLED_MESH)
868
+ draw_axes(kind, 2)
869
+
870
+ when :surface
871
+ if narray?(z) && z.ndim == 2
872
+ a, b = z.shape
873
+ x = (1..b).to_a
874
+ y = (1..a).to_a
875
+ elsif equal_length(x, y, z)
876
+ x, y, z = GR.gridit(x, y, z, 200, 200)
877
+ end
878
+ if kvs[:accelerate] == false
879
+ GR._surface_(x, y, z, GR::OPTION_COLORED_MESH)
880
+ else
881
+ require 'gr3'
882
+ GR3.clear
883
+ GR3.surface(x, y, z, GR::OPTION_COLORED_MESH)
884
+ end
885
+ draw_axes(kind, 2)
886
+ colorbar(0.05)
887
+
888
+ when :volume
889
+ algorithm = kvs[:algorithm] || 0
890
+ require 'gr3'
891
+ GR3.clear
892
+ dmin, dmax = GR3.volume(z, algorithm)
893
+ draw_axes(kind, 2)
894
+ kvs[:zrange] = [dmin, dmax]
895
+ colorbar(0.05)
896
+
897
+ when :plot3
898
+ mask = GR.uselinespec(spec)
899
+ GR.polyline3d(x, y, z) if hasline(mask)
900
+ GR.polymarker3d(x, y, z) if hasmarker(mask)
901
+ draw_axes(kind, 2)
902
+
903
+ when :scatter3
904
+ GR.setmarkertype(GR::MARKERTYPE_SOLID_CIRCLE)
905
+ if c
906
+ cmin, cmax = kvs[:crange]
907
+ c = c.map { |i| normalize_color(i, cmin, cmax) }
908
+ cind = c.map { |i| (1000 + i * 255).round }
909
+ x.length.times do |i|
910
+ GR.setmarkercolorind(cind[i])
911
+ GR.polymarker3d([x[i]], [y[i]], [z[i]])
912
+ end
913
+ else
914
+ GR.polymarker3d(x, y, z)
915
+ end
916
+ draw_axes(kind, 2)
917
+
918
+ when :imshow
919
+ plot_img(z)
920
+
921
+ when :isosurface
922
+ plot_iso(z)
923
+
924
+ when :polar
925
+ GR.uselinespec(spec)
926
+ plot_polar(x, y)
927
+
928
+ when :trisurf
929
+ GR.trisurface(x, y, z)
930
+ draw_axes(kind, 2)
931
+ colorbar(0.05)
932
+
933
+ when :tricont
934
+ zmin, zmax = kvs[:zrange]
935
+ levels = linspace(zmin, zmax, 20)
936
+ GR.tricontour(x, y, z, levels)
937
+
938
+ when :shade
939
+ xform = kvs[:xform] || 5
940
+ if x.to_a.include? Float::NAN # FIXME: Ruby is different from Julia?
941
+ # How to check NArray?
942
+ GR.shadelines(x, y, xform: xform)
943
+ else
944
+ GR.shadepoints(x, y, xform: xform)
945
+ 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
+ end
957
+
958
+ GR.restorestate
959
+ end
960
+
961
+ draw_legend if %i[line step scatter stem].include?(kind) && kvs.has_key?(:labels)
962
+
963
+ if kvs[:update]
964
+ GR.updatews
965
+ # if GR.isinline()
966
+ # restore_context()
967
+ # return GR.show()
968
+ # end
969
+ end
970
+
971
+ # flag && restore_context()
972
+ end
973
+
974
+ def draw_legend
975
+ w, h = legend_size
976
+ viewport = kvs[:viewport]
977
+ location = kvs[:location] || 1
978
+ num_labels = kvs[:labels].length
979
+ GR.savestate
980
+ GR.selntran 0
981
+ GR.setscale 0
982
+ px = case location
983
+ when 11, 12, 13
984
+ viewport[1] + 0.11
985
+ when 8, 9, 10
986
+ 0.5 * (viewport[0] + viewport[1] - w + 0.05)
987
+ when 2, 3, 6
988
+ viewport[0] + 0.11
989
+ else
990
+ viewport[1] - 0.05 - w
991
+ end
992
+ py = case location
993
+ when 5, 6, 7, 10, 12
994
+ 0.5 * (viewport[2] + viewport[3] + h - 0.03)
995
+ when 13
996
+ viewport[2] + h
997
+ when 3, 4, 8
998
+ viewport[2] + h + 0.03
999
+ when 11
1000
+ viewport[3] - 0.03
1001
+ else
1002
+ viewport[3] - 0.06
1003
+ end
1004
+ GR.setfillintstyle(GR::INTSTYLE_SOLID)
1005
+ GR.setfillcolorind(0)
1006
+ GR.fillrect(px - 0.08, px + w + 0.02, py + 0.03, py - h)
1007
+ GR.setlinetype(GR::LINETYPE_SOLID)
1008
+ GR.setlinecolorind(1)
1009
+ GR.setlinewidth(1)
1010
+ GR.drawrect(px - 0.08, px + w + 0.02, py + 0.03, py - h)
1011
+ i = 0
1012
+ GR.uselinespec(' ')
1013
+ args.each do |_x, _y, _z, _c, spec|
1014
+ if i < num_labels
1015
+ label = kvs[:labels][i]
1016
+ label = label.to_s
1017
+ _tbx, tby = inqtext(0, 0, label)
1018
+ dy = [(tby[2] - tby[0]) - 0.03, 0].max
1019
+ py -= 0.5 * dy
1020
+ end
1021
+ GR.savestate
1022
+ mask = GR.uselinespec(spec || '')
1023
+ GR.polyline([px - 0.07, px - 0.01], [py, py]) if hasline(mask)
1024
+ GR.polymarker([px - 0.06, px - 0.02], [py, py]) if hasmarker(mask)
1025
+ GR.restorestate
1026
+ GR.settextalign(GR::TEXT_HALIGN_LEFT, GR::TEXT_VALIGN_HALF)
1027
+ if i < num_labels
1028
+ text(px, py, label)
1029
+ py -= 0.5 * dy
1030
+ i += 1
1031
+ end
1032
+ py -= 0.03
1033
+ end
1034
+ GR.selntran(1)
1035
+ GR.restorestate
1036
+ end
1037
+
1038
+ def to_svg
1039
+ ## Need IRuby improvemend.
1040
+ GR.show(false) if ENV['GKS_WSTYPE'] == 'svg'
1041
+ end
1042
+
1043
+ private
1044
+
1045
+ def hasline(mask)
1046
+ mask == 0x00 || (mask & 0x01 != 0)
1047
+ end
1048
+
1049
+ def hasmarker(mask)
1050
+ mask & 0x02 != 0
1051
+ end
1052
+
1053
+ def colormap
1054
+ # rgb
1055
+ Array.new(256) do |colorind|
1056
+ color = GR.inqcolor(1000 + colorind)
1057
+ [(color & 0xff) / 255.0,
1058
+ ((color >> 8) & 0xff) / 255.0,
1059
+ ((color >> 16) & 0xff) / 255.0]
1060
+ end
1061
+ end
1062
+
1063
+ def to_rgba(value, cmap)
1064
+ begin
1065
+ r, g, b = cmap[(value * 255).round]
1066
+ a = 1.0
1067
+ rescue StandardError # nil
1068
+ r = 0
1069
+ g = 0
1070
+ b = 0
1071
+ a = 0
1072
+ end
1073
+
1074
+ ((a * 255).round << 24) + ((b * 255).round << 16) +
1075
+ ((g * 255).round << 8) + (r * 255).round
1076
+ end
1077
+
1078
+ # https://gist.github.com/rysk-t/8d1aef0fb67abde1d259#gistcomment-1925021
1079
+ def linspace(low, high, num)
1080
+ [*0..(num - 1)].collect { |i| low + i.to_f * (high - low) / (num - 1) }
1081
+ end
1082
+
1083
+ def plot_args(args)
1084
+ # FIXME
1085
+ args = [args] unless args.all? do |i|
1086
+ i.is_a?(Array) && (i[0].is_a?(Array) || narray?(i[0]))
1087
+ end
1088
+ args.map do |xyzc|
1089
+ spec = nil
1090
+ case xyzc.last
1091
+ when String
1092
+ spec = xyzc.pop
1093
+ when Hash
1094
+ spec = xyzc.pop[:spec]
1095
+ end
1096
+
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
1106
+ end
1107
+ [x, y, z, c, spec]
1108
+ end
1109
+ end
1110
+
1111
+ # Normalize a color c with the range [cmin, cmax]
1112
+ # 0 <= normalize_color(c, cmin, cmax) <= 1
1113
+ def normalize_color(c, cmin, cmax)
1114
+ # NOTE: narray.map{|i| normalize_color(i)} There's room for speedup.
1115
+ c = c.to_f # if c is Integer
1116
+ c = c.clamp(cmin, cmax) - cmin
1117
+ c /= (cmax - cmin) if cmin != cmax
1118
+ c
1119
+ end
1120
+
1121
+ def inqtext(x, y, s)
1122
+ s = s.to_s
1123
+ if s.length >= 2 && s[0] == '$' && s[-1] == '$'
1124
+ GR.inqmathtex(x, y, s[1..-2])
1125
+ elsif s.include?('\\') || s.include?('_') || s.include?('^')
1126
+ GR.inqtextext(x, y, s)
1127
+ else
1128
+ GR.inqtext(x, y, s)
1129
+ end
1130
+ end
1131
+
1132
+ def text(x, y, s)
1133
+ s = s.to_s
1134
+ if s.length >= 2 && s[0] == '$' && s[-1] == '$'
1135
+ GR.mathtex(x, y, s[1..-2])
1136
+ elsif s.include?('\\') || s.include?('_') || s.include?('^')
1137
+ GR.textext(x, y, s)
1138
+ else
1139
+ GR.text(x, y, s)
1140
+ end
1141
+ end
1142
+
1143
+ def fix_minmax(a, b)
1144
+ if a == b
1145
+ a -= a != 0 ? 0.1 * a : 0.1
1146
+ b += b != 0 ? 0.1 * b : 0.1
1147
+ end
1148
+ [a, b]
1149
+ end
1150
+
1151
+ def minmax(kind)
1152
+ xmin = ymin = zmin = cmin = Float::INFINITY
1153
+ xmax = ymax = zmax = cmax = -Float::INFINITY
1154
+ scale = kvs[:scale]
1155
+ args.each do |x, y, z, c|
1156
+ if x
1157
+ if scale & GR::OPTION_X_LOG != 0
1158
+ # duck typing for NArray
1159
+ x = x.map { |v| v > 0 ? v : Float::NAN }
1160
+ end
1161
+ x0, x1 = x.minmax
1162
+ xmin = [x0, xmin].min
1163
+ xmax = [x1, xmax].max
1164
+ elsif kind == :volume
1165
+ xmin = -1
1166
+ xmax = 1
1167
+ else
1168
+ xmin = 0
1169
+ xmax = 1
1170
+ end
1171
+ if y
1172
+ if scale & GR::OPTION_Y_LOG != 0
1173
+ y = y.map { |v| v > 0 ? v : Float::NAN }
1174
+ end
1175
+ y0, y1 = y.minmax
1176
+ ymin = [y0, ymin].min
1177
+ ymax = [y1, ymax].max
1178
+ elsif kind == :volume
1179
+ ymin = -1
1180
+ ymax = 1
1181
+ else
1182
+ ymin = 0
1183
+ ymax = 1
1184
+ end
1185
+ if z
1186
+ if scale & GR::OPTION_Z_LOG != 0
1187
+ z = z.map { |v| v > 0 ? v : Float::NAN }
1188
+ end
1189
+ z0, z1 = z.minmax
1190
+ zmin = [z0, zmin].min
1191
+ zmax = [z1, zmax].max
1192
+ end
1193
+ if c
1194
+ c0, c1 = c.minmax
1195
+ cmin = [c0, cmin].min
1196
+ cmax = [c1, cmax].max
1197
+ elsif z
1198
+ z0, z1 = z.minmax
1199
+ cmin = [z0, zmin].min
1200
+ cmax = [z1, zmax].max
1201
+ end
1202
+ end
1203
+ xmin, xmax = fix_minmax(xmin, xmax)
1204
+ ymin, ymax = fix_minmax(ymin, ymax)
1205
+ zmin, zmax = fix_minmax(zmin, zmax)
1206
+
1207
+ # 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)]
1211
+
1212
+ if kvs.has_key?(:clim)
1213
+ c0, c1 = kvs[:clim]
1214
+ c0 ||= cmin
1215
+ c1 ||= cmax
1216
+ kvs[:crange] = [c0, c1]
1217
+ else
1218
+ kvs[:crange] = [cmin, cmax]
1219
+ end
1220
+ end
1221
+
1222
+ def to_wc(wn)
1223
+ xmin, ymin = GR.ndctowc(wn[0], wn[2])
1224
+ xmax, ymax = GR.ndctowc(wn[1], wn[3])
1225
+ [xmin, xmax, ymin, ymax]
1226
+ end
1227
+
1228
+ def auto_tick(amin, amax)
1229
+ scale = 10.0**Math.log10(amax - amin).truncate
1230
+ 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
1233
+ end
1234
+ tick_size[i - 1] * scale
1235
+ end
1236
+
1237
+ def legend_size
1238
+ scale = GR.inqscale
1239
+ GR.selntran(0)
1240
+ GR.setscale(0)
1241
+ w = 0
1242
+ h = 0
1243
+ kvs[:labels].each do |label|
1244
+ label = label.to_s
1245
+ tbx, tby = inqtext(0, 0, label)
1246
+ w = [w, tbx[2] - tbx[0]].max
1247
+ h += [tby[2] - tby[0], 0.03].max
1248
+ end
1249
+ GR.setscale(scale)
1250
+ GR.selntran(1)
1251
+ [w, h]
1252
+ end
1253
+
1254
+ def equal_length(*args)
1255
+ GRCommons::GRCommonUtils.equal_length(*args)
1256
+ end
1257
+
1258
+ def narray?(data)
1259
+ GRCommons::GRCommonUtils.narray?(data)
1260
+ end
1261
+ end
1262
+
1263
+ class << self
1264
+ # (Plot) Draw one or more line plots.
1265
+ def plot(*args)
1266
+ create_plot(:line, *args)
1267
+ end
1268
+
1269
+ # (Plot) Draw one or more step or staircase plots.
1270
+ def step(*args)
1271
+ create_plot(:step, *args)
1272
+ end
1273
+
1274
+ # (Plot) Draw one or more scatter plots.
1275
+ def scatter(*args)
1276
+ create_plot(:scatter, *args)
1277
+ end
1278
+
1279
+ # (Plot) Draw a stem plot.
1280
+ def stem(*args)
1281
+ create_plot(:stem, *args)
1282
+ end
1283
+
1284
+ # (Plot)
1285
+ def polarhistogram(x, kv = {})
1286
+ plt = GR::Plot.new(x, kv)
1287
+ plt.kvs[:kind] = :polarhist
1288
+ nbins = plt.kvs[:nbins] || 0
1289
+ x, y = hist(x, nbins)
1290
+ plt.args = [[x, y, nil, nil, '']]
1291
+ plt.plot_data
1292
+ end
1293
+
1294
+ # (Plot) Draw a heatmap.
1295
+ 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)
1301
+ 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, '']]
1305
+ end
1306
+ end
1307
+
1308
+ # (Plot) Draw a polarheatmap.
1309
+ 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, '']]
1320
+ end
1321
+ end
1322
+
1323
+ # (Plot) Draw a nonuniformpolarheatmap.
1324
+ 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)
1330
+ 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, '']]
1334
+ end
1335
+ end
1336
+
1337
+ alias _contour_ contour
1338
+ # (Plot) Draw a contour plot.
1339
+ def contour(*args)
1340
+ create_plot(:contour, *format_xyzc(*args))
1341
+ end
1342
+
1343
+ alias _contourf_ contourf
1344
+ # (Plot) Draw a filled contour plot.
1345
+ def contourf(*args)
1346
+ create_plot(:contourf, *format_xyzc(*args))
1347
+ end
1348
+
1349
+ alias _hexbin_ hexbin
1350
+ # (Plot) Draw a hexagon binning plot.
1351
+ def hexbin(*args)
1352
+ create_plot(:hexbin, *args)
1353
+ end
1354
+
1355
+ # (Plot) Draw a triangular contour plot.
1356
+ def tricont(*args)
1357
+ create_plot(:tricont, *format_xyzc(*args))
1358
+ end
1359
+
1360
+ # (Plot) Draw a three-dimensional wireframe plot.
1361
+ def wireframe(*args)
1362
+ create_plot(:wireframe, *format_xyzc(*args))
1363
+ end
1364
+
1365
+ # (Plot) Draw a three-dimensional surface plot.
1366
+ alias _surface_ surface
1367
+ def surface(*args)
1368
+ create_plot(:surface, *format_xyzc(*args))
1369
+ end
1370
+
1371
+ # (Plot)
1372
+ def polar(*args)
1373
+ create_plot(:polar, *args)
1374
+ end
1375
+
1376
+ # (Plot) Draw a triangular surface plot.
1377
+ def trisurf(*args)
1378
+ create_plot(:trisurf, *format_xyzc(*args))
1379
+ end
1380
+
1381
+ # (Plot) Draw one or more three-dimensional line plots.
1382
+ def plot3(*args)
1383
+ create_plot(:plot3, *args)
1384
+ end
1385
+
1386
+ # (Plot) Draw one or more three-dimensional scatter plots.
1387
+ def scatter3(*args)
1388
+ create_plot(:scatter3, *args)
1389
+ end
1390
+
1391
+ alias _shade_ shade
1392
+ # (Plot)
1393
+ def shade(*args)
1394
+ create_plot(:shade, *args)
1395
+ end
1396
+
1397
+ # (Plot)
1398
+ def volume(v, kv = {})
1399
+ create_plot(:volume, v, kv) do |plt|
1400
+ plt.args = [[nil, nil, v, nil, '']]
1401
+ end
1402
+ end
1403
+
1404
+ # (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]
1410
+ plt.args = [[hc, wc, nil, nil, '']]
1411
+ plt.kvs[:yticks] = [1, 1]
1412
+ plt.kvs[:yticklabels] = labels
1413
+ else
1414
+ plt.args = [[wc, hc, nil, nil, '']]
1415
+ plt.kvs[:xticks] = [1, 1]
1416
+ plt.kvs[:xticklabels] = labels
1417
+ end
1418
+ end
1419
+ end
1420
+
1421
+ # (Plot) Draw a histogram.
1422
+ def histogram(series, kv = {})
1423
+ create_plot(:hist, series, kv) do |plt|
1424
+ nbins = plt.kvs[:nbins] || 0
1425
+ 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
1431
+ end
1432
+ end
1433
+
1434
+ # (Plot) Draw an image.
1435
+ def imshow(img, kv = {})
1436
+ img = Numo::DFloat.cast(img) # Umm...
1437
+ create_plot(:imshow, img, kv) do |plt|
1438
+ plt.args = [[nil, nil, img, nil, '']]
1439
+ end
1440
+ end
1441
+
1442
+ # (Plot) Draw an isosurface.
1443
+ def isosurface(v, kv = {})
1444
+ v = Numo::DFloat.cast(v) # Umm...
1445
+ create_plot(:isosurface, v, kv) do |plt|
1446
+ plt.args = [[nil, nil, v, nil, '']]
1447
+ end
1448
+ end
1449
+
1450
+ def hold(flag = true)
1451
+ plt = GR::Plot.last_plot
1452
+ plt.kvs.slice(:window, :scale, :xaxis, :yaxis, :zaxis).merge({ ax: flag, clear: !flag })
1453
+ end
1454
+
1455
+ # Set current subplot index.
1456
+ def subplot(nr, nc, p, kv = {})
1457
+ xmin = 1
1458
+ xmax = 0
1459
+ ymin = 1
1460
+ ymax = 0
1461
+ p = [p] if p.is_a? Integer
1462
+ p.each do |i|
1463
+ r = (nr - (i - 1) / nc).to_f
1464
+ c = ((i - 1) % nc + 1).to_f
1465
+ xmin = [xmin, (c - 1) / nc].min
1466
+ xmax = [xmax, c / nc].max
1467
+ ymin = [ymin, (r - 1) / nr].min
1468
+ ymax = [ymax, r / nr].max
1469
+ end
1470
+ {
1471
+ subplot: [xmin, xmax, ymin, ymax],
1472
+ # The policy of clearing when p[0]==1 is controversial
1473
+ clear: p[0] == 1,
1474
+ update: p[-1] == nr * nc
1475
+ }.merge kv
1476
+ end
1477
+
1478
+ # (Plot) Save the current figure to a file.
1479
+ def savefig(filename, kv = {})
1480
+ GR.beginprint(filename)
1481
+ plt = GR::Plot.last_plot
1482
+ plt.kvs.merge!(kv)
1483
+ plt.plot_data(false)
1484
+ GR.endprint
1485
+ end
1486
+
1487
+ private
1488
+
1489
+ def create_plot(type, *args)
1490
+ plt = GR::Plot.new(*args)
1491
+ plt.kvs[:kind] = type
1492
+ yield(plt) if block_given?
1493
+ plt.plot_data
1494
+ plt
1495
+ end
1496
+
1497
+ def format_xyzc(*args)
1498
+ kv = if args[-1].is_a? Hash
1499
+ args.pop
1500
+ else
1501
+ {}
1502
+ end
1503
+
1504
+ args = [args] unless args.all? do |i|
1505
+ i.is_a?(Array) && (i[0].is_a?(Array) || narray?(i[0]))
1506
+ end
1507
+ args.map! do |xyzc|
1508
+ if xyzc.size == 1
1509
+ if xyzc[0].is_a? Array
1510
+ z = Numo::DFloat.cast(xyzc[0])
1511
+ elsif narray?(xyzc[0])
1512
+ z = xyzc[0]
1513
+ end
1514
+ xsize, ysize = z.shape
1515
+ x = (1..ysize).to_a * xsize
1516
+ y = (1..xsize).map { |i| Array.new(ysize, i) }.flatten
1517
+ [x, y, z]
1518
+ else
1519
+
1520
+ xyzc
1521
+ end
1522
+ end
1523
+ [*args, kv]
1524
+ end
1525
+
1526
+ 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]
1538
+ end
1539
+
1540
+ def barcoordinates(heights, barwidth = 0.8, baseline = 0.0)
1541
+ halfw = barwidth / 2.0
1542
+ wc = []
1543
+ hc = []
1544
+ heights.each_with_index do |value, i|
1545
+ wc << i - halfw
1546
+ wc << i + halfw
1547
+ hc << baseline
1548
+ hc << value
1549
+ end
1550
+ [wc, hc]
1551
+ end
1552
+ end
1553
+ end