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}}))))