Skip to content

Commit 6fff607

Browse files
committed
plotting scatter plots
1 parent d2671c5 commit 6fff607

6 files changed

Lines changed: 42 additions & 126 deletions

File tree

CONTRIBUTING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Developer notes
22

33
## Co-ordinate system
4+
5+
Rubyplot assumes that the co-ordinate system has the origin at the top left corner
6+
of the graph. This helps in keeping all pixel co-ordinates positive values.
7+
48
Each Artist contains a `(abs_x, abs_y)` pair that denotes the absolute position of the
59
Artist on the canvas. For `Figure` and `Axes` this pair denotes the top left corner.
610

lib/rubyplot/artist/axes.rb

Lines changed: 14 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ class Axes < Base
5656
attr_accessor :y_axis_margin
5757
# Position of the legend box.
5858
attr_accessor :legend_box_position
59+
# Rubyplot::Artist::XAxis object.
60+
attr_reader :x_axis
61+
# Rubyplot::Artist::YAxis object.
62+
attr_reader :y_axis
5963

6064
# @param figure [Rubyplot::Figure] Figure object to which this Axes belongs.
6165
def initialize figure
@@ -124,9 +128,8 @@ def draw
124128
calculate_xy_axes_origin
125129
configure_xy_axes
126130
configure_legends
127-
# configure_plotting_data
131+
configure_plotting_data
128132
actually_draw
129-
# @plots.each(&:draw)
130133
end
131134

132135
def scatter! *args, &block
@@ -212,103 +215,6 @@ def prepare_legend
212215
@legends = @plots.map(&:create_legend)
213216
@legends.each { |l| l.draw }
214217
end
215-
216-
# Calculates size of drawable area and generates normalized data.
217-
#
218-
# * line markers
219-
# * legend
220-
# * title
221-
# * labels
222-
# * X/Y offsets
223-
def setup_drawing
224-
calculate_spread
225-
normalize # FIXME: maybe doesnt need to go here.
226-
setup_graph_measurements
227-
end
228-
229-
# Calculate spread of the data.
230-
def calculate_spread
231-
@y_spread = @y_range[1].to_f - @y_range[0].to_f
232-
unless @x_range[0].nil? && @x_range[1].nil?
233-
@x_spread = @x_range[1].to_f - @x_range[0].to_f
234-
@x_spread = @x_spread > 0 ? @x_spread : 1
235-
end
236-
end
237-
238-
# Normalize data with values scaled between 0-100.
239-
def normalize
240-
@plots.each do |p|
241-
p.normalize @x_spread, @y_spread
242-
end
243-
end
244-
245-
##
246-
# Calculates size of drawable area, general font dimensions, etc.
247-
# This is the most crucial part of the code and is based on geometry.
248-
# It calcuates the measurments in pixels to figure out the positioning
249-
# gap pixels of Legends, Labels and Titles from the picture edge.
250-
def setup_graph_measurements
251-
@marker_caps_height = @backend.caps_height @font, @marker_font_size
252-
@title_caps_height = @geometry.hide_title || @title.nil? ? 0 :
253-
@backend.caps_height(@font, @title_font_size) *
254-
@title.lines.to_a.size
255-
@legend_caps_height = @backend.caps_height @font, @legend_font_size
256-
257-
# For now, the labels feature only focuses on the dot graph so it
258-
# makes sense to only have this as an attribute for this kind of
259-
# graph and not for others.
260-
if @geometry.has_left_labels
261-
text = @y_ticks.values.inject('') { |value, memo|
262-
value.to_s.length > memo.to_s.length ? value : memo
263-
}
264-
longest_left_label_width = @backend.string_width(
265-
@marker_font_size, text) * 1.25
266-
else
267-
longest_left_label_width = @backend.string_width(
268-
@font, @marker_font_size,
269-
label_string(@y_range[1].to_f, @geometry.increment))
270-
end
271-
272-
# Shift graph if left line numbers are hidden
273-
line_number_width = @geometry.hide_line_numbers && !@geometry.has_left_labels ?
274-
0.0 : (longest_left_label_width + LABEL_MARGIN * 2)
275-
# Pixel offset from the left edge of the plot
276-
@graph_left = @geometry.left_margin +
277-
line_number_width +
278-
(@geometry.y_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN * 2)
279-
# Make space for half the width of the rightmost column label.
280-
# last_label = @x_ticks.keys.max.to_i
281-
# extra_room_for_long_label = last_label >= (@geometry.column_count - 1) &&
282-
# @geometry.center_labels_over_point ?
283-
# @backend.string_width(
284-
# @font,
285-
# @marker_font_size,
286-
# @x_ticks[last_label]) / 2.0 : 0
287-
extra_room_for_long_label = 0
288-
# Margins
289-
@graph_right_margin = @geometry.right_margin + extra_room_for_long_label
290-
@graph_bottom_margin = @geometry.bottom_margin + @marker_caps_height + LABEL_MARGIN
291-
292-
@graph_right = @geometry.raw_columns - @graph_right_margin
293-
@graph_width = @geometry.raw_columns - @graph_left - @graph_right_margin
294-
295-
# When @hide title, leave a title_margin space for aesthetics.
296-
@graph_top = @geometry.legend_at_bottom ?
297-
@geometry.top_margin :
298-
(@geometry.top_margin +
299-
(@geometry.hide_title ?
300-
@title_margin :
301-
@title_caps_height + @title_margin) +
302-
(@legend_caps_height + @legend_margin))
303-
304-
x_axis_label_height = @geometry.x_axis_label .nil? ? 0.0 :
305-
@marker_caps_height + LABEL_MARGIN
306-
307-
# The actual height of the graph inside the whole image in pixels.
308-
@graph_bottom = @raw_rows - @graph_bottom_margin -
309-
x_axis_label_height - @geometry.label_stagger_height
310-
@graph_height = @graph_bottom - @graph_top
311-
end
312218

