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
@@ -0,0 +1,19 @@
1
+ module Charty
2
+ module PlotMethods
3
+ # Show the given data as rectangular bars.
4
+ #
5
+ # @param x
6
+ # @param y
7
+ # @param color
8
+ # @param data
9
+ def bar_plot(x=nil, y=nil, color=nil, **options, &block)
10
+ Plotters::BarPlotter.new(x, y, color, **options, &block)
11
+ end
12
+
13
+ def box_plot(x=nil, y=nil, color=nil, **options, &block)
14
+ Plotters::BoxPlotter.new(x, y, color, **options, &block)
15
+ end
16
+ end
17
+
18
+ extend PlotMethods
19
+ end
@@ -240,19 +240,19 @@ module Charty
240
240
  @backend.render(self, filename)
241
241
  end
242
242
 
243
- def save(filename=nil)
244
- @backend.save(self, filename)
243
+ def save(filename=nil, **kw)
244
+ @backend.save(self, filename, **kw)
245
245
  end
246
246
 
247
247
  def apply(backend)
248
248
  case
249
- when !@series.empty?
250
- backend.series = @series
251
- when @function
252
- linspace = Linspace.new(@range[:x], 100)
253
- # TODO: set label with function
254
- # TODO: set ys to xs when gruff curve with function
255
- @series << Series.new(linspace.to_a, linspace.map{|x| @function.call(x) }, label: "function" )
249
+ when !@series.empty?
250
+ backend.series = @series
251
+ when @function
252
+ linspace = Linspace.new(@range[:x], 100)
253
+ # TODO: set label with function
254
+ # TODO: set ys to xs when gruff curve with function
255
+ @series << Series.new(linspace.to_a, linspace.map{|x| @function.call(x) }, label: "function" )
256
256
  end
257
257
 
258
258
  @backend = backend
