Merge branch 'release/1.0.1'

This commit is contained in:
simon 2017-07-18 14:21:00 +01:00
commit 45865eaae2
12 changed files with 307 additions and 112 deletions

View file

@ -1,11 +1,12 @@
(defproject swinging-needle-meter "1.0.0" (defproject swinging-needle-meter "1.0.1"
:description "A swinging needle meter, as an experiment in animating SVG from re-frame. Draws heavily on re-com." :description "A swinging needle meter, as an experiment in animating SVG from re-frame. Draws heavily on re-com."
:dependencies [[org.clojure/clojure "1.8.0"] :dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.229"] [org.clojure/clojurescript "1.9.229"]
[reagent "0.6.0"] [reagent "0.6.0"]
[re-frame "0.9.4"] [re-frame "0.9.4"]
[org.clojure/core.async "0.2.391"] [org.clojure/core.async "0.2.391"]
[re-com "2.0.0"]] [re-com "2.0.0"]
[org.webjars.bower/snap.svg "0.4.1"]]
:plugins [[lein-cljsbuild "1.1.4"]] :plugins [[lein-cljsbuild "1.1.4"]]
@ -20,9 +21,7 @@
:profiles :profiles
{:dev {:dev
{:dependencies [[binaryage/devtools "0.8.2"]] {:dependencies [[binaryage/devtools "0.8.2"]]
:plugins [[lein-figwheel "0.5.9"]]}}
:plugins [[lein-figwheel "0.5.9"]]
}}
:cljsbuild :cljsbuild
{:builds {:builds
@ -44,8 +43,4 @@
:output-to "resources/public/js/compiled/app.js" :output-to "resources/public/js/compiled/app.js"
:optimizations :advanced :optimizations :advanced
:closure-defines {goog.DEBUG false} :closure-defines {goog.DEBUG false}
:pretty-print false}} :pretty-print false}}]})
]}
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

View file

