cossincalc 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|