@@ -0,0 +1,4 @@
1
+ require_relative "plotters/abstract_plotter"
2
+ require_relative "plotters/categorical_plotter"
3
+ require_relative "plotters/bar_plotter"
4
+ require_relative "plotters/box_plotter"
@@ -0,0 +1,81 @@
1
+ module Charty
2
+ module Plotters
3
+ class AbstractPlotter
4
+ def initialize(x, y, color, **options)
5
+ self.x = x
6
+ self.y = y
7
+ self.color = color
8
+ self.data = data
9
+ self.palette = palette
10
+ substitute_options(options)
11
+ yield self if block_given?
12
+ end
13
+
14
+ attr_reader :x, :y, :color, :data, :palette
15
+
16
+ def x=(x)
17
+ @x = check_dimension(x, :x)
18
+ end
19
+
20
+ def y=(y)
21
+ @y = check_dimension(y, :y)
22
+ end
23
+
24
+ def color=(color)
25
+ # @color = check_dimension(color, :color)
26
+ unless color.nil?
27
+ raise NotImplementedError,
28
+ "Specifying color variable is not supported yet"
29
+ end
30
+ end
31
+
32
+ def data=(data)
33
+ @data = case data
34
+ when nil, Charty::Table
35
+ data
36
+ else
37
+ Charty::Table.new(data)
38
+ end
39
+ end
40
+
41
+ def palette=(palette)
42
+ @palette = case palette
43
+ when nil, Charty::Palette, Symbol, String
44
+ palette
45
+ else
46
+ raise ArgumentError,
47
+ "invalid type for palette (given #{palette.class}, " +
48
+ "expected Charty::Palette, Symbol, or String)"
49
+ end
50
+ end
51
+
52
+ private def substitute_options(options)
53
+ options.each do |key, val|
54
+ send("#{key}=", val)
55
+ end
56
+ end
57
+
58
+ private def check_dimension(value, name)
59
+ case value
60
+ when nil, Symbol, String, method(:array?)
61
+ value
62
+ when ->(x) { x.respond_to?(:to_str) }
63
+ value.to_str
64
+ else
65
+ raise ArgumentError,
66
+ "invalid type of dimension for #{name} (given #{value.inspect})",
67
+ caller
68
+ end
69
+ end
70
+
71
+ private def array?(value)
72
+ TableAdapters::HashAdapter.array?(value)
73
+ end
74
+
75
+ def to_iruby
76
+ result = render
77
+ ["text/html", result] if result
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,19 @@
1
+ module Charty
2
+ module Plotters
3
+ class BarPlotter < CategoricalPlotter
4
+ def render
5
+ backend = Backends.current
6
+ backend.begin_figure
7
+ draw_bars(backend)
8
+ annotate_axes(backend)
9
+ backend.show
10
+ end
11
+
12
+ private def draw_bars(backend)
13
+ statistic = @plot_data.map {|xs| Statistics.mean(xs) }
14
+ bar_pos = (0 ... statistic.length).to_a
15
+ backend.bar(bar_pos, statistic, color: @colors)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module Charty
2
+ module Plotters
3
+ class BoxPlotter < CategoricalPlotter
4
+ def render
5
+ backend = Backends.current
6
+ backend.begin_figure
7
+ draw_box_plot(backend)
8
+ annotate_axes(backend)
9
+ backend.show
10
+ end
11
+
12
+ private def draw_box_plot(backend)
13
+ plot_data = @plot_data.each do |group_data|
14
+ next nil if group_data.empty?
15
+
16
+ group_data = Array(group_data)
17
+ remove_na!(group_data)
18
+
19
+ group_data
20
+ end
21
+ backend.box_plot(plot_data, (0 ... @plot_data.length).to_a,
22
+ color: @colors, gray: @gray)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,148 @@
1
+ module Charty
2
+ module Plotters
3
+ class CategoricalPlotter < AbstractPlotter
4
+ def initialize(x, y, color, **options, &block)
5
+ super
6
+
7
+ setup_variables
8
+ setup_colors
9
+ end
10
+
11
+ attr_reader :order
12
+
13
+ def order=(order)
14
+ @order = Array(order).map(&method(:normalize_name))
15
+ end
16
+
17
+ private def normalize_name(value)
18
+ case value
19
+ when String, Symbol
20
+ value
21
+ else
22
+ value.to_str
23
+ end
24
+ end
25
+
26
+ attr_reader :group_names, :plot_data, :group_label, :value_label
27
+
28
+ private def setup_variables
29
+ if x.nil? && y.nil?
30
+ setup_variables_with_wide_form_dataset
31
+ else
32
+ setup_variables_with_long_form_dataset
33
+ end
34
+ end
35
+
36
+ private def setup_variables_with_wide_form_dataset
37
+ if @color
38
+ raise ArgumentError,
39
+ "Cannot use `color` without `x` or `y`"
40
+ end
41
+
42
+ # No color grouping with wide inputs
43
+ @plot_colors = nil
44
+ @color_title = nil
45
+ @color_names = nil
46
+
47
+ # No statistical units with wide inputs
48
+ @plot_units = nil
49
+
50
+ @value_label = nil
51
+ @group_label = nil
52
+
53
+ order = @order # TODO: supply order via parameter
54
+ unless order
55
+ order = @data.column_names.select do |cn|
56
+ @data[cn].all? {|x| Float(x, exception: false) }
57
+ end
58
+ end
59
+ order ||= @data.column_names
60
+ @plot_data = order.map {|cn| @data[cn] }
61
+ @group_names = order
62
+ end
63
+
64
+ private def setup_variables_with_long_form_dataset
65
+ x, y = @x, @y
66
+ if @data
67
+ x = @data[x] || x
68
+ y = @data[y] || y
69
+ end
70
+
71
+ # Validate inputs
72
+ [x, y].each do |input|
73
+ next if array?(input)
74
+ raise RuntimeError,
75
+ "Could not interpret interpret input `#{input.inspect}`"
76
+ end
77
+
78
+ if x.nil? || y.nil?
79
+ setup_single_data
80
+ else
81
+ # FIXME: Assume vertical plot
82
+ groups, vals = x, y
83
+
84
+ if groups.respond_to?(:name)
85
+ @group_label = groups.name
86
+ end
87
+
88
+ if vals.respond_to?(:name)
89
+ @value_label = vals.name
90
+ end
91
+
92
+ # FIXME: Assume groups has only unique values
93
+ @group_names = groups
94
+ @plot_data = vals.map {|v| [v] }
95
+ end
96
+ end
97
+
98
+ private def setup_single_data
99
+ raise NotImplementedError,
100
+ "Single data plot is not supported yet"
101
+ end
102
+
103
+ private def setup_colors
104
+ n_colors = @plot_data.length
105
+ if @palette.nil?
106
+ current_palette = Palette.default
107
+ if n_colors <= current_palette.n_colors
108
+ palette = Palette.new(current_palette.colors, n_colors)
109
+ else
110
+ palette = Palette.husl(n_colors, l: 0.7r)
111
+ end
112
+ else
113
+ case @palette
114
+ when Hash
115
+ # Assume @palette has a hash table that maps
116
+ # group_names to colors
117
+ palette = @group_names.map {|gn| @palette[gn] }
118
+ else
119
+ palette = @palette
120
+ end
121
+ palette = Palette.new(palette, n_colors)
122
+ end
123
+
124
+ @colors = palette.colors.map {|c| c.to_rgb }
125
+ lightness_values = @colors.map {|c| c.to_hsl.l }
126
+ lum = lightness_values.min * 0.6r
127
+ @gray = Colors::RGB.new(lum, lum, lum) # TODO: Use Charty::Gray
128
+ end
129
+
130
+ private def annotate_axes(backend)
131
+ backend.set_xlabel(@group_label)
132
+ backend.set_ylabel(@value_label)
133
+ backend.set_xticks((0 ... @plot_data.length).to_a)
134
+ backend.set_xtick_labels(@group_names)
135
+ backend.disable_xaxis_grid
136
+ backend.set_xlim(-0.5, @plot_data.length - 0.5)
137
+ end
138
+
139
+ private def remove_na!(ary)
140
+ ary.reject! do |x|
141
+ next true if x.nil?
142
+ x.respond_to?(:nan?) && x.nan?
143
+ end
144
+ ary
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,29 @@
1
+ module Charty
2
+ module Statistics
3
+ begin
4
+ require "enumerable/statistics"
5
+
6
+ def self.mean(enum)
7
+ enum.mean
8
+ end
9
+
10
+ def self.stdev(enum)
11
+ enum.stdev
12
+ end
13
+ rescue LoadError
14
+ def self.mean(enum)
15
+ xs = enum.to_a
16
+ xs.sum / xs.length.to_f
17
+ end
18
+
19
+ def self.stdev(enum, population: false)
20
+ xs = enum.to_a
21
+ n = xs.length
22
+ mean = xs.sum.to_f / n
23
+ ddof = population ? 0 : 1
24
+ var = xs.map {|x| (x - mean)**2 }.sum / (n - ddof)
25
+ Math.sqrt(var)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -26,6 +26,7 @@ module Charty
26
26
  attr_reader :adapter
