001  (ns org.clojars.punit-naik.core
002    (:require [org.clojars.punit-naik.canvas :refer [add-mark set-backgroud set-config set-height set-width]]
003              [org.clojars.punit-naik.chart-actions :refer [add-tooltip increase-hover-area select-on-click zoom-on-scroll]]
004              [org.clojars.punit-naik.data :refer [add-dataset tick-count]]
005              [org.clojars.punit-naik.data-transformations :refer [apply-filter]]
006              [org.clojars.punit-naik.draw :refer [add-axes add-dashes add-opacity add-rule-for-line custom-stack-colours stack]]))
007  
008  (defn init
009    "Initialises the Vega-lite spec with default values and returns the same"
010    []
011    (-> {}
012        (set-height 600)
013        (set-width 600)
014        (set-backgroud "transparent")
015        (set-config {:view {:stroke "transparent"}
016                     :mark {:cursor "pointer"}})))
017  
018  (defn axis-data
019    [data axis-fld-name]
020    (map axis-fld-name data))
021  
022  (defn mark-config
023    [{:keys [chart-type chart-interpolate point?]
024      :or {chart-type "line"
025           chart-interpolate "monotone"}}]
026    (cond-> {:type chart-type
027             :invalid "filter"}
028      (= chart-type "moving-avg") (assoc :type "line")
029      (or (= chart-type "line")
030          (= chart-type "moving-avg")) (assoc :interpolate chart-interpolate)
031      (and (or (= chart-type "line")
032               (= chart-type "moving-avg"))
033           point?) (assoc :point {:filled true :size 60})))
034  
035  (defmulti axis-config
036    (fn [axis _ _ _ _] axis))
037  
038  (defmethod axis-config :x
039    [_
040     data
041     {:keys [x-fld-name x-fld-type x-fld-title
042             x-label-angle x-title-font-size
043             x-label-font-size x-label-overlap
044             x-grid x-tick-count]
045      :or {x-fld-name "x"
046           x-fld-type "temporal"
047           x-fld-title "Time"
048           x-label-angle 0
049           x-title-font-size 14
050           x-label-font-size 12
051           x-label-overlap false
052           x-grid false
053           x-tick-count (tick-count data)}}
054     {:keys [y-fld-type]
055      :or {y-fld-type "quantitative"}}
056     {:keys [chart-type bucket bucket-mapper]
057      :or {chart-type "line"
058           bucket "1d"
059           bucket-mapper {"1d" "datemonthyear"
060                          "1w" "datemonthyear"
061                          "1M" "monthyear"
062                          "1q" "datemonthyear"
063                          "1y" "year"}}}]
064    (let [x-fld-temporal? (= x-fld-type "temporal")
065          y-fld-temporal? (= y-fld-type "temporal")
066          time-unit (if (= chart-type "moving-avg")
067                      "datemonthyear"
068                      (get bucket-mapper bucket))]
069      {:x-fld-name x-fld-name :x-fld-type x-fld-type
070       :x-fld-opts (cond-> {:title x-fld-title
071                            :axis (merge {:labelAngle x-label-angle
072                                          :labelOverlap x-label-overlap
073                                          :grid x-grid
074                                          :titleFontSize x-title-font-size
075                                          :labelFontSize x-label-font-size
076                                          :tickCount x-tick-count}
077                                         (when (and (not= x-fld-type "temporal")
078                                                    (= chart-type "bar"))
079                                           {:tickOffset {:expr (str "width/" (* -2.25 x-tick-count))}}))}
080                     x-fld-temporal? (assoc :timeUnit time-unit)
081                     x-fld-temporal? (assoc-in [:axis :labelAngle] -25)
082                     x-fld-temporal? (assoc-in [:axis :values] (->> (axis-data data (keyword x-fld-name))
083                                                                    distinct
084                                                                    sort))
085                     y-fld-temporal? (assoc :impute {:value 0}))}))
086  
087  (defmethod axis-config :y
088    [_
089     data
090     {:keys [x-fld-type]
091      :or {x-fld-type "temporal"}}
092     {:keys [y-fld-name y-fld-type y-fld-title
093             y-label-angle y-title-font-size
094             y-label-font-size y-label-overlap
095             y-grid y-tick-count]
096      :or {y-fld-name "y"
097           y-fld-type "quantitative"
098           y-fld-title "Count"
099           y-label-angle 0
100           y-title-font-size 14
101           y-label-font-size 12
102           y-label-overlap false
103           y-grid false
104           y-tick-count (tick-count data)}}
105     {:keys [chart-type bucket bucket-mapper]
106      :or {chart-type "line"
107           bucket "1d"
108           bucket-mapper {"1d" "datemonthyear"
109                          "1w" "datemonthyear"
110                          "1M" "monthyear"
111                          "1q" "datemonthyear"
112                          "1y" "year"}}}]
113    (let [x-fld-temporal? (= x-fld-type "temporal")
114          y-fld-temporal? (= y-fld-type "temporal")
115          time-unit (if (= chart-type "moving-avg")
116                      "datemonthyear"
117                      (get bucket-mapper bucket))]
118      {:y-fld-name y-fld-name :y-fld-type y-fld-type
119       :y-fld-opts (cond-> {:title y-fld-title
120                            :axis {:labelAngle y-label-angle
121                                   :labelOverlap y-label-overlap
122                                   :grid y-grid
123                                   :titleFontSize y-title-font-size
124                                   :labelFontSize y-label-font-size
125                                   :tickCount y-tick-count}}
126                     y-fld-temporal? (assoc :timeUnit time-unit)
127                     y-fld-temporal? (assoc-in [:axis :labelAngle] -25)
128                     y-fld-temporal? (assoc-in [:axis :values] (->> (axis-data data (keyword y-fld-name))
129                                                                    distinct
130                                                                    sort))
131                     x-fld-temporal? (assoc :impute {:value 0}))}))
132  
133  (defn gen-chart-spec
134    [id data
135     {:keys [x-fld-name]
136      :or {x-fld-name "x"}
137      :as x-fld-info}
138     {:keys [y-fld-name]
139      :or {y-fld-name "y"}
140      :as y-fld-info}
141     {:keys [chart-type chart-interpolate tooltip?
142             dash-fld-name opacity-fld-name
143             increase-hover-area? zoom-on-scroll?
144             dashed-lines? add-opacity? select-on-click?
145             disable-legend?]
146      :or {chart-type "line"
147           chart-interpolate "monotone"
148           dashed-lines? false
149           add-opacity? false
150           select-on-click? false
151           dash-fld-name "stroke"
152           opacity-fld-name "opacity"
153           tooltip? true
154           disable-legend? false}
155      :as chart-info}
156     {:keys [stack-fld-name stack-fld-type stack-fld-title stack-fld-opts
157             custom-stack-colours-map]
158      :or {stack-fld-name "label"
159           stack-fld-type "nominal"
160           stack-fld-title "Labels"
161           stack-fld-opts {:title stack-fld-title :legend {:orient "top"}}}
162      :as stack-info}]
163    (let [line-chart? (or (= chart-type "moving-avg")
164                          (= chart-type "line"))
165          dashed-lines? (and dashed-lines?
166                             line-chart?)
167          add-rule-for-line? (and (> (count data) 1)
168                                  line-chart?)]
169      (cond-> (-> (init)
170                  (add-dataset data)
171                  (assoc :id id)
172                  (add-axes (merge (axis-config :x data x-fld-info y-fld-info chart-info)
173                                   (axis-config :y data x-fld-info y-fld-info chart-info)))
174                  (add-mark (mark-config chart-info))
175                  (apply-filter {:field x-fld-name :valid true})
176                  (apply-filter {:field y-fld-name :valid true}))
177        add-opacity? (add-opacity opacity-fld-name)
178        dashed-lines? (add-dashes dash-fld-name)
179        increase-hover-area? increase-hover-area
180        zoom-on-scroll? zoom-on-scroll
181        add-rule-for-line? (add-rule-for-line chart-type x-fld-name y-fld-name stack-fld-name chart-interpolate)
182  
183        (seq stack-info)
184        (stack {:stack-fld stack-fld-name :stack-fld-type stack-fld-type
185                :stack-fld-opts stack-fld-opts})
186  
187        (seq custom-stack-colours-map)
188        (custom-stack-colours custom-stack-colours-map)
189  
190        select-on-click? (select-on-click {:select-fld stack-fld-name :select-name :A})
191        tooltip? (add-tooltip x-fld-info y-fld-info stack-info)
192        (or disable-legend?
193            (and (not (seq stack-info))
194                 add-rule-for-line?)) (update :config merge {:legend {:disable true}}))))