Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Adding a legend to the chart plotter.

  • Loading branch information...
commit ab3117932db645f801d664caf498a9eb8b0ce3d4 1 parent 69b0796
Chris Schlaeger scrapper authored

Showing 1 changed file with 85 additions and 29 deletions. Show diff stats Hide diff stats

  1. +85 29 lib/taskjuggler/reports/ChartPlotter.rb
114 lib/taskjuggler/reports/ChartPlotter.rb
@@ -18,16 +18,47 @@ class TaskJuggler
18 18 class ChartPlotter
19 19
20 20 def initialize(width, height, data)
  21 + # +------------------------------------------------
  22 + # | ^
  23 + # | topMargin | legendGap
  24 + # | v <->
  25 + # | | -x- foo
  26 + # |<-leftMargin->| -x- bar
  27 + # | | <-legend
  28 + # | | Width---->
  29 + # | +------------
  30 + # | ^ <-rightMargin->
  31 + # | bottomMargin|
  32 + # | v
  33 + # +------------------------------------------------
  34 + # <-----------------canvasWidth-------------------->
  35 + # The width of the canvas area
21 36 @width = width
  37 + # The height of the canvas area
22 38 @height = height
  39 + # The raw data to plot as loaded from the CSV file.
23 40 @data = data
24 41
25   - @edge = 30
26   - @x0 = @edge
27   - @y0 = @height - @edge
  42 + # The margins between the graph plotting area and the canvas borders.
  43 + @topMargin = 30
  44 + @bottomMargin = 30
  45 + @leftMargin = 40
  46 + @rightMargin = 150
28 47
29   - @headers = []
30   - @columns = []
  48 + @legendGap = 20
  49 + @markerWidth = 20
  50 + @markerX = @width - @rightMargin + @legendGap
  51 + @markerGap = 5
  52 + @labelX = @markerX + @markerWidth + @markerGap
  53 + @labelHeight = 20
  54 +
  55 + # The location of the 0/0 point of the graph plotter.
  56 + @x0 = @leftMargin
  57 + @y0 = @height - @bottomMargin
  58 +
  59 + @labels = []
  60 + @yData = []
  61 + @xData = nil
31 62 @xMinDate = nil
32 63 @xMaxDate = nil
33 64 @yMinDate = nil
@@ -37,19 +68,34 @@ def initialize(width, height, data)
37 68 def generate
38 69 analyzeData
39 70 @painter = Painter.new(@width, @height) do |p|
40   - p.group(:stroke => p.color(:black)) do |p|
41   - p.line(x2c(0), y2c(0), x2c(@width - 2 * @edge), y2c(0))
42   - p.line(x2c(0), y2c(0), x2c(0), y2c(@height - 2 * @edge))
  71 + p.group(:stroke => p.color(:black), :font_size => 11) do |p|
  72 + p.line(x2c(0), y2c(0),
  73 + x2c(@width - (@leftMargin + @rightMargin)), y2c(0))
  74 + p.line(x2c(0), y2c(0),
  75 + x2c(0), y2c(@height - (@topMargin + @bottomMargin)))
43 76 end
44   - 1.upto(@columns.length - 1) do |ci|
45   - col = @columns[ci]
46   - lastX = lastY = nil
47   - col.length.times do |i|
48   - color = Painter::Color.new(i / 6, 255 - (i / 6), 230)
49   - p.group(:stroke => color, :fill => color) do |p|
50   - if col[i]
51   - yDate = col[i]
52   - xc = xDate2c(@columns[0][i])
  77 + 0.upto(@yData.length - 1) do |ci|
  78 + # Compute a unique and distinguishable color for each data set. We
  79 + # primarily use the hue value of the HSV color space for this. It
  80 + # has 6 main colors each 60 degrees apart from each other. After the
  81 + # first 360 round, we shift the angle by 60 / round so we get a
  82 + # different color set than in the previous round. Additionally, the
  83 + # saturation is decreased with each data set.
  84 + color = Painter::Color.new(
  85 + (60 * (ci % 6) + (60 / (1 + ci / 6))) % 360,
  86 + 255 - (ci / 8), 230, :hsv)
  87 +
  88 + values = @yData[ci]
  89 + p.group(:stroke_width => 3, :stroke => color, :fill => color,
  90 + :font_size => 11) do |p|
  91 + lastX = lastY = nil
  92 + # Plot markers for each x/y data pair of the set and connect the
  93 + # dots with lines. If a y value is nil, the line will be
  94 + # interrupted.
  95 + values.length.times do |i|
  96 + if values[i]
  97 + yDate = values[i]
  98 + xc = xDate2c(@xData[i])
