From f313c77aaa6c2712ddafcfee8fab1d425f8565d2 Mon Sep 17 00:00:00 2001
From: =?utf-8?q?F=C3=A9lix=20de=20R=C3=ADo=20Benigno?= <fario@portatil.(none)>
Date: Mon, 11 May 2009 10:21:22 +0200
Subject: [PATCH] Added Gruff::Dial graph and unit test

---
 lib/gruff.rb      |    2 +
 lib/gruff/dial.rb |  131 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 test/test_dial.rb |   51 +++++++++++++++++++++
 3 files changed, 184 insertions(+), 0 deletions(-)
 create mode 100644 lib/gruff/dial.rb
 create mode 100644 test/test_dial.rb

diff --git a/lib/gruff.rb b/lib/gruff.rb
index a809963..a87ba12 100644
--- a/lib/gruff.rb
+++ b/lib/gruff.rb
@@ -15,6 +15,8 @@
   side_bar
   accumulator_bar
 
+  dial
+
   scene
 
   mini/legend
diff --git a/lib/gruff/dial.rb b/lib/gruff/dial.rb
new file mode 100644
index 0000000..95f5303
--- /dev/null
+++ b/lib/gruff/dial.rb
@@ -0,0 +1,131 @@
+# Dial graph which shows only the first value of each domain.
+# !!Experimental
+#
+# Authors:
+#   Félix del Rio Benigno <fario@emergya.es>
+#
+# Copyright 2008 Emergya, sca. (www.emergya.es)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.dirname(__FILE__) + '/base'
+
+class Gruff::Dial < Gruff::Base
+  attr_accessor :min_value, :max_value, :start_angle, :end_angle
+  attr_accessor :circle_bg_color, :circle_stroke_color, :circle_opacity
+  
+  def initialize(*args)
+    super
+    # min value the dial will mark
+    @min_value = 0.0
+    # max value the dial will mark
+    @max_value = 100.0
+    # angle of the min value 0 is 3 o'clock, as in rmagick
+    @start_angle = Math::PI * 0.25  + Math::PI / 2
+    # angle of the max value
+    @end_angle = Math::PI * 1.75 + Math::PI / 2
+    # circle background color
+    @circle_bg_color = "gray"
+    # circle stroke color
+    @circle_stroke_color = "white"
+    # circle opacity
+    @circle_opacity = 0.5
+  end
+
+  def draw
+    @hide_line_numbers = true
+    @hide_line_markers = true
+
+    super
+
+    # circle position
+    @radius = @graph_height / 2.0
+    @center_x = @graph_left + (@graph_width / 2.0)
+    @center_y = @graph_top + (@graph_height / 2.0)
+
+    # Draw the circle first
+    @d.fill @circle_bg_color
+    @d.fill_opacity @circle_opacity
+    @d.stroke @circle_stroke_color
+    @d.circle(@center_x, @center_y, @radius + @center_x, @center_y)
+
+    # these are useful to calculate the markers and dials offsets
+    @angle_diff = (@end_angle - @start_angle)
+    @value_diff = (@max_value - @min_value)
+
+    draw_labels
+    
+    draw_dials
+
+    @d.draw(@base_image)
+  end
+
+  private
+
+  def draw_labels
+    @labels.each do |val, label|
+      langle = (val/@value_diff) * @angle_diff + @start_angle
+      cos = Math.cos(langle)
+      sin = Math.sin(langle)
+      # TODO do this in a more ruby way
+      # position of the text
+      tx = (@radius + 20 + label.length) * cos + @center_x
+      ty = (@radius + 20 + label.length) * sin + @center_y
+      # staring mark position
+      lx = (@radius + 5) * cos + @center_x
+      ly = (@radius + 5) * sin + @center_y
+      # ending mark position
+      lex = (@radius - 20) * cos + @center_x
+      ley = (@radius - 20) * sin + @center_y
+
+      @d.stroke_width 3
+      @d.stroke @marker_color
+      @d.fill @marker_color
+      @d.line(lx, ly, lex, ley)
+
+      @d.fill = @font_color
+      @d.font = @font if @font
+      @d.pointsize = scale_fontsize(20)
+      @d.stroke = 'transparent'
+      @d.font_weight = BoldWeight
+      @d.gravity = CenterGravity
+      @d.annotate_scaled(@base_image, 0, 0, tx, ty, label, @scale)
+    end
+  end
+
+  def draw_dials
+    @d.stroke_width 3
+    @data.each do |name, points, color|
+      # Keep he dial between the limits
+      val = [points[0] || @min_value, @min_value].max
+      val = [val, @max_value].min
+
+      d_angle = (val/@value_diff) * @angle_diff + @start_angle
+
+      # the knob is a triangle, so these are for the 3 sides
+      cos, sin = Math.cos(d_angle), Math.sin(d_angle)
+      cos1, sin1 = Math.cos(d_angle + Math::PI / 2), Math.sin(d_angle + Math::PI / 2)
+      cos2, sin2 = Math.cos(d_angle - Math::PI / 2), Math.sin(d_angle - Math::PI / 2)
+
+      @d.stroke color
+      @d.fill color
+      dx, dy = (@radius - 20) * cos + @center_x, (@radius - 20) * sin + @center_y
+
+      @d.circle(@center_x, @center_y, @center_x + 10, @center_y)
+      @d.polygon(dx, dy,
+                 10 * cos1 + @center_x, 10 * sin1 + @center_y,
+                 10 * cos2 + @center_x, 10 * sin2 + @center_y)
+    end
+  end
+end
diff --git a/test/test_dial.rb b/test/test_dial.rb
new file mode 100644
index 0000000..f5daa6b
--- /dev/null
+++ b/test/test_dial.rb
@@ -0,0 +1,51 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffStackedBar < GruffTestCase
+
+  def setup
+    @datasets = [
+      [:Jimmy, [25]],
+      [:Charles, [81]],
+      [:Julie, [63]],
+      ]
+    # Max value should be specified
+    @max_value = 90
+
+    # Labels are used for the markers on the dial graph
+    @labels = {}
+    (0..@max_value).step(5) { |i| @labels[i] = "#{i}" }
+  end
+
+  def test_dial_graph
+    g = Gruff::Dial.new
+    g.title = "Dial Graph Test"
+    g.labels = @labels
+    g.max_value = @max_value
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write "test/output/dial.png"
+  end
+
+
+  def test_dial_small_solid
+    g = Gruff::Dial.new(400)
+    g.title = "Dial Graph Test"
+    g.max_value = @max_value
+    g.circle_opacity = 1
+    g.circle_bg_color = "red"
+    g.circle_stroke_color = "green"
+    g.labels = {}
+    #You can specify start and end angle of the metering zone
+    g.start_angle = Math::PI
+    g.end_angle = 2*Math::PI
+    (0..@max_value).step(15) { |i| g.labels[i] = "#{i}" }
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write "test/output/test_dia_small_alpha.png"
+  end
+  
+end
-- 
1.5.6.5

