1+ (ns clojure.core.async.flow-static
2+ (:require
3+ [clojure.java.io :as io]
4+ [hiccup2.core :as h]
5+ [clojure.data.json :as json]))
6+
7+ (defn titleize-keyword [kw]
8+ (as-> (name kw) $
9+ (clojure.string/replace $ #"-" " " )
10+ (clojure.string/split $ #"\s +" )
11+ (map clojure.string/capitalize $)
12+ (clojure.string/join " " $)))
13+
14+ (defn flow-relationships [data]
15+ (reduce (fn [res [[from-proc out-port] [to-proc in-port]]]
16+ (-> res
17+ (update-in [from-proc :from ] (fnil conj []))
18+ (update-in [to-proc :to ] (fnil conj []))
19+ (update-in [from-proc :ins ] (fnil conj []))
20+ (update-in [to-proc :outs ] (fnil conj []))
21+ (update-in [to-proc :from ] (fnil conj []) from-proc)
22+ (update-in [from-proc :to ] (fnil conj []) to-proc)
23+ (update-in [to-proc :ins ] (fnil conj []) in-port)
24+ (update-in [from-proc :outs ] (fnil conj []) out-port)))
25+ {} data))
26+
27+ (defn flow-levels [relationships]
28+ (loop [result []
29+ current-level (filter (fn [[_ v]] (empty? (:from v))) relationships)
30+ remaining (apply dissoc relationships (map first current-level))]
31+ (if (empty? current-level)
32+ result
33+ (let [next-level (select-keys remaining (mapcat (fn [[_ v]] (:to v)) current-level))]
34+ (recur (conj result (map (fn [[k v]] {k v}) current-level))
35+ next-level
36+ (apply dissoc remaining (keys next-level)))))))
37+
38+ (defn proc-card [proc]
39+ [:div.middle-section-one-container
40+ [:div.title-container [:h2.title (titleize-keyword proc)]]])
41+
42+ (defn proc-el [proc-map]
43+ (let [proc (-> proc-map keys first)
44+ ins (-> proc-map vals first :ins )
45+ outs (-> proc-map vals first :outs )]
46+ [:div.card-container {:id (name proc)}
47+ [:div.proc-card {:class " expanded" }
48+ [:div.expanded-view
49+ [:div.header-labels
50+ (for [io-id ins]
51+ [:div.header-label {:id (str proc " -" io-id)} io-id])]
52+ (proc-card proc)
53+ [:div.output-section
54+ [:div.output-container
55+ (for [io-id outs]
56+ [:div.output {:id (str proc " -" io-id)} io-id])]]]]]))
57+
58+ (defn proc-row [idx row]
59+ [:div.row-3
60+ (for [proc row]
61+ (proc-el proc))])
62+
63+ (defn chart [conns]
64+ (let [relationships (flow-relationships conns)]
65+ [:div#chart
66+ (for [[idx row] (map-indexed vector (flow-levels relationships))]
67+ (proc-row idx row))]))
68+
69+ (defn json-friendly [conn]
70+ (let [from-data (first conn)
71+ to-data (second conn)
72+ from-proc (name (first from-data))
73+ from-port (name (second from-data))
74+ to-proc (name (first to-data))
75+ to-port (name (second to-data))]
76+ {" from" {" proc" from-proc " port" from-port}
77+ " to" {" proc" to-proc " port" to-port}}))
78+
79+ (defn connections-to-json [connections]
80+ (json/write-str (mapv json-friendly connections)))
81+
82+ (defn template [{:keys [conns]}]
83+ (str
84+ (h/html
85+ [:html
86+ [:head
87+ [:title " Flow Chart" ]
88+ [:script (h/raw (slurp (io/resource " public/assets/js/vendor/leader-line.min.js" )))]
89+ [:div#flow-data {:style " display: none;" :data-connections (h/raw (connections-to-json conns))}]
90+ [:script (h/raw "
91+ window.addEventListener('load', function() {
92+ const dataEl = document.getElementById('flow-data');
93+ const connectionsJSON = dataEl.getAttribute('data-connections');
94+ const connections = JSON.parse(connectionsJSON);
95+ function drawConnections() {
96+ setTimeout(() => {
97+ connections.forEach(conn => {
98+ const outSocketId = `:${conn.from.proc}-:${conn.from.port}`;
99+ const inSocketId = `:${conn.to.proc}-:${conn.to.port}`;
100+ const outSocketEl = document.getElementById(outSocketId);
101+ const inSocketEl = document.getElementById(inSocketId);
102+ const line = new LeaderLine(
103+ outSocketEl,
104+ inSocketEl,
105+ {color: '#52606D',
106+ size: 3,
107+ startSocket: 'bottom',
108+ endSocket: 'top',
109+ path: 'grid',
110+ // hide: true,
111+ animOptions: {duration: 1000, timing: 'ease'}});
112+ // line.show('draw');
113+ });
114+ }, 500);
115+ }
116+ drawConnections();});" )]
117+ [:style "html,\nbody {\n margin: 0;\n padding: 0;\n}\n\nbody {\n height: 100vh;\n width: 100vw;\n margin-top: 50px;\n background-color: #CBD2D9;\n}\n\n.row-3 {\n display: flex;\n flex-direction: row;\n justify-content: center;\n gap: 10px;\n align-items: center;\n}\n\n.card-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n margin-bottom: 40px;\n min-width: 220px;\n}\n.card-container .proc-card {\n background: #F5F7FA;\n border-radius: 3px;\n width: 100%;\n display: inline-block;\n position: relative;\n transition: all 0.4s ease-in-out;\n will-change: height;\n margin-bottom: 55px;\n}\n.card-container .proc-card.expanded .expanded-view {\n max-height: 500px;\n opacity: 1;\n visibility: visible;\n}\n.card-container .proc-card .expanded-view {\n transition: all 0.4s ease-in-out;\n max-height: 0;\n opacity: 0;\n overflow: hidden;\n visibility: hidden;\n}\n.card-container .proc-card .expanded-view .header-labels {\n display: flex;\n justify-content: center;\n padding: 0 15px;\n}\n.card-container .proc-card .expanded-view .header-labels .header-label {\n font-size: 1.75em;\n font-weight: 500;\n color: #4a4a4a;\n text-align: center;\n width: 150px;\n}\n.card-container .proc-card .expanded-view .middle-section-one-container {\n box-sizing: border-box;\n background: #52606D;\n color: #E4E7EB;\n border-radius: 2px;\n position: relative;\n padding: 10px 0;\n width: calc(100% - 20px);\n margin: auto;\n}\n.card-container .proc-card .expanded-view .title-container {\n text-align: center;\n}\n.card-container .proc-card .expanded-view .title-container .title {\n font-size: 2.3em;\n font-weight: 600;\n margin: 0;\n color: white;\n}\n.card-container .proc-card .output-section {\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n box-sizing: border-box;\n}\n.card-container .proc-card .output-section .output-container {\n display: flex;\n flex-direction: row;\n gap: 15px;\n justify-content: center;\n align-items: center;\n width: calc(100% - 14px);\n margin: 0 7px;\n padding: 0 0;\n box-sizing: border-box;\n}\n.card-container .proc-card .output-section .output-container .output {\n flex: 1;\n min-width: 110px;\n padding: 0 8px;\n font-size: 1.75em;\n color: #4a4a4a;\n text-align: center;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n white-space: nowrap;\n}\n"]]
118+ [:body
119+ (chart conns)]])))
120+
121+ (defn graph [flow-config]
122+ (tagged-literal 'flare/html {:html (template flow-config)
123+ :title " Flow Chart" }))
0 commit comments