53 99 yc = yDate2c(yDate)
54 100 p.line(lastX, lastY, xc, yc) if lastY
55 101 setMarker(p, ci, xc, yc)
@@ -59,6 +105,15 @@ def generate
59 105 lastY = lastX = nil
60 106 end
61 107 end
  108 +
  109 + # Add the marker to the legend
  110 + labelY = @topMargin + @labelHeight / 2 + ci * @labelHeight
  111 + markerY = labelY + @labelHeight / 2
  112 + setMarker(p, ci, @markerX + @markerWidth / 2, markerY)
  113 + p.line(@markerX, markerY, @markerX + @markerWidth, markerY)
  114 + p.text(@labelX, labelY + @labelHeight - 5, @labels[ci],
  115 + :stroke => p.color(:black), :stroke_width => 0,
  116 + :fill => p.color(:black))
62 117 end
63 118 end
64 119 end
@@ -68,9 +123,7 @@ def to_svg
68 123 @painter.to_svg
69 124 end
70 125
71   - def xyToCanvas(point)
72   - [ x2c(point[0]), y2c(point[1]) ]
73   - end
  126 + private
74 127
75 128 def x2c(x)
76 129 @x0 + x
@@ -81,19 +134,18 @@ def y2c(y)
81 134 end
82 135
83 136 def xDate2c(date)
84   - x2c(((date - @xMinDate) * (@width - 2 * @edge)) / (@xMaxDate - @xMinDate))
  137 + x2c(((date - @xMinDate) * (@width - (@leftMargin + @rightMargin))) /
  138 + (@xMaxDate - @xMinDate))
85 139 end
86 140
87 141 def yDate2c(date)
88   - y2c(((date - @yMinDate) * (@height - 2 * @edge)) /
  142 + y2c(((date - @yMinDate) * (@height - 2 * (@topMargin + @bottomMargin))) /
89 143 (@yMaxDate - @yMinDate))
90 144 end
91 145
92   - private
93   -
94 146 def setMarker(p, type, x, y)
95 147 r = 4
96   - case type % 5
  148 + case (type / 5) % 5
97 149 when 0
98 150 # Diamond
99 151 points = [ [ x - r, y ],
@@ -125,7 +177,8 @@ def setMarker(p, type, x, y)
125 177 end
126 178
127 179 def analyzeData
128   - # Convert the @data from a line-based list into a column-based list.
  180 + # Convert the @data from a line list into a column list. Each element of
  181 + # the list is an Array for the other dimension.
129 182 columns = []
130 183 ri = 0
131 184 @data.each do |row|
@@ -164,8 +217,8 @@ def analyzeData
164 217 @xMaxDate = date if @xMaxDate.nil? || date > @xMaxDate
165 218 end
166 219 end
167   - @headers << columns[0][0]
168   - @columns << columns[0][1..-1]
  220 + # And the xData values.
  221 + @xData = columns[0][1..-1]
169 222
170 223 unless @xMinDate && @xMaxDate
171 224 error("First column does not contain valid dates.")
@@ -185,7 +238,10 @@ def analyzeData
185 238 @yMinDate = cell if @yMinDate.nil? || cell < @yMinDate
186 239 @yMaxDate = cell if @yMaxDate.nil? || cell > @yMaxDate
187 240 end
188   - @columns << col[1..-1] unless badCol
  241 + # Store the header of the column. It will be used as label.
  242 + @labels << col[0]
  243 + # Add the column values as another entry into the yData list.
  244 + @yData << col[1..-1] unless badCol
189 245 end
190 246
191 247 unless @yMinDate && @yMaxDate

0 comments on commit ab31179

Please sign in to comment.
Something went wrong with that request. Please try again.