@ -8,13 +8,13 @@
.snm-cursor { .snm-cursor {
stroke:#ff8500; stroke:#ff8500;
stroke-width:5; stroke-width: 3%;
stroke-opacity: 0.5; stroke-opacity: 0.5;
} }
.snm-frame { .snm-frame {
fill: none; fill: none;
stroke-width: 10; stroke-width: 5%;
stroke-linejoin: round; stroke-linejoin: round;
stroke: #444444; stroke: #444444;
} }
@ -25,7 +25,7 @@
} }
.snm-gradation text { .snm-gradation text {
font-size: 50%; font-size: 150%;
font-weight: lighter; font-weight: lighter;
} }
@ -33,11 +33,6 @@
fill: #444444; fill: #444444;
} }
.snm-limit {
text-align: center;
font-size: 66%;
}
.snm-meter { .snm-meter {
height: 50%; height: 50%;
width: auto; width: auto;
@ -51,13 +46,13 @@
.snm-redzone { .snm-redzone {
fill:none; fill:none;
stroke: maroon; stroke: maroon;
stroke-width: 15; stroke-width: 10%;
} }
.snm-scale { .snm-scale {
fill: none; fill: none;
stroke: green; stroke: green;
stroke-width: 15; stroke-width: 10%;
} }
.snm-target .snm-frame { .snm-target .snm-frame {
@ -65,6 +60,7 @@
} }
.snm-value { .snm-value {
font-size: 200%;
text-align: center; text-align: center;
} }

View file

@ -14,6 +14,7 @@
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js"></script>
<script src="js/compiled/app.js"></script> <script src="js/compiled/app.js"></script>
<script>swinging_needle_meter.core.init();</script> <script>swinging_needle_meter.core.init();</script>
</body> </body>

View file

@ -1,6 +1,6 @@
(ns swinging-needle-meter.core (ns swinging-needle-meter.core
(:require [reagent.core :as reagent] (:require [reagent.core :as reagent]
[re-frame.core :as re-frame] [re-frame.core :as rf]
[swinging-needle-meter.events] [swinging-needle-meter.events]
[swinging-needle-meter.subs] [swinging-needle-meter.subs]
[swinging-needle-meter.views :as views] [swinging-needle-meter.views :as views]
@ -14,12 +14,20 @@
(enable-console-print!) (enable-console-print!)
(println "dev mode"))) (println "dev mode")))
(defn dispatch-timer-event
[]
(let [now (js/Date.)]
(rf/dispatch [:timer now]))) ;; <-- dispatch used
;; call the dispatching function every tenth of a second
(defonce do-timer (js/setInterval dispatch-timer-event 100))
(defn mount-root [] (defn mount-root []
(re-frame/clear-subscription-cache!) (rf/clear-subscription-cache!)
(reagent/render [views/main-panel] (reagent/render [views/main-panel]
(.getElementById js/document "app"))) (.getElementById js/document "app")))
(defn ^:export init [] (defn ^:export init []
(re-frame/dispatch-sync [:initialize-db]) (rf/dispatch-sync [:initialize-db])
(dev-setup) (dev-setup)
(mount-root)) (mount-root))

View file

@ -1,4 +0,0 @@
(ns swinging-needle-meter.db)
(def default-db
{:name "re-frame"})

View file

@ -1,10 +1,62 @@
(ns swinging-needle-meter.events (ns swinging-needle-meter.events
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[swinging-needle-meter.db :as db])) [swinging-needle-meter.state :as state]))
;;; This file is unchanged (except this line) from the leiningen recom template
;; Reset.
(re-frame/reg-event-db (re-frame/reg-event-db
:initialize-db :initialize-db
(fn [_ _] (fn [_ _]
db/default-db)) state/default-state))
;; The clock ticked. Implement a mechanical swing.
(re-frame/reg-event-db
:timer
(fn [db [x value]]
(let [old-value (:old-value db)
target (:value db)
new-value (+ old-value (/ (- target old-value) 10))]
(assoc db :old-value new-value))))
(re-frame/reg-event-db
:set-value
(fn [db [x value]]
(assoc
(assoc db :old-value (:value db))
:value
value)))
(re-frame/reg-event-db
:set-setpoint
(fn [db [_ value]]
(assoc db :setpoint value)))
(re-frame/reg-event-db
:set-gradations
(fn [db [_ value]]
(assoc db :gradations value)))
(re-frame/reg-event-db
:set-size
(fn [db [_ value]]
(assoc db :size value)))
(re-frame/reg-event-db
:set-min-value
(fn [db [_ value]]
(assoc db :min-val value)))
(re-frame/reg-event-db
:set-max-value
(fn [db [_ value]]
(assoc db :max-val value)))
(re-frame/reg-event-db
:set-warning-value
(fn [db [_ value]]
(assoc db :warn-val value)))
(re-frame/reg-event-db
:set-unit
(fn [db [_ value]]
(assoc db :unit value)))

View file

@ -0,0 +1,15 @@
(ns ^{:doc "Client state."
:author "Simon Brooke"}
swinging-needle-meter.state)
(def default-state
{:timer (js/Date.)
:value 60
:old-value 0
:setpoint 75
:gradations 5
:size 70
:min-val 0
:max-val 100
:warn-val 80
:unit "Mw"})

View file

@ -2,9 +2,58 @@
(:require-macros [reagent.ratom :refer [reaction]]) (:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :as re-frame])) (:require [re-frame.core :as re-frame]))
;;; This file is unchanged (except this line) from the leiningen recom template
(re-frame/reg-sub (re-frame/reg-sub
:name :name
(fn [db] (fn [db]
(:name db))) (:name db)))
(re-frame/reg-sub
:value
(fn [db]
(:value db)))
(re-frame/reg-sub
:old-value
(fn [db]
(:old-value db)))
(re-frame/reg-sub
:setpoint
(fn [db]
(:setpoint db)))
(re-frame/reg-sub
:unit
(fn [db]
(:unit db)))
(re-frame/reg-sub
:min-val
(fn [db]
(:min-val db)))
(re-frame/reg-sub
:max-val
(fn [db]
(:max-val db)))
(re-frame/reg-sub
:gradations
(fn [db]
(:gradations db)))
(re-frame/reg-sub
:warn-val
(fn [db]
(:warn-val db)))
(re-frame/reg-sub
:size
(fn [db]
(:size db)))
(re-frame/reg-sub
:timer
(fn [db]
(:timer db)))

View file

