From 1e76d8dcb907d1248ec7c4a536fc00f9d109d1a9 Mon Sep 17 00:00:00 2001
From: Tom Robinson <tlrobinson@gmail.com>
Date: Wed, 29 Mar 2017 01:23:16 -0700
Subject: [PATCH] Filter pin map by drawing rectangle

---
 .../visualizations/components/LeafletMap.jsx  | 63 ++++++++++++++++++-
 .../visualizations/components/PinMap.jsx      | 28 +++++++--
 package.json                                  |  1 +
 yarn.lock                                     |  4 ++
 4 files changed, 90 insertions(+), 6 deletions(-)

diff --git a/frontend/src/metabase/visualizations/components/LeafletMap.jsx b/frontend/src/metabase/visualizations/components/LeafletMap.jsx
index e6e3d1bf109..64d03521f66 100644
--- a/frontend/src/metabase/visualizations/components/LeafletMap.jsx
+++ b/frontend/src/metabase/visualizations/components/LeafletMap.jsx
@@ -5,9 +5,14 @@ import MetabaseSettings from "metabase/lib/settings";
 
 import "leaflet/dist/leaflet.css";
 import L from "leaflet";
+import "leaflet-draw";
 
 import _ from "underscore";
 
+import { updateIn } from "icepick";
+import * as Query from "metabase/lib/query/query";
+import { mbqlEq } from "metabase/lib/query/util";
+
 export default class LeafletMap extends Component {
     componentDidMount() {
         try {
@@ -16,7 +21,26 @@ export default class LeafletMap extends Component {
             const map = this.map = L.map(element, {
                 scrollWheelZoom: false,
                 minZoom: 2
-            })
+            });
+
+            const drawnItems = new L.FeatureGroup();
+            map.addLayer(drawnItems);
+            const drawControl = this.drawControl = new L.Control.Draw({
+                draw: {
+                    rectangle: false,
+                    polyline: false,
+                    polygon: false,
+                    circle: false,
+                    marker: false
+                },
+                edit: {
+                    featureGroup: drawnItems,
+                    edit: false,
+                    remove: false
+                }
+            });
+            map.addControl(drawControl);
+            map.on("draw:created", this.onFilter);
 
             map.setView([0,0], 8);
 
@@ -53,6 +77,43 @@ export default class LeafletMap extends Component {
         }
     }
 
+    startFilter() {
+        this._filter = new L.Draw.Rectangle(this.map, this.drawControl.options.rectangle);
+        this._filter.enable();
+        this.props.onFiltering(true);
+    }
+    stopFilter() {
+        this._filter && this._filter.disable();
+        this.props.onFiltering(false);
+    }
+    onFilter = (e) => {
+        const bounds = e.layer.getBounds();
+
+        const { series: [{ card, data: { cols } }], settings, setCardAndRun } = this.props;
+
+        const latitudeColumn = _.findWhere(cols, { name: settings["map.latitude_column"] });
+        const longitudeColumn = _.findWhere(cols, { name: settings["map.longitude_column"] });
+
+        const filter = [
+            "inside",
+            latitudeColumn.id, longitudeColumn.id,
+            bounds.getNorth(), bounds.getWest(), bounds.getSouth(), bounds.getEast()
+        ]
+
+        setCardAndRun(updateIn(card, ["dataset_query", "query"], (query) => {
+            const index = _.findIndex(Query.getFilters(query), (filter) =>
+                mbqlEq(filter[0], "inside") && filter[1] === latitudeColumn.id && filter[2] === longitudeColumn.id
+            );
+            if (index >= 0) {
+                return Query.updateFilter(query, index, filter);
+            } else {
+                return Query.addFilter(query, filter);
+            }
+        }));
+
+        this.props.onFiltering(false);
+    }
+
     render() {
         const { className } = this.props;
         return (
diff --git a/frontend/src/metabase/visualizations/components/PinMap.jsx b/frontend/src/metabase/visualizations/components/PinMap.jsx
index ff434eefc77..0665507efe1 100644
--- a/frontend/src/metabase/visualizations/components/PinMap.jsx
+++ b/frontend/src/metabase/visualizations/components/PinMap.jsx
@@ -110,6 +110,7 @@ export default class PinMap extends Component<*, Props, State> {
                 { Map ?
                     <Map
                         {...this.props}
+                        ref={map => this._map = map}
                         className="absolute top left bottom right z1"
                         onMapCenterChange={this.onMapCenterChange}
                         onMapZoomChange={this.onMapZoomChange}
@@ -118,13 +119,30 @@ export default class PinMap extends Component<*, Props, State> {
                         zoom={zoom}
                         points={points}
                         bounds={bounds}
+                        onFiltering={(filtering) => this.setState({ filtering })}
                     />
                 : null }
-                { isEditing || !isDashboard ?
-                    <div className={cx("PinMapUpdateButton Button Button--small absolute top right m1 z2", { "PinMapUpdateButton--disabled": disableUpdateButton })} onClick={this.updateSettings}>
-                        Save as default view
-                    </div>
-                : null }
+                <div className="absolute top right m1 z2 flex flex-column">
+                    { isEditing || !isDashboard ?
+                        <div className={cx("PinMapUpdateButton Button Button--small mb1", { "PinMapUpdateButton--disabled": disableUpdateButton })} onClick={this.updateSettings}>
+                            Save as default view
+                        </div>
+                    : null }
+                    { !isDashboard &&
+                        <div
+                            className={cx("PinMapUpdateButton Button Button--small mb1")}
+                            onClick={() => {
+                                if (!this.state.filtering && this._map && this._map.startFilter) {
+                                    this._map.startFilter();
+                                } else if (this.state.filtering && this._map && this._map.stopFilter) {
+                                    this._map.stopFilter();
+                                }
+                            }}
+                        >
+                            { !this.state.filtering ? "Filter by rectange" : "Cancel filter" }
+                        </div>
+                    }
+                </div>
             </div>
         );
     }
diff --git a/package.json b/package.json
index ace8b699ed3..a5d0fc0329d 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
     "js-cookie": "^2.1.2",
     "jsrsasign": "^7.1.0",
     "leaflet": "^1.0.1",
+    "leaflet-draw": "^0.4.9",
     "moment": "2.14.1",
     "node-libs-browser": "^2.0.0",
     "normalizr": "^3.0.2",
diff --git a/yarn.lock b/yarn.lock
index 862aac64097..d017488f221 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4635,6 +4635,10 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
+leaflet-draw@^0.4.9:
+  version "0.4.9"
+  resolved "https://registry.yarnpkg.com/leaflet-draw/-/leaflet-draw-0.4.9.tgz#44105088310f47e4856d5ede37d47ecfad0cf2d5"
+
 leaflet@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.0.3.tgz#1f401b98b45c8192134c6c8d69686253805007c8"
-- 
GitLab