charty 0.2.0 → 0.2.1

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +38 -0
  3. data/charty.gemspec +1 -0
  4. data/examples/Gemfile +1 -0
  5. data/examples/active_record.ipynb +1 -1
  6. data/examples/daru.ipynb +1 -1
  7. data/examples/iris_dataset.ipynb +1 -1
  8. data/examples/nmatrix.ipynb +1 -1
  9. data/examples/{numo-narray.ipynb → numo_narray.ipynb} +1 -1
  10. data/examples/palette.rb +71 -0
  11. data/examples/sample.png +0 -0
  12. data/examples/sample_pyplot.ipynb +40 -38
  13. data/lib/charty.rb +7 -0
  14. data/lib/charty/backend_methods.rb +8 -0
  15. data/lib/charty/backends.rb +25 -1
  16. data/lib/charty/backends/bokeh.rb +29 -29
  17. data/lib/charty/backends/{google_chart.rb → google_charts.rb} +74 -32
  18. data/lib/charty/backends/plotly.rb +145 -7
  19. data/lib/charty/backends/pyplot.rb +163 -33
  20. data/lib/charty/backends/rubyplot.rb +1 -1
  21. data/lib/charty/palette.rb +235 -0
  22. data/lib/charty/plot_methods.rb +19 -0
  23. data/lib/charty/plotter.rb +9 -9
  24. data/lib/charty/plotters.rb +4 -0
  25. data/lib/charty/plotters/abstract_plotter.rb +81 -0
  26. data/lib/charty/plotters/bar_plotter.rb +19 -0
  27. data/lib/charty/plotters/box_plotter.rb +26 -0
  28. data/lib/charty/plotters/categorical_plotter.rb +148 -0
  29. data/lib/charty/statistics.rb +29 -0
  30. data/lib/charty/table.rb +1 -0
  31. data/lib/charty/table_adapters/active_record_adapter.rb +1 -1
  32. data/lib/charty/table_adapters/daru_adapter.rb +2 -0
  33. data/lib/charty/table_adapters/datasets_adapter.rb +4 -0
  34. data/lib/charty/table_adapters/hash_adapter.rb +2 -0
  35. data/lib/charty/table_adapters/narray_adapter.rb +1 -1
  36. data/lib/charty/table_adapters/nmatrix_adapter.rb +1 -1
  37. data/lib/charty/version.rb +1 -1
  38. metadata +30 -5
  39. data/.travis.yml +0 -10
@@ -1,8 +1,15 @@
1
1
  require_relative "charty/version"
2
2
 
3
+ require "colors"
4
+
3
5
  require_relative "charty/backends"
6
+ require_relative "charty/backend_methods"
4
7
  require_relative "charty/plotter"
5
8
  require_relative "charty/layout"
6
9
  require_relative "charty/linspace"
10
+ require_relative "charty/plotters"
11
+ require_relative "charty/plot_methods"
12
+ require_relative "charty/palette"
7
13
  require_relative "charty/table_adapters"
8
14
  require_relative "charty/table"
15
+ require_relative "charty/statistics"
@@ -0,0 +1,8 @@
1
+ module Charty
2
+ module BackendMethods
3
+ def use_backend(backend)
4
+ end
5
+ end
6
+
7
+ extend BackendMethods
8
+ end
@@ -6,6 +6,30 @@ module Charty
6
6
  module Backends
7
7
  @backends = {}
8
8
 
9
+ @current = nil
10
+
11
+ def self.current
12
+ @current
13
+ end
14
+
15
+ def self.current=(backend_name)
16
+ backend_class = Backends.find_backend_class(backend_name)
17
+ @current = backend_class.new
18
+ end
19
+
20
+ def self.use(backend)
21
+ if block_given?
22
+ begin
23
+ saved, self.current = self.current, backend
24
+ yield
25
+ ensure
26
+ self.current = saved
27
+ end
28
+ else
29
+ self.current = backend
30
+ end
31
+ end
32
+
9
33
  def self.names
10
34
  @backends.keys
11
35
  end
@@ -48,7 +72,7 @@ module Charty
48
72
  end
49
73
 
50
74
  require "charty/backends/bokeh"
51
- require "charty/backends/google_chart"
75
+ require "charty/backends/google_charts"
52
76
  require "charty/backends/gruff"
53
77
  require "charty/backends/plotly"
54
78
  require "charty/backends/pyplot"
@@ -37,42 +37,42 @@ module Charty
37
37
  plot.yaxis[0].axis_label = context&.ylabel
38
38
 
39
39
  case context.method
