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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +38 -0
- data/charty.gemspec +1 -0
- data/examples/Gemfile +1 -0
- data/examples/active_record.ipynb +1 -1
- data/examples/daru.ipynb +1 -1
- data/examples/iris_dataset.ipynb +1 -1
- data/examples/nmatrix.ipynb +1 -1
- data/examples/{numo-narray.ipynb → numo_narray.ipynb} +1 -1
- data/examples/palette.rb +71 -0
- data/examples/sample.png +0 -0
- data/examples/sample_pyplot.ipynb +40 -38
- data/lib/charty.rb +7 -0
- data/lib/charty/backend_methods.rb +8 -0
- data/lib/charty/backends.rb +25 -1
- data/lib/charty/backends/bokeh.rb +29 -29
- data/lib/charty/backends/{google_chart.rb → google_charts.rb} +74 -32
- data/lib/charty/backends/plotly.rb +145 -7
- data/lib/charty/backends/pyplot.rb +163 -33
- data/lib/charty/backends/rubyplot.rb +1 -1
- data/lib/charty/palette.rb +235 -0
- data/lib/charty/plot_methods.rb +19 -0
- data/lib/charty/plotter.rb +9 -9
- data/lib/charty/plotters.rb +4 -0
- data/lib/charty/plotters/abstract_plotter.rb +81 -0
- data/lib/charty/plotters/bar_plotter.rb +19 -0
- data/lib/charty/plotters/box_plotter.rb +26 -0
- data/lib/charty/plotters/categorical_plotter.rb +148 -0
- data/lib/charty/statistics.rb +29 -0
- data/lib/charty/table.rb +1 -0
- data/lib/charty/table_adapters/active_record_adapter.rb +1 -1
- data/lib/charty/table_adapters/daru_adapter.rb +2 -0
- data/lib/charty/table_adapters/datasets_adapter.rb +4 -0
- data/lib/charty/table_adapters/hash_adapter.rb +2 -0
- data/lib/charty/table_adapters/narray_adapter.rb +1 -1
- data/lib/charty/table_adapters/nmatrix_adapter.rb +1 -1
- data/lib/charty/version.rb +1 -1
- metadata +30 -5
- data/.travis.yml +0 -10
data/lib/charty.rb
CHANGED
@@ -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"
|
data/lib/charty/backends.rb
CHANGED
@@ -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/
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
55
|
-
|
54
|
+
when :boxplot
|
55
|
+
raise NotImplementedError
|
56
56
|
|
57
|
-
|
58
|
-
|
57
|
+
when :bubble
|
58
|
+
raise NotImplementedError
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
71
|
-
|
70
|
+
when :error_bar
|
71
|
+
raise NotImplementedError
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
4
|
-
Backends.register(:
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
schema = [
|
74
|
+
["string", "ID"],
|
75
|
+
["number", "X"],
|
76
|
+
["number", "Y"],
|
77
|
+
["string", "GROUP"],
|
78
|
+
["number", "SIZE"],
|
79
|
+
]
|
80
80
|
when :curve
|
81
|
-
|
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
|
-
|
84
|
+
schema << ["number", series_data.label || index]
|
84
85
|
end
|
85
86
|
else
|
86
|
-
|
87
|
+
schema = ["string", context.xlabel]
|
87
88
|
context.series.to_a.each_with_index do |series_data, index|
|
88
|
-
|
89
|
+
schema << ["number", series_data.label || index]
|
89
90
|
end
|
90
91
|
end
|
91
92
|
|
92
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
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] <<
|
139
|
+
hash[x_label] << nil
|
122
140
|
end
|
123
141
|
end
|
124
142
|
end
|
125
143
|
end
|
126
144
|
end
|
127
145
|
|
128
|
-
def
|
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]
|
137
|
-
series_data.ys.to_a[data_index]
|
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]
|
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
|
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(#{
|
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
|