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
@@ -0,0 +1,73 @@
|
|
1
|
+
module CosSinCalc
|
2
|
+
class Triangle
|
3
|
+
class Formatter
|
4
|
+
UNIT_FACTORS = { :radian => Math::PI, :degree => 180.0, :gon => 200.0 }
|
5
|
+
|
6
|
+
attr_reader :triangle, :precision # References to associated triangle object and data precision.
|
7
|
+
|
8
|
+
def initialize(triangle, precision = 2)
|
9
|
+
@triangle = triangle
|
10
|
+
@precision = precision
|
11
|
+
end
|
12
|
+
|
13
|
+
# Converts an input value to a number.
|
14
|
+
def self.parse(value)
|
15
|
+
return value if value.is_a? Float
|
16
|
+
return nil if value.nil? || value.to_s !~ /\S/
|
17
|
+
|
18
|
+
value = value.to_s.gsub(/[^\d.,]+/, '').split(/[^\d]+/)
|
19
|
+
value.push('.' + value.pop) if value.length > 1
|
20
|
+
value.join.to_f
|
21
|
+
end
|
22
|
+
|
23
|
+
# Converts an input angle value in some unit to a radian number.
|
24
|
+
def self.parse_angle(value, from_unit)
|
25
|
+
value.is_a?(Float) ? value : convert_angle(parse(value), from_unit)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Converts the given value from the unit specified to radians.
|
29
|
+
# If reverse is true, the value will be converted from radians to the specified unit.
|
30
|
+
def self.convert_angle(value, unit, reverse = false)
|
31
|
+
return nil if value.nil?
|
32
|
+
|
33
|
+
factor = (Math::PI / UNIT_FACTORS[unit.to_sym])
|
34
|
+
value * (reverse ? 1.0 / factor : factor)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the size of the angle to the given variable, rounded and converted to the prefered unit.
|
38
|
+
def angle(v)
|
39
|
+
format(Formatter.convert_angle(t.angle(v), t.angles.unit, true), @precision)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
# Shorthand-method referencing the associated triangle object.
|
44
|
+
def t
|
45
|
+
@triangle
|
46
|
+
end
|
47
|
+
|
48
|
+
# Rounds the value down to the specified amount of decimals.
|
49
|
+
def round(value, decimals)
|
50
|
+
multiplier = 10 ** decimals
|
51
|
+
(value * multiplier).round.to_f / multiplier
|
52
|
+
end
|
53
|
+
|
54
|
+
# Formats the given value to improve human readability.
|
55
|
+
def format(value, decimals)
|
56
|
+
"%.#{decimals}f" % round(value, decimals)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the number of significant digits of the given value.
|
60
|
+
def significant_digits(number)
|
61
|
+
number.to_s.gsub(/^[0.]+/, '').length
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(name, *args)
|
65
|
+
if [:side, :altitude, :median, :angle_bisector, :area, :circumference].include?(name)
|
66
|
+
format(t.send(name, *args), @precision)
|
67
|
+
else
|
68
|
+
super(name, *args)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module CosSinCalc
|
2
|
+
class Triangle
|
3
|
+
class Formatter
|
4
|
+
class Latex
|
5
|
+
# Initializes the LaTeX renderer.
|
6
|
+
def initialize(formatter)
|
7
|
+
@formatter = formatter
|
8
|
+
@triangle = formatter.triangle
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns the generated LaTeX code.
|
12
|
+
def to_tex(filename = nil)
|
13
|
+
document(@triangle.alt ? (<<-EOT) : document_content(filename))
|
14
|
+
#{document_content(filename ? filename + '-1' : nil)}
|
15
|
+
|
16
|
+
\\newpage
|
17
|
+
\\section*{Alternative triangle}
|
18
|
+
Another triangle can be constructed based on the variables given.\\\\[0.2 cm]
|
19
|
+
|
20
|
+
#{Latex.new(@triangle.alt.humanize(f.precision)).document_content(filename ? filename + '-2' : nil)}
|
21
|
+
EOT
|
22
|
+
end
|
23
|
+
|
24
|
+
# Saves the generated LaTeX to a TeX file.
|
25
|
+
# The filename should be provided without the .tex extension.
|
26
|
+
def save_tex(filename)
|
27
|
+
File.open("#{filename}.tex", 'w') { |f| f.write(to_tex(filename)) }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Saves the generated LaTeX to a TeX file and then converts it using pdflatex.
|
31
|
+
# The filename should be provided without the .pdf extension.
|
32
|
+
def save_pdf(filename)
|
33
|
+
save_tex(filename)
|
34
|
+
`pdflatex \"#{filename}.tex\" --output-directory #{File.dirname(filename)}`
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the content of the LaTeX document.
|
38
|
+
def document_content(image_filename = nil)
|
39
|
+
variable_table + "\\\\[0.2 cm]\n\n" + equations + "\n\n" + drawing(image_filename)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
# Shorthand-method referencing the associated formatter object.
|
44
|
+
def f
|
45
|
+
@formatter
|
46
|
+
end
|
47
|
+
|
48
|
+
# Wraps the variables calculated in a LaTeX table.
|
49
|
+
def variable_table
|
50
|
+
table = "\\begin{tabular}{ r r r r r }\n"
|
51
|
+
table << ['Angles', 'Sides', 'Altitudes', 'Medians', 'Angle bisectors'].join(' & ') + " \\\\ \\hline\n"
|
52
|
+
|
53
|
+
@triangle.each do |v|
|
54
|
+
table << [ "$#{v.to_s.upcase}=#{format_angle(f.angle(v), @triangle.angles.unit)}$", "$#{v}=#{f.side(v)}$",
|
55
|
+
"$h_#{v.to_s.upcase}=#{f.altitude(v)}$", "$m_#{v}=#{f.median(v)}$",
|
56
|
+
"$t_#{v.to_s.upcase}=#{f.angle_bisector(v)}$" ].join(' & ') + " \\\\\n"
|
57
|
+
end
|
58
|
+
|
59
|
+
table << "\\end{tabular}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Adds the appropriate unit to the angle value.
|
63
|
+
def format_angle(value, unit)
|
64
|
+
value + case unit
|
65
|
+
when :degree then '\degree'
|
66
|
+
when :gon then '\unit{gon}'
|
67
|
+
when :radian then '\unit{radian}'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Formats the equation for embedding into a LaTeX document.
|
72
|
+
def format_equation(latex, variables, angle_unit)
|
73
|
+
latex.gsub! /@pi/, (angle_unit == :radian ? '\pi' :
|
74
|
+
format_angle(CosSinCalc::Triangle::Formatter::UNIT_FACTORS[angle_unit].to_i.to_s, angle_unit))
|
75
|
+
|
76
|
+
symbols = latex.gsub /[$@]\d/ do |match|
|
77
|
+
v = variables[match[1..-1].to_i - 1]
|
78
|
+
(match[0] == ?@ ? v.to_s.upcase : v.to_s)
|
79
|
+
end
|
80
|
+
|
81
|
+
values = latex.gsub /[$@]\d/ do |match|
|
82
|
+
v = variables[match[1..-1].to_i - 1]
|
83
|
+
(match[0] == ?@ ? format_angle(f.angle(v), angle_unit) : f.side(v))
|
84
|
+
end.split('=').reverse.join('=')
|
85
|
+
|
86
|
+
symbols + ' &= ' + values
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the list of equations performed during calculation.
|
90
|
+
def equations
|
91
|
+
"\\begin{align*}\n" +
|
92
|
+
@triangle.equations.map { |latex, vars| format_equation(latex, vars, @triangle.angles.unit) }.join("\\\\\n") +
|
93
|
+
"\n\\end{align*}"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Saves the associated drawing and returns the embedding LaTeX code.
|
97
|
+
# If no filename is given no image will be saved (can be used in testing).
|
98
|
+
def drawing(filename = nil)
|
99
|
+
CosSinCalc::Triangle::Drawing.new(f).save_png(filename) if filename
|
100
|
+
"\\begin{center}\n\\includegraphics[scale=0.4]{#{filename || 'placeholder'}}\n\\end{center}"
|
101
|
+
end
|
102
|
+
|
103
|
+
# Wraps the given LaTeX content into a LaTeX document ready to write to filesystem.
|
104
|
+
def document(content)
|
105
|
+
<<-EOT
|
106
|
+
\\documentclass{article}
|
107
|
+
\\usepackage{amsmath}
|
108
|
+
\\usepackage{amsfonts}
|
109
|
+
\\usepackage{graphicx}
|
110
|
+
|
111
|
+
\\DeclareMathSymbol{*}{\\mathbin}{symbols}{"01}
|
112
|
+
\\newcommand{\\unit}[1]{\\ensuremath{\\, \\mathrm{#1}}}
|
113
|
+
\\newcommand{\\degree}{\\ensuremath{^{\\circ}}}
|
114
|
+
|
115
|
+
% Uncomment to use a comma as the decimal separator (you would still write a period in the source).
|
116
|
+
% \\DeclareMathSymbol{,}{\\mathpunct}{letters}{"3B}
|
117
|
+
% \\DeclareMathSymbol{.}{\\mathord}{letters}{"3B}
|
118
|
+
% \\DeclareMathSymbol{\\decimal}{\\mathord}{letters}{"3A}
|
119
|
+
|
120
|
+
\\title{CosSinCalc Calculation Results}
|
121
|
+
\\author{Calculated by the CosSinCalc Triangle Calculator}
|
122
|
+
|
123
|
+
\\begin{document}
|
124
|
+
\\maketitle
|
125
|
+
|
126
|
+
#{content}
|
127
|
+
|
128
|
+
\\end{document}
|
129
|
+
EOT
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module CosSinCalc
|
2
|
+
class Triangle
|
3
|
+
class Validator
|
4
|
+
NOT_ENOUGH_VARIABLES = '3 values must be specified.'
|
5
|
+
TOO_MANY_VARIABLES = 'Only 3 values should be specified.'
|
6
|
+
NO_SIDES = 'At least one side must be given.'
|
7
|
+
INVALID_SIDE = 'Only numbers (above zero) are accepted values for a side.'
|
8
|
+
INVALID_ANGLE = 'Only numbers (above zero) are accepted values for an angle. Furthermore the angle must remain inside the scope of the sum of all angles in the triangle.'
|
9
|
+
INVALID_TRIANGLE = 'The specified values do not match a valid triangle.'
|
10
|
+
CALCULATOR_PRECISION = 0.01
|
11
|
+
|
12
|
+
class ValidationError < Exception
|
13
|
+
attr_reader :messages, :sides_valid, :angles_valid
|
14
|
+
|
15
|
+
def initialize(messages, sides_valid = {}, angles_valid = {})
|
16
|
+
@messages, @sides_valid, @angles_valid = messages, sides_valid, angles_valid
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(triangle)
|
21
|
+
@triangle = triangle
|
22
|
+
end
|
23
|
+
|
24
|
+
# Validates the triangle before calculation and raises an exception on errors.
|
25
|
+
def validate
|
26
|
+
@sides_valid, @angles_valid = {}, {}
|
27
|
+
|
28
|
+
t.each do |v|
|
29
|
+
@sides_valid[v] = t.side(v).nil? || valid_side?(t.side(v))
|
30
|
+
@angles_valid[v] = t.angle(v).nil? || valid_angle?(t.angle(v))
|
31
|
+
end
|
32
|
+
|
33
|
+
errors = error_messages
|
34
|
+
errors.empty? || (raise ValidationError.new(errors, @sides_valid, @angles_valid))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Checks whether the calculation was successful and the values given/calculated match a triangle.
|
38
|
+
def validate_calculation
|
39
|
+
t.each do |v, r|
|
40
|
+
raise ValidtionError.new([INVALID_TRIANGLE]) unless valid_side?(t.side(v)) && valid_angle?(t.angle(v))
|
41
|
+
|
42
|
+
angle = t.calculate_angle_by_sides(v, r)
|
43
|
+
unless angle > t.angle(v) - CALCULATOR_PRECISION && angle < t.angle(v) + CALCULATOR_PRECISION
|
44
|
+
raise ValidtionError.new([INVALID_TRIANGLE])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
# Shorthand-method referencing the associated triangle object.
|
52
|
+
def t
|
53
|
+
@triangle
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns an array of unique errors raised when validating the triangle.
|
57
|
+
def error_messages
|
58
|
+
errors = []
|
59
|
+
errors << INVALID_SIDE if @sides_valid.values.include?(false)
|
60
|
+
errors << INVALID_ANGLE if @angles_valid.values.include?(false)
|
61
|
+
|
62
|
+
if errors.empty?
|
63
|
+
errors << NOT_ENOUGH_VARIABLES if total_values < 3
|
64
|
+
errors << TOO_MANY_VARIABLES if total_values > 3
|
65
|
+
errors << NO_SIDES if t.sides.amount < 1
|
66
|
+
end
|
67
|
+
|
68
|
+
return errors
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the total number of variables given.
|
72
|
+
def total_values
|
73
|
+
@total_values ||= t.sides.amount + t.angles.amount
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns whether the value is a valid side.
|
77
|
+
def valid_side?(value)
|
78
|
+
value.is_a?(Float) && value.finite? && value > 0
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns whether the value is a valid angle.
|
82
|
+
def valid_angle?(value)
|
83
|
+
valid_side?(value) && value < Math::PI
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CosSinCalc
|
2
|
+
class Triangle
|
3
|
+
class VariableHash < Hash
|
4
|
+
attr_accessor :unit
|
5
|
+
|
6
|
+
# Initializes the variables.
|
7
|
+
def initialize
|
8
|
+
super()
|
9
|
+
CosSinCalc::Triangle::VARIABLES.each { |v| self[v] = nil }
|
10
|
+
end
|
11
|
+
|
12
|
+
# If a symbol is given, returns the associated value.
|
13
|
+
# If an array of symbols is given, returns an array of the associated values.
|
14
|
+
def [](vars)
|
15
|
+
vars.is_a?(Array) ? vars.map { |v| super(v) } : super(vars)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the amount of variables that have a value.
|
19
|
+
def amount
|
20
|
+
self.values.compact.size
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cossincalc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Molte Emil Strange Andersen
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-06-30 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: |
|
23
|
+
CosSinCalc is a web application able to calculate the variables of a triangle. The live site is located at http://cossincalc.com/. This is an offline version of the calculator.
|
24
|
+
|
25
|
+
You can use the included command line utility to generate a PDF page containing all the results, formulae and a drawing of the triangle, or you can include it as a library in your Ruby application and use just the features you care about.
|
26
|
+
|
27
|
+
email: molte@cossincalc.com
|
28
|
+
executables:
|
29
|
+
- cossincalc
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files: []
|
33
|
+
|
34
|
+
files:
|
35
|
+
- MIT-LICENSE
|
36
|
+
- Rakefile
|
37
|
+
- lib/core_ext.rb
|
38
|
+
- lib/cossincalc/triangle/calculator.rb
|
39
|
+
- lib/cossincalc/triangle/drawing/svg.rb
|
40
|
+
- lib/cossincalc/triangle/drawing.rb
|
41
|
+
- lib/cossincalc/triangle/formatter/latex.rb
|
42
|
+
- lib/cossincalc/triangle/formatter.rb
|
43
|
+
- lib/cossincalc/triangle/validator.rb
|
44
|
+
- lib/cossincalc/triangle/variable_hash.rb
|
45
|
+
- lib/cossincalc/triangle.rb
|
46
|
+
- lib/cossincalc/version.rb
|
47
|
+
- lib/cossincalc.rb
|
48
|
+
- bin/cossincalc
|
49
|
+
- bin/trollop.rb
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: http://cossincalc.com/
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.3.7
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Triangle calculator
|
84
|
+
test_files: []
|
85
|
+
|