Class: Rubyplot::Line

Inherits:
Artist
  • Object
show all
Defined in:
lib/rubyplot/scripting/line/data.rb,
lib/rubyplot/scripting/line/draw.rb

Overview

This class helps draw line graphs for both “y axis data” and for “x-y axis data”.

Constant Summary

Constants inherited from Artist

Artist::DATA_COLOR_INDEX, Artist::DATA_LABEL_INDEX, Artist::DATA_VALUES_INDEX, Artist::DATA_VALUES_X_INDEX, Artist::DEFAULT_MARGIN, Artist::DEFAULT_TARGET_WIDTH, Artist::LABEL_MARGIN, Artist::LEGEND_MARGIN, Artist::THOUSAND_SEPARATOR

Instance Attribute Summary collapse

Attributes inherited from Artist

#base_image, #font, #font_color, #has_left_labels, #hide_legend, #hide_line_numbers, #labels, #legend_font_size, #legend_margin, #marker_color, #marker_count, #marker_shadow_color, #maximum_value, #minimum_value, #title, #title_margin, #x_axis_label, #y_axis_label

Instance Method Summary collapse

Methods inherited from Artist

#artist_draw, #calculate_caps_height, #calculate_spread, #calculate_width, #center, #clip_value_if_greater_than, #construct_colors_array, #data, #draw_axis_labels, #draw_label, #draw_legend, #draw_line_markers!, #draw_title, #get_colors_array, #initialize_variables, #normalize, #render_gradiated_background, #reset_themes, #scale, #scale_fontsize, #set_colors_array, #setup_drawing, #setup_graph_measurements, #significant, #sum, #theme=, #write

Constructor Details

#initialize(*args) ⇒ Line

Call with target pixel width of graph (800, 400, 300), and/or 'false' to omit lines (points only).

g = Rubyplot::Line.new(400) # 400px wide with lines.

g = Rubyplot::Line.new(400, false) # 400px wide, no lines

g = Rubyplot::Line.new(false) # Defaults to 800px wide, no lines

Raises:

  • (ArgumentError)

12
13
14
15
16
17
18
19
20
21
# File 'lib/rubyplot/scripting/line/draw.rb', line 12

def initialize(*args)
  raise ArgumentError, 'Wrong number of arguments' if args.length > 2
  if args.empty? || (!(Numeric === args.first) && !(String === args.first))
    super()
  else
    super args.shift # TODO: Figure out a better alternative here.
  end

  @geometry = Rubyplot::LineGeometry.new
end

Instance Attribute Details

#dot_radiusObject

Returns the value of attribute dot_radius


5
6
7
# File 'lib/rubyplot/scripting/line/draw.rb', line 5

def dot_radius
  @dot_radius
end

#line_widthObject

Dimensions of lines and dots; calculated based on dataset size if left unspecified


4
5
6
# File 'lib/rubyplot/scripting/line/draw.rb', line 4

def line_width
  @line_width
end

Instance Method Details

#baseline_valueObject

Get the value if somebody has defined it.


24
25
26
# File 'lib/rubyplot/scripting/line/draw.rb', line 24

def baseline_value
  @geometry.reference_lines[:baseline][:value] if @geometry.reference_lines.key?(:baseline)
end

#baseline_value=(new_value) ⇒ Object

Set a value for a baseline reference line.


29
30
31
32
# File 'lib/rubyplot/scripting/line/draw.rb', line 29

def baseline_value=(new_value)
  @geometry.reference_lines[:baseline] ||= {}
  @geometry.reference_lines[:baseline][:value] = new_value
end

#contains_one_point_only?(data_row) ⇒ Boolean

Returns:

  • (Boolean)

89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/rubyplot/scripting/line/data.rb', line 89

def contains_one_point_only?(data_row)
  # A helper function that acts as a sanity check for the data.
  # It spins through data to determine if there is just one_value present.
  one_point = false
  data_row[DATA_VALUES_INDEX].each do |data_point|
    next if data_point.nil?
    if one_point
      # more than one point, bail
      return false
    end
    # there is at least one data point
    one_point = true
  end
  one_point
