diff --git a/projects/behave/src/cljs/behave/components/results/graphs.cljs b/projects/behave/src/cljs/behave/components/results/graphs.cljs index 61dff3d2e..4f98ff878 100644 --- a/projects/behave/src/cljs/behave/components/results/graphs.cljs +++ b/projects/behave/src/cljs/behave/components/results/graphs.cljs @@ -7,18 +7,19 @@ graph-settings @(subscribe [:worksheet/graph-settings ws-uuid])] (when (and graph-enabled? graph-settings) (let [*output-uuids (subscribe [:worksheet/graphed-output-uuids ws-uuid]) + y-axis-limits @(subscribe [:worksheet/graph-settings-y-axis-limits ws-uuid]) graph-data (->> cell-data (group-by first) (reduce (fn [acc [_row-id cell-data]] (conj acc (->> (reduce (fn [acc [_row-id col-uuid _repeat-id value]] - (let [fmt-fn (-> @(subscribe [:worksheet/result-table-formatters [col-uuid]]) - (get col-uuid identity))] - (assoc acc - @(subscribe [:wizard/gv-uuid->resolve-result-variable-name col-uuid]) - (fmt-fn value)))) - {} - cell-data) + (let [fmt-fn (-> @(subscribe [:worksheet/result-table-formatters [col-uuid]]) + (get col-uuid identity))] + (assoc acc + @(subscribe [:wizard/gv-uuid->resolve-result-variable-name col-uuid]) + (fmt-fn value)))) + {} + cell-data) (remove (fn [[_ value]] (= value -1))) (into {})))) [])) @@ -29,11 +30,9 @@ [:div.wizard-graph__header "Graphs"] (for [output-uuid @*output-uuids :when (not @(subscribe [:wizard/discrete-group-variable? output-uuid])) - :let [y-axis-limit (->> (:graph-settings/y-axis-limits graph-settings) - (filter #(= output-uuid (:y-axis-limit/group-variable-uuid %))) - (first)) - y-min (:y-axis-limit/min y-axis-limit) - y-max (:y-axis-limit/max y-axis-limit)]] + :let [[_ y-min y-max] (->> y-axis-limits + (filter #(= output-uuid (first %))) + (first))]] [:div.wizard-results__graph [:div.wizard-graph__output-header @(subscribe [:wizard/gv-uuid->resolve-result-variable-name output-uuid])] diff --git a/projects/behave/src/cljs/behave/wizard/events.cljs b/projects/behave/src/cljs/behave/wizard/events.cljs index e17179200..119721c01 100644 --- a/projects/behave/src/cljs/behave/wizard/events.cljs +++ b/projects/behave/src/cljs/behave/wizard/events.cljs @@ -1,20 +1,20 @@ (ns behave.wizard.events (:require [behave-routing.main :refer [routes current-route-order]] [behave.lib.units :refer [convert]] - [browser-utils.core :refer [scroll-top!]] [behave.solver.core :refer [solve-worksheet]] - [behave.vms.store :as vms] [behave.store :as s] + [behave.vms.store :as vms] [bidi.bidi :refer [path-for]] - [goog.string :as gstring] + [browser-utils.core :refer [scroll-top!]] [clojure.string :as str] [clojure.walk :refer [postwalk]] [datascript.core :as d] - [re-frame.core :as rf] + [day8.re-frame.async-flow-fx] + [goog.string :as gstring] [number-utils.interface :refer [is-numeric? parse-float]] + [re-frame.core :as rf] [string-utils.interface :refer [->str]] - [vimsical.re-frame.cofx.inject :as inject] - [day8.re-frame.async-flow-fx])) + [vimsical.re-frame.cofx.inject :as inject])) ;;; Helpers @@ -103,26 +103,26 @@ :results-page :settings)] {:fx [[:dispatch [:navigate path]] [:dispatch [:worksheet/update-all-table-filters-from-results ws-uuid]] - [:dispatch [:worksheet/update-all-y-axis-limits-from-results ws-uuid]] + [:dispatch [:worksheet/update-all-axis-limits-from-results ws-uuid]] [:dispatch [:worksheet/set-default-graph-settings ws-uuid]] [:dispatch [:state/set :worksheet-computing? false]]]}))) (defn- solve-flow [params] {:first-dispatch [:wizard/before-solve params] - :rules [{:when :seen-all-of? - :events [:worksheet/remove-unused-inputs - :worksheet/proccess-conditonally-set-output-group-variables - :worksheet/process-search-table-output-group-variables - :worksheet/proccess-conditonally-set-input-group-variables - :worksheet/delete-existing-diagrams - :worksheet/delete-existing-result-table] - :dispatch [:wizard/solve params]} - {:when :seen? - :events :wizard/solve - :dispatch [:wizard/after-solve params]} - {:when :seen? - :events :wizard/after-solve - :halt? true}]}) + :rules [{:when :seen-all-of? + :events [:worksheet/remove-unused-inputs + :worksheet/proccess-conditonally-set-output-group-variables + :worksheet/process-search-table-output-group-variables + :worksheet/proccess-conditonally-set-input-group-variables + :worksheet/delete-existing-diagrams + :worksheet/delete-existing-result-table] + :dispatch [:wizard/solve params]} + {:when :seen? + :events :wizard/solve + :dispatch [:wizard/after-solve params]} + {:when :seen? + :events :wizard/after-solve + :halt? true}]}) (rf/reg-event-fx :wizard/run-solve @@ -380,8 +380,8 @@ (rf/reg-event-fx :wizard/standard-navigate-io-tab (fn [_ [_ ws-uuid io]] - {:fx [[:dispatch [:navigate (path-for routes :ws/wizard-standard - {:ws-uuid ws-uuid - :workflow :standard - :io io})]]] + {:fx [[:dispatch [:navigate (path-for routes :ws/wizard-standard + {:ws-uuid ws-uuid + :workflow :standard + :io io})]]] :browser/scroll-top {}})) diff --git a/projects/behave/src/cljs/behave/worksheet/events.cljs b/projects/behave/src/cljs/behave/worksheet/events.cljs index 84c59d857..60d904bae 100644 --- a/projects/behave/src/cljs/behave/worksheet/events.cljs +++ b/projects/behave/src/cljs/behave/worksheet/events.cljs @@ -4,6 +4,7 @@ [behave.logger :refer [log]] [behave.solver.core :refer [solve-worksheet]] [behave.wizard.subs :refer [all-conditionals-pass?]] + [clojure.math :as math] [clojure.string :as str] [datascript.core :as d] [number-utils.core :refer [to-precision]] @@ -72,6 +73,23 @@ :input-group/group-uuid group-uuid :input-group/repeat-id repeat-id}) +(defn ^:private nice-step-size + "Computes a nice axis step size for a given y-max, targeting ~8 tick intervals. + Mirrors the D3/Vega-Lite tick interval algorithm." + [y-max] + (if (zero? y-max) + 1 + (let [n-ticks 8 + raw-step (/ y-max n-ticks) + magnitude (Math/pow 10 (Math/floor (/ (Math/log raw-step) (Math/log 10)))) + normalized (/ raw-step magnitude)] + (* magnitude + (cond + (< normalized 1.5) 1 + (< normalized 3) 2 + (< normalized 7) 5 + :else 10))))) + ;;; Events (rf/reg-fx :ws/import-worksheet import-worksheet) @@ -414,26 +432,24 @@ (when (pos? (count multi-value-input-uuids)) (let [graph-setting-id (get-in worksheet [:worksheet/graph-settings :db/id]) gv-uuids (sort-by #(deref (rf/subscribe [:wizard/discrete-group-variable? %])) multi-value-input-uuids)] - (cond-> {:transact [; First clear any existing graph settings. - [:db/retract graph-setting-id :graph-settings/x-axis-group-variable-uuid] - [:db/retract graph-setting-id :graph-settings/y-axis-group-variable-uuid] - [:db/retract graph-setting-id :graph-settings/z-axis-group-variable-uuid] - ;; Then default any multi valued variables starting from the lowest to highest dimensions x->z. - (cond-> {:db/id graph-setting-id} + {:transact [; First clear any existing graph settings. + [:db/retract graph-setting-id :graph-settings/x-axis-group-variable-uuid] + [:db/retract graph-setting-id :graph-settings/y-axis-group-variable-uuid] + [:db/retract graph-setting-id :graph-settings/z-axis-group-variable-uuid] + ;; Then default any multi valued variables starting from the lowest to highest dimensions x->z. + (cond-> {:db/id graph-setting-id} - ;; sets default x-axis selection if available - (first multi-value-input-uuids) - (assoc :graph-settings/x-axis-group-variable-uuid (first gv-uuids)) + ;; sets default x-axis selection if available + (first multi-value-input-uuids) + (assoc :graph-settings/x-axis-group-variable-uuid (first gv-uuids)) - ;; sets default z-axis selection if available - (second multi-value-input-uuids) - (assoc :graph-settings/z-axis-group-variable-uuid (second gv-uuids)) + ;; sets default z-axis selection if available + (second multi-value-input-uuids) + (assoc :graph-settings/z-axis-group-variable-uuid (second gv-uuids)) - ;; sets default z2-axis selection if available - (nth multi-value-input-uuids 2 nil) - (assoc :graph-settings/z2-axis-group-variable-uuid (nth gv-uuids 2)))]} - (first gv-uuids) - (assoc :fx [[:dispatch [:worksheet/upsert-x-axis-limit ws-uuid (first gv-uuids)]]])))))) + ;; sets default z2-axis selection if available + (nth multi-value-input-uuids 2 nil) + (assoc :graph-settings/z2-axis-group-variable-uuid (nth gv-uuids 2)))]})))) (rp/reg-event-fx :worksheet/toggle-graph-settings @@ -463,31 +479,89 @@ group-var-uuid))] {:transact [(assoc {:db/id y} attr value)]}))) +(rp/reg-event-fx + :worksheet/update-all-axis-limits-from-results + (fn [_ [_ ws-uuid]] + {:fx [[:dispatch [:worksheet/update-all-y-axis-limits-from-results ws-uuid]] + [:dispatch [:worksheet/update-all-x-axis-limits-from-results ws-uuid]]]})) + (rp/reg-event-fx :worksheet/update-all-y-axis-limits-from-results - [(rf/inject-cofx ::inject/sub (fn [[_ ws-uuid]] [:worksheet/output-uuid->result-min-values ws-uuid])) + [(rp/inject-cofx :ds) + (rf/inject-cofx ::inject/sub (fn [[_ ws-uuid]] [:worksheet/output-uuid->result-min-values ws-uuid])) (rf/inject-cofx ::inject/sub (fn [[_ ws-uuid]] [:worksheet/output-uuid->result-max-values ws-uuid]))] - (fn [{output-uuid->result-min-values :worksheet/output-uuid->result-min-values + (fn [{ds :ds + output-uuid->result-min-values :worksheet/output-uuid->result-min-values output-uuid->result-max-values :worksheet/output-uuid->result-max-values} [_ ws-uuid]] - (let [gv-uuids (keys output-uuid->result-min-values)] - {:fx (reduce (fn [acc gv-uuid] - (let [max-val (get output-uuid->result-max-values gv-uuid)] - (-> acc - (conj [:dispatch [:worksheet/update-y-axis-limit-attr - ws-uuid - gv-uuid - :y-axis-limit/min - 0]]) - (conj [:dispatch [:worksheet/update-y-axis-limit-attr - ws-uuid - gv-uuid - :y-axis-limit/max - (if (< max-val 1) - (to-precision max-val 1) - (.ceil js/Math max-val))]])))) - [] - gv-uuids)}))) + (let [gv-uuids (keys output-uuid->result-min-values) + graph-settings (d/q '[:find ?g . + :in $ ?ws-uuid + :where + [?w :worksheet/uuid ?ws-uuid] + [?w :worksheet/graph-settings ?g]] + ds ws-uuid) + existing-y-eids (d/q '[:find [?y ...] + :in $ ?ws-uuid + :where + [?w :worksheet/uuid ?ws-uuid] + [?w :worksheet/graph-settings ?g] + [?g :graph-settings/y-axis-limits ?y]] + ds ws-uuid) + retract-txs (mapv (fn [eid] [:db.fn/retractEntity eid]) existing-y-eids) + new-y-limits (mapv (fn [gv-uuid] + (let [max-val (get output-uuid->result-max-values gv-uuid) + y-max-raw (if (< max-val 1) + (to-precision max-val 1) + (math/round max-val)) + step (nice-step-size y-max-raw) + y-max (+ y-max-raw step)] + {:y-axis-limit/group-variable-uuid gv-uuid + :y-axis-limit/min 0 + :y-axis-limit/max y-max})) + gv-uuids)] + {:transact (conj retract-txs + {:db/id graph-settings + :graph-settings/y-axis-limits new-y-limits})}))) + +(rp/reg-event-fx + :worksheet/update-all-x-axis-limits-from-results + [(rp/inject-cofx :ds) + (rf/inject-cofx ::inject/sub (fn [[_ ws-uuid]] [:worksheet/multi-value-input-uuid+value ws-uuid])) + (rf/inject-cofx ::inject/sub (fn [[_ ws-uuid]] [:worksheet/multi-value-input-uuids ws-uuid]))] + (fn [{ds :ds + multi-value-input-uuid+value :worksheet/multi-value-input-uuid+value + multi-value-input-uuids :worksheet/multi-value-input-uuids} + [_ ws-uuid]] + (let [graph-settings (d/q '[:find ?g . + :in $ ?ws-uuid + :where + [?w :worksheet/uuid ?ws-uuid] + [?w :worksheet/graph-settings ?g]] + ds ws-uuid) + existing-x-eids (d/q '[:find [?x ...] + :in $ ?ws-uuid + :where + [?w :worksheet/uuid ?ws-uuid] + [?w :worksheet/graph-settings ?g] + [?g :graph-settings/x-axis-limits ?x]] + ds ws-uuid) + retract-txs (mapv (fn [eid] [:db.fn/retractEntity eid]) existing-x-eids) + x-gv-uuid (first (sort-by #(deref (rf/subscribe [:wizard/discrete-group-variable? %])) + multi-value-input-uuids)) + x-values-str (second (first (filter #(= x-gv-uuid (first %)) multi-value-input-uuid+value))) + x-max-raw (when x-values-str (apply max (map js/parseFloat (str/split x-values-str ",")))) + x-max (when x-max-raw + (let [rounded (if (< x-max-raw 1) + (to-precision x-max-raw 1) + (math/round x-max-raw))] + (+ rounded (nice-step-size rounded))))] + (when x-gv-uuid + {:transact (conj retract-txs + {:db/id graph-settings + :graph-settings/x-axis-limits {:x-axis-limit/group-variable-uuid x-gv-uuid + :x-axis-limit/min 0 + :x-axis-limit/max x-max}})})))) (rp/reg-event-fx :worksheet/update-x-axis-limit-attr