@ -4,7 +4,8 @@
[re-com.box :refer [flex-child-style]] [re-com.box :refer [flex-child-style]]
[re-com.util :refer [deref-or-value]] [re-com.util :refer [deref-or-value]]
[re-com.validate :refer [number-or-string? css-style? html-attr? validate-args-macro]] [re-com.validate :refer [number-or-string? css-style? html-attr? validate-args-macro]]
[reagent.core :as reagent])) [reagent.core :as reagent]
[swinging-needle-meter.utils :refer [abs]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; ;;;;
@ -41,10 +42,10 @@
:validate-fn number-or-string? :description "current value of the variable being watched. A number between 0 and 100"} :validate-fn number-or-string? :description "current value of the variable being watched. A number between 0 and 100"}
{:name :setpoint :required false :type "double | atom" {:name :setpoint :required false :type "double | atom"
:validate-fn number-or-string? :description "current setpoint for the variable being watched, if any. A number between 0 and 100"} :validate-fn number-or-string? :description "current setpoint for the variable being watched, if any. A number between 0 and 100"}
{:name :width :required false :type "string" :default "100%" {:name :width :required false :type "integer" :default "300"
:validate-fn string? :description "a CSS width"} :validate-fn integer? :description "a CSS width"}
{:name :height :required false :type "string" :default "100%" {:name :height :required false :type "integer" :default "200"
:validate-fn string? :description "a CSS height"} :validate-fn integer? :description "a CSS height"}
{:name :min-value :required false :type "double" :default 0 {:name :min-value :required false :type "double" :default 0
:validate-fn number? :description "the minimum value model can take"} :validate-fn number? :description "the minimum value model can take"}
{:name :max-value :required false :type "double" :default 100 {:name :max-value :required false :type "double" :default 100
@ -82,29 +83,21 @@
{:name :attr :required false :type "HTML attr map" {:name :attr :required false :type "HTML attr map"
:validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}]) :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
(defn abs
"Return the absolute value of the (numeric) argument."
[n] (max n (- n)))
;; the constant 140 represents the full sweep of the needle ;; the constant 140 represents the full sweep of the needle
;; from the left end of the scale to right end, in degrees. ;; from the left end of the scale to right end, in degrees.
(def full-scale-deflection 140) (def full-scale-deflection 140)
;; ultimately this should be resizeable, and radius should be a function of
;; size...
(def scale-radius 75)
(defn deflection (defn deflection
"Return the deflection of a needle given this `value` on the "Return the linear deflection of a needle given this `value` on the
range `min-value`...`max-value`." range `min-value`...`max-value`."
[value min-value max-value] [value min-value max-value]
(let [range (- max-value min-value) (let [range (- max-value min-value)
deflection (/ value range)
zero-offset (/ (- 0 min-value) range) zero-offset (/ (- 0 min-value) range)
limited (min (max (+ zero-offset deflection) 0) 1)] limited (min (max (+ zero-offset (/ value range)) 0) 1)]
(js/console.log (str "zero-offset: " zero-offset))
(* (- limited 0.5) full-scale-deflection))) (* (- limited 0.5) full-scale-deflection)))
(defn polar-to-cartesian (defn polar-to-cartesian
"Return, as a map with keys :x. :y, the cartesian coordinates at the point "Return, as a map with keys :x. :y, the cartesian coordinates at the point
`radius` distance at `theta` (degrees) angle from a point at `radius` distance at `theta` (degrees) angle from a point at
@ -115,6 +108,7 @@
{:x (+ cx (* radius (.cos js/Math in-radians))) {:x (+ cx (* radius (.cos js/Math in-radians)))
:y (+ cy (* radius (.sin js/Math in-radians)))})) :y (+ cy (* radius (.sin js/Math in-radians)))}))
(defn describe-arc (defn describe-arc
"Return as a string an SVG path definition describing an arc centred "Return as a string an SVG path definition describing an arc centred
at `cx`, cy` starting at `start-angle` and ending at `end-angle` (both at `cx`, cy` starting at `start-angle` and ending at `end-angle` (both
@ -141,25 +135,34 @@
"Return as a string an SVG path definition describing a radial stroke from a center "Return as a string an SVG path definition describing a radial stroke from a center
at `cx`, cy` starting at `min-radius` and extending to `max-radius`." at `cx`, cy` starting at `min-radius` and extending to `max-radius`."
[cx cy min-radius max-radius angle label] [cx cy min-radius max-radius angle label]
(let [:g {:class "snm-gradation"
[start (polar-to-cartesian cx cy min-radius angle) :transform (string/join " " ["rotate(" angle cx cy ")"])}
mid (polar-to-cartesian cx cy (+ min-radius [:path {:d (string/join
(* (- max-radius min-radius) 0.333)) " "
angle) ["M"
end (polar-to-cartesian cx cy max-radius angle)] cx
[:g {:class "snm-gradation"} (- cy
[:path {:d (string/join " " ["M" (:x mid) (:y mid) "L" (:x end) (:y end)])}] (+ min-radius
(* (- max-radius min-radius) 0.333)))
"L"
cx
(- cy max-radius)])}]
[:text {:text-anchor "middle" [:text {:text-anchor "middle"
:x (:x start) :x cx
:y (:y start) :y (- cy min-radius)} (as-label label)]])
:transform (string/join " " ["rotate(" angle (:x start) (:y start) ")"])} (as-label label)]]))
(defn as-mm
"return the argument, as a string, with 'mm' appended"
[arg]
(str arg "mm"))
(defn swinging-needle-meter (defn swinging-needle-meter
"Render an SVG swinging needle meter" "Render an SVG swinging needle meter"
[& {:keys [model setpoint width height min-value max-value warn-value tolerance class gradations alarm-class cursor-class frame-class hub-class needle-class redzone-class scale-class target-class unit id style attr] [& {:keys [model setpoint width height min-value max-value warn-value tolerance class gradations alarm-class cursor-class frame-class hub-class needle-class redzone-class scale-class target-class unit id style attr]
:or {width "100%" :or {width 300
height "100%" height 200
min-value 0 min-value 0
max-value 100 max-value 100
warn-value 80 warn-value 80
@ -179,11 +182,11 @@
(let [model (deref-or-value model) (let [model (deref-or-value model)
setpoint (deref-or-value setpoint) setpoint (deref-or-value setpoint)
mid-point-deflection (/ full-scale-deflection 2) mid-point-deflection (/ full-scale-deflection 2)
;; if warn-value is greater than max-value, we don't want a red-zone at all. cx (/ width 2)
red-zone-deflection (if cy (* height 0.90)
(< warn-value max-value) needle-length (* height 0.75)
(* full-scale-deflection (/ warn-value max-value)) scale-radius (* height 0.7)
full-scale-deflection)] gradation-inner (* height 0.55)]
[box [box
:align :start :align :start
:child [:div :child [:div
@ -197,53 +200,58 @@
{:width width :height height} {:width width :height height}
style)} style)}
attr) attr)
[:svg {:xmlns:svg "http://www.w3.org/2000/svg" [:svg {:xmlSpace "preserve"
:xmlns "http://www.w3.org/2000/svg"
:xml:space "preserve"
:overflow "visible" :overflow "visible"
:viewBox "0 0 180 120" :viewBox (string/join " " [0 0 width height])
:width (str width "px")
:height (str height "px")
:y "0px" :y "0px"
:x "0px" :x "0px"
:version "1.1" :version "1.1"
:id id :id id
:class (str "snm-meter " class)} :class (str "snm-meter " class)}
[:text [:text
{:text-anchor "middle" {:text-anchor "middle"
:x 80 :x (/ width 2)
:y 70 :y (/ height 2)
:width "100" :width "100"
:id (str id "-current-value") :id (str id "-current-value")
:class "snm-value"}[:tspan (str (as-label model) (if unit " ") unit)]] :class "snm-value"}[:tspan (str (as-label model) (if unit " ") unit)]]
[:path {:class scale-class [:path {:class scale-class
:id (str id "-scale") :id (str id "-scale")
:d (describe-arc 80 100 scale-radius (- 0 mid-point-deflection) mid-point-deflection)}] :d (describe-arc cx cy scale-radius
(deflection min-value min-value max-value)
(deflection max-value min-value max-value))}]
[:path {:class redzone-class [:path {:class redzone-class
:id (str id "-redzone") :id (str id "-redzone")
:d (describe-arc 80 100 scale-radius (- red-zone-deflection mid-point-deflection) mid-point-deflection)}] :d (describe-arc cx cy scale-radius
(deflection warn-value min-value max-value)
(deflection max-value min-value max-value))}]
[:path {:class cursor-class [:path {:class cursor-class
:id (str id "-cursor") :id (str id "-cursor")
:d "M 80,20 80,100" :d (str "M " cx "," (- cy needle-length) " " cx "," cy) ;; "M cx,20 cx,100"
:visibility (if (and (number? setpoint) (> setpoint min-value)) "visible" "hidden") :visibility (if (and (number? setpoint) (> setpoint min-value)) "visible" "hidden")
:transform (str "rotate( " (deflection setpoint min-value max-value) ", 80, 100)")}] :transform (str "rotate( " (deflection setpoint min-value max-value) "," cx "," cy ")")}]
[:path {:class needle-class [:path {:class needle-class
:id (str id "-needle") :id (str id "-needle")
:d "M 80,20 80,100" :d (str "M " cx "," (- cy needle-length) " " cx "," cy) ;; "M cx,20 cx,100"
:transform (str "rotate( " (deflection model min-value max-value) ", 80, 100)") }] :transform (str "rotate( " (deflection model min-value max-value) "," cx "," cy ")") }]
(apply vector (cons :g (map #(gradation 80 100 60 82 (if (> gradations 0)
(- (* % (apply vector (cons :g (map #(let
(/ full-scale-deflection gradations)) [value (+ min-value
mid-point-deflection)
(+ min-value
(* (*
(/ (/
(- max-value min-value) (- max-value min-value)
gradations) %))) gradations) %))]
(range 0 (+ gradations 1))))) (gradation cx cy gradation-inner needle-length
(deflection value min-value max-value)
value))
(range 0 (+ gradations 1))))))
[:rect {:class frame-class [:rect {:class frame-class
:id (str id "-frame") :id (str id "-frame")
:x "5" :y "5" :height "100" :width "150"}] :x (* width 0.05) :y (* height .05) :height cy :width (* width 0.9)}]
[:circle {:class hub-class [:circle {:class hub-class
:id (str id "-hub") :id (str id "-hub")
:r "10" :cx "80" :cy "100"}]] :r (/ height 10) :cx cx :cy cy}]]
]])) ]]))