40
- when :bar
41
- context.series.each do |data|
42
- diffs = data.xs.to_a.each_cons(2).map {|n, m| (n - m).abs }
43
- width = diffs.min * 0.8
44
- plot.vbar(data.xs.to_a, width, data.ys.to_a)
45
- end
40
+ when :bar
41
+ context.series.each do |data|
42
+ diffs = data.xs.to_a.each_cons(2).map {|n, m| (n - m).abs }
43
+ width = diffs.min * 0.8
44
+ plot.vbar(data.xs.to_a, width, data.ys.to_a)
45
+ end
46
46
 
47
- when :barh
48
- context.series.each do |data|
49
- diffs = data.xs.to_a.each_cons(2).map {|n, m| (n - m).abs }
50
- height = diffs.min * 0.8
51
- plot.hbar(data.xs.to_a, height, data.ys.to_a)
52
- end
47
+ when :barh
48
+ context.series.each do |data|
49
+ diffs = data.xs.to_a.each_cons(2).map {|n, m| (n - m).abs }
50
+ height = diffs.min * 0.8
51
+ plot.hbar(data.xs.to_a, height, data.ys.to_a)
52
+ end
53
53
 
54
- when :boxplot
55
- raise NotImplementedError
54
+ when :boxplot
55
+ raise NotImplementedError
56
56
 
57
- when :bubble
58
- raise NotImplementedError
57
+ when :bubble
58
+ raise NotImplementedError
59
59
 
60
- when :curve
61
- context.series.each do |data|
62
- plot.line(data.xs.to_a, data.ys.to_a)
63
- end
60
+ when :curve
61
+ context.series.each do |data|
62
+ plot.line(data.xs.to_a, data.ys.to_a)
63
+ end
64
64
 
65
- when :scatter
66
- context.series.each do |data|
67
- plot.scatter(data.xs.to_a, data.ys.to_a)
68
- end
65
+ when :scatter
66
+ context.series.each do |data|
67
+ plot.scatter(data.xs.to_a, data.ys.to_a)
68
+ end
69
69
 
70
- when :error_bar
71
- raise NotImplementedError
70
+ when :error_bar
71
+ raise NotImplementedError
72
72
 
73
- when :hist
74
- raise NotImplementedError
75
- end
73
+ when :hist
74
+ raise NotImplementedError
75
+ end
76
76
  plot
77
77
  end
78
78
  end
@@ -1,7 +1,7 @@
1
1
  module Charty
2
2
  module Backends
3
- class GoogleChart
4
- Backends.register(:google_chart, self)
3
+ class GoogleCharts
4
+ Backends.register(:google_charts, self)
5
5
 
6
6
  attr_reader :context
7
7
 
@@ -70,47 +70,65 @@ module Charty
70
70
  def data_column_js
71
71
  case context.method
72
72
  when :bubble
73
- column_js = <<-COLUMN_JS
74
- data.addColumn('string', 'ID');
75
- data.addColumn('number', 'X');
76
- data.addColumn('number', 'Y');
77
- data.addColumn('string', 'GROUP');
78
- data.addColumn('number', 'SIZE');
79
- COLUMN_JS
73
+ schema = [
74
+ ["string", "ID"],
75
+ ["number", "X"],
76
+ ["number", "Y"],
77
+ ["string", "GROUP"],
78
+ ["number", "SIZE"],
79
+ ]
80
80
  when :curve
81
- column_js = "data.addColumn('number', '#{context.xlabel}');"
81
+ schema = []
82
+ schema << [detect_type(context.series.first.xs), context.xlabel]
82
83
  context.series.to_a.each_with_index do |series_data, index|
83
- column_js << "data.addColumn('number', '#{series_data.label || index}');"
84
+ schema << ["number", series_data.label || index]
84
85
  end
85
86
  else
86
- column_js = "data.addColumn('string', '#{context.xlabel}');"
87
+ schema = ["string", context.xlabel]
87
88
  context.series.to_a.each_with_index do |series_data, index|
88
- column_js << "data.addColumn('number', '#{series_data.label || index}');"
89
+ schema << ["number", series_data.label || index]
89
90
  end
90
91
  end
91
92
 
92
- column_js
93
+ columns = schema.collect do |type, label|
94
+ "data.addColumn(#{type.to_json}, #{label.to_s.to_json});"
95
+ end
96
+ columns.join
97
+ end
98
+
99
+ def detect_type(values)
100
+ case values.first
101
+ when Time
102
+ "date"
103
+ when String
104
+ "string"
105
+ else
106
+ "number"
107
+ end
93
108
  end
94
109
 
95
110
  def x_labels