313219
# Figure out the co-ordinates of the title text w.r.t Axes.
314220
def configure_title
@@ -343,12 +249,21 @@ def configure_legends
343249
self, abs_x: legend_box_ix, abs_y: legend_box_iy)
344250
end
345251

252+
# Make adjustments to the data that will be plotted. Maps the data
253+
# contained in the plot to actual pixel values.
254+
def configure_plotting_data
255+
@plots.each do |plot|
256+
plot.normalize
257+
end
258+
end
259+
346260
# Call the respective draw methods on each of the elements of this Axes.
347261
def actually_draw
348262
@x_axis.draw
349263
@y_axis.draw
350264
@title.draw
351265
@legend_box.draw
266+
@plots.each(&:draw)
352267
end
353268

354269
# Return a formatted string representing a number value that should be

lib/rubyplot/artist/axis/x_axis.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ def draw
2727

2828
def configure_axis_line
2929
@line = Rubyplot::Artist::Line2D.new(
30-
self, abs_x1: @abs_x1, abs_y1: @abs_y1, abs_x2: @abs_x2, abs_y2: @abs_y2,
31-
)#stroke_width: @stroke_width)
30+
self, abs_x1: @abs_x1, abs_y1: @abs_y1, abs_x2: @abs_x2, abs_y2: @abs_y2)
31+
#stroke_width: @stroke_width)
3232
end
3333

