cossincalc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +19 -0
- data/Rakefile +18 -0
- data/bin/cossincalc +66 -0
- data/bin/trollop.rb +782 -0
- data/lib/core_ext.rb +18 -0
- data/lib/cossincalc.rb +13 -0
- data/lib/cossincalc/triangle.rb +124 -0
- data/lib/cossincalc/triangle/calculator.rb +118 -0
- data/lib/cossincalc/triangle/drawing.rb +76 -0
- data/lib/cossincalc/triangle/drawing/svg.rb +127 -0
- data/lib/cossincalc/triangle/formatter.rb +73 -0
- data/lib/cossincalc/triangle/formatter/latex.rb +134 -0
- data/lib/cossincalc/triangle/validator.rb +87 -0
- data/lib/cossincalc/triangle/variable_hash.rb +24 -0
- data/lib/cossincalc/version.rb +3 -0
- metadata +85 -0
data/lib/core_ext.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Taken from Active Support (copyright (c) 2005-2008 David Heinemeier Hansson),
|
2
|
+
# which is realeased under the MIT license as part of the Ruby on Rails framework.
|
3
|
+
# http://github.com/rails/rails/blob/babbc1580da9e4a23921ab68d47c7c0d2e8447da/activesupport/lib/active_support/core_ext/symbol.rb
|
4
|
+
|
5
|
+
unless :to_proc.respond_to?(:to_proc)
|
6
|
+
class Symbol
|
7
|
+
# Turns the symbol into a simple proc, which is especially useful for enumerations. Examples:
|
8
|
+
#
|
9
|
+
# # The same as people.collect { |p| p.name }
|
10
|
+
# people.collect(&:name)
|
11
|
+
#
|
12
|
+
# # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
|
13
|
+
# people.select(&:manager?).collect(&:salary)
|
14
|
+
def to_proc
|
15
|
+
Proc.new { |*args| args.shift.__send__(self, *args) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/cossincalc.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'core_ext'
|
2
|
+
require 'cossincalc/version'
|
3
|
+
require 'cossincalc/triangle/calculator'
|
4
|
+
require 'cossincalc/triangle/drawing/svg'
|
5
|
+
require 'cossincalc/triangle/drawing'
|
6
|
+
require 'cossincalc/triangle/formatter'
|
7
|
+
require 'cossincalc/triangle/formatter/latex'
|
8
|
+
require 'cossincalc/triangle/validator'
|
9
|
+
require 'cossincalc/triangle/variable_hash'
|
10
|
+
require 'cossincalc/triangle'
|
11
|
+
|
12
|
+
module CosSinCalc
|
13
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module CosSinCalc
|
2
|
+
class Triangle
|
3
|
+
include Calculator
|
4
|
+
|
5
|
+
VARIABLES = [:a, :b, :c]
|
6
|
+
|
7
|
+
attr_reader :alt # Reference to alternative triangle at ambiguous case.
|
8
|
+
attr_reader :equations # Steps performed to calculate the result.
|
9
|
+
|
10
|
+
# Initializes a triangle object with the given sides and angles and an optional reference to an alternative triangle.
|
11
|
+
#
|
12
|
+
# The sides and angles may be given either as a VariableHash object or an ordinary hash.
|
13
|
+
# The angle unit may be specified inside the given_angles hash (using the key :unit and value either :degree, :gon or :radian).
|
14
|
+
# If no angle unit is given it defaults to :degree.
|
15
|
+
# If a hash is used then value parsing and conversion will only occur if the values are provided as strings.
|
16
|
+
# Float angle values are expected to be radians no matter the given angle unit.
|
17
|
+
def initialize(given_sides, given_angles, alternative = nil)
|
18
|
+
initialize_variables
|
19
|
+
|
20
|
+
given_sides.each { |v, value| side[v] = Formatter.parse(value) }
|
21
|
+
|
22
|
+
angles.unit = (given_angles.respond_to?(:unit) ? given_angles.unit : given_angles.delete(:unit)) || :degree
|
23
|
+
given_angles.each { |v, value| angle[v] = Formatter.parse_angle(value, angles.unit) }
|
24
|
+
|
25
|
+
@alt = alternative
|
26
|
+
end
|
27
|
+
|
28
|
+
# Calculates the unknown variables in the triangle.
|
29
|
+
def calculate!
|
30
|
+
Validator.new(self).validate
|
31
|
+
calculate_variables
|
32
|
+
Validator.new(self).validate_calculation
|
33
|
+
@calculated = true
|
34
|
+
rescue Errno::EDOM
|
35
|
+
Validator::ValidationError.new([Validator::INVALID_TRIANGLE])
|
36
|
+
rescue Validator::ValidationError => exception
|
37
|
+
exception
|
38
|
+
end
|
39
|
+
|
40
|
+
def humanize(precision = 2)
|
41
|
+
Formatter.new(self, precision)
|
42
|
+
end
|
43
|
+
|
44
|
+
def angle(v = nil)
|
45
|
+
v.nil? ? @angles : @angles[v]
|
46
|
+
end
|
47
|
+
alias_method :angles, :angle
|
48
|
+
|
49
|
+
def side(v = nil)
|
50
|
+
v.nil? ? @sides : @sides[v]
|
51
|
+
end
|
52
|
+
alias_method :sides, :side
|
53
|
+
|
54
|
+
# Returns the length of the line which starts at the corner and is perpendicular with the opposite side.
|
55
|
+
def altitude(v)
|
56
|
+
require_calculation
|
57
|
+
r = rest(v)
|
58
|
+
Math.sin(angle(r[0])) * side(r[1])
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the length of the line going from the corner to the middle of the opposite side.
|
62
|
+
def median(v)
|
63
|
+
require_calculation
|
64
|
+
Math.sqrt((2 * sq(sides(rest(v))).inject(&:+) - sq(side(v))) / 4)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the length of the line between a corner and the opposite side which bisects the angle at the corner.
|
68
|
+
def angle_bisector(v)
|
69
|
+
require_calculation
|
70
|
+
r = rest(v)
|
71
|
+
Math.sin(angle(r[0])) * side(r[1]) / Math.sin(angle(r[1]) + angle(v) / 2)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the area of the triangle.
|
75
|
+
def area
|
76
|
+
require_calculation
|
77
|
+
side(VARIABLES[0]) * side(VARIABLES[1]) * Math.sin(angle(VARIABLES[2])) / 2
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the circumference of the triangle.
|
81
|
+
def circumference
|
82
|
+
require_calculation
|
83
|
+
sides(VARIABLES).inject(&:+)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Executes the given block for each variable symbol.
|
87
|
+
# If an array of variable names is given, only those variables will be iterated through.
|
88
|
+
def each(array = VARIABLES, &block)
|
89
|
+
array.each { |v| block.arity == 2 ? yield(v, rest(v)) : yield(v) }
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns all the variable symbols except those given.
|
93
|
+
def rest(*vars)
|
94
|
+
VARIABLES - vars
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns whether the missing values have been successfully calculated.
|
98
|
+
def calculated?
|
99
|
+
@calculated
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns whether the given value is acute or not.
|
103
|
+
def acute?(value)
|
104
|
+
value < Math::PI / 2
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns whether the given value is obtuse or not.
|
108
|
+
def obtuse?(value)
|
109
|
+
value > Math::PI / 2
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
# Reset the sides, angles etc. to their default values.
|
114
|
+
def initialize_variables
|
115
|
+
@sides = VariableHash.new
|
116
|
+
@angles = VariableHash.new
|
117
|
+
end
|
118
|
+
|
119
|
+
# Calculate the missing values of the triangle if not already done.
|
120
|
+
def require_calculation
|
121
|
+
calculate! unless calculated?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module CosSinCalc
|
2
|
+
class Triangle
|
3
|
+
module Calculator
|
4
|
+
include Math
|
5
|
+
|
6
|
+
def calculate_variables
|
7
|
+
case sides.amount
|
8
|
+
when 3 then calculate_three_angles
|
9
|
+
when 2 then calculate_two_angles
|
10
|
+
when 1 then calculate_two_sides
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Calculates the last unknown angle and side.
|
15
|
+
# This function is public so it is derectly callable (used with ambiguous case).
|
16
|
+
def calculate_side_and_angle
|
17
|
+
calculate_two_sides
|
18
|
+
end
|
19
|
+
|
20
|
+
# Calculates the value of an angle when all the sides are known.
|
21
|
+
def calculate_angle_by_sides(v, r)
|
22
|
+
acos (sq(sides(r)).inject(&:+) - sq(side(v))) / (2 * sides(r).inject(&:*))
|
23
|
+
end
|
24
|
+
|
25
|
+
# Add a calculation step to the list of equations performed.
|
26
|
+
def equation(latex, *variables)
|
27
|
+
@equations ||= []
|
28
|
+
@equations << [latex, variables]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def each(*args, &block)
|
33
|
+
@triangle.each(*args, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def sq(value)
|
37
|
+
value.is_a?(Array) ? value.map { |n| n * n } : (value * value)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Calculates all three angles when all three sides are known.
|
41
|
+
def calculate_three_angles
|
42
|
+
each do |v, r|
|
43
|
+
unless angle(v)
|
44
|
+
angle[v] = calculate_angle_by_sides(v, r)
|
45
|
+
equation('@1=\arccos\left(\frac{$2^2+$3^2-$1^2}{2 * $2 * $3}\right)', v, *r)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Calculates two unknown angles when two sides and one angle are known.
|
51
|
+
def calculate_two_angles
|
52
|
+
each do |v, r|
|
53
|
+
if angle(v)
|
54
|
+
unless side(v)
|
55
|
+
side[v] = sqrt sq(sides(r)).inject(&:+) -
|
56
|
+
2 * sides(r).inject(&:*) * cos(angle(v))
|
57
|
+
equation('$1=\sqrt{$2^2+$3^2-2 * $2 * $3 * \cos(@1)}', v, *r)
|
58
|
+
calculate_three_angles
|
59
|
+
break
|
60
|
+
end
|
61
|
+
|
62
|
+
each(r) do |v2|
|
63
|
+
if side(v2)
|
64
|
+
angle[v2] = asin sin(angle(v)) * side(v2) / side(v)
|
65
|
+
equation('@2=\arcsin\left(\frac{\sin(@1) * $2}{$1}\right)', v, v2)
|
66
|
+
|
67
|
+
if ambiguous_case?(v, v2)
|
68
|
+
@alt = CosSinCalc::Triangle.new(sides, angles, self)
|
69
|
+
@alt.angle[v2] = PI - angle(v2)
|
70
|
+
@alt.equation('@2=@pi-\arcsin\left(\frac{\sin(@1) * $2}{$1}\right)', v, v2)
|
71
|
+
@alt.calculate_side_and_angle
|
72
|
+
end
|
73
|
+
|
74
|
+
calculate_two_sides
|
75
|
+
break
|
76
|
+
end
|
77
|
+
end
|
78
|
+
break
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Calculates up to two unknown sides when at least one side and two angles are known.
|
84
|
+
def calculate_two_sides
|
85
|
+
calculate_last_angle
|
86
|
+
|
87
|
+
each do |v, r|
|
88
|
+
if side(v)
|
89
|
+
each(r) do |v2|
|
90
|
+
unless side(v2)
|
91
|
+
side[v2] = sin(angle(v2)) * side(v) / sin(angle(v))
|
92
|
+
equation('$2=\frac{\sin(@2) * $1}{\sin(@1)}', v, v2)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Calculates the last unknown angle.
|
101
|
+
def calculate_last_angle
|
102
|
+
each do |v, r|
|
103
|
+
unless angle(v)
|
104
|
+
angle[v] = PI - angles(r).inject(&:+)
|
105
|
+
equation('@1=@pi-@2-@3', v, *r)
|
106
|
+
break
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Calculates and returns whether the triangle has multiple solutions.
|
112
|
+
# See http://en.wikipedia.org/wiki/Law_of_sines#The_ambiguous_case
|
113
|
+
def ambiguous_case?(v1, v2)
|
114
|
+
acute?(angle(v1)) && side(v1) < side(v2) && side(v1) > side(v2) * sin(angle(v1))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module CosSinCalc
|
2
|
+
class Triangle
|
3
|
+
class Drawing
|
4
|
+
include Svg
|
5
|
+
|
6
|
+
# Initializes the drawing object of the given formatter's triangle with the provided maximum size and border padding.
|
7
|
+
def initialize(formatter, size = 500, padding = 50)
|
8
|
+
@formatter, @size, @padding = formatter, size, padding
|
9
|
+
@triangle = @formatter.triangle
|
10
|
+
@coords = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
# Calculates the coordinates of the verticies of the triangle and scales it to maximum allowed size.
|
15
|
+
def draw
|
16
|
+
calculate_coords
|
17
|
+
resize
|
18
|
+
apply_padding
|
19
|
+
end
|
20
|
+
|
21
|
+
# Shorthand-method referencing the associated triangle object.
|
22
|
+
def t; @triangle end
|
23
|
+
|
24
|
+
# Shorthand-method referencing the associated triangle formatter object.
|
25
|
+
def f; @formatter end
|
26
|
+
|
27
|
+
# Calculates the coordinates for the corners of the triangle.
|
28
|
+
def calculate_coords
|
29
|
+
@coords[:a] = [ 0, 0 ]
|
30
|
+
@coords[:b] = [ t.side(:c) * Math.cos(t.angle(:a)), t.altitude(:b) ]
|
31
|
+
@coords[:c] = [ t.side(:b), 0 ]
|
32
|
+
|
33
|
+
if t.obtuse?(t.angle(:a))
|
34
|
+
move_coords(-@coords[:b][0])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Moves the corners of the triangle with the given distance to the right.
|
39
|
+
# Pass a negative value to move them to the left.
|
40
|
+
def move_coords(distance)
|
41
|
+
t.each { |v| @coords[v][0] += distance }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Scales the coordiantes to fit the size of the canvas.
|
45
|
+
def resize
|
46
|
+
scale_coords @size / [@coords[:b][0], @coords[:c][0], @coords[:b][1]].max
|
47
|
+
end
|
48
|
+
|
49
|
+
# Scales the coordinates with the given amount.
|
50
|
+
def scale_coords(scalar)
|
51
|
+
t.each do |v|
|
52
|
+
@coords[v][0] *= scalar
|
53
|
+
@coords[v][1] *= scalar
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Switches between bottom-left and top-left origin of the coordiante system.
|
58
|
+
def invert_coords
|
59
|
+
t.each { |v| @coords[v][1] = canvas_size - @coords[v][1] }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Adds a padding around the triangle.
|
63
|
+
def apply_padding
|
64
|
+
t.each do |v|
|
65
|
+
@coords[v][0] += @padding
|
66
|
+
@coords[v][1] += @padding
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the total size of the canvas, ie. the sum of the size of the triangle and the padding on both sides of it.
|
71
|
+
def canvas_size
|
72
|
+
@size + @padding * 2
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module CosSinCalc
|
2
|
+
class Triangle
|
3
|
+
class Drawing
|
4
|
+
module Svg
|
5
|
+
VERTEX_LABEL_MARGIN = 10 # The distance between the middle of the vertex label and the vertex itself.
|
6
|
+
VERTEX_VALUE_MARGIN = 55 # The distance between the middle of the vertex value and the vertex itself.
|
7
|
+
FONT_SIZE = 12 # The font size of the labels.
|
8
|
+
ARC_RADIUS = 25 # The radius of the circular arcs at the vertices.
|
9
|
+
NEXT_VARIABLE = { :a => :b, :b => :c, :c => :a } # The association between a variable and the next.
|
10
|
+
|
11
|
+
# Returns a drawing of the triangle in SVG (Scalable Vector Graphics) format.
|
12
|
+
def to_svg
|
13
|
+
draw
|
14
|
+
invert_coords
|
15
|
+
|
16
|
+
polygon = @coords.values.map { |c| c.join(',') }.join(' ')
|
17
|
+
labels = ''
|
18
|
+
|
19
|
+
t.each { |v| labels << vertex_label(v) << vertex_arc(v) << vertex_value(v) }
|
20
|
+
t.each { |v| labels << edge_label(v) } # Needs to be drawn last in order to make ImageMagick render it correctly.
|
21
|
+
|
22
|
+
<<-EOT
|
23
|
+
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
24
|
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="#{canvas_size}" height="#{canvas_size}">
|
25
|
+
<polygon fill="#f5eae5" stroke="#993300" stroke-width="1" points="#{polygon}"/>
|
26
|
+
#{labels}</svg>
|
27
|
+
EOT
|
28
|
+
end
|
29
|
+
|
30
|
+
# Saves a drawing of the triangle as an PNG file.
|
31
|
+
# The filename should be provided without the .png extension.
|
32
|
+
def save_png(filename)
|
33
|
+
save_svg(filename)
|
34
|
+
system "convert \"#{filename}.svg\" \"#{filename}.png\""
|
35
|
+
end
|
36
|
+
|
37
|
+
# Saves a drawing of the triangle as an SVG file.
|
38
|
+
# The filename should be provided without the .svg extension.
|
39
|
+
def save_svg(filename)
|
40
|
+
File.open("#{filename}.svg", 'w') { |f| f.write(to_svg) }
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
# Returns the SVG code for a label containing the given text at the given position.
|
45
|
+
def label(x, y, text, attributes = nil)
|
46
|
+
%[<text font-size="#{FONT_SIZE}" font-family="Verdana" fill="#333333" text-anchor="middle" x="#{x}" y="#{y + FONT_SIZE / 2}"#{' ' + attributes if attributes}>#{text}</text>\n]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the equivalent cartesian coordiantes (x and y) of the given polar coordiantes (angle and distance).
|
50
|
+
def polar_to_cartesian(angle, distance)
|
51
|
+
[ Math.cos(angle), Math.sin(angle) ].map { |val| distance * val }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the coordiantes of the vertex label of the given variable.
|
55
|
+
def vertex_label_coords(v)
|
56
|
+
x, y = *polar_to_cartesian(bisector_angle_to_x(v), VERTEX_LABEL_MARGIN)
|
57
|
+
[ @coords[v][0] - x, @coords[v][1] + y ]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the SVG code for the vertex label of the given variable.
|
61
|
+
def vertex_label(v)
|
62
|
+
x, y = *vertex_label_coords(v)
|
63
|
+
label(x, y, v.to_s.upcase)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the angle between the first edge (the right relative to the angle) connected to the given verted and the x-axis.
|
67
|
+
def angle_to_x(v)
|
68
|
+
case v
|
69
|
+
when :a then 0.0
|
70
|
+
when :b then -(t.angle(:b) + t.angle(:c))
|
71
|
+
when :c then Math::PI - t.angle(:c)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the angle between the angle bisector of the given vertex and the x-axis.
|
76
|
+
def bisector_angle_to_x(v)
|
77
|
+
t.angle(v) / 2 + angle_to_x(v)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the SVG code for the circular arcs at the given vertex.
|
81
|
+
def vertex_arc(v)
|
82
|
+
x1, y1 = *polar_to_cartesian(angle_to_x(v), ARC_RADIUS)
|
83
|
+
x2, y2 = *polar_to_cartesian(angle_to_x(v) + t.angle(v), ARC_RADIUS)
|
84
|
+
%[<path d="M #{@coords[v][0] + x1},#{@coords[v][1] - y1} A #{ARC_RADIUS},#{ARC_RADIUS} 0 0 0 #{@coords[v][0] + x2},#{@coords[v][1] - y2}" stroke="#90ee90" stroke-width="2" fill="none"/>\n]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the coordiantes of the vertex value label of the given variable.
|
88
|
+
def vertex_value_coords(v)
|
89
|
+
x, y = *polar_to_cartesian(bisector_angle_to_x(v), VERTEX_VALUE_MARGIN)
|
90
|
+
[ @coords[v][0] + x, @coords[v][1] - y ]
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns the SVG code for the vertex label containing the value of the angle including the unit.
|
94
|
+
def vertex_value(v)
|
95
|
+
x, y = *vertex_value_coords(v)
|
96
|
+
label(x, y, format_angle(f.angle(v), t.angles.unit))
|
97
|
+
end
|
98
|
+
|
99
|
+
# Adds the appropriate unit to the angle value.
|
100
|
+
def format_angle(value, unit)
|
101
|
+
if unit == :degree
|
102
|
+
value + '°'
|
103
|
+
else
|
104
|
+
value + ' gon'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the SVG code for the given edge label.
|
109
|
+
def edge_label(v)
|
110
|
+
r = t.rest(v)
|
111
|
+
x = (@coords[r[1]][0] - @coords[r[0]][0]) / 2 + @coords[r[0]][0]
|
112
|
+
y = (@coords[r[1]][1] - @coords[r[0]][1]) / 2 + @coords[r[0]][1]
|
113
|
+
|
114
|
+
v2 = NEXT_VARIABLE[v]
|
115
|
+
angle = CosSinCalc::Triangle::Formatter.convert_angle(angle_to_x(v2) + t.angle(v2), :degree, true)
|
116
|
+
text = "#{v} = #{f.side(v)}"
|
117
|
+
|
118
|
+
if angle < 90 && angle > -90
|
119
|
+
label(x, y - FONT_SIZE, text, %[transform="rotate(#{-angle} #{x},#{y})"])
|
120
|
+
else
|
121
|
+
label(x, y + FONT_SIZE, text, %[transform="rotate(#{180 - angle}, #{x},#{y})"])
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|