mistercrunch closed pull request #5902: [refactor] Remove dependency on 
personal fork of supercluster from mapbox visualizations
URL: https://github.com/apache/incubator-superset/pull/5902
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/UPDATING.md b/UPDATING.md
index 4b182ae0c8..eec22c2315 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -11,11 +11,13 @@ assists people when migrating to a new version.
   dashboards through an automated db migration script. We
   do recommend that you take a backup prior to this migration.
 
+* Superset 0.28 deprecates the `median` cluster label aggregator for mapbox 
visualizations. This particular aggregation is not supported on mapbox 
visualizations going forward.
+
 ## Superset 0.27.0
-* Superset 0.27 start to use nested layout for dashboard builder, which is not 
+* Superset 0.27 start to use nested layout for dashboard builder, which is not
 backward-compatible with earlier dashboard grid data. We provide migration 
script
-to automatically convert dashboard grid to nested layout data. To be safe, 
please 
-take a database backup prior to this upgrade. It's the only way people could 
go 
+to automatically convert dashboard grid to nested layout data. To be safe, 
please
+take a database backup prior to this upgrade. It's the only way people could go
 back to a previous state.
 
 
@@ -38,7 +40,7 @@ The PRs bellow have more information around the breaking 
changes:
 * [4565](https://github.com/apache/incubator-superset/pull/4565) : we've
   changed the security model a bit where in the past you would have to
   define your authentication scheme by inheriting from Flask
-  App Builder's 
+  App Builder's
   `from flask_appbuilder.security.sqla.manager import SecurityManager`,
   you now have to derive Superset's
   own derivative `superset.security.SupersetSecurityManager`. This
@@ -46,7 +48,7 @@ The PRs bellow have more information around the breaking 
changes:
   permissions to another system as needed. For all implementation, you
   simply have to import and derive `SupersetSecurityManager` in place
   of the `SecurityManager`
-* [4835](https://github.com/apache/incubator-superset/pull/4835) : 
+* [4835](https://github.com/apache/incubator-superset/pull/4835) :
   our `setup.py` now only pins versions where required, giving you
   more latitude in using versions of libraries as needed. We do now
   provide a `requirements.txt` with pinned versions if you want to run
diff --git a/superset/assets/package.json b/superset/assets/package.json
index d5571e08bb..f326ec6d6b 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -124,7 +124,7 @@
     "shortid": "^2.2.6",
     "sprintf-js": "^1.1.1",
     "srcdoc-polyfill": "^1.0.0",
-    "supercluster": 
"https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40";,
+    "supercluster": "^4.1.1",
     "underscore": "^1.8.3",
     "urijs": "^1.18.10",
     "viewport-mercator-project": "^5.0.0"
diff --git a/superset/assets/src/explore/controls.jsx 
b/superset/assets/src/explore/controls.jsx
index 9700454bd8..f6f256279d 100644
--- a/superset/assets/src/explore/controls.jsx
+++ b/superset/assets/src/explore/controls.jsx
@@ -1361,7 +1361,6 @@ export const controls = {
       'mean',
       'min',
       'max',
-      'median',
       'stdev',
       'var',
     ]),
diff --git a/superset/assets/src/visualizations/MapBox/MapBox.jsx 
b/superset/assets/src/visualizations/MapBox/MapBox.jsx
index 85cd1b1ad3..f2381588e3 100644
--- a/superset/assets/src/visualizations/MapBox/MapBox.jsx
+++ b/superset/assets/src/visualizations/MapBox/MapBox.jsx
@@ -48,10 +48,6 @@ class MapBox extends React.Component {
       height,
     }).fitBounds(bounds);
     const { latitude, longitude, zoom } = mercator;
-    // Compute the clusters based on the bounds. Again, this is only done once 
because
-    // we don't update the clusters as we pan/zoom in the current design.
-    const bbox = [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]];
-    this.clusters = this.props.clusterer.getClusters(bbox, Math.round(zoom));
 
     this.state = {
       viewport: {
@@ -80,10 +76,19 @@ class MapBox extends React.Component {
       pointRadiusUnit,
       renderWhileDragging,
       rgb,
+      hasCustomMetric,
+      bounds,
     } = this.props;
     const { viewport } = this.state;
     const isDragging = viewport.isDragging === undefined ? false :
                        viewport.isDragging;
+
+    // Compute the clusters based on the original bounds and current zoom 
level. Note when zoom/pan
+    // to an area outside of the original bounds, no additional queries are 
made to the backend to
+    // retrieve additional data.
+    const bbox = [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]];
+    const clusters = this.props.clusterer.getClusters(bbox, 
Math.round(viewport.zoom));
+
     return (
       <MapGL
         {...viewport}
@@ -98,14 +103,14 @@ class MapBox extends React.Component {
           isDragging={isDragging}
           width={width}
           height={height}
-          locations={Immutable.fromJS(this.clusters)}
+          locations={Immutable.fromJS(clusters)}
           dotRadius={pointRadius}
           pointRadiusUnit={pointRadiusUnit}
           rgb={rgb}
           globalOpacity={globalOpacity}
           compositeOperation={'screen'}
           renderWhileDragging={renderWhileDragging}
-          aggregatorName={aggregatorName}
+          aggregation={hasCustomMetric ? aggregatorName : null}
           lngLatAccessor={(location) => {
             const coordinates = location.get('geometry').get('coordinates');
             return [coordinates.get(0), coordinates.get(1)];
@@ -119,34 +124,10 @@ class MapBox extends React.Component {
 MapBox.propTypes = propTypes;
 MapBox.defaultProps = defaultProps;
 
-function createReducer(aggregatorName, customMetric) {
-  if (aggregatorName === 'sum' || !customMetric) {
-    return (a, b) => a + b;
-  } else if (aggregatorName === 'min') {
-    return Math.min;
-  } else if (aggregatorName === 'max') {
-    return Math.max;
-  }
-  return function (a, b) {
-    if (a instanceof Array) {
-      if (b instanceof Array) {
-        return a.concat(b);
-      }
-      a.push(b);
-      return a;
-    }
-    if (b instanceof Array) {
-      b.push(a);
-      return b;
-    }
-    return [a, b];
-  };
-}
-
 function mapbox(slice, payload, setControlValue) {
   const { formData, selector } = slice;
   const {
-    customMetric,
+    hasCustomMetric,
     geoJSON,
     bounds,
     mapboxApiKey,
@@ -170,18 +151,41 @@ function mapbox(slice, payload, setControlValue) {
     return;
   }
 
-  const clusterer = supercluster({
+  const opts = {
     radius: clusteringRadius,
     maxZoom: DEFAULT_MAX_ZOOM,
-    metricKey: 'metric',
-    metricReducer: createReducer(aggregatorName, customMetric),
-  });
+  };
+  if (hasCustomMetric) {
+    opts.initial = () => ({
+      sum: 0,
+      squaredSum: 0,
+      min: Infinity,
+      max: -Infinity,
+    });
+    opts.map = prop => ({
+      sum: prop.metric,
+      squaredSum: Math.pow(prop.metric, 2),
+      min: prop.metric,
+      max: prop.metric,
+    });
+    opts.reduce = (accu, prop) => {
+      // Temporarily disable param-reassignment linting to work with 
supercluster's api
+      /* eslint-disable no-param-reassign */
+      accu.sum += prop.sum;
+      accu.squaredSum += prop.squaredSum;
+      accu.min = Math.min(accu.min, prop.min);
+      accu.max = Math.max(accu.max, prop.max);
+      /* eslint-enable no-param-reassign */
+    };
+  }
+  const clusterer = supercluster(opts);
   clusterer.load(geoJSON.features);
 
   ReactDOM.render(
     <MapBox
       width={slice.width()}
       height={slice.height()}
+      hasCustomMetric={hasCustomMetric}
       aggregatorName={aggregatorName}
       clusterer={clusterer}
       globalOpacity={globalOpacity}
diff --git 
a/superset/assets/src/visualizations/MapBox/ScatterPlotGlowOverlay.jsx 
b/superset/assets/src/visualizations/MapBox/ScatterPlotGlowOverlay.jsx
index ea4e115de3..40c5861c27 100644
--- a/superset/assets/src/visualizations/MapBox/ScatterPlotGlowOverlay.jsx
+++ b/superset/assets/src/visualizations/MapBox/ScatterPlotGlowOverlay.jsx
@@ -11,6 +11,7 @@ import {
 } from '../../utils/common';
 
 const propTypes = {
+  aggregation: PropTypes.string,
   locations: PropTypes.instanceOf(Immutable.List).isRequired,
   lngLatAccessor: PropTypes.func,
   renderWhileDragging: PropTypes.bool,
@@ -35,6 +36,30 @@ const contextTypes = {
   isDragging: PropTypes.bool,
 };
 
+const computeClusterLabel = (properties, aggregation) => {
+  const count = properties.get('point_count');
+  if (!aggregation) {
+    return count;
+  }
+  if (aggregation === 'sum' || aggregation === 'min' || aggregation === 'max') 
{
+    return properties.get(aggregation);
+  }
+  const sum = properties.get('sum');
+  const mean = sum / count;
+  if (aggregation === 'mean') {
+    return Math.round(100 * mean) / 100;
+  }
+    const squaredSum = properties.get('squaredSum');
+    const variance = (squaredSum / count) - Math.pow(sum / count, 2);
+    if (aggregation === 'var') {
+      return Math.round(100 * variance) / 100;
+    } else if (aggregation === 'stdev') {
+      return Math.round(100 * Math.sqrt(variance)) / 100;
+    }
+    // fallback to point_count, this really shouldn't happen
+    return count;
+};
+
 class ScatterPlotGlowOverlay extends React.Component {
   constructor(props) {
     super(props);
@@ -90,34 +115,14 @@ class ScatterPlotGlowOverlay extends React.Component {
     const mercator = new ViewportMercator(props);
     const rgb = props.rgb;
     const clusterLabelMap = [];
-    let maxLabel = -1;
 
     props.locations.forEach(function (location, i) {
       if (location.get('properties').get('cluster')) {
-        let clusterLabel = location.get('properties').get('metric')
-          ? location.get('properties').get('metric')
-          : location.get('properties').get('point_count');
-
-        if (clusterLabel instanceof Immutable.List) {
-          clusterLabel = clusterLabel.toArray();
-          if (props.aggregatorName === 'mean') {
-            clusterLabel = d3.mean(clusterLabel);
-          } else if (props.aggregatorName === 'median') {
-            clusterLabel = d3.median(clusterLabel);
-          } else if (props.aggregatorName === 'stdev') {
-            clusterLabel = d3.deviation(clusterLabel);
-          } else {
-            clusterLabel = d3.variance(clusterLabel);
-          }
-        }
-
-        clusterLabel = isNumeric(clusterLabel)
-          ? d3.round(clusterLabel, 2)
-          : location.get('properties').get('point_count');
-        maxLabel = Math.max(clusterLabel, maxLabel);
-        clusterLabelMap[i] = clusterLabel;
+        clusterLabelMap[i] = computeClusterLabel(location.get('properties'),
+            props.aggregation);
       }
     }, this);
+    const maxLabel = Math.max(...Object.values(clusterLabelMap));
 
     ctx.save();
     ctx.scale(pixelRatio, pixelRatio);
diff --git a/superset/assets/yarn.lock b/superset/assets/yarn.lock
index e0fc1ea6f1..317de4f77e 100644
--- a/superset/assets/yarn.lock
+++ b/superset/assets/yarn.lock
@@ -7114,7 +7114,7 @@ just-extend@^3.0.0:
   version "3.0.0"
   resolved 
"https://registry.yarnpkg.com/just-extend/-/just-extend-3.0.0.tgz#cee004031eaabf6406da03a7b84e4fe9d78ef288";
 
-kdbush@^1.0.0, kdbush@^1.0.1:
+kdbush@^1.0.1:
   version "1.0.1"
   resolved 
"https://registry.yarnpkg.com/kdbush/-/kdbush-1.0.1.tgz#3cbd03e9dead9c0f6f66ccdb96450e5cecc640e0";
 
@@ -11732,18 +11732,12 @@ supercluster@^2.3.0:
   dependencies:
     kdbush "^1.0.1"
 
-supercluster@^4.0.1:
+supercluster@^4.0.1, supercluster@^4.1.1:
   version "4.1.1"
   resolved 
"https://registry.yarnpkg.com/supercluster/-/supercluster-4.1.1.tgz#cf13c3b28a3fb3db5290bfad7f524e244bd4ce78";
   dependencies:
     kdbush "^2.0.1"
 
-"supercluster@https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40":
-  version "2.1.0"
-  resolved 
"https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40#3ac2d8efc0224fb6f4332a7192b619ded4b967a6";
-  dependencies:
-    kdbush "^1.0.0"
-
 [email protected], [email protected]:
   version "3.1.2"
   resolved 
"https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5";
diff --git a/superset/viz.py b/superset/viz.py
index 77d8da551a..d0158c0ea0 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -1960,9 +1960,9 @@ def get_data(self, df):
             return None
         fd = self.form_data
         label_col = fd.get('mapbox_label')
-        custom_metric = label_col and len(label_col) >= 1
+        has_custom_metric = label_col is not None and len(label_col) > 0
         metric_col = [None] * len(df.index)
-        if custom_metric:
+        if has_custom_metric:
             if label_col[0] == fd.get('all_columns_x'):
                 metric_col = df[fd.get('all_columns_x')]
             elif label_col[0] == fd.get('all_columns_y'):
@@ -2003,7 +2003,7 @@ def get_data(self, df):
 
         return {
             'geoJSON': geo_json,
-            'customMetric': custom_metric,
+            'hasCustomMetric': has_custom_metric,
             'mapboxApiKey': config.get('MAPBOX_API_KEY'),
             'mapStyle': fd.get('mapbox_style'),
             'aggregatorName': fd.get('pandas_aggfunc'),


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to