3434
def populate_major_x_ticks
35-
value_distance = (@max_val - @min_val) / @major_ticks_count.to_f
35+
value_distance = (@max_val) / @major_ticks_count.to_f
3636
@major_ticks_count.times do |count|
3737
count += 1
3838
@x_ticks << Rubyplot::Artist::XTick.new(

lib/rubyplot/artist/circle.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
module Rubyplot
22
module Artist
33
class Circle < Base
4-
def initialize(owner, x, y, radius, stroke_opacity: 1.0,
4+
def initialize(owner, abs_x:, abs_y:, radius: , stroke_opacity: 0.0,
55
color: '#000000', stroke_width:)
6+
super(owner.backend, abs_x, abs_y)
67
@owner = owner
7-
@x = x
8-
@y = y
98
@radius = radius
109
@stroke_width = stroke_width
1110
@stroke_opacity = stroke_opacity
@@ -15,7 +14,7 @@ def initialize(owner, x, y, radius, stroke_opacity: 1.0,
1514

1615
def draw
1716
@backend.draw_circle(
18-
x: @x, y: @y, radius: @radius, stroke_opacity: @stroke_opacity,
17+
x: @abs_x, y: @abs_y, radius: @radius, stroke_opacity: @stroke_opacity,
1918
stroke_width: @stroke_width, color: @color
2019
)
2120
end

lib/rubyplot/artist/plot/base.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def initialize axes
1717
:x_values => nil
1818
}
1919
@stroke_width = 4.0
20-
@stroke_opacity = 1.0
20+
@stroke_opacity = 0.0
2121
end
2222

2323
def label
@@ -53,19 +53,19 @@ def data x_values, y_values
5353
@axes.geometry.has_data = true
5454
end
5555

56-
# Normalize original data between the spread of the data.
57-
def normalize x_spread, y_spread
56+
# Normalize original data to values between 0-100.
57+
def normalize
58+
x_min = @axes.x_range[0] < 0 ? @axes.x_range[0] : 0
59+
y_min = @axes.y_range[0] < 0 ? @axes.y_range[0] : 0
60+
x_spread = @axes.x_range[1] - x_min
61+
y_spread = @axes.y_range[1] - y_min
5862
@normalized_data[:x_values] = @data[:x_values].map do |x|
59-
(x.to_f - @axes.x_range[0]) / x_spread
63+
(x.to_f - x_min) / x_spread
6064
end
6165
@normalized_data[:y_values] = @data[:y_values].map do |y|
62-
(y.to_f - @axes.y_range[0]) / y_spread
66+
(y.to_f - y_min) / y_spread
6367
end
6468
end
65-
66-
def create_legend
67-
Rubyplot::Artist::Legend.new(@axes, @data[:label], @data[:color])
68-
end
6969
end # class Base
7070
end # module Plot
7171
end # module Artist

lib/rubyplot/artist/plot/scatter.rb

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,20 @@ def initialize(*)
1010
end
1111

1212
def draw
13-
@x_increment = if @axes.geometry.column_count > 1
14-
(@axes.graph_width / (@axes.geometry.column_count-1)).to_f
15-
else
16-
@axes.graph_width
17-
end
13+
puts "data: #{@data}"
14+
puts "norm: #{@normalized_data}"
15+
y_axis_length = (@axes.y_axis.abs_y2 - @axes.y_axis.abs_y1).abs
1816
@normalized_data[:y_values].each_with_index do |iy, idx_y|
1917
ix = @normalized_data[:x_values][idx_y]
2018
next if iy.nil? || ix.nil?
21-
relative_ix = ix * @axes.graph_width + @axes.graph_left
22-
relative_iy = @axes.graph_top + (@axes.graph_height -
23-
iy * @axes.graph_height)
19+
abs_x = ix * (@axes.x_axis.abs_x2 - @axes.x_axis.abs_x1).abs + @axes.abs_x +
20+
@axes.y_axis_margin
21+
abs_y = (y_axis_length - iy * y_axis_length) + @axes.abs_y
2422
Rubyplot::Artist::Circle.new(
2523
self,
26-
relative_ix,
27-
relative_iy,
28-
@circle_radius,
24+
abs_x: abs_x,
25+
abs_y: abs_y,
26+
radius: @circle_radius,
2927
stroke_opacity: @stroke_opacity,
3028
stroke_width: @stroke_width
3129
).draw

0 commit comments

Comments
 (0)