Skip to content

Commit 01a90bb

Browse files
committed
port code for bubble plots
1 parent 8aa1f90 commit 01a90bb

8 files changed

Lines changed: 224 additions & 10 deletions

File tree

lib/rubyplot/axes.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ def area! *args, &block
6868
add_plot "Area", *args, &block
6969
end
7070

71+
def bubble! *args, &block
72+
add_plot "Bubble", *args, &block
73+
end
74+
7175
def write file_name
7276
@plots[0].write file_name
7377
end

lib/rubyplot/magick_wrapper/artist.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def setup_graph_measurements
251251
else
252252
longest_left_label_width = calculate_width(
253253
@marker_font_size,
254-
label(@geometry.maximum_value.to_f, @geometry.increment))
254+
label_string(@geometry.maximum_value.to_f, @geometry.increment))
255255
end
256256

257257
# Shift graph if left line numbers are hidden
@@ -471,7 +471,7 @@ def draw_line_markers!
471471
@d = @d.scale_annotation(@base_image,
472472
@graph_left - LABEL_MARGIN, 1.0,
473473
0.0, y,
474-
label(marker_label, @geometry.increment), @scale)
474+
label_string(marker_label, @geometry.increment), @scale)
475475
end
476476
@d = @d.stroke_antialias true
477477
# string = 'hello' + '.png'
@@ -543,7 +543,7 @@ def draw_label(x_offset, index)
543543

544544
# Return a formatted string representing a number value that should be
545545
# printed as a label.
546-
def label(value, increment)
546+
def label_string(value, increment)
547547
label =
548548
if increment
549549
if increment >= 10 || (increment * 1) == (increment * 1).to_i.to_f

lib/rubyplot/magick_wrapper/artist/math_methods.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,28 @@ module MagickWrapper
33
class Artist
44
module MathMethods
55
# Make copy of data with values scaled between 0-100
6-
# TODO: Add spec for this method.
6+
# norm_data gets populated here.
7+
# FIXME: change this method to reflect arch change.
78
def normalize
89
@geometry.norm_data = []
910
#@data.each do |data_row|
1011
data_row = @data
12+
norm_data_points = []
13+
data_row[:y_values].each do |data_point|
14+
norm_data_points << ((data_point.to_f - @geometry.minimum_value.to_f) / @spread)
15+
# Add support for nil values in data etc.
16+
end
17+
@geometry.norm_data << [data_row[:label], norm_data_points]
18+
#end
19+
@geometry.norm_data[0] << @data[:color]
20+
if @data[:x_values]
1121
norm_data_points = []
12-
data_row[:y_values].each do |data_point|
22+
data_row[:x_values].each do |data_point|
1323
norm_data_points << ((data_point.to_f - @geometry.minimum_value.to_f) / @spread)
1424
# Add support for nil values in data etc.
1525
end
16-
@geometry.norm_data << [data_row[:label], norm_data_points]
17-
#end
26+
@geometry.norm_data[0] << norm_data_points
27+
end
1828
end
1929

2030
def clip_value_if_greater_than(value, max_value) # :nodoc:

lib/rubyplot/magick_wrapper/plot.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
require_relative 'plot/bar'
33
require_relative 'plot/line'
44
require_relative 'plot/area'
5+
require_relative 'plot/bubble'
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
require_relative 'bubble/geometry'
2+
3+
module Rubyplot
4+
module MagickWrapper
5+
module Plot
6+
class Bubble < MagickWrapper::Plot::Scatter
7+
# Rubyplot::Bubble takes the same parameters as the Rubyplot::Bubble graph
8+
#
9+
# ==== Example
10+
# g = Rubyplot::Bubble.new
11+
#
12+
def initialize(*)
13+
super
14+
@all_colors_array = Magick.colors
15+
@plot_colors = []
16+
@z_data = []
17+
@geometry = Plot::Bubble::Geometry.new
18+
end
19+
20+
# The first parameter is the name of the dataset. The next two are the
21+
# x and y axis data points contain in their own array in that respective
22+
# order. Then the Z axis data points refer to the radius of the bubble plots.
23+
# The final parameter is the color.
24+
#
25+
# Can be called multiple times with different datasets for a multi-valued
26+
# graph.
27+
#
28+
# If the color argument is nil, the next color from the default theme will
29+
# be used.
30+
#
31+
# ==== Parameters to data function
32+
# name:: String or Symbol containing the name of the dataset.
33+
# x_data_points:: An Array of x-axis data points.
34+
# y_data_points:: An Array of y-axis data points.
35+
# z_data_points:: An Array containing the radius of the data points.
36+
# color:: The hex string for the color of the dataset. Defaults to nil.
37+
#
38+
#
39+
# ==== Examples
40+
# plot = Rubyplot::Bubble.new
41+
# plot.data(:apples, [-1, 19, -4, -23], [-35, 21, 23, -4], [45, 10, 21, 9])
42+
# plot.data(:peaches, [20, 30, -6, -3], [-1, 5, -27, -3], [13, 10, 20, 10])
43+
# plot.write('spec/reference_images/bubble_test_1.png')
44+
#
45+
def data(x_data_points = [], y_data_points = [], z_data_points = [])
46+
# name = label == :default ? ' ' : label.to_s
47+
# the existing data routine for the y axis data
48+
data_y(y_data_points, z_data_points)
49+
# append the x data to the last entry that was just added in the @data member
50+
# last_elem = @data.length - 1
51+
# @data[last_elem] << x_data_points
52+
@data[:x_values] = x_data_points
53+
54+
if @geometry.maximum_x_value.nil? && @geometry.minimum_x_value.nil?
55+
@geometry.maximum_x_value = @geometry.minimum_x_value = x_data_points.first
56+
end
57+
@z_data << z_data_points
58+
x_z_array_sum = [x_data_points, z_data_points].transpose.map { |x| x.reduce(:+) }
59+
x_z_array_diff = [x_data_points, z_data_points].transpose.map { |x| x.reduce(:-) }
60+
61+
@geometry.maximum_x_value = x_z_array_sum.max > @geometry.maximum_x_value ?
62+
x_z_array_sum.max : @geometry.maximum_x_value
63+
@geometry.minimum_x_value = x_z_array_sum.min < @geometry.minimum_x_value ?
64+
x_z_array_sum.min : @geometry.minimum_x_value
65+
66+
@geometry.maximum_x_value = x_z_array_diff.max > @geometry.maximum_x_value ?
67+
x_z_array_diff.max : @geometry.maximum_x_value
68+
@geometry.minimum_x_value = x_z_array_diff.min < @geometry.minimum_x_value ?
69+
x_z_array_diff.min : @geometry.minimum_x_value
70+
end
71+
72+
private
73+
74+
# Helper function to normalize the data along Y axis.
75+
def data_y(data_points = [], z_data_points)
76+
data_points = Array(data_points) # make sure it's an array
77+
# TODO: Adding an empty color array which can be developed later
78+
# to make graphs super customizable with regards to coloring of
79+
# individual data points.
80+
@data[:y_values] = data_points
81+
# Set column count if this is larger than previous column counts
82+
@geometry.column_count = data_points.length > @geometry.column_count ?
83+
data_points.length : @geometry.column_count
84+
85+
y_z_array_sum = [data_points, z_data_points].transpose.map { |x| x.reduce(:+) }
86+
y_z_array_diff = [data_points, z_data_points].transpose.map { |x| x.reduce(:-) }
87+
if @geometry.maximum_value.nil? && @geometry.maximum_value.nil?
88+
@geometry.maximum_value = @geometry.minimum_value = data_points.first
89+
end
90+
@geometry.maximum_value = y_z_array_sum.max > @geometry.maximum_value ?
91+
y_z_array_sum.max : @geometry.maximum_value
92+
@geometry.minimum_value = y_z_array_sum.min < @geometry.minimum_value ?
93+
y_z_array_sum.min : @geometry.minimum_value
94+
@geometry.maximum_value = y_z_array_diff.max > @geometry.maximum_value ?
95+
y_z_array_diff.max : @geometry.maximum_value
96+
@geometry.minimum_value = y_z_array_diff.min < @geometry.minimum_value ?
97+
y_z_array_diff.min : @geometry.minimum_value
98+
@geometry.has_data = true
99+
end
100+
101+
def draw
102+
super
103+
# Check to see if more than one datapoint was given. NaN can result otherwise.
104+
@x_increment = @geometry.column_count > 1 ?
105+
(@graph_width / (@geometry.column_count - 1).to_f) : @graph_width
106+
107+
@geometry.norm_data.each_with_index do |data_row, data_row_index|
108+
data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
109+
@d = @d.fill @plot_colors[data_row_index]
110+
x_value = data_row[DATA_VALUES_X_INDEX][index]
111+
next if data_point.nil? || x_value.nil?
112+
113+
new_x = get_x_coord(x_value, @graph_width, @graph_left)
114+
new_y = @graph_top + (@graph_height - data_point * @graph_height)
115+
116+
# Reset each time to avoid thin-line errors
117+
@d = @d.stroke_opacity 1.0
118+
@d.fill_opacity(0.3)
119+
@d.fill_color(@plot_colors[data_row_index])
120+
@d = @d.stroke_width @stroke_width ||
121+
clip_value_if_greater_than(@columns /
122+
(@geometry.norm_data.first[1].size * 4), 5.0)
123+
circle_radius = 2 * @z_data[data_row_index][index]
124+
@d = @d.circle(new_x, new_y, new_x - circle_radius, new_y)
125+
end
126+
end
127+
@d.draw(@base_image)
128+
end
129+
end # class Bubble
130+
end # module Plot
131+
end # module MagickWrapper
132+
end # module Rubyplot
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module Rubyplot
2+
module MagickWrapper
3+
module Plot
4+
class Bubble < MagickWrapper::Plot::Scatter
5+
class Geometry < MagickWrapper::Plot::Scatter::Geometry
6+
attr_accessor :all_colors_array
7+
attr_accessor :z_data
8+
attr_accessor :plot_colors
9+
attr_accessor :maximum_x_value
10+
attr_accessor :minimum_x_value
11+
12+
def initialize
13+
super
14+
@all_colors_array = Magick.colors
15+
@plot_colors = []
16+
@z_data = []
17+
@maximum_x_value = nil
18+
@minimum_x_value = nil
19+
end
20+
end # class Geometry
21+
end # class Bar
22+
end # module Plot
23+
end # module MagickWrapper
24+
end # module Rubyplot

