Not so much of a branch, more a whole new project

This commit is contained in:
Simon Brooke 2024-07-08 22:08:10 +01:00
parent 985c91a72e
commit 3a1ae81f08
18 changed files with 204 additions and 123 deletions

2
.gitignore vendored
View file

@ -11,3 +11,5 @@ out
*.tgz
*.zip
.lsp/
.clj-kondo/

1
.lein-failures Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -1,6 +1,6 @@
# swingometer
# radial-svg-graph
A [re-frame](https://github.com/Day8/re-frame) application designed to show votes in an election.
A [re-frame](https://github.com/Day8/re-frame) application designed to show a radial SVG graph, possibly with several rings.
## Development Mode

View file

@ -1,11 +1,17 @@
(defproject swingometer "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.229"]
[reagent "0.6.0"]
[re-frame "0.9.4"]
[re-com "2.0.0"]]
(defproject rsvggraph "0.1.0-SNAPSHOT"
:dependencies [[clojure2d "1.4.5"] ;; (mainly) for colours
[generateme/fastmath "2.4.0"]
[hiccup "2.0.0-RC3"]
[javax.xml.bind/jaxb-api "2.4.0-b180830.0359"]
[org.clojure/clojure "1.8.0"]
;; [org.clojure/clojurescript "1.9.229"]
;; [org.omcljs/om "1.0.0-beta1"]
;; [reagent "0.6.0"]
;; [re-frame "0.9.4"]
;; [re-com "2.0.0"]
]
:plugins [[lein-cljsbuild "1.1.4"]]
;; :plugins [[lein-cljsbuild "1.1.4"]]
:min-lein-version "2.5.3"
@ -15,36 +21,32 @@
:figwheel {:css-dirs ["resources/public/css"]}
:profiles
{:dev
{:dependencies [[binaryage/devtools "0.8.2"]]
;; :profiles
;; {:dev
;; {:dependencies [[binaryage/devtools "0.8.2"]]
:plugins [[lein-figwheel "0.5.9"]]
}}
;; :plugins [[lein-figwheel "0.5.9"]]
;; }}
:cljsbuild
{:builds
[{:id "dev"
:source-paths ["src/cljs"]
:figwheel {:on-jsload "swingometer.core/mount-root"}
:compiler {:main swingometer.core
:output-to "resources/public/js/compiled/app.js"
:output-dir "resources/public/js/compiled/out"
:asset-path "js/compiled/out"
:source-map-timestamp true
:preloads [devtools.preload]
:external-config {:devtools/config {:features-to-install :all}}
}}
;; :cljsbuild
;; {:builds
;; [{:id "dev"
;; :source-paths ["src/cljs"]
;; :figwheel {:on-jsload "rsvggraph.core/mount-root"}
;; :compiler {:main rsvggraph.core
;; :output-to "resources/public/js/compiled/app.js"
;; :output-dir "resources/public/js/compiled/out"
;; :asset-path "js/compiled/out"
;; :source-map-timestamp true
;; :preloads [devtools.preload]
;; :external-config {:devtools/config {:features-to-install :all}}
;; }}
{:id "min"
:source-paths ["src/cljs"]
:compiler {:main swingometer.core
:output-to "resources/public/js/compiled/app.js"
:optimizations :advanced
:closure-defines {goog.DEBUG false}
:pretty-print false}}
]}
)
;; {:id "min"
;; :source-paths ["src/cljs"]
;; :compiler {:main rsvggraph.core
;; :output-to "resources/public/js/compiled/app.js"
;; :optimizations :advanced
;; :closure-defines {goog.DEBUG false}
;; :pretty-print false}}]})
)

View file