27
27
 
28
28
  def_delegator :@adapter, :column_names
29
+ def_delegator :@adapter, :data, :raw_data
29
30
 
30
31
  def columns
31
32
  @column_accessor ||= ColumnAccessor.new(@adapter)
@@ -15,7 +15,7 @@ module Charty
15
15
  @columns = nil
16
16
  end
17
17
 
18
- attr_reader :column_names
18
+ attr_reader :column_names, :data
19
19
 
20
20
  def [](row, column)
21
21
  fetch_records unless @columns
@@ -13,6 +13,8 @@ module Charty
13
13
  @data = check_type(Daru::DataFrame, data, :data)
14
14
  end
15
15
 
16
+ attr_reader :data
17
+
16
18
  def column_names
17
19
  @data.vectors.to_a
18
20
  end
@@ -15,6 +15,10 @@ module Charty
15
15
  @records = []
16
16
  end
17
17
 
18
+ def data
19
+ @table
20
+ end
21
+
18
22
  def column_names
19
23
  @table.column_names
20
24
  end
@@ -61,6 +61,8 @@ module Charty
61
61
  end
62
62
  end
63
63
 
64
+ attr_reader :data
65
+
64
66
  def_delegator :@data, :keys, :column_names
65
67
 
66
68
  def [](row, column)
