charty 0.1.5.dev → 0.2.0
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/.travis.yml +4 -5
- data/README.md +1 -1
- data/charty.gemspec +6 -0
- data/examples/sample_bokeh.ipynb +156 -0
- data/examples/sample_google_chart.ipynb +229 -68
- data/examples/sample_images/bar_bokeh.html +85 -0
- data/examples/sample_images/barh_bokeh.html +85 -0
- data/examples/sample_images/box_plot_bokeh.html +85 -0
- data/examples/sample_images/curve_bokeh.html +85 -0
- data/examples/sample_images/curve_with_function_bokeh.html +85 -0
- data/examples/sample_images/scatter_bokeh.html +85 -0
- data/lib/charty.rb +2 -1
- data/lib/charty/backends.rb +55 -0
- data/lib/charty/backends/bokeh.rb +61 -55
- data/lib/charty/backends/google_chart.rb +187 -129
- data/lib/charty/backends/gruff.rb +91 -83
- data/lib/charty/backends/plotly.rb +109 -0
- data/lib/charty/backends/pyplot.rb +96 -88
- data/lib/charty/backends/rubyplot.rb +82 -74
- data/lib/charty/plotter.rb +41 -33
- data/lib/charty/table.rb +44 -3
- data/lib/charty/table_adapters.rb +23 -0
- data/lib/charty/table_adapters/active_record_adapter.rb +55 -0
- data/lib/charty/table_adapters/daru_adapter.rb +34 -0
- data/lib/charty/table_adapters/datasets_adapter.rb +41 -0
- data/lib/charty/table_adapters/hash_adapter.rb +108 -0
- data/lib/charty/table_adapters/narray_adapter.rb +57 -0
- data/lib/charty/table_adapters/nmatrix_adapter.rb +57 -0
- data/lib/charty/version.rb +1 -1
- metadata +104 -5
- data/lib/charty/plotter_adapter.rb +0 -17
@@ -1,98 +1,106 @@
|
|
1
|
-
require '
|
1
|
+
require 'fileutils'
|
2
2
|
|
3
3
|
module Charty
|
4
|
-
|
5
|
-
|
4
|
+
module Backends
|
5
|
+
class Gruff
|
6
|
+
Backends.register(:gruff, self)
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
class << self
|
9
|
+
def prepare
|
10
|
+
require 'gruff'
|
11
|
+
end
|
12
|
+
end
|
10
13
|
|
11
|
-
|
12
|
-
|
14
|
+
def initialize
|
15
|
+
@plot = ::Gruff
|
16
|
+
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
end
|
18
|
+
def label(x, y)
|
19
|
+
end
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
def series=(series)
|
22
|
+
@series = series
|
23
|
+
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
def render_layout(layout)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def render(context, filename="")
|
30
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
31
|
+
plot(@plot, context).write(filename)
|
32
|
+
end
|
26
33
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
def plot(plot, context)
|
35
|
+
# case
|
36
|
+
# when plot.respond_to?(:xlim)
|
37
|
+
# plot.xlim(context.range_x.begin, context.range_x.end)
|
38
|
+
# plot.ylim(context.range_y.begin, context.range_y.end)
|
39
|
+
# when plot.respond_to?(:set_xlim)
|
40
|
+
# plot.set_xlim(context.range_x.begin, context.range_x.end)
|
41
|
+
# plot.set_ylim(context.range_y.begin, context.range_y.end)
|
42
|
+
# end
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
44
|
+
case context.method
|
45
|
+
when :bar
|
46
|
+
p = plot::Bar.new
|
47
|
+
p.title = context.title if context.title
|
48
|
+
p.x_axis_label = context.xlabel if context.xlabel
|
49
|
+
p.y_axis_label = context.ylabel if context.ylabel
|
50
|
+
context.series.each do |data|
|
51
|
+
p.data(data.label, data.xs.to_a)
|
52
|
+
end
|
53
|
+
p
|
54
|
+
when :barh
|
55
|
+
p = plot::SideBar.new
|
56
|
+
p.title = context.title if context.title
|
57
|
+
p.x_axis_label = context.xlabel if context.xlabel
|
58
|
+
p.y_axis_label = context.ylabel if context.ylabel
|
59
|
+
labels = context.series.map {|data| data.xs.to_a}.flatten.uniq
|
60
|
+
labels.each do |label|
|
61
|
+
data_ys = context.series.map do |data|
|
62
|
+
if data.xs.to_a.index(label)
|
63
|
+
data.ys.to_a[data.xs.to_a.index(label)]
|
64
|
+
else
|
65
|
+
0
|
66
|
+
end
|
59
67
|
end
|
68
|
+
p.data(label, data_ys)
|
60
69
|
end
|
61
|
-
p.
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
p
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
p
|
70
|
+
p.labels = context.series.each_with_index.inject({}) do |attr, (data, i)|
|
71
|
+
attr[i] = data.label
|
72
|
+
attr
|
73
|
+
end
|
74
|
+
p
|
75
|
+
when :box_plot
|
76
|
+
# refs. https://github.com/topfunky/gruff/issues/155
|
77
|
+
raise NotImplementedError
|
78
|
+
when :bubble
|
79
|
+
raise NotImplementedError
|
80
|
+
when :curve
|
81
|
+
p = plot::Line.new
|
82
|
+
p.title = context.title if context.title
|
83
|
+
p.x_axis_label = context.xlabel if context.xlabel
|
84
|
+
p.y_axis_label = context.ylabel if context.ylabel
|
85
|
+
context.series.each do |data|
|
86
|
+
p.data(data.label, data.xs.to_a)
|
87
|
+
end
|
88
|
+
p
|
89
|
+
when :scatter
|
90
|
+
p = plot::Scatter.new
|
91
|
+
p.title = context.title if context.title
|
92
|
+
p.x_axis_label = context.xlabel if context.xlabel
|
93
|
+
p.y_axis_label = context.ylabel if context.ylabel
|
94
|
+
context.series.each do |data|
|
95
|
+
p.data(data.label, data.xs.to_a, data.ys.to_a)
|
96
|
+
end
|
97
|
+
p
|
98
|
+
when :error_bar
|
99
|
+
# refs. https://github.com/topfunky/gruff/issues/163
|
100
|
+
raise NotImplementedError
|
101
|
+
when :hist
|
102
|
+
raise NotImplementedError
|
89
103
|
end
|
90
|
-
p
|
91
|
-
when :error_bar
|
92
|
-
# refs. https://github.com/topfunky/gruff/issues/163
|
93
|
-
raise NotImplementedError
|
94
|
-
when :hist
|
95
|
-
raise NotImplementedError
|
96
104
|
end
|
97
105
|
end
|
98
106
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Charty
|
4
|
+
module Backends
|
5
|
+
class Plotly
|
6
|
+
Backends.register(:plotly, self)
|
7
|
+
|
8
|
+
attr_reader :context
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_writer :chart_id, :with_api_load_tag, :plotly_src
|
12
|
+
|
13
|
+
def chart_id
|
14
|
+
@chart_id ||= 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def with_api_load_tag
|
18
|
+
return @with_api_load_tag unless @with_api_load_tag.nil?
|
19
|
+
|
20
|
+
@with_api_load_tag = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def plotly_src
|
24
|
+
@plotly_src ||= 'https://cdn.plot.ly/plotly-latest.min.js'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def initilize
|
29
|
+
end
|
30
|
+
|
31
|
+
def label(x, y)
|
32
|
+
end
|
33
|
+
|
34
|
+
def series=(series)
|
35
|
+
@series = series
|
36
|
+
end
|
37
|
+
|
38
|
+
def render(context, filename)
|
39
|
+
plot(nil, context)
|
40
|
+
end
|
41
|
+
|
42
|
+
def plot(plot, context)
|
43
|
+
context = context
|
44
|
+
self.class.chart_id += 1
|
45
|
+
|
46
|
+
case context.method
|
47
|
+
when :bar
|
48
|
+
render_graph(context, :bar)
|
49
|
+
when :curve
|
50
|
+
render_graph(context, :scatter)
|
51
|
+
when :scatter
|
52
|
+
render_graph(context, nil, options: {data: {mode: "markers"}})
|
53
|
+
else
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def plotly_load_tag
|
61
|
+
if self.class.with_api_load_tag
|
62
|
+
"<script type='text/javascript' src='#{self.class.plotly_src}'></script>"
|
63
|
+
else
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def div_id
|
68
|
+
"charty-plotly-#{self.class.chart_id}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def div_style
|
72
|
+
"width: 100%;height: 100%;"
|
73
|
+
end
|
74
|
+
|
75
|
+
def render_graph(context, type, options: {})
|
76
|
+
data = context.series.map do |series|
|
77
|
+
{
|
78
|
+
type: type,
|
79
|
+
x: series.xs.to_a,
|
80
|
+
y: series.ys.to_a,
|
81
|
+
name: series.label
|
82
|
+
}.merge(options[:data] || {})
|
83
|
+
end
|
84
|
+
layout = {
|
85
|
+
title: { text: context.title },
|
86
|
+
xaxis: {
|
87
|
+
title: context.xlabel,
|
88
|
+
range: [context.range[:x].first, context.range[:x].last]
|
89
|
+
},
|
90
|
+
yaxis: {
|
91
|
+
title: context.ylabel,
|
92
|
+
range: [context.range[:y].first, context.range[:y].last]
|
93
|
+
}
|
94
|
+
}
|
95
|
+
render_html(data, layout)
|
96
|
+
end
|
97
|
+
|
98
|
+
def render_html(data, layout)
|
99
|
+
<<~FRAGMENT
|
100
|
+
#{plotly_load_tag unless self.class.chart_id > 1}
|
101
|
+
<div id="#{div_id}" style="#{div_style}"></div>
|
102
|
+
<script>
|
103
|
+
Plotly.plot('#{div_id}', #{JSON.dump(data)}, #{JSON.dump(layout)} );
|
104
|
+
</script>
|
105
|
+
FRAGMENT
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -1,109 +1,117 @@
|
|
1
|
-
require '
|
1
|
+
require 'fileutils'
|
2
2
|
|
3
3
|
module Charty
|
4
|
-
|
5
|
-
|
4
|
+
module Backends
|
5
|
+
class Pyplot
|
6
|
+
Backends.register(:pyplot, self)
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def self.activate_iruby_integration
|
12
|
-
require 'matplotlib/iruby'
|
13
|
-
Matplotlib::IRuby.activate
|
14
|
-
end
|
15
|
-
|
16
|
-
def label(x, y)
|
17
|
-
end
|
18
|
-
|
19
|
-
def series=(series)
|
20
|
-
@series = series
|
21
|
-
end
|
22
|
-
|
23
|
-
def render_layout(layout)
|
24
|
-
(fig, axes) = *@plot.subplots(nrows: layout.num_rows, ncols: layout.num_cols)
|
25
|
-
layout.rows.each_with_index do |row, y|
|
26
|
-
row.each_with_index do |cel, x|
|
27
|
-
plot = layout.num_rows > 1 ? axes[y][x] : axes[x]
|
28
|
-
plot(plot, cel, subplot: true)
|
8
|
+
class << self
|
9
|
+
def prepare
|
10
|
+
require 'matplotlib/pyplot'
|
29
11
|
end
|
30
12
|
end
|
31
|
-
@plot.show
|
32
|
-
end
|
33
13
|
|
34
|
-
|
35
|
-
|
36
|
-
if filename
|
37
|
-
FileUtils.mkdir_p(File.dirname(filename))
|
38
|
-
@plot.savefig(filename)
|
14
|
+
def initialize
|
15
|
+
@plot = ::Matplotlib::Pyplot
|
39
16
|
end
|
40
|
-
@plot.show
|
41
|
-
end
|
42
17
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
FileUtils.mkdir_p(File.dirname(filename))
|
47
|
-
@plot.savefig(filename)
|
18
|
+
def self.activate_iruby_integration
|
19
|
+
require 'matplotlib/iruby'
|
20
|
+
::Matplotlib::IRuby.activate
|
48
21
|
end
|
49
|
-
end
|
50
22
|
|
51
|
-
|
52
|
-
|
53
|
-
# case
|
54
|
-
# when plot.respond_to?(:xlim)
|
55
|
-
# plot.xlim(context.range_x.begin, context.range_x.end)
|
56
|
-
# plot.ylim(context.range_y.begin, context.range_y.end)
|
57
|
-
# when plot.respond_to?(:set_xlim)
|
58
|
-
# plot.set_xlim(context.range_x.begin, context.range_x.end)
|
59
|
-
# plot.set_ylim(context.range_y.begin, context.range_y.end)
|
60
|
-
# end
|
23
|
+
def label(x, y)
|
24
|
+
end
|
61
25
|
|
62
|
-
|
63
|
-
|
64
|
-
plot.xlabel(context.xlabel) if context.xlabel
|
65
|
-
plot.ylabel(context.ylabel) if context.ylabel
|
26
|
+
def series=(series)
|
27
|
+
@series = series
|
66
28
|
end
|
67
29
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
context.series.each do |data|
|
76
|
-
plot.barh(data.xs.to_a.map(&:to_s), data.ys.to_a)
|
30
|
+
def render_layout(layout)
|
31
|
+
_fig, axes = @plot.subplots(nrows: layout.num_rows, ncols: layout.num_cols)
|
32
|
+
layout.rows.each_with_index do |row, y|
|
33
|
+
row.each_with_index do |cel, x|
|
34
|
+
plot = layout.num_rows > 1 ? axes[y][x] : axes[x]
|
35
|
+
plot(plot, cel, subplot: true)
|
36
|
+
end
|
77
37
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
38
|
+
@plot.show
|
39
|
+
end
|
40
|
+
|
41
|
+
def render(context, filename)
|
42
|
+
plot(@plot, context)
|
43
|
+
if filename
|
44
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
45
|
+
@plot.savefig(filename)
|
83
46
|
end
|
84
|
-
plot.
|
85
|
-
|
86
|
-
|
87
|
-
|
47
|
+
@plot.show
|
48
|
+
end
|
49
|
+
|
50
|
+
def save(context, filename)
|
51
|
+
plot(@plot, context)
|
52
|
+
if filename
|
53
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
54
|
+
@plot.savefig(filename)
|
88
55
|
end
|
89
|
-
|
90
|
-
|
91
|
-
|
56
|
+
end
|
57
|
+
|
58
|
+
def plot(plot, context, subplot: false)
|
59
|
+
# TODO: Since it is not required, research and change conditions.
|
60
|
+
# case
|
61
|
+
# when plot.respond_to?(:xlim)
|
62
|
+
# plot.xlim(context.range_x.begin, context.range_x.end)
|
63
|
+
# plot.ylim(context.range_y.begin, context.range_y.end)
|
64
|
+
# when plot.respond_to?(:set_xlim)
|
65
|
+
# plot.set_xlim(context.range_x.begin, context.range_x.end)
|
66
|
+
# plot.set_ylim(context.range_y.begin, context.range_y.end)
|
67
|
+
# end
|
68
|
+
|
69
|
+
plot.title(context.title) if context.title
|
70
|
+
if !subplot
|
71
|
+
plot.xlabel(context.xlabel) if context.xlabel
|
72
|
+
plot.ylabel(context.ylabel) if context.ylabel
|
92
73
|
end
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
data.xs.to_a,
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
74
|
+
|
75
|
+
case context.method
|
76
|
+
when :bar
|
77
|
+
context.series.each do |data|
|
78
|
+
plot.bar(data.xs.to_a.map(&:to_s), data.ys.to_a, label: data.label)
|
79
|
+
end
|
80
|
+
plot.legend()
|
81
|
+
when :barh
|
82
|
+
context.series.each do |data|
|
83
|
+
plot.barh(data.xs.to_a.map(&:to_s), data.ys.to_a)
|
84
|
+
end
|
85
|
+
when :box_plot
|
86
|
+
plot.boxplot(context.data.to_a, labels: context.labels)
|
87
|
+
when :bubble
|
88
|
+
context.series.each do |data|
|
89
|
+
plot.scatter(data.xs.to_a, data.ys.to_a, s: data.zs.to_a, alpha: 0.5, label: data.label)
|
90
|
+
end
|
91
|
+
plot.legend()
|
92
|
+
when :curve
|
93
|
+
context.series.each do |data|
|
94
|
+
plot.plot(data.xs.to_a, data.ys.to_a)
|
95
|
+
end
|
96
|
+
when :scatter
|
97
|
+
context.series.each do |data|
|
98
|
+
plot.scatter(data.xs.to_a, data.ys.to_a, label: data.label)
|
99
|
+
end
|
100
|
+
plot.legend()
|
101
|
+
when :error_bar
|
102
|
+
context.series.each do |data|
|
103
|
+
plot.errorbar(
|
104
|
+
data.xs.to_a,
|
105
|
+
data.ys.to_a,
|
106
|
+
data.xerr,
|
107
|
+
data.yerr,
|
108
|
+
label: data.label,
|
109
|
+
)
|
110
|
+
end
|
111
|
+
plot.legend()
|
112
|
+
when :hist
|
113
|
+
plot.hist(context.data.to_a)
|
103
114
|
end
|
104
|
-
plot.legend()
|
105
|
-
when :hist
|
106
|
-
plot.hist(context.data.to_a)
|
107
115
|
end
|
108
116
|
end
|
109
117
|
end
|