96
- [].tap do |label|
97
- context.series.each do |series|
98
- xs_series = if series.xs.detect { |xs_data| xs_data.is_a?(String) }
99
- series.xs.sort_by {|x| format('%10s', "#{x}")}
100
- else
101
- series.xs.sort
102
- end
103
- xs_series.each do |xs_data|
104
- label << xs_data unless label.any? { |label| label == xs_data }
105
- end
111
+ labels = {}
112
+ have_string = false
113
+ context.series.each do |series|
114
+ series.xs.each do |x|
115
+ next if labels.key?(x)
116
+ have_string = true if x.is_a?(String)
117
+ labels[x] = true
106
118
  end
107
119
  end
120
+ if have_string
121
+ labels.keys.sort_by {|label| "%10s" % x.to_s}
122
+ else
123
+ labels.keys.sort
124
+ end
108
125
  end
109
126
 
110
127
  def data_hash
111
128
  {}.tap do |hash|
129
+ _x_labels = x_labels
112
130
  context.series.to_a.each_with_index do |series_data, series_index|
113
- x_labels.each do |x_label|
131
+ _x_labels.each do |x_label|
114
132
  unless hash[x_label]
115
133
  hash[x_label] = []
116
134
  end
@@ -118,14 +136,14 @@ module Charty
118
136
  if data_index = series_data.xs.to_a.index(x_label)
119
137
  hash[x_label] << series_data.ys.to_a[data_index]
120
138
  else
121
- hash[x_label] << "null"
139
+ hash[x_label] << nil
122
140
  end
123
141
  end
124
142
  end
125
143
  end
126
144
  end
127
145
 
128
- def formatted_data_array
146
+ def rows
129
147
  case context.method
130
148
  when :bubble
131
149
  [].tap do |data_array|
@@ -133,10 +151,10 @@ module Charty
133
151
  series_data.xs.to_a.each_with_index do |data, data_index|
134
152
  data_array << [
135
153
  "",
136
- series_data.xs.to_a[data_index] || "null",
137
- series_data.ys.to_a[data_index] || "null",
154
+ series_data.xs.to_a[data_index],
155
+ series_data.ys.to_a[data_index],
138
156
  series_data[:label] || series_index.to_s,
139
- series_data.zs.to_a[data_index] || "null",
157
+ series_data.zs.to_a[data_index],
140
158
  ]
141
159
  end
142
160
  end
@@ -150,12 +168,36 @@ module Charty
150
168
  else
151
169
  [].tap do |data_array|
152
170
  data_hash.each do |k, v|
153
- data_array << [k.to_s, v].flatten
171
+ data_array << [k, v].flatten
154
172
  end
155
173
  end
156
174
  end
157
175
  end
158
176
 
177
+ def convert_to_javascript(data)
178
+ case data
179
+ when Array
180
+ converted_data = data.collect do |element|
181
+ convert_to_javascript(element)
182
+ end
183
+ "[#{converted_data.join(", ")}]"
184
+ when Time
185
+ time = data.dup.utc
186
+ args = [
187
+ time.year,
188
+ time.month - 1,
189
+ time.day,
190
+ time.hour,
191
+ time.min,
192
+ time.sec,
193
+ time.nsec / 1000 / 1000,
194
+ ]
195
+ "new Date(Date.UTC(#{args.join(", ")}))"
196
+ else
197
+ data.to_json
198
+ end
199
+ end
200
+
159
201
  def x_range_option
160
202
  x_range = if context.method != :barh
161
203
  context&.range&.fetch(:x, nil)