end

#dataxy(name, x_data_points = [], y_data_points = [], color = nil) ⇒ Object

This method allows one to plot a dataset with both X and Y data.

Parameters:

name: string, the title of the datasets.
x_data_points: an array containing the x data points for the graph.
y_data_points: an array containing the y data points for the graph.

or

name: string, the title of the dataset.
xy_data_points: an array containing both x and y data points for the graph.

TODO: Add Color features.

Notes:
 -if (x_data_points.length != y_data_points.length) an error is
   returned.
 -if you want to use a preset theme, you must set it before calling
   dataxy().

Example:

g = Rubyplot::Line.new
g.title = "X/Y Dataset"
g.dataxy("Apples", [1,3,4,5,6,10], [1, 2, 3, 4, 4, 3])
g.dataxy("Bapples", [1,3,4,5,7,9], [1, 1, 2, 2, 3, 3])
g.dataxy("Capples", [[1,1],[2,3],[3,4],[4,5],[5,7],[6,9]])
#you can still use the old data method too if you want:
g.data("Capples", [1, 1, 2, 2, 3, 3])
#labels will be drawn at the x locations of the keys passed in.
In this example the lables are drawn at x positions 2, 4, and 6:
g.labels = {0 => '2003', 2 => '2004', 4 => '2005', 6 => '2006'}
The 0 => '2003' label will be ignored since it is outside the chart range.

Raises:

  • (ArgumentError)

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/rubyplot/scripting/line/data.rb', line 36

def dataxy(name, x_data_points = [], y_data_points = [], color = nil)
  raise ArgumentError, 'x_data_points is nil!' if x_data_points.empty?

  if x_data_points.all? { |p| p.is_a?(Array) && p.size == 2 }
    y_data_points = x_data_points.map { |p| p[1] }
    x_data_points = x_data_points.map { |p| p[0] }
  end

  raise ArgumentError, 'x_data_points.length != y_data_points.length!' if x_data_points.length != y_data_points.length

  # call the existing data routine for the y data.
  data(name, y_data_points, color)

  x_data_points = Array(x_data_points) # make sure it's an array
  # append the x data to the last entry that was just added in the @data member
  @data.last[DATA_VALUES_X_INDEX] = x_data_points

  # Update the global min/max values for the x data
  x_data_points.each do |x_data_point|
    next if x_data_point.nil?

    # Setup max/min so spread starts at the low end of the data points
    if @geometry.maximum_x_value.nil? && @geometry.minimum_x_value.nil?
      @geometry.maximum_x_value = @geometry.minimum_x_value = x_data_point
    end

    @geometry.maximum_x_value = x_data_point > @geometry.maximum_x_value ?
        x_data_point : @geometry.maximum_x_value
    @geometry.minimum_x_value = x_data_point < @geometry.minimum_x_value ?
        x_data_point : @geometry.minimum_x_value
  end
end

#drawObject


44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/rubyplot/scripting/line/draw.rb', line 44