View file

@ -1,9 +1,15 @@
(ns swinging-needle-meter.utils (ns swinging-needle-meter.utils
(:require [re-com.core :refer [h-box v-box box gap title line label hyperlink-href align-style]])) (:require [re-com.core :refer [h-box v-box box gap title line label hyperlink-href align-style]]))
;;;; This file is just stolen wholesale from re-demo in the re-com package; ;;;; This file is mostly stolen wholesale from re-demo in the re-com package;
;;;; I claim no credit for it. ;;;; I claim no credit for it.
(defn abs
"Return the absolute value of the (numeric) argument."
[n] (max n (- n)))
(defn github-hyperlink (defn github-hyperlink
"given a label and a relative path, return a component which hyperlinks to the GitHub URL in a new tab" "given a label and a relative path, return a component which hyperlinks to the GitHub URL in a new tab"
[label src-path] [label src-path]

View file

@ -1,18 +1,25 @@
(ns swinging-needle-meter.views (ns swinging-needle-meter.views
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as rf]
[re-com.core :refer [h-box v-box box gap line label title progress-bar slider checkbox p]] [re-com.core :refer [h-box v-box box gap line label title progress-bar slider checkbox p single-dropdown]]
[re-com.util :refer [deref-or-value]]
[swinging-needle-meter.swinging-needle-meter :refer [swinging-needle-meter swinging-needle-args-desc]] [swinging-needle-meter.swinging-needle-meter :refer [swinging-needle-meter swinging-needle-args-desc]]
[swinging-needle-meter.utils :refer [panel-title title2 args-table github-hyperlink status-text]] [swinging-needle-meter.utils :refer [panel-title title2 args-table github-hyperlink status-text]]
[reagent.core :as reagent])) [reagent.core :as reagent]
[swinging-needle-meter.utils :refer [abs]]))
;; ------------------------------------------------------------------------------------ ;; ------------------------------------------------------------------------------------
;; Demo: swinging-needle-meter ;; Demo: swinging-needle-meter
;; ------------------------------------------------------------------------------------ ;; ------------------------------------------------------------------------------------
(defn swinging-needle-demo (defn swinging-needle-demo
[] []
(let [value (reagent/atom 75) (let [unit @(rf/subscribe [:unit])
setpoint (reagent/atom 75)] min-val @(rf/subscribe [:min-val])
max-val @(rf/subscribe [:max-val])
warn-val @(rf/subscribe [:warn-val])
gradations @(rf/subscribe [:gradations])
size @(rf/subscribe [:size])]
(fn (fn
[] []
[v-box [v-box
@ -54,37 +61,99 @@
[v-box [v-box
:gap "20px" :gap "20px"
:children [[swinging-needle-meter :children [[swinging-needle-meter
:model value :model @(rf/subscribe [:old-value])
:setpoint setpoint :setpoint @(rf/subscribe [:setpoint])
:unit "Mw" :unit @(rf/subscribe [:unit])
;; :min-value 20 :min-value @(rf/subscribe [:min-val])
;; :warn-value 35 :warn-value @(rf/subscribe [:warn-val])
;; :max-value 40 :max-value @(rf/subscribe [:max-val])
;; :max-value (aget js/Math "PI")
:tolerance 2 :tolerance 2
:alarm-class "snm-warning" :alarm-class "snm-warning"
:width "350px"] :gradations @(rf/subscribe [:gradations])
:height (int (* @(rf/subscribe [:size]) 6))
:width (int (* @(rf/subscribe [:size]) 10))]
[title :level :level3 :label "Parameters"] [title :level :level3 :label "Parameters"]
[h-box [h-box
:gap "10px" :gap "10px"
:children [[box :align :start :child [:code ":model"]] :children [[box :align :start :child [:code ":model"]]
[slider [slider
:model value :model @(rf/subscribe [:value])
:min 0 :min -100
:max 100 :max 100
:width "200px" :width "200px"
:on-change #(reset! value %)] :on-change #(rf/dispatch [:set-value %])]
[label :label @value]]] [label :label @(rf/subscribe [:value])]]]
[h-box [h-box
:gap "10px" :gap "10px"
:children [[box :align :start :child [:code ":setpoint"]] :children [[box :align :start :child [:code ":setpoint"]]
[slider [slider
:model setpoint :model @(rf/subscribe [:setpoint])
:min 0 :min -100
:max 100 :max 100
:width "200px" :width "200px"
:on-change #(reset! setpoint %)] :on-change #(rf/dispatch [:set-setpoint %])]
[label :label @setpoint]]] [label :label @(rf/subscribe [:setpoint])]]]
[h-box
:gap "10px"
:children [[box :align :start :child [:code ":min-val"]]
[slider
:model @(rf/subscribe [:min-val])
:min -100
:max 100
:width "200px"
:on-change #(rf/dispatch [:set-min-value %])]
[label :label @(rf/subscribe [:min-val])]]]
[h-box
:gap "10px"
:children [[box :align :start :child [:code ":max-val"]]
[slider
:model @(rf/subscribe [:max-val])
:min -100
:max 100
:width "200px"
:on-change #(rf/dispatch [:set-max-value %])]
[label :label @(rf/subscribe [:max-val])]]]
[h-box
:gap "10px"
:children [[box :align :start :child [:code ":warn-val"]]
[slider
:model @(rf/subscribe [:warn-val])
:min -100
:max 100
:width "200px"
:on-change #(rf/dispatch [:set-warning-value %])]
[label :label @(rf/subscribe [:warn-val])]]]
[h-box
:gap "10px"
:children [[box :align :start :child [:code ":gradations"]]
[slider
:model @(rf/subscribe [:gradations])
:min 0
:max 10
:width "200px"
:on-change #(rf/dispatch [:set-gradations %])]
[label :label @(rf/subscribe [:gradations])]]]
[h-box
:gap "10px"
:children [[box :align :start :child [:code ":unit"]]
[single-dropdown
:model @(rf/subscribe [:unit])
:choices [{:id "Mw" :label "Megawatts" :group "Electrical"}
{:id "M/s" :label "Metres per second" :group "Motion"}
{:id "F/f" :label "Furlongs per fortnight" :group "Motion"}
{:id "°C" :label "Degrees Celsius" :group "Temperature"}]
:width "200px"
:on-change #(rf/dispatch [:set-unit %])]]]
[h-box
:gap "10px"
:children [[box :align :start :child [:code ":size"]]
[slider
:model size
:min 25
:max 100
:width "200px"
:on-change #(rf/dispatch [:set-size %])]
[label :label size]]]
]]]]]]]]))) ]]]]]]]])))