diff --git a/frontend/src/metabase/visualizations/components/LeafletMap.jsx b/frontend/src/metabase/visualizations/components/LeafletMap.jsx index e6e3d1bf109dfda4e0199a64e311acc6eaee701d..64d03521f66e42f91f70d080bbc87e20da956d60 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 ff434eefc77991d348e5bb278e58b8357e11a03d..0665507efe1ed10c3afce8f3e746df2ed2baf7fd 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 ace8b699ed34df85e90cb1be03d9f6741621b91b..a5d0fc0329d6e5af5a6c9f0993fa79247a491d2a 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 862aac640974ad0e985bf73a603ec46f027e7d48..d017488f2215f42f97dee87a9676a3a34ea8684c 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"