def draw
  super
  return unless @geometry.has_data

  # Check to see if more than one datapoint was given. NaN can result otherwise.
  @x_increment = @geometry.column_count > 1 ? (@graph_width / (@geometry.column_count - 1).to_f) : @graph_width

  if @geometry.show_vertical_markers # false in the base case
    (0..@geometry.column_count).each do |column|
      x = @graph_left + @graph_width - column.to_f * @x_increment

      @d = @d.fill @plot_colors[column]

      @d = @d.line(x, @graph_bottom, x, @graph_top)
      # If the user specified a marker shadow color, draw a shadow just below it
      unless @marker_shadow_color.nil?
        @d = @d.line(x + 1, @graph_bottom, x + 1, @graph_top)
      end
    end
  end

  @geometry.norm_data.each_with_index do |data_row, row_num|
    # Initially the previous x,y points are nil and then
    # they are set with values.
    prev_x = prev_y = nil

    @one_point = contains_one_point_only?(data_row)

    @d = @d.fill @plot_colors[row_num]
    data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
      x_data = data_row[DATA_VALUES_X_INDEX]
      if x_data.nil?
        new_x = @graph_left + (@x_increment * index)
        draw_label(new_x, index)
      else
        new_x = get_x_coord(x_data[index], @graph_width, @graph_left)
        @labels.each do |label_pos, _|
          draw_label(@graph_left + ((label_pos - @geometry.minimum_x_value) * @graph_width) / (@geometry.maximum_x_value - @geometry.minimum_x_value), label_pos)
        end
      end
      unless data_point # we can't draw a line for a null data point, we can still label the axis though
        prev_x = prev_y = nil
        next
      end
      new_y = @graph_top + (@graph_height - data_point * @graph_height)
      # Reset each time to avoid thin-line errors.
      #  @d = @d.stroke data_row[DATA_COLOR_INDEX]
      # @d = @d.fill data_row[DATA_COLOR_INDEX]
      @d = @d.stroke_opacity 1.0
      @d = @d.stroke_width line_width ||
                           clip_value_if_greater_than(@columns / (@geometry.norm_data.first[DATA_VALUES_INDEX].size * 4), 5.0)

      circle_radius = dot_radius ||
                      clip_value_if_greater_than(@columns / (@geometry.norm_data.first[DATA_VALUES_INDEX].size * 2.5), 5.0)

      if !@geometry.hide_lines && !prev_x.nil? && !prev_y.nil?
        @d = @d.line(prev_x, prev_y, new_x, new_y)
      elsif @one_point
        # Show a circle if there's just one_point
        @d = DotRenderers.renderer(@geometry.dot_style).render(@d, new_x, new_y, circle_radius)
      end

      unless @geometry.hide_dots
        @d = DotRenderers.renderer(@geometry.dot_style).render(@d, new_x, new_y, circle_radius)
      end

      prev_x = new_x
      prev_y = new_y
    end
  end

  @d.draw(@base_image)
end

#draw_reference_line(reference_line, left, right, top, bottom) ⇒ Object


34
35
36
37
38
39
40
41
42
# File 'lib/rubyplot/scripting/line/draw.rb', line 34

def draw_reference_line(reference_line, left, right, top, bottom)
  @d = @d.push
  @d.stroke_color(@reference_line_default_color)
  @d.fill_opacity 0.0
  @d.stroke_dasharray(10, 20)
  @d.stroke_width(reference_line[:width] || @reference_line_default_width)
  @d.line(left, top, right, bottom)
  @d = @d.pop
end

#get_x_coord(x_data_point, width, offset) ⇒ Object

Returns the X co-ordinate of a given data point.


119
120
121
# File 'lib/rubyplot/scripting/line/draw.rb', line 119

def get_x_coord(x_data_point, width, offset)
  x_data_point * width + offset
end

#normalize!Object


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/rubyplot/scripting/line/data.rb', line 69

def normalize!
  # First call the standard math function to normalize the values based on spread.
  super
  # TODO: Take care of the reference_lines

  # normalize the x data if it is specified
  @data.each_with_index do |data_row, index|
    norm_x_data_points = []
    next if data_row[DATA_VALUES_X_INDEX].nil?
    data_row[DATA_VALUES_X_INDEX].each do |x_data_point|
      norm_x_data_points << ((x_data_point.to_f - @geometry.minimum_x_value.to_f) / (@geometry.maximum_x_value.to_f - @geometry.minimum_x_value.to_f))
    end
    @geometry.norm_data[index] << norm_x_data_points
  end
end

#sort_norm_dataObject


85
86
87
# File 'lib/rubyplot/scripting/line/data.rb', line 85

def sort_norm_data
  super unless @data.any? { |d| d[DATA_VALUES_X_INDEX] }
end