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]
Tags: mathematics geometry