@ -1,70 +1,73 @@
/***************************************************************************\
* *
* swinging-needle-meter.css *
* rsvggraph.css *
* *
* CSS styling for the swinging needle meter itself. *
* CSS styling for the radial svg graph itself. *
* *
\***************************************************************************/
.snm-cursor {
svg {
border: thin solid gray;
object-fit: contain;
}
.rsvggraph-cursor {
stroke:#ff8500;
stroke-width: 3%;
stroke-opacity: 0.5;
}
.snm-frame {
.rsvggraph-frame {
fill: none;
stroke-width: 5%;
stroke-linejoin: round;
stroke: #444444;
stroke: none;
}
.snm-gradation path {
.rsvggraph-gradation path {
stroke: black;
stroke-width: 1;
}
.snm-gradation text {
.rsvggraph-gradation text {
font-size: 200%;
font-weight: lighter;
}
.snm-hub {
.rsvggraph-hub {
fill: #444444;
}
.snm-meter {
.rsvggraph-graph {
height: 50%;
width: auto;
}
.snm-needle {
.rsvggraph-needle {
stroke: black;
stroke-width: 1;
}
.snm-redzone {
.rsvggraph-redzone {
fill:none;
stroke: maroon;
stroke-width: 10%;
}
.snm-scale {
.rsvggraph-scale {
fill: none;
stroke: silver;
stroke-width: 10%;
}
.snm-target .snm-frame {
.rsvggraph-target .rsvggraph-frame {
stroke: green;
}
.snm-value {
.rsvggraph-value {
font-size: 400%;
font-weight: bold;
text-align: center;
}
.snm-warning .snm-frame {
.rsvggraph-warning .rsvggraph-frame {
stroke: maroon;
}

View file

@ -5,16 +5,16 @@
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="vendor/css/material-design-iconic-font.min.css">
<link rel="stylesheet" href="css/re-com.css">
<link rel="stylesheet" href="css/swingometer.css">
<link rel="stylesheet" href="css/rsvggraph.css">
<link href="http://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic" rel="stylesheet" type="text/css">
<link href="http://fonts.googleapis.com/css?family=Roboto+Condensed:400,300" rel="stylesheet" type="text/css">
<title>Example swingometer following re-com conventions.</title>
<title>Example rsvggraph following re-com conventions.</title>
</head>
<body>
<div id="app"></div>
<script src="js/compiled/app.js"></script>
<script>swingometer.core.init();</script>
<script>rsvggraph.core.init();</script>
</body>
</html>

View file

@ -0,0 +1,40 @@
{:id "ge2024"
:label "UK General Election 2024"
:children [{:id "no-show"
:label "Did not vote"
:magnitude 18365357}
{:id "voted"
:label "Voted"
:children [{:id "reform"
:label "Reform UK Ltd."
:magnitude 4091549}
{:id "greenew"
:label "Green Party of England and Wales"
:magnitude 1939502}
{:id "pc"
:label "Plaid Cymru"
:magnitude 194811}
{:id "sf"
:label "Sinn Féin"
:magnitude 210891}
{:id "ld"
:label "Liberal Democrats"
:magnitude 3499933}
{:id "labour"
:label "Labour"
:magnitude 9712011}
{:id "apni"
:label "Alliance Party"
:magnitude 117191}
{:id "sdlp"
:label "Social Democratic and Labour Party"
:magnitude 86861}
{:id "dup"
:label "Democratic Unionist Party"
:magnitude 172058}
{:id "snp"
:label "Scottish National Party"
:magnitude 708759}
{:id "con"
:label "Conservative"
:magnitude 6814469}]}]}

View file

@ -0,0 +1 @@
(ns rsvggraph.core)

View file

@ -0,0 +1,29 @@
(ns rsvggraph.data
"Normalise data for use in generating radial graphs."
(:require [clojure2d.color :refer [gradient]]))
(def ;; ^:dynamic
*gradient*
"The gradient to use to automatically assign pleasing colours to sectors, if
no colours are defined in the data. Suitable gradients are defined
[here](https://clojure2d.github.io/clojure2d/docs/static/gradients/)."
:rainbow2)
(def children-fn
"Basic (overridable) children function; assumes `data` is a map, and returns
the value of the `:children` key within that map."
(memoize (fn [data]
(:children data))))
(def quantity-fn
"Basic (overridable) children function; assumes `data` is a map. If the value
of the `:children` key within that map is a sequence, sums the result of
mapping itself over that sequence. Otherwise, returns the value of the
`:quantity` key, if present and a number, or `1` as a final default."
(memoize (fn [data]
(let [c (children-fn data)
q (:quantity data)]
(cond (coll? c) (reduce + 0 (map quantity-fn c))
(number? q) q
:else 1)))))

View file

@ -1 +0,0 @@
(ns swingometer.core)

View file

@ -1,4 +1,4 @@
(ns swingometer.config)
(ns rsvggraph.config)
(def debug?
^boolean goog.DEBUG)

View file

@ -1,10 +1,10 @@
(ns swingometer.core
(ns rsvggraph.core
(:require [reagent.core :as reagent]
[re-frame.core :as re-frame]
[swingometer.events]
[swingometer.subs]
[swingometer.views :as views]
[swingometer.config :as config]))
[rsvggraph.events]
[rsvggraph.subs]
[rsvggraph.views :as views]
[rsvggraph.config :as config]))
(defn dev-setup []

View file

@ -1,4 +1,4 @@
(ns swingometer.db)
(ns rsvggraph.db)
(def default-db
{:name "re-frame"})

View file

@ -1,6 +1,6 @@
(ns swingometer.events
(ns rsvggraph.events
(:require [re-frame.core :as re-frame]
[swingometer.db :as db]))
[rsvggraph.db :as db]))
(re-frame/reg-event-db
:initialize-db

View file

@ -1,14 +1,13 @@
(ns swingometer.swingometer
(ns rsvggraph.rsvggraph
(:require [clojure.string :as string]
[re-com.core :refer [h-box v-box box gap line label title slider checkbox p]]
[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]))
[re-com.validate :refer [number-or-string? css-style? html-attr? validate-args-macro]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; swingometer: an experiment in animating SVG from re-frame.
;;;; rsvggraph: an experiment in animating SVG from re-frame.
;;;; Draws heavily on re-com..
;;;;
;;;; This program is free software; you can redistribute it and/or
@ -31,12 +30,12 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ------------------------------------------------------------------------------------
;; Component: swingometer
;; Component: rsvggraph
;; ------------------------------------------------------------------------------------
;;; It seems the defaults given here are just documentation; the defaults
;;; that are actually used are those given in the :or clause of the argument map.
(def swingometer-args-desc
(def rsvggraph-args-desc
[{:name :model :required true :type "map | atom"
:validate-fn map? :description "A map mapping keys to maps of the following structure: {:id :snp :name \"Scottish National Party\" :colour \"yellow\" :votes 1234}"}
{:name :width :required false :type "integer" :default "300"
@ -45,12 +44,12 @@
:validate-fn integer? :description "a CSS height"}
{:name :class :required false :type "string"
:validate-fn string? :description "CSS class names, space separated, for the top-level SVG element"}
{:name :frame-class :required false :type "string" :default "snm-frame"
{:name :frame-class :required false :type "string" :default "rsvggraph-frame"
:validate-fn string? :description "CSS class names, space separated, for the frame"}
{:name :scale-class :required false :type "string" :default "snm-scale"
{:name :scale-class :required false :type "string" :default "rsvggraph-scale"
:validate-fn string? :description "CSS class names, space separated, for the scale"}
{:name :id :required false :type "string" :default "meter"
:validate-fn string? :description "Element id for this instance of the meter"}
{:name :id :required false :type "string" :default "graph"
:validate-fn string? :description "Element id for this instance of the graph"}
{:name :gradations :reduired false :type "integer" :default 5
:validate-fn integer? :description "Number of gradations to show on the scale, not counting the point."}
{:name :style :required false :type "CSS style map"
@ -59,9 +58,9 @@
:validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
;; 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)
(def full-scale-deflection
"the full sweep of the needle from the left end of the scale to right end, in degrees."
360)
(defn deflection
@ -112,7 +111,7 @@
at `cx`, cy` starting at `min-radius` and extending to `max-radius`, with the specified
`label`."
[cx cy min-radius max-radius angle label]
[:g {:class "snm-gradation"
[:g {:class "rsvggraph-gradation"
:transform (string/join " " ["rotate(" angle cx cy ")"])}
[:path {:d (string/join
" "
@ -157,63 +156,68 @@
others (recursively-draw-segments (rest still-to-do) (cons party done) total-votes cx cy radius)
vote-share (* (/ (:votes party) total-votes) 100)]
(if (> vote-share 1)
(cons [:g [:path {:class "snm-scale"
(cons [:g [:path {:class "rsvggraph-scale"
:id (str (:id party) "-segment")
:style {:stroke (:colour party)}
:d (describe-arc cx cy radius start-angle end-angle)}]
(gradation cx cy (* radius 0.8) (* radius 1.1) start-angle
(str
(if (> vote-share 5) (name (:id party)) "")
(if (> vote-share 10) (str " " (as-label vote-share) "%"))))]
(when (> vote-share 5) (name (:id party)) "")
(when (> vote-share 10) (str " " (as-label vote-share) "%"))))]
others)
others))))
(defn swingometer
"Render an SVG swinging needle meter"
(defn rsvggraph
"Render an SVG radial graph. The idea here is there is a stack of rings,
each with zero or more segments. Each ring has an inner diameter and an
outer diameter, each of which is expressed as a number in the range 0...1,
representing a fraction of the overall dimension of the graph.
The rings are drawn in ascending order of inner diameter.
Each segment has a label and a magnitude"
[& {:keys [model width height class scale-class frame-class id style attr]
:or {width 300
height 200
scale-class "snm-scale"
frame-class "snm-frame"
id "meter"}
height 300
scale-class "rsvggraph-scale"
frame-class "rsvggraph-frame"
id "graph"}
:as args}]
{:pre [(validate-args-macro swingometer-args-desc args "swingometer")]}
{:pre [(validate-args-macro rsvggraph-args-desc args "rsvggraph")]}
(let [model (deref-or-value model)
mid-point-deflection (/ full-scale-deflection 2)
cx (/ width 2)
cy (* height 0.90)
needle-length (* height 0.75)
scale-radius (* height 0.7)
gradation-inner (* height 0.55)
gradations 5
dimension (min width height)
cx (/ dimension 2)
cy (* dimension 0.50)
scale-radius (* dimension 0.45)
total-votes (reduce + (map #(:votes %) (vals model)))]
[box
:align :start
:child [:div
(merge
{:class (str "swingometer " class)
{:class (str "rsvggraph " class)
:style (merge (flex-child-style "none")
{:width width :height height}
{:width dimension :height dimension}
style)}
attr)
[:svg {:xmlSpace "preserve"
:overflow "visible"
:viewBox (string/join " " [0 0 width height])
:width (str width "px")
:height (str height "px")
:viewBox (string/join " " [0 0 dimension dimension])
:width (str dimension "px")
:height (str dimension "px")
:y "0px"
:x "0px"
:version "1.1"
:id id
:class (str "snm-meter " class)}
:class (str "rsvggraph-graph " class)}
[:text
{:text-anchor "middle"
:x (/ width 2)
:y (/ height 2)
:width "100"
:x (/ dimension 2)
:y (/ dimension 2)
:width (/ dimension 4)
:id (str id "-total-votes")
:class "snm-value"}[:tspan (reduce + (map :votes (vals model)))]]
:class "rsvggraph-value"}[:tspan (reduce + (map :votes (vals model)))]]
[:path {:class scale-class
:id (str id "-scale")
:d (describe-arc cx cy scale-radius

View file

@ -1,4 +1,4 @@
(ns swingometer.subs
(ns rsvggraph.subs
(:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :as re-frame]))

View file

@ -1,4 +1,4 @@
(ns swingometer.utils
(ns rsvggraph.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;

View file

@ -1,12 +1,12 @@
(ns swingometer.views
(ns rsvggraph.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 single-dropdown]]
[re-com.util :refer [deref-or-value]]
[swingometer.swingometer :refer [swingometer swingometer-args-desc]]
[swingometer.utils :refer [panel-title title2 args-table github-hyperlink status-text]]
[rsvggraph.rsvggraph :refer [rsvggraph rsvggraph-args-desc]]
[rsvggraph.utils :refer [panel-title title2 args-table github-hyperlink status-text]]
[reagent.core :as reagent]))
(defn swingometer-demo
(defn rsvggraph-demo
[]
(let [model (reagent/atom {:snp {:id :snp :name "Scottish National Party" :colour "yellow" :votes 10}
:lab {:id :lab :name "Labour Party" :colour "red" :votes 10}
@ -19,7 +19,7 @@
[v-box
:size "auto"
:gap "10px"
:children [[panel-title "Swingometer"]
:children [[panel-title "rsvggraph"]
[h-box
:gap "100px"
:children [[v-box
@ -27,21 +27,21 @@
:width "450px"
:children [[title2 "Notes"]
[status-text "Wildly experimental"]
[p "An SVG swingometer intended to be useful in elections."]
[p "An SVG rsvggraph intended to be useful in elections."]
[title2 "Behaviour"]
[args-table swingometer-args-desc]]]
[args-table rsvggraph-args-desc]]]
[v-box
:gap "10px"
:children [[title2 "Demo"]
[v-box
:gap "20px"
:children [[swingometer
:children [[rsvggraph
:model model
:height 600
:width 1000]
:height 500
:width 500]
[title :level :level3 :label "Parameters"]
[h-box
:gap "10px"
@ -127,11 +127,11 @@
;; core holds a reference to panel, so need one level of indirection to get figwheel updates
(defn panel
[]
[swingometer-demo])
[rsvggraph-demo])
(defn main-panel []
(fn []
[v-box
:height "100%"
:children [[swingometer-demo]]]))
:children [[rsvggraph-demo]]]))