December 31, 2019
Trigonometry of right triangles
For my last post of the year, I made this reagent app that solves for sides and angles of a right triangle using trigonometric ratios. Tested with problems taken from Khan Academy trigonometry course.
(require '[reagent.core :as r])
(defonce triangle
(r/atom {:base 12
:height 5
:hypotenuse nil
:angle1 {:name "A"
:degrees nil}
:angle2 {:name "B"
:degrees nil}
:angle3 {:name "C"
:degrees 90}}))
(defn input [type label value on-change]
[:label label
[:input
{:style {:width 50
:background-color "lightgray"}
:type type
:value value
:on-change on-change}]])
(defn button [label onclick]
[:button
{:on-click onclick}
label])
(defn polygon [& points]
[:polygon {:stroke "#000000"
:stroke-width 0.1
:fill "none"
:points (apply str (interpose " " points))}])
(defn right-angle-box [x y]
[:rect {:width 1
:height 1
:fill "none"
:x x
:y y
:stroke "#000000"
:stroke-width 0.05}])
(defn right-triangle [base height]
[:svg {:width "100%"
:view-box (str "0 0 " (+ 2 base) " " (+ 2 height))}
[polygon 1 1 1 (inc height) (inc base) (inc height)]
[right-angle-box 1 height]])
(defn sin [deg]
(.sin js/Math (* deg (/ js/Math.PI 180))))
(defn asin [deg]
(* (.asin js/Math deg) (/ 180 js/Math.PI)))
(defn acos [deg]
(* (.acos js/Math deg) (/ 180 js/Math.PI)))
(defn tan [deg]
(.tan js/Math (* deg (/ js/Math.PI 180))))
(defn atan [deg]
(* (.atan js/Math deg) (/ 180 js/Math.PI)))
(defn cos [deg]
(.cos js/Math (* deg (/ js/Math.PI 180))))
(defn solve-triangle [triangle]
(let [{:keys [base height hypotenuse angle1 angle2]} triangle]
(cond
(and
(pos? (:degrees angle1))
(pos? hypotenuse)) (assoc triangle
:height (* hypotenuse (cos (:degrees angle1)))
:base (* hypotenuse (sin (:degrees angle1))))
(and
(pos? (:degrees angle1))
(pos? height)) (assoc triangle
:base (* height (tan (:degrees angle1)))
:hypotenuse (/ height (cos (:degrees angle1))))
(and
(pos? (:degrees angle1))
(pos? base)) (assoc triangle
:height (/ base (tan (:degrees angle1)))
:hypotenuse (/ base (sin (:degrees angle1))))
(and
(pos? (:degrees angle2))
(pos? hypotenuse)) (assoc triangle
:base (* hypotenuse (cos (:degrees angle2)))
:height (* hypotenuse (sin (:degrees angle2))))
(and
(pos? (:degrees angle2))
(pos? height)) (assoc triangle
:hypotenuse (/ height (sin (:degrees angle2)))
:base (/ height (tan (:degrees angle2))))
(and
(pos? (:degrees angle2))
(pos? base)) (assoc triangle
:height (* base (tan (:degrees angle2)))
:hypotenuse (/ base (cos (:degrees angle2))))
;; solve for angles
(and (pos? base) (pos? hypotenuse)) (assoc-in (assoc-in triangle [:angle1 :degrees] (asin (/ base hypotenuse)))
[:angle2 :degrees] (acos (/ base hypotenuse)))
(and (pos? height) (pos? hypotenuse)) (assoc-in (assoc-in triangle [:angle1 :degrees] (acos (/ height hypotenuse)))
[:angle2 :degrees] (asin (/ height hypotenuse)))
(and (pos? height) (pos? base)) (assoc-in (assoc-in triangle [:angle1 :degrees] (atan (/ base height)))
[:angle2 :degrees] (atan (/ height base)))
:else "Does not compute")))
(defn app []
(let [base (:base @triangle)
height (:height @triangle)
angle1 (:name (:angle1 @triangle))
angle2 (:name (:angle2 @triangle))
angle3 (:name (:angle3 @triangle))]
[:div#app
[:h2 "Trigonometry of right triangles"]
[:div "Angles: "
[input "text" "" angle1 #(swap! triangle assoc-in [:angle1 :name] (-> % .-target .-value))]
[input "text" "" angle2 #(swap! triangle assoc-in [:angle2 :name] (-> % .-target .-value))]
[input "text" "" angle3 #(swap! triangle assoc-in [:angle3 :name] (-> % .-target .-value))]]
[:div
[input "number" "Base: " base #(swap! triangle assoc :base (-> % .-target .-value js/parseInt))]
[input "number" " Height: " height #(swap! triangle assoc :height (-> % .-target .-value js/parseInt))]
[input "number" " Hypotenuse: " (:hypotenuse @triangle) #(swap! triangle assoc :hypotenuse (-> % .-target .-value js/parseInt))]
[:div
[input "number" (str "∠" angle1 ": ") (:degrees (:angle1 @triangle)) #(swap! triangle assoc-in [:angle1 :degrees] (-> % .-target .-value js/parseInt))]
[input "number" (str "∠" angle2 ": ") (:degrees (:angle2 @triangle)) #(swap! triangle assoc-in [:angle2 :degrees] (-> % .-target .-value js/parseInt))]]]
[button "Solve" #(swap! triangle solve-triangle)]
[button "Clear" (fn [e] (swap! triangle #(assoc % :base nil :height nil :hypotenuse nil))
(swap! triangle assoc-in [:angle1 :degrees] nil)
(swap! triangle assoc-in [:angle2 :degrees] nil))]
[:br]
[right-triangle base height]]))
[app]