lib/rubyplot/magick_wrapper/plot/scatter.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def draw_line_markers!
239239
@d = @d.stroke_antialias true
240240
end
241241

242-
def label(value, increment)
242+
def label_string(value, increment)
243243
if @geometry.y_axis_label_format
244244
@geometry.y_axis_label_format.call(value)
245245
else
@@ -251,7 +251,7 @@ def vertical_label(value, increment)
251251
if @geometry.x_axis_label_format
252252
@geometry.x_axis_label_format.call(value)
253253
else
254-
label(value, increment)
254+
label_string(value, increment)
255255
end
256256
end
257257

spec/axes_spec.rb

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,50 @@
1313
]
1414
end
1515

16-
context "#area!", focus: true do
16+
context "#bubble!", focus: true do
17+
before do
18+
@temp_dir = SPEC_ROOT + "temp/bubble"
19+
@fix_dir = SPEC_ROOT + "fixtures/bubble"
20+
FileUtils.mkdir_p @temp_dir
21+
end
22+
23+
it "plots a single bubble plot", focus: true do
24+
fig = Rubyplot::Figure.new
25+
axes = fig.add_subplot 0,0
26+
axes.bubble! do |p|
27+
p.data [-1, 19, -4, -23], [-35, 21, 23, -4], [45, 10, 21, 9]
28+
p.label = "apples"
29+
p.color = :blue
30+
end
31+
axes.title = "simple bubble plot."
32+
33+
file = "/#{Rubyplot.backend}_simple_bubble.png"
34+
fig.write(@temp_dir + file)
35+
36+
#expect("temp/bubble" + file).to eq_image("fixtures/bubble" + file)
37+
end
38+
39+
it "plots multiple bubble plots on same axes." do
40+
fig = Rubyplot::Figure.new
41+
axes = fig.add_subplot 0,0
42+
axes.bubble! do |p|
43+
p.data [-1, 19, -4, -23], [-35, 21, 23, -4], [45, 10, 21, 9]
44+
p.label = "apples"
45+
end
46+
axes.bubble! do |p|
47+
p.data [20, 30, -6, -3], [-1, 5, -27, -3], [13, 10, 20, 10]
48+
p.label = "peaches"
49+
end
50+
axes.title = "simple bubble plot."
51+
52+
file = "/#{Rubyplot.backend}_multiple_bubble.png"
53+
fig.write(@temp_dir + file)
54+
55+
#expect("temp/bubble" + file).to eq_image("fixtures/bubble" + file)
56+
end
57+
end
58+
59+
context "#area!" do
1760
before do
1861
@temp_dir = SPEC_ROOT + "temp/area"
1962
@fix_dir = SPEC_ROOT + "fixtures/area"

0 commit comments

Comments
 (0)