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."
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.229"]
[reagent "0.6.0"]
[re-frame "0.9.4"]
[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"]]
@ -20,9 +21,7 @@
:profiles
{:dev
{:dependencies [[binaryage/devtools "0.8.2"]]
:plugins [[lein-figwheel "0.5.9"]]
}}
:plugins [[lein-figwheel "0.5.9"]]}}
:cljsbuild
{:builds
@ -44,8 +43,4 @@
:output-to "resources/public/js/compiled/app.js"
:optimizations :advanced
: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 {
stroke:#ff8500;
stroke-width:5;
stroke-width: 3%;
stroke-opacity: 0.5;
}
.snm-frame {
fill: none;
stroke-width: 10;
stroke-width: 5%;
stroke-linejoin: round;
stroke: #444444;
}
@ -25,7 +25,7 @@
}
.snm-gradation text {
font-size: 50%;
font-size: 150%;
font-weight: lighter;
}
@ -33,11 +33,6 @@
fill: #444444;
}
.snm-limit {
text-align: center;
font-size: 66%;
}
.snm-meter {
height: 50%;
width: auto;
@ -51,13 +46,13 @@
.snm-redzone {
fill:none;
stroke: maroon;
stroke-width: 15;
stroke-width: 10%;
}
.snm-scale {
fill: none;
stroke: green;
stroke-width: 15;
stroke-width: 10%;
}
.snm-target .snm-frame {
@ -65,6 +60,7 @@
}
.snm-value {
font-size: 200%;
text-align: center;
}

View file

@ -14,6 +14,7 @@
</head>
<body>
<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>swinging_needle_meter.core.init();</script>
</body>

View file

@ -1,6 +1,6 @@
(ns swinging-needle-meter.core
(:require [reagent.core :as reagent]
[re-frame.core :as re-frame]
[re-frame.core :as rf]
[swinging-needle-meter.events]
[swinging-needle-meter.subs]
[swinging-needle-meter.views :as views]
@ -14,12 +14,20 @@
(enable-console-print!)
(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 []
(re-frame/clear-subscription-cache!)
(rf/clear-subscription-cache!)
(reagent/render [views/main-panel]
(.getElementById js/document "app")))
(defn ^:export init []
(re-frame/dispatch-sync [:initialize-db])
(rf/dispatch-sync [:initialize-db])
(dev-setup)
(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
(:require [re-frame.core :as re-frame]
[swinging-needle-meter.db :as db]))
;;; This file is unchanged (except this line) from the leiningen recom template
[swinging-needle-meter.state :as state]))
;; Reset.
(re-frame/reg-event-db
:initialize-db
(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 [re-frame.core :as re-frame]))
;;; This file is unchanged (except this line) from the leiningen recom template
(re-frame/reg-sub
:name
(fn [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.util :refer [deref-or-value]]
[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"}
{: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"}
{:name :width :required false :type "string" :default "100%"
:validate-fn string? :description "a CSS width"}
{:name :height :required false :type "string" :default "100%"
:validate-fn string? :description "a CSS height"}
{:name :width :required false :type "integer" :default "300"
:validate-fn integer? :description "a CSS width"}
{:name :height :required false :type "integer" :default "200"
:validate-fn integer? :description "a CSS height"}
{:name :min-value :required false :type "double" :default 0
:validate-fn number? :description "the minimum value model can take"}
{:name :max-value :required false :type "double" :default 100
@ -82,29 +83,21 @@
{: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"]}])
(defn abs
"Return the absolute value of the (numeric) argument."
[n] (max n (- n)))
;; the constant 140 represents the full sweep of the needle
;; from the left end of the scale to right end, in degrees.
(def full-scale-deflection 140)
;; ultimately this should be resizeable, and radius should be a function of
;; size...
(def scale-radius 75)
(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`."
[value min-value max-value]
(let [range (- max-value min-value)
deflection (/ value range)
zero-offset (/ (- 0 min-value) range)
limited (min (max (+ zero-offset deflection) 0) 1)]
(js/console.log (str "zero-offset: " zero-offset))
limited (min (max (+ zero-offset (/ value range)) 0) 1)]
(* (- limited 0.5) full-scale-deflection)))
(defn polar-to-cartesian
"Return, as a map with keys :x. :y, the cartesian coordinates at the point
`radius` distance at `theta` (degrees) angle from a point at
@ -115,6 +108,7 @@
{:x (+ cx (* radius (.cos js/Math in-radians)))
:y (+ cy (* radius (.sin js/Math in-radians)))}))
(defn describe-arc
"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
@ -141,25 +135,34 @@
"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`."
[cx cy min-radius max-radius angle label]
(let
[start (polar-to-cartesian cx cy min-radius angle)
mid (polar-to-cartesian cx cy (+ min-radius
(* (- max-radius min-radius) 0.333))
angle)
end (polar-to-cartesian cx cy max-radius angle)]
[:g {:class "snm-gradation"}
[:path {:d (string/join " " ["M" (:x mid) (:y mid) "L" (:x end) (:y end)])}]
[:g {:class "snm-gradation"
:transform (string/join " " ["rotate(" angle cx cy ")"])}
[:path {:d (string/join
" "
["M"
cx
(- cy
(+ min-radius
(* (- max-radius min-radius) 0.333)))
"L"
cx
(- cy max-radius)])}]
[:text {:text-anchor "middle"
:x (:x start)
:y (:y start)
:transform (string/join " " ["rotate(" angle (:x start) (:y start) ")"])} (as-label label)]]))
:x cx
:y (- cy min-radius)} (as-label label)]])
(defn as-mm
"return the argument, as a string, with 'mm' appended"
[arg]
(str arg "mm"))
(defn 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]
:or {width "100%"
height "100%"
:or {width 300
height 200
min-value 0
max-value 100
warn-value 80
@ -179,11 +182,11 @@
(let [model (deref-or-value model)
setpoint (deref-or-value setpoint)
mid-point-deflection (/ full-scale-deflection 2)
;; if warn-value is greater than max-value, we don't want a red-zone at all.
red-zone-deflection (if
(< warn-value max-value)
(* full-scale-deflection (/ warn-value max-value))
full-scale-deflection)]
cx (/ width 2)
cy (* height 0.90)
needle-length (* height 0.75)
scale-radius (* height 0.7)
gradation-inner (* height 0.55)]
[box
:align :start
:child [:div
@ -197,53 +200,58 @@
{:width width :height height}
style)}
attr)
[:svg {:xmlns:svg "http://www.w3.org/2000/svg"
:xmlns "http://www.w3.org/2000/svg"
:xml:space "preserve"
[:svg {:xmlSpace "preserve"
:overflow "visible"
:viewBox "0 0 180 120"
:viewBox (string/join " " [0 0 width height])
:width (str width "px")
:height (str height "px")
:y "0px"
:x "0px"
:version "1.1"
:id id
:class (str "snm-meter " class)}
[:text
{:text-anchor "middle"
:x 80
:y 70
:x (/ width 2)
:y (/ height 2)
:width "100"
:id (str id "-current-value")
:class "snm-value"}[:tspan (str (as-label model) (if unit " ") unit)]]
[:path {:class scale-class
: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
: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
: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")
: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
:id (str id "-needle")
:d "M 80,20 80,100"
:transform (str "rotate( " (deflection model min-value max-value) ", 80, 100)") }]
(apply vector (cons :g (map #(gradation 80 100 60 82
(- (* %
(/ full-scale-deflection gradations))
mid-point-deflection)
(+ min-value
:d (str "M " cx "," (- cy needle-length) " " cx "," cy) ;; "M cx,20 cx,100"
:transform (str "rotate( " (deflection model min-value max-value) "," cx "," cy ")") }]
(if (> gradations 0)
(apply vector (cons :g (map #(let
[value (+ min-value
(*
(/
(- max-value min-value)
gradations) %)))
(range 0 (+ gradations 1)))))
gradations) %))]
(gradation cx cy gradation-inner needle-length
(deflection value min-value max-value)
value))
(range 0 (+ gradations 1))))))
[:rect {:class frame-class
: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
: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
(: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.
(defn abs
"Return the absolute value of the (numeric) argument."
[n] (max n (- n)))
(defn github-hyperlink
"given a label and a relative path, return a component which hyperlinks to the GitHub URL in a new tab"
[label src-path]

View file

@ -1,18 +1,25 @@
(ns swinging-needle-meter.views
(:require [re-frame.core :as re-frame]
[re-com.core :refer [h-box v-box box gap line label title progress-bar slider checkbox p]]
(:require [re-frame.core :as rf]
[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.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
;; ------------------------------------------------------------------------------------
(defn swinging-needle-demo
[]
(let [value (reagent/atom 75)
setpoint (reagent/atom 75)]
(let [unit @(rf/subscribe [:unit])
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
[]
[v-box
@ -54,37 +61,99 @@
[v-box
:gap "20px"
:children [[swinging-needle-meter
:model value
:setpoint setpoint
:unit "Mw"
;; :min-value 20
;; :warn-value 35
;; :max-value 40
;; :max-value (aget js/Math "PI")
:model @(rf/subscribe [:old-value])
:setpoint @(rf/subscribe [:setpoint])
:unit @(rf/subscribe [:unit])
:min-value @(rf/subscribe [:min-val])
:warn-value @(rf/subscribe [:warn-val])
:max-value @(rf/subscribe [:max-val])
:tolerance 2
: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"]
[h-box
:gap "10px"
:children [[box :align :start :child [:code ":model"]]
[slider
:model value
:min 0
:model @(rf/subscribe [:value])
:min -100
:max 100
:width "200px"
:on-change #(reset! value %)]
[label :label @value]]]
:on-change #(rf/dispatch [:set-value %])]
[label :label @(rf/subscribe [:value])]]]
[h-box
:gap "10px"
:children [[box :align :start :child [:code ":setpoint"]]
[slider
:model setpoint
:min 0
:model @(rf/subscribe [:setpoint])
:min -100
:max 100
:width "200px"
:on-change #(reset! setpoint %)]
[label :label @setpoint]]]
:on-change #(rf/dispatch [:set-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]]]
]]]]]]]])))