"Zoom" transform for different geo semantic types. Entry points:
Possible transformations:
Query transformation follows rules from other
Question transformation:
All geographic zooms require both a These drills are only for 'cell' context for specific values. Geographic zooms are of the following flavors:
| (ns metabase.lib.drill-thru.zoom-in-geographic (:require [medley.core :as m] [metabase.lib.binning :as lib.binning] [metabase.lib.breakout :as lib.breakout] [metabase.lib.drill-thru.common :as lib.drill-thru.common] [metabase.lib.filter :as lib.filter] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.remove-replace :as lib.remove-replace] [metabase.lib.schema :as lib.schema] [metabase.lib.schema.binning :as lib.schema.binning] [metabase.lib.schema.drill-thru :as lib.schema.drill-thru] [metabase.lib.schema.metadata :as lib.schema.metadata] [metabase.lib.types.isa :as lib.types.isa] [metabase.lib.underlying :as lib.underlying] [metabase.lib.util :as lib.util] [metabase.util.malli :as mu])) |
(def ^:private ContextWithLatLon [:merge ::lib.schema.drill-thru/context [:map [:lat-column ::lib.schema.metadata/column] [:lon-column ::lib.schema.metadata/column] [:lat-value [:maybe number?]] [:lon-value [:maybe number?]]]]) | |
(mu/defn- context-with-lat-lon :- [:maybe ContextWithLatLon] [query :- ::lib.schema/query stage-number :- :int {:keys [row], :as context} :- ::lib.schema.drill-thru/context] (let [stage (lib.util/query-stage query stage-number) ;; First check returned columns in case we breakout by lat/lon so we maintain the binning, othwerwise check visible. [lat-column lon-column] (some (fn [columns] (when-let [lat-column (m/find-first lib.types.isa/latitude? columns)] (when-let [lon-column (m/find-first lib.types.isa/longitude? columns)] [lat-column lon-column]))) [(lib.metadata.calculation/returned-columns query stage-number stage) (lib.metadata.calculation/visible-columns query stage-number stage)])] (when (and lat-column lon-column) (letfn [(same-column? [col-x col-y] (if (:id col-x) (= (:id col-x) (:id col-y)) (= (:lib/desired-column-alias col-x) (:lib/desired-column-alias col-y)))) (column-value [column] (some (fn [row-value] (when (same-column? column (:column row-value)) (:value row-value))) row))] (assoc context :lat-column lat-column :lon-column lon-column :lat-value (column-value lat-column) :lon-value (column-value lon-column)))))) | |
available-drill-thrus | |
(mu/defn- country-state-city->binned-lat-lon-drill :- [:maybe ::lib.schema.drill-thru/drill-thru.zoom-in.geographic.country-state-city->binned-lat-lon] [{:keys [column value lat-column lon-column], :as _context} :- ContextWithLatLon lat-lon-bin-width :- ::lib.schema.binning/bin-width] (when value {:lib/type :metabase.lib.drill-thru/drill-thru :type :drill-thru/zoom-in.geographic :subtype :drill-thru.zoom-in.geographic/country-state-city->binned-lat-lon :column column :value value :latitude {:column lat-column :bin-width lat-lon-bin-width} :longitude {:column lon-column :bin-width lat-lon-bin-width}})) | |
(mu/defn- country->binned-lat-lon-drill :- [:maybe ::lib.schema.drill-thru/drill-thru.zoom-in.geographic.country-state-city->binned-lat-lon] [{:keys [column], :as context} :- ContextWithLatLon] (when (some-> column lib.types.isa/country?) (country-state-city->binned-lat-lon-drill context 10))) | |
(mu/defn- state->binned-lat-lon-drill :- [:maybe ::lib.schema.drill-thru/drill-thru.zoom-in.geographic.country-state-city->binned-lat-lon] [{:keys [column], :as context} :- ContextWithLatLon] (when (some-> column lib.types.isa/state?) (country-state-city->binned-lat-lon-drill context 1))) | |
(mu/defn- city->binned-lat-lon-drill :- [:maybe ::lib.schema.drill-thru/drill-thru.zoom-in.geographic.country-state-city->binned-lat-lon] [{:keys [column], :as context} :- ContextWithLatLon] (when (some-> column lib.types.isa/city?) (country-state-city->binned-lat-lon-drill context 0.1))) | |
(mu/defn- binned-lat-lon->binned-lat-lon-drill :- [:maybe ::lib.schema.drill-thru/drill-thru.zoom-in.geographic.binned-lat-lon->binned-lat-lon] [metadata-providerable :- ::lib.schema.metadata/metadata-providerable {:keys [lat-column lon-column lat-value lon-value], :as _context} :- ContextWithLatLon] (when (and lat-value lon-value) (when-let [{lat-bin-width :bin-width} (lib.binning/resolve-bin-width metadata-providerable lat-column lat-value)] (when-let [{lon-bin-width :bin-width} (lib.binning/resolve-bin-width metadata-providerable lon-column lon-value)] (let [[new-lat-bin-width new-lon-bin-width] (if (and (>= lat-bin-width 20) (>= lon-bin-width 20)) [10 10] [(/ lat-bin-width 10.0) (/ lon-bin-width 10.0)])] {:lib/type :metabase.lib.drill-thru/drill-thru :type :drill-thru/zoom-in.geographic :subtype :drill-thru.zoom-in.geographic/binned-lat-lon->binned-lat-lon :latitude {:column lat-column :bin-width new-lat-bin-width :min lat-value :max (+ lat-value lat-bin-width)} :longitude {:column lon-column :bin-width new-lon-bin-width :min lon-value :max (+ lon-value lon-bin-width)}}))))) | |
(mu/defn zoom-in-geographic-drill :- [:maybe ::lib.schema.drill-thru/drill-thru.zoom-in.geographic] "Return a `:drill-thru/zoom-in.geographic` drill if appropriate. See docstring for [[metabase.lib.drill-thru.zoom-in-geographic]] for more information on what circumstances this is returned in and what it means to apply this drill." [query :- ::lib.schema/query _stage-number :- :int {:keys [value], :as context} :- ::lib.schema.drill-thru/context] (when (and value (not= value :null)) (when-let [context (context-with-lat-lon query (lib.underlying/top-level-stage-number query) context)] (some (fn [f] (f context)) [country->binned-lat-lon-drill state->binned-lat-lon-drill city->binned-lat-lon-drill (partial binned-lat-lon->binned-lat-lon-drill query)])))) | |
Application | |
(mu/defn- add-or-update-binning :- ::lib.schema/query [query :- ::lib.schema/query stage-number :- :int column :- ::lib.schema.metadata/column bin-width :- pos?] (let [binning {:strategy :bin-width :bin-width bin-width}] (if-let [existing-breakout (first (lib.breakout/existing-breakouts query stage-number column))] (let [new-breakout (lib.binning/with-binning existing-breakout binning)] (lib.remove-replace/replace-clause query stage-number existing-breakout new-breakout)) (lib.breakout/breakout query stage-number (lib.binning/with-binning column binning))))) | |
(mu/defn- add-or-update-lat-lon-binning :- ::lib.schema/query [query :- ::lib.schema/query stage-number :- :int {{lat :column, lat-bin-width :bin-width} :latitude {lon :column, lon-bin-width :bin-width} :longitude} :- ::lib.schema.drill-thru/drill-thru.zoom-in.geographic] (-> query (add-or-update-binning stage-number lat lat-bin-width) (add-or-update-binning stage-number lon lon-bin-width))) | |
(mu/defn- apply-country-state-city->binned-lat-lon-drill :- ::lib.schema/query [query :- ::lib.schema/query stage-number :- :int {:keys [column value], :as drill} :- ::lib.schema.drill-thru/drill-thru.zoom-in.geographic.country-state-city->binned-lat-lon] (let [resolved-column (lib.drill-thru.common/breakout->resolved-column query stage-number column)] (-> query (lib.breakout/remove-existing-breakouts-for-column stage-number column) ;; TODO -- remove/update existing filter? (lib.filter/filter stage-number (lib.filter/= resolved-column value)) (add-or-update-lat-lon-binning stage-number drill)))) | |
(mu/defn- apply-binned-lat-lon->binned-lat-lon-drill :- ::lib.schema/query [query :- ::lib.schema/query stage-number :- :int {{lat :column, lat-min :min, lat-max :max} :latitude {lon :column, lon-min :min, lon-max :max} :longitude :as drill} :- ::lib.schema.drill-thru/drill-thru.zoom-in.geographic.binned-lat-lon->binned-lat-lon] (-> query ;; TODO -- remove/update existing filters on these columns? (lib.filter/filter stage-number (lib.filter/>= lat lat-min)) (lib.filter/filter stage-number (lib.filter/< lat lat-max)) (lib.filter/filter stage-number (lib.filter/>= lon lon-min)) (lib.filter/filter stage-number (lib.filter/< lon lon-max)) (add-or-update-lat-lon-binning stage-number drill))) | |
(mu/defmethod lib.drill-thru.common/drill-thru-method :drill-thru/zoom-in.geographic :- ::lib.schema/query [query :- ::lib.schema/query _stage-number :- :int {:keys [subtype], :as drill} :- ::lib.schema.drill-thru/drill-thru.zoom-in.geographic] (let [stage-number (lib.underlying/top-level-stage-number query)] (case subtype :drill-thru.zoom-in.geographic/country-state-city->binned-lat-lon (apply-country-state-city->binned-lat-lon-drill query stage-number drill) :drill-thru.zoom-in.geographic/binned-lat-lon->binned-lat-lon (apply-binned-lat-lon->binned-lat-lon-drill query stage-number drill)))) | |