@@ -189,7 +231,7 @@ module Charty
189
231
  function drawChart() {
190
232
  const data = new google.visualization.DataTable();
191
233
  #{data_column_js}
192
- data.addRows(#{formatted_data_array})
234
+ data.addRows(#{convert_to_javascript(rows)})
193
235
 
194
236
  const view = new google.visualization.DataView(data);
195
237
 
@@ -55,24 +55,22 @@ module Charty
55
55
  end
56
56
  end
57
57
 
58
- private
59
-
60
- def plotly_load_tag
58
+ private def plotly_load_tag
61
59
  if self.class.with_api_load_tag
62
60
  "<script type='text/javascript' src='#{self.class.plotly_src}'></script>"
63
61
  else
64
62
  end
65
63
  end
66
64
 
67
- def div_id
65
+ private def div_id
68
66
  "charty-plotly-#{self.class.chart_id}"
69
67
  end
70
68
 
71
- def div_style
69
+ private def div_style
72
70
  "width: 100%;height: 100%;"
73
71
  end
74
72
 
75
- def render_graph(context, type, options: {})
73
+ private def render_graph(context, type, options: {})
76
74
  data = context.series.map do |series|
77
75
  {
78
76
  type: type,
@@ -95,7 +93,7 @@ module Charty
95
93
  render_html(data, layout)
96
94
  end
97
95
 
98
- def render_html(data, layout)
96
+ private def render_html(data, layout)
99
97
  <<~FRAGMENT
100
98
  #{plotly_load_tag unless self.class.chart_id > 1}
101
99
  <div id="#{div_id}" style="#{div_style}"></div>
@@ -104,6 +102,146 @@ module Charty
104
102
  </script>
105
103
  FRAGMENT
106
104
  end
105
+
106
+ # ==== NEW PLOTTING API ====
107
+
108
+ class HTML
109
+ def initialize(html)
110
+ @html = html
111
+ end
112
+
113
+ def to_iruby
114
+ ["text/html", @html]
115
+ end
116
+ end
117
+
118
+ def begin_figure
119
+ @traces = []
120
+ @layout = {}
121
+ end
122
+
123
+ def bar(bar_pos, values, color: nil, width: 0.8r, align: :center, orient: :v)
124
+ color = Array(color).map(&:to_hex_string)
125
+ @traces << {
126
+ type: :bar,
127
+ x: bar_pos,
128
+ y: values,
129
+ marker: {color: color}
130
+ }
131
+ @layout[:showlegend] = false
132
+ end
133
+
134
+ def box_plot(plot_data, positions, color:, gray:,
135
+ width: 0.8r, flier_size: 5, whisker: 1.5, notch: false)
136
+ color = Array(color).map(&:to_hex_string)
137
+ plot_data.each_with_index do |group_data, i|
138
+ data = if group_data.empty?
139
+ {type: :box, y: [] }
140
+ else
141
+ {type: :box, y: group_data, marker: {color: color[i]}}
142
+ end
143
+ @traces << data
144
+ end
145
+ @layout[:showlegend] = false
146
+ end
147
+
148
+ def set_xlabel(label)
149
+ @layout[:xaxis] ||= {}
150
+ @layout[:xaxis][:title] = label
151
+ end
152
+
153
+ def set_ylabel(label)
154
+ @layout[:yaxis] ||= {}
155
+ @layout[:yaxis][:title] = label
156
+ end
157
+
158
+ def set_xticks(values)
159
+ @layout[:xaxis] ||= {}
160
+ @layout[:xaxis][:tickmode] = "array"
161
+ @layout[:xaxis][:tickvals] = values
162
+ end
163
+
164
+ def set_xtick_labels(labels)
165
+ @layout[:xaxis] ||= {}
166
+ @layout[:xaxis][:tickmode] = "array"
167
+ @layout[:xaxis][:ticktext] = labels
168
+ end
169
+
170
+ def set_xlim(min, max)
171
+ @layout[:xaxis] ||= {}
172
+ @layout[:xaxis][:range] = [min, max]
173
+ end
174
+
175
+ def disable_xaxis_grid
176
+ # do nothing
177
+ end
178
+
179
+ def show
180
+ unless defined?(IRuby)
181
+ raise NotImplementedError,
182
+ "Plotly backend outside of IRuby is not supported"
183
+ end
184
+
185
+ IRubyOutput.prepare
186
+
187
+ html = <<~HTML
188
+ <div id="%{id}" style="width: 100%%; height:100%%;"></div>
189
+ <script type="text/javascript">
190
+ requirejs(["plotly"], function (Plotly) {
191
+ Plotly.newPlot("%{id}", %{data}, %{layout});
192
+ });
193
+ </script>
194
+ HTML
195
+
196
+ html %= {
197
+ id: SecureRandom.uuid,
198
+ data: JSON.dump(@traces),
199
+ layout: JSON.dump(@layout)
200
+ }
201
+ IRuby.display(html, mime: "text/html")
202
+ nil
203
+ end
204
+
205
+ module IRubyOutput
206
+ @prepared = false
207
+
208
+ def self.prepare
209
+ return if @prepared
210
+
211
+ html = <<~HTML
212
+ <script type="text/javascript">
213
+ %{win_config}
214
+ %{mathjax_config}
215
+ require.config({
216
+ paths: {
217
+ plotly: "https://cdn.plot.ly/plotly-latest.min"
218
+ }
219
+ });
220
+ </script>
221
+ HTML
222
+
223
+ html %= {
224
+ win_config: window_plotly_config,
225
+ mathjax_config: mathjax_config
226
+ }
227
+
228
+ IRuby.display(html, mime: "text/html")
229
+ @prepared = true
230
+ end
231
+
232
+ def self.window_plotly_config
233
+ <<~END
234
+ window.PlotlyConfig = {MathJaxConfig: 'local'};
235
+ END
236
+ end
237
+
238
+
239
+ def self.mathjax_config
240
+ <<~END
241
+ if (window.MathJax) {MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}
242
+ END
243
+ end
244
+ end
107
245
  end
108
246
  end
109
247
  end