nyaplot 0.1.6 → 0.2.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.gitmodules +4 -3
- data/examples/notebook/3DPlot.ipynb +398 -455
- data/examples/notebook/Interaction_with_DataFrame.ipynb +572 -650
- data/examples/notebook/Introduction.ipynb +452 -525
- data/examples/notebook/Mapnya.ipynb +441 -212
- data/examples/notebook/Nyaplotv2.ipynb +307 -0
- data/lib/nyaplot.rb +7 -3
- data/lib/nyaplot/base.rb +80 -46
- data/lib/nyaplot/core.rb +45 -5
- data/lib/nyaplot/data.rb +220 -172
- data/lib/nyaplot/glyph.rb +227 -0
- data/lib/nyaplot/interactive.rb +94 -0
- data/lib/nyaplot/pane.rb +43 -0
- data/lib/nyaplot/plot.rb +173 -124
- data/lib/nyaplot/position.rb +7 -0
- data/lib/nyaplot/scale.rb +19 -0
- data/lib/nyaplot/sheet.rb +83 -0
- data/lib/nyaplot/simple/stage.rb +29 -0
- data/lib/nyaplot/stage2d.rb +56 -0
- data/lib/nyaplot/templates/init.js.erb +1 -1
- data/lib/nyaplot/templates/init_interactive.js +77 -0
- data/lib/nyaplot/templates/iruby.erb +2 -9
- data/lib/nyaplot/version.rb +1 -1
- data/lib/nyaplot3d/core.rb +1 -1
- data/nyaplot.gemspec +1 -26
- metadata +29 -10
- data/lib/mapnya/datasets/geo-boundaries-world-110m/README.md +0 -3
- data/lib/mapnya/datasets/geo-boundaries-world-110m/countries.geojson +0 -360
- data/lib/mapnya/datasets/geo-boundaries-world-110m/datapackage.json +0 -35
- data/lib/nyaplot/database.rb +0 -22
- data/lib/nyaplot/diagram.rb +0 -341
- data/lib/nyaplot/frame.rb +0 -70
@@ -0,0 +1,227 @@
|
|
1
|
+
module Nyaplot
|
2
|
+
module Glyph
|
3
|
+
class << self
|
4
|
+
#@example
|
5
|
+
# Nyaplot::Glyph.instantiate(:scatter)
|
6
|
+
def instantiate(df, name, hash)
|
7
|
+
glyph = Kernel
|
8
|
+
.const_get("Nyaplot")
|
9
|
+
.const_get("Glyph")
|
10
|
+
.const_get(name.to_s.capitalize)
|
11
|
+
|
12
|
+
hash[:data] = df
|
13
|
+
glyph.new(hash)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Glyph2D
|
18
|
+
include Nyaplot::Base
|
19
|
+
optional_args :transform
|
20
|
+
|
21
|
+
def range(label)
|
22
|
+
if data[label].all? {|v| v.is_a? Numeric}
|
23
|
+
[data[label].min, data[label].max]
|
24
|
+
else
|
25
|
+
data[label].uniq
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def range_x
|
30
|
+
range(x)
|
31
|
+
end
|
32
|
+
|
33
|
+
def range_y
|
34
|
+
range(y)
|
35
|
+
end
|
36
|
+
|
37
|
+
def position(*args) ; end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Histogram < Glyph::Glyph2D
|
41
|
+
required_args :data, :value, :position, :scalex
|
42
|
+
optional_args :bin_num, :width, :color, :stroke_color, :stroke_width
|
43
|
+
type :histogram
|
44
|
+
|
45
|
+
def initialize(*args)
|
46
|
+
super
|
47
|
+
bin_num(20)
|
48
|
+
end
|
49
|
+
|
50
|
+
def before_to_json
|
51
|
+
scalex(position.x)
|
52
|
+
end
|
53
|
+
|
54
|
+
def range_x
|
55
|
+
[data[value].min, data[value].max]
|
56
|
+
end
|
57
|
+
|
58
|
+
def range_y
|
59
|
+
min, max = range_x
|
60
|
+
bin = (max - min)/bin_num
|
61
|
+
max_bin = (1..bin_num).to_a
|
62
|
+
.map{|val| [min+bin*(val-1), min+bin*val]}
|
63
|
+
.map{|range| data[value].to_a.select{|val| val>=range[0] && val<range[1]}.length}
|
64
|
+
.max
|
65
|
+
print max_bin
|
66
|
+
[0, max_bin*1.2]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Scatter < Glyph::Glyph2D
|
71
|
+
required_args :data, :x, :y, :position
|
72
|
+
optional_args :color, :shape, :size, :stroke_color, :stroke_width
|
73
|
+
type :scatter
|
74
|
+
|
75
|
+
# Change symbol size according to data in specified column
|
76
|
+
def size_by(column_name)
|
77
|
+
range = size.nil? ? [10, 100] : size
|
78
|
+
df_scale = Nyaplot::DataFrameScale.new(data: data, column: column_name, range: range)
|
79
|
+
scale = Nyaplot::RowScale.new(column: column_name, scale: df_scale)
|
80
|
+
self.size(scale)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Change symbol shape according to data in specified column
|
84
|
+
# Value range (ex. "circle", "diamond", ...) can be specified using Scatter#shape
|
85
|
+
# @example
|
86
|
+
# x = ["a", "b", "a"]; y = [1, 2, 3]
|
87
|
+
# sc = Scatter.new(data: data, x: :x, y: :y)
|
88
|
+
# sc.shape_by(:x) #-> circle, triangle-up, circle (default)
|
89
|
+
# sc.shape([:square, :cross]).shape_by(:x) #-> square, cross, square
|
90
|
+
#
|
91
|
+
def shape_by(column_name)
|
92
|
+
range = shape.nil? ? ['circle','triangle-up', 'diamond', 'square', 'triangle-down', 'cross'] : shape
|
93
|
+
df_scale = Nyaplot::DataFrameScale.new(data: data, column: column_name, range: range)
|
94
|
+
scale = Nyaplot::RowScale.new(column: column_name, scale: df_scale)
|
95
|
+
self.shape(scale)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Line < Glyph::Glyph2D
|
100
|
+
required_args :data, :x, :y, :position
|
101
|
+
optional_args :color, :stroke_width
|
102
|
+
type :line
|
103
|
+
end
|
104
|
+
|
105
|
+
class Rect < Glyph::Glyph2D
|
106
|
+
required_args :data, :x, :y
|
107
|
+
optional_args :width, :height, :color, :stroke_width, :stroke_color, :x_base, :y_base
|
108
|
+
type :rect
|
109
|
+
end
|
110
|
+
|
111
|
+
class LineSegment < Glyph::Glyph2D
|
112
|
+
required_args :data, :x1, :y1, :x2, :y2
|
113
|
+
optional_args :color, :stroke_width
|
114
|
+
type :line_segment
|
115
|
+
end
|
116
|
+
|
117
|
+
class Bar < Glyph::Rect
|
118
|
+
optional_args :bin_size
|
119
|
+
|
120
|
+
# @example
|
121
|
+
# Plot.new(:bar, x: :[:hoge, :nya, :nyan], y: [100, 200, 10])
|
122
|
+
# -> df = DataFrame.new(x: [:hoge, :nya, :nyan], y: [100, 200, 10])
|
123
|
+
# -> Bar.new(x: :x, y: :y).data(df)
|
124
|
+
#
|
125
|
+
def initialize(*args)
|
126
|
+
super()
|
127
|
+
y_base "bottom"
|
128
|
+
bin_size 0.8
|
129
|
+
data args.first[:data]
|
130
|
+
@x_label = args.first[:x]
|
131
|
+
@y_label = args.first[:y]
|
132
|
+
end
|
133
|
+
|
134
|
+
def range_x
|
135
|
+
data[@x_label]
|
136
|
+
end
|
137
|
+
|
138
|
+
def range_y
|
139
|
+
column = data[@y_label]
|
140
|
+
min = column.min < 0 ? column.min : 0
|
141
|
+
[min, column.max]
|
142
|
+
end
|
143
|
+
|
144
|
+
def position(pos)
|
145
|
+
x RowScale.new(column: @x_label, scale: pos.x)
|
146
|
+
y(pos.y.range.max)
|
147
|
+
scale = Scale.new(range: pos.y.range.reverse, domain: pos.y.domain, type: pos.y.type)
|
148
|
+
height RowScale.new(column: @y_label, scale: scale)
|
149
|
+
width((pos.x.range.max/self.range_x.length)*bin_size)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class HeatMap < Glyph::Rect
|
154
|
+
end
|
155
|
+
|
156
|
+
class Box < Glyph::Rect
|
157
|
+
optional_args :bin_size
|
158
|
+
attr_reader :child
|
159
|
+
|
160
|
+
# @example
|
161
|
+
# df = DataFrame.new({hoge: [1,2,3], nya: [2,3,4], nyan: [5,6,7]})
|
162
|
+
# Plot.from(df).add(:box, :columns: [:hoge, :nya, :nyan])
|
163
|
+
#
|
164
|
+
def initialize(*args)
|
165
|
+
super()
|
166
|
+
bin_size 0.9
|
167
|
+
rows = args.first[:columns].map do |label|
|
168
|
+
column = args.first[:data][label]
|
169
|
+
q1, q3 = column.percentil(25), column.percentil(75)
|
170
|
+
h = q3 - q1
|
171
|
+
min = q1 - column.min > 1.5*h ? q1-1.5*h : column.min
|
172
|
+
max = column.max - q3 > 1.5*h ? q3+1.5*h : column.max
|
173
|
+
[
|
174
|
+
label,
|
175
|
+
max,
|
176
|
+
min,
|
177
|
+
q1,
|
178
|
+
q3,
|
179
|
+
q3 - q1,
|
180
|
+
column.median,
|
181
|
+
column.to_a.select{|val| val < min || val > max}.to_a
|
182
|
+
]
|
183
|
+
end
|
184
|
+
|
185
|
+
columns = rows.transpose
|
186
|
+
@outlier = columns.pop
|
187
|
+
@child = LineSegment.new
|
188
|
+
|
189
|
+
class << @child
|
190
|
+
def range_x ; []; end
|
191
|
+
def range_y ; [Float::INFINITY, -Float::INFINITY]; end
|
192
|
+
end
|
193
|
+
|
194
|
+
add_dependency(@child)
|
195
|
+
data(DataFrame.new(columns, labels: [:label, :max, :min, :q1, :q3, :height, :median]))
|
196
|
+
end
|
197
|
+
|
198
|
+
def range_x
|
199
|
+
data[:label]
|
200
|
+
end
|
201
|
+
|
202
|
+
def range_y
|
203
|
+
[data[:min].min, data[:max].max]
|
204
|
+
end
|
205
|
+
|
206
|
+
def position(pos)
|
207
|
+
padding = pos.x.range.max/data.length
|
208
|
+
|
209
|
+
x RowScale.new(column: :label, scale: pos.x)
|
210
|
+
y RowScale.new(column: :q3, scale: pos.y)
|
211
|
+
height RowScale.new(column: :height, scale: pos.y)
|
212
|
+
width padding * bin_size
|
213
|
+
transform("translate(" + ((padding*(1-bin_size))/2).to_s + ",0)")
|
214
|
+
|
215
|
+
@child
|
216
|
+
.data(data)
|
217
|
+
.x1(self.x)
|
218
|
+
.x2(self.x)
|
219
|
+
.y1(RowScale.new(column: :min, scale: pos.y))
|
220
|
+
.y2(RowScale.new(column: :max, scale: pos.y))
|
221
|
+
.stroke_width(1.5)
|
222
|
+
.color("#000")
|
223
|
+
.transform("translate(" + (padding/2).to_s + ",0)")
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Nyaplot
|
2
|
+
module Interactive
|
3
|
+
@@callbacks = {}
|
4
|
+
|
5
|
+
def init_interactive_layer
|
6
|
+
path = File.expand_path("../templates/init_interactive.js", __FILE__)
|
7
|
+
js = File.read(path)
|
8
|
+
IRuby.display(IRuby.javascript(js))
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.included(cls)
|
12
|
+
init_interactive_layer
|
13
|
+
comm_id = SecureRandom.hex(16)
|
14
|
+
@@comm = IRuby::Comm.new("nyaplot_interactive", comm_id)
|
15
|
+
@@comm.open
|
16
|
+
IRuby::Kernel.instance.register_comm(comm_id, @@comm)
|
17
|
+
|
18
|
+
on_msg = Proc.new do |msg|
|
19
|
+
content = msg
|
20
|
+
# msg: {type: "selected", uuid: , range: [0, 100]}
|
21
|
+
if content[:type.to_s] == "selected"
|
22
|
+
cb = @@callbacks[content[:stage_uuid.to_s]]
|
23
|
+
cb.call(content[:range.to_s])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@@comm.on_msg(on_msg)
|
27
|
+
@@comm.send({type: "hello"})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Filter
|
32
|
+
include Nyaplot::Base
|
33
|
+
required_args :scalex, :scaley, :stage_uuid
|
34
|
+
optional_args :opacity, :color
|
35
|
+
type :interactive_layer
|
36
|
+
end
|
37
|
+
|
38
|
+
class Plot2D
|
39
|
+
def add_filter(&block)
|
40
|
+
if (stages = @pane.dependency.select{|d| d.is_a?(Nyaplot::Stage2D)}).length == 1
|
41
|
+
stages.first.add_filter(&block)
|
42
|
+
else
|
43
|
+
raise "specify stage to add filter."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Stage2D
|
49
|
+
def add_filter(&block)
|
50
|
+
@filter = Filter.new(stage_uuid: @uuid)
|
51
|
+
add_sheet(@filter)
|
52
|
+
@@callbacks[@uuid] = block
|
53
|
+
end
|
54
|
+
|
55
|
+
def before_to_json
|
56
|
+
unless @filter.nil?
|
57
|
+
xscale = @axis.xscale
|
58
|
+
yscale = @axis.yscale
|
59
|
+
@filter.scalex(xscale).scaley(yscale)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class EmptyPlot
|
65
|
+
include PlotBase
|
66
|
+
|
67
|
+
def initialize(*args)
|
68
|
+
@pane = args.select{|a| a.is_a?(Nyaplot::Pane)}.first
|
69
|
+
args.delete(@pane)
|
70
|
+
@others = args
|
71
|
+
end
|
72
|
+
|
73
|
+
def update
|
74
|
+
# should be refactored
|
75
|
+
old_pane_uuid = @pane.uuid
|
76
|
+
msg = {
|
77
|
+
type: :update,
|
78
|
+
model: self.to_json
|
79
|
+
}
|
80
|
+
new_pane_uuid = @pane.uuid
|
81
|
+
msg[:model].gsub!(new_pane_uuid, old_pane_uuid)
|
82
|
+
@pane.uuid = old_pane_uuid
|
83
|
+
@@comm.send(msg)
|
84
|
+
STDERR.puts msg
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_json(*args)
|
88
|
+
gen_list = generate_gen_list(@others)
|
89
|
+
list = gen_list.sort_by{|k, v| v}.map{|arr| arr.first.to_json}.reverse
|
90
|
+
list.push(@pane.to_json)
|
91
|
+
"[" + list.join(",") + "]"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/nyaplot/pane.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Nyaplot
|
2
|
+
# The wrapper for pane of Nyaplotjs
|
3
|
+
# (https://github.com/domitry/Nyaplotjs/blob/v2/src/parser/pane.js)
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# p1 = Pane.new.columns(s1, s2)
|
7
|
+
# p2 = Pane.new.columns(s3, s4)
|
8
|
+
# p3 = Pane.new.rows(p1, p2)
|
9
|
+
# p3.to_iruby
|
10
|
+
|
11
|
+
class Pane
|
12
|
+
include Nyaplot::Base
|
13
|
+
|
14
|
+
type :pane
|
15
|
+
required_args :parent_id, :layout
|
16
|
+
|
17
|
+
def before_to_json
|
18
|
+
@uuid = SecureRandom.uuid
|
19
|
+
parent_id("vis-" + @uuid)
|
20
|
+
end
|
21
|
+
|
22
|
+
def add(name, *stages)
|
23
|
+
contents = stages.map do |s|
|
24
|
+
if s.is_a? Nyaplot::Pane
|
25
|
+
s.layout
|
26
|
+
else s.is_a? Nyaplot::Stage2D
|
27
|
+
add_dependency(s)
|
28
|
+
{sync: s.uuid}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
layout({type: name, contents: contents})
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.columns(*stages)
|
36
|
+
self.new.add(:columns, *stages)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.rows(*stages)
|
40
|
+
self.new.add(:rows, *stages)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/nyaplot/plot.rb
CHANGED
@@ -1,150 +1,199 @@
|
|
1
1
|
module Nyaplot
|
2
|
+
# Base module for Plot-something of Nyaplot. (e.g. Nyaplot::Plot2D or Nyaplot::Plot)
|
3
|
+
# Plot have one root named "pane".
|
4
|
+
# Plot resolve dependency according to pane, create model, and generate html code according to it.
|
5
|
+
module PlotBase
|
6
|
+
def initialize(*args)
|
7
|
+
@pane = Nyaplot::Pane.new
|
8
|
+
end
|
2
9
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# @!attribute y_label
|
20
|
-
# @return [String] the name of label placed along y-axis
|
21
|
-
# @!attribute bg_color
|
22
|
-
# @return [String] the code of color which background is filled in
|
23
|
-
# @!attribute grid_color
|
24
|
-
# @return [String] the code of color which grid lines are filled in
|
25
|
-
# @!attribute legend
|
26
|
-
# @return [Boolean] whether to show legend or not
|
27
|
-
# @!attribute legend_width
|
28
|
-
# @return [Numeric] the width of legend area
|
29
|
-
# @!attribute legend_options
|
30
|
-
# @return [Hash] the name of width set
|
31
|
-
# @!attribute zoom
|
32
|
-
# @return [Boolean] whether to enable zooming
|
33
|
-
# @!attribute rotate_x_label
|
34
|
-
# @return [Numeric] the angle to rotate x label (radian)
|
35
|
-
# @!attribute rotate_y_label
|
36
|
-
# @return [Numeric] the angle to rotate y label (radian)
|
37
|
-
# @!attribute x_scale
|
38
|
-
# @return [String] the type of scale ("linear", "log" and "pow" are supported.)
|
39
|
-
# @!attribute y_scale
|
40
|
-
# @return [String] the type of scale ("linear", "log" and "pow" are supported.)
|
41
|
-
define_properties(:diagrams, :filter)
|
42
|
-
define_group_properties(:options, [:width, :height, :margin, :xrange, :yrange, :x_label, :y_label, :bg_color, :grid_color, :legend, :legend_width, :legend_options, :zoom, :rotate_x_label, :rotate_y_label, :x_scale, :y_scale])
|
43
|
-
|
44
|
-
def initialize(&block)
|
45
|
-
init_properties
|
46
|
-
set_property(:diagrams, [])
|
47
|
-
set_property(:options, {})
|
48
|
-
set_property(:width, nil)
|
49
|
-
set_property(:legend, nil)
|
50
|
-
set_property(:zoom, nil)
|
51
|
-
|
52
|
-
yield if block_given?
|
53
|
-
end
|
54
|
-
|
55
|
-
# Add diagram with Array
|
56
|
-
# @param [Symbol] type the type of diagram to add
|
57
|
-
# @param [Array<Array>] *data array from which diagram is created
|
58
|
-
# @example
|
59
|
-
# plot.add(:scatter, [0,1,2], [0,1,2])
|
60
|
-
def add(type, *data)
|
61
|
-
labels = data.map.with_index{|d, i| 'data' + i.to_s}
|
62
|
-
raw_data = data.each.with_index.reduce({}){|memo, (d, i)| memo[labels[i]]=d; next memo}
|
63
|
-
df = DataFrame.new(raw_data)
|
64
|
-
return add_with_df(df, type, *labels)
|
65
|
-
end
|
66
|
-
|
67
|
-
# Add diagram with DataFrame
|
68
|
-
# @param [DataFrame] DataFrame from which diagram is created
|
69
|
-
# @param [Symbol] type the type of diagram to add
|
70
|
-
# @param [Array<Symbol>] *labels column labels for x, y or some other dimension
|
71
|
-
# @example
|
72
|
-
# df = Nyaplot::DataFrame.new({x: [0,1,2], y: [0,1,2]})
|
73
|
-
# plot.add(df, :scatter, :x, :y)
|
74
|
-
def add_with_df(df, type, *labels)
|
75
|
-
diagram = Diagram.new(df, type, labels)
|
76
|
-
diagrams = get_property(:diagrams)
|
77
|
-
diagrams.push(diagram)
|
78
|
-
return diagram
|
10
|
+
# @return
|
11
|
+
# {Obj1: 2, Obj2: 0, Obj3: 15, .., Objn: 0}
|
12
|
+
def generate_gen_list(stack)
|
13
|
+
gen = 0; gen_list = {}
|
14
|
+
|
15
|
+
while stack.length > 0
|
16
|
+
stack = stack.reduce([]) do |memo, obj|
|
17
|
+
gen_list[obj] = gen
|
18
|
+
obj.resolve_dependency if obj.respond_to? :resolve_dependency
|
19
|
+
memo.concat(obj.dependency)
|
20
|
+
next memo
|
21
|
+
end
|
22
|
+
gen += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
gen_list
|
79
26
|
end
|
80
27
|
|
28
|
+
# generate model
|
29
|
+
def to_json(*args)
|
30
|
+
gen_list = generate_gen_list([@pane])
|
31
|
+
"[" + gen_list.sort_by{|k, v| v}.map{|arr| arr.first.to_json}.reverse.join(",") + "]"
|
32
|
+
end
|
33
|
+
|
34
|
+
# generate html code for <body> tag
|
35
|
+
def generate_body
|
36
|
+
path = File.expand_path("../templates/iruby.erb", __FILE__)
|
37
|
+
template = File.read(path)
|
38
|
+
model = self.to_json
|
39
|
+
id = @pane.uuid
|
40
|
+
ERB.new(template).result(binding)
|
41
|
+
end
|
42
|
+
|
43
|
+
# generate static html file
|
44
|
+
# @return [String] generated html
|
45
|
+
def generate_html
|
46
|
+
body = generate_body
|
47
|
+
init = Nyaplot.generate_init_code
|
48
|
+
path = File.expand_path("../templates/static_html.erb", __FILE__)
|
49
|
+
template = File.read(path)
|
50
|
+
ERB.new(template).result(binding)
|
51
|
+
end
|
81
52
|
|
82
|
-
#
|
53
|
+
# export static html file
|
54
|
+
def export_html(path="./plot.html")
|
55
|
+
path = File.expand_path(path, Dir::pwd)
|
56
|
+
str = generate_html
|
57
|
+
File.write(path, str)
|
58
|
+
end
|
59
|
+
|
60
|
+
# show plot automatically on IRuby notebook
|
83
61
|
def to_iruby
|
84
|
-
|
62
|
+
html = generate_body
|
63
|
+
['text/html', html]
|
85
64
|
end
|
86
65
|
|
87
|
-
#
|
66
|
+
# show plot on IRuby notebook
|
88
67
|
def show
|
89
|
-
|
68
|
+
IRuby.display(self)
|
90
69
|
end
|
70
|
+
end
|
91
71
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
72
|
+
# Base class for general 2-dimentional plots
|
73
|
+
# Plot2D have one pane, some stage2ds and glyphs
|
74
|
+
class Plot2D
|
75
|
+
include PlotBase
|
76
|
+
attr_accessor :pane, :stages, :glyphs
|
77
|
+
|
78
|
+
def initialize
|
79
|
+
super
|
80
|
+
stage = Stage2D.new
|
81
|
+
@pane = Pane.rows(stage)
|
82
|
+
@dependency = [@pane, stage]
|
97
83
|
end
|
98
84
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
85
|
+
class << self
|
86
|
+
# shortcut method for Plot#add
|
87
|
+
# @example
|
88
|
+
# Plot.add(:scatter, a, b)
|
89
|
+
#
|
90
|
+
def add(*args)
|
91
|
+
self.new.add(*args)
|
92
|
+
end
|
106
93
|
|
107
|
-
|
108
|
-
|
109
|
-
|
94
|
+
# shortcut method for Plot#from
|
95
|
+
# @example
|
96
|
+
# df = DataFrame.new({hoge: [1,2,3], nya: [2,3,4]})
|
97
|
+
# Plot.from(df).add(:scatter, :hoge, :nya)
|
98
|
+
#
|
99
|
+
def from(df)
|
100
|
+
self.new.from(df)
|
101
|
+
end
|
102
|
+
end
|
110
103
|
|
111
|
-
|
112
|
-
|
104
|
+
def from(df)
|
105
|
+
if df.is_a? DataFrame
|
106
|
+
@df = df
|
107
|
+
self
|
108
|
+
else
|
109
|
+
raise ""
|
110
|
+
end
|
111
|
+
end
|
113
112
|
|
114
|
-
|
115
|
-
|
116
|
-
|
113
|
+
# Add glyph, sheet or stage to Plot
|
114
|
+
# @example
|
115
|
+
# Plot.add(:scatter, x, y)
|
116
|
+
# Plot.add(sc)
|
117
|
+
#
|
118
|
+
def add(*args)
|
119
|
+
if args.first.is_a? Symbol
|
120
|
+
name = args.shift
|
121
|
+
raise "invalid arguments" unless args.length == 1 && args.first.is_a?(Hash)
|
122
|
+
if (hash = args.first) && !(@df.nil?)
|
123
|
+
glyph = Nyaplot::Glyph.instantiate(@df, name, hash)
|
117
124
|
else
|
118
|
-
|
125
|
+
# hash: {x: [0, 1, 2], y: [1, 2, 3]}
|
126
|
+
df = DataFrame.new(hash)
|
127
|
+
arg = hash.reduce({}){|memo, val| memo[val[0]] = val[0]; memo}
|
128
|
+
glyph = Nyaplot::Glyph.instantiate(df, name, arg)
|
119
129
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
if range.all? {|r| r.length == 2} # continuous data
|
128
|
-
range = range.transpose
|
129
|
-
range = [range[0].min, range[1].max]
|
130
|
-
self.send(symbol, range)
|
131
|
-
else # discrete data
|
132
|
-
range.flatten!.uniq!
|
133
|
-
self.send(symbol, range)
|
130
|
+
add_glyph(glyph)
|
131
|
+
else
|
132
|
+
args.each do |obj|
|
133
|
+
if obj.is_a? Nyaplot::Glyph2D
|
134
|
+
add_glyph(obj)
|
135
|
+
elsif obj.is_a? Nyaplot::Stage2D
|
136
|
+
add_stage(obj)
|
134
137
|
end
|
135
138
|
end
|
136
139
|
end
|
140
|
+
self
|
137
141
|
end
|
138
142
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
143
|
+
def add_glyph(glyph)
|
144
|
+
stages = @dependency.select{|obj| obj.is_a? Nyaplot::Stage2D}
|
145
|
+
if stages.length == 0
|
146
|
+
stage = Nyaplot::Stage2D.new
|
147
|
+
add_stage(stage)
|
148
|
+
stage.context.add(glyph)
|
149
|
+
elsif stages.length == 1
|
150
|
+
stages.first.context.add(glyph)
|
151
|
+
else
|
152
|
+
raise "Specify stage to add the glyph."
|
153
|
+
end
|
154
|
+
@dependency.push(glyph)
|
155
|
+
end
|
156
|
+
|
157
|
+
def add_stage(stage)
|
158
|
+
@pane = Pane.columns(@pane, stage)
|
159
|
+
@dependency.push(stage)
|
160
|
+
end
|
161
|
+
|
162
|
+
def glyphs
|
163
|
+
if (s = self.stages).length == 1
|
164
|
+
context = s.first.context
|
165
|
+
context.glyphs
|
166
|
+
else
|
167
|
+
raise "Specify stage from which select glyphs from"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def glyph
|
172
|
+
self.glyphs.first
|
148
173
|
end
|
174
|
+
|
175
|
+
def stages
|
176
|
+
@pane.dependency.select{|d| d.is_a?(Nyaplot::Stage2D)}
|
177
|
+
end
|
178
|
+
|
179
|
+
def stage
|
180
|
+
if (s = self.stages).length == 1
|
181
|
+
s.first
|
182
|
+
else
|
183
|
+
raise "This plot has 2>= stages."
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def method_missing(name, *args)
|
188
|
+
super if @dependency.all?{|obj| !(obj.respond_to? name)}
|
189
|
+
@dependency.each do |obj|
|
190
|
+
break obj.send(name, *args) if obj.respond_to? name
|
191
|
+
end
|
192
|
+
self
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# shortcut for Nyaplot::Plot2D
|
197
|
+
class Plot < Plot2D
|
149
198
|
end
|
150
199
|
end
|