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
@@ -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
|
+
|