@@ -20,7 +20,7 @@ module Charty
20
20
  @column_names = generate_column_names(data.shape[1], columns)
21
21
  end
22
22
 
23
- attr_reader :column_names
23
+ attr_reader :column_names, :data
24
24
 
25
25
  def [](row, column)
26
26
  if row
@@ -20,7 +20,7 @@ module Charty
20
20
  @column_names = generate_column_names(data.shape[1], columns)
21
21
  end
22
22
 
23
- attr_reader :column_names
23
+ attr_reader :column_names, :data
24
24
 
25
25
  def [](row, column)
26
26
  if row
@@ -1,5 +1,5 @@
1
1
  module Charty
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
 
4
4
  module Version
5
5
  numbers, TAG = VERSION.split("-")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: charty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - youchan
@@ -10,8 +10,22 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2019-09-27 00:00:00.000000000 Z
13
+ date: 2020-01-09 00:00:00.000000000 Z
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: red-colors
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
15
29
  - !ruby/object:Gem::Dependency
16
30
  name: bundler
17
31
  requirement: !ruby/object:Gem::Requirement
@@ -161,8 +175,8 @@ executables: []
161
175
  extensions: []
162
176
  extra_rdoc_files: []
163
177
  files:
178
+ - ".github/workflows/ci.yml"
164
179
  - ".gitignore"
165
- - ".travis.yml"
166
180
  - Dockerfile.dev
167
181
  - Gemfile
168
182
  - LICENSE
@@ -177,7 +191,9 @@ files:
177
191
  - examples/daru.ipynb
178
192
  - examples/iris_dataset.ipynb
179
193
  - examples/nmatrix.ipynb
180
- - examples/numo-narray.ipynb
194
+ - examples/numo_narray.ipynb
195
+ - examples/palette.rb
196
+ - examples/sample.png
181
197
  - examples/sample_bokeh.ipynb
182
198
  - examples/sample_google_chart.ipynb
183
199
  - examples/sample_gruff.ipynb
@@ -211,16 +227,25 @@ files:
211
227
  - examples/sample_rubyplot.ipynb
212
228
  - images/design_concept.png
213
229
  - lib/charty.rb
230
+ - lib/charty/backend_methods.rb
214
231
  - lib/charty/backends.rb
215
232
  - lib/charty/backends/bokeh.rb
216
- - lib/charty/backends/google_chart.rb
233
+ - lib/charty/backends/google_charts.rb
217
234
  - lib/charty/backends/gruff.rb
218
235
  - lib/charty/backends/plotly.rb
219
236
  - lib/charty/backends/pyplot.rb
220
237
  - lib/charty/backends/rubyplot.rb
221
238
  - lib/charty/layout.rb
222
239
  - lib/charty/linspace.rb
240
+ - lib/charty/palette.rb
241
+ - lib/charty/plot_methods.rb
223
242
  - lib/charty/plotter.rb
243
+ - lib/charty/plotters.rb
244
+ - lib/charty/plotters/abstract_plotter.rb
245
+ - lib/charty/plotters/bar_plotter.rb
246
+ - lib/charty/plotters/box_plotter.rb
247
+ - lib/charty/plotters/categorical_plotter.rb
248
+ - lib/charty/statistics.rb
224
249
  - lib/charty/table.rb
225
250
  - lib/charty/table_adapters.rb
226
251
  - lib/charty/table_adapters/active_record_adapter.rb