You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by cc...@apache.org on 2018/08/30 21:43:46 UTC

[incubator-superset] branch master updated: [SIP-5] Repair and refactor CountryMap (#5721)

This is an automated email from the ASF dual-hosted git repository.

ccwilliams pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new f72cdc3  [SIP-5] Repair and refactor CountryMap (#5721)
f72cdc3 is described below

commit f72cdc38dfcc2d1bf5574a8e0204927090225720
Author: Krist Wongsuphasawat <kr...@gmail.com>
AuthorDate: Thu Aug 30 14:43:40 2018 -0700

    [SIP-5] Repair and refactor CountryMap (#5721)
    
    * Extract slice and formData
    
    * update css indent
    
    * remove no-effect call
    
    * improve text label
    
    * adjust text size
    
    * fix bound calculation
    
    * use string literal
    
    * make path constant
---
 superset/assets/src/visualizations/country_map.css |  32 ++--
 superset/assets/src/visualizations/country_map.js  | 213 +++++++++++++--------
 2 files changed, 150 insertions(+), 95 deletions(-)

diff --git a/superset/assets/src/visualizations/country_map.css b/superset/assets/src/visualizations/country_map.css
index 8a16105..7bde0c8 100644
--- a/superset/assets/src/visualizations/country_map.css
+++ b/superset/assets/src/visualizations/country_map.css
@@ -1,5 +1,5 @@
 .country_map svg {
-    background-color: #feffff;
+  background-color: #feffff;
 }
 
 .country_map {
@@ -7,30 +7,36 @@
 }
 
 .country_map .background {
-    fill: rgba(255,255,255,0);
-    pointer-events: all;
+  fill: rgba(255,255,255,0);
+  pointer-events: all;
 }
 
 .country_map .map-layer {
-    fill: #fff;
-    stroke: #aaa;
+  fill: #fff;
+  stroke: #aaa;
 }
 
 .country_map .effect-layer {
-    pointer-events: none;
+  pointer-events: none;
 }
 
-.country_map text {
-    font-weight: 300;
-    color: #333333;
+.country_map .text-layer {
+  color: #333333;
+  text-anchor: middle;
+  pointer-events: none;
+}
+
+.country_map text.result-text {
+  font-weight: 300;
+  font-size: 24px;
 }
 
 .country_map text.big-text {
-    font-size: 30px;
-    font-weight: 400;
-    color: #333333;
+  font-weight: 700;
+  font-size: 16px;
 }
 
 .country_map path.region {
-    cursor: pointer;
+  cursor: pointer;
+  stroke: #eee;
 }
diff --git a/superset/assets/src/visualizations/country_map.js b/superset/assets/src/visualizations/country_map.js
index 09d325d..92c799b 100644
--- a/superset/assets/src/visualizations/country_map.js
+++ b/superset/assets/src/visualizations/country_map.js
@@ -1,83 +1,112 @@
 import d3 from 'd3';
-import './country_map.css';
+import PropTypes from 'prop-types';
 import { colorScalerFactory } from '../modules/colors';
+import './country_map.css';
 
-
-function countryMapChart(slice, payload) {
-  // CONSTANTS
-  const fd = payload.form_data;
-  let path;
-  let g;
-  let bigText;
-  let resultText;
-  const container = slice.container;
-  const data = payload.data;
-  const format = d3.format(fd.number_format);
-
-  const colorScaler = colorScalerFactory(fd.linear_color_scheme, data, v => v.metric);
+const propTypes = {
+  data: PropTypes.arrayOf(PropTypes.shape({
+    country_id: PropTypes.string,
+    metric: PropTypes.number,
+  })),
+  width: PropTypes.number,
+  height: PropTypes.number,
+  country: PropTypes.string,
+  linearColorScheme: PropTypes.string,
+  mapBaseUrl: PropTypes.string,
+  numberFormat: PropTypes.string,
+};
+
+const maps = {};
+
+function CountryMap(element, props) {
+  PropTypes.checkPropTypes(propTypes, props, 'prop', 'CountryMap');
+
+  const {
+    data,
+    width,
+    height,
+    country,
+    linearColorScheme,
+    mapBaseUrl = '/static/assets/src/visualizations/countries',
+    numberFormat,
+  } = props;
+
+  const container = element;
+  const format = d3.format(numberFormat);
+  const colorScaler = colorScalerFactory(linearColorScheme, data, v => v.metric);
   const colorMap = {};
   data.forEach((d) => {
     colorMap[d.country_id] = colorScaler(d.metric);
   });
   const colorFn = d => colorMap[d.properties.ISO] || 'none';
 
-  let centered;
-  path = d3.geo.path();
-  d3.select(slice.selector).selectAll('*').remove();
-  const div = d3.select(slice.selector)
-    .append('svg:svg')
-    .attr('width', slice.width())
-    .attr('height', slice.height())
+  const path = d3.geo.path();
+  const div = d3.select(container);
+  div.selectAll('*').remove();
+  container.style.height = `${height}px`;
+  container.style.width = `${width}px`;
+  const svg = div.append('svg:svg')
+    .attr('width', width)
+    .attr('height', height)
     .attr('preserveAspectRatio', 'xMidYMid meet');
+  const backgroundRect = svg.append('rect')
+    .attr('class', 'background')
+    .attr('width', width)
+    .attr('height', height);
+  const g = svg.append('g');
+  const mapLayer = g.append('g')
+    .classed('map-layer', true);
+  const textLayer = g.append('g')
+    .classed('text-layer', true)
+    .attr('transform', `translate(${width / 2}, 45)`);
+  const bigText = textLayer.append('text')
+    .classed('big-text', true);
+  const resultText = textLayer.append('text')
+    .classed('result-text', true)
+    .attr('dy', '1em');
 
-  container.css('height', slice.height());
-  container.css('width', slice.width());
+  let centered;
 
   const clicked = function (d) {
+    const hasCenter = d && centered !== d;
     let x;
     let y;
     let k;
-    let bigTextX;
-    let bigTextY;
-    let bigTextSize;
-    let resultTextX;
-    let resultTextY;
+    const halfWidth = width / 2;
+    const halfHeight = height / 2;
 
-    if (d && centered !== d) {
+    if (hasCenter) {
       const centroid = path.centroid(d);
       x = centroid[0];
       y = centroid[1];
-      bigTextX = centroid[0];
-      bigTextY = centroid[1] - 40;
-      resultTextX = centroid[0];
-      resultTextY = centroid[1] - 40;
-      bigTextSize = '6px';
       k = 4;
       centered = d;
     } else {
-      x = slice.width() / 2;
-      y = slice.height() / 2;
-      bigTextX = 0;
-      bigTextY = 0;
-      resultTextX = 0;
-      resultTextY = 0;
-      bigTextSize = '30px';
+      x = halfWidth;
+      y = halfHeight;
       k = 1;
       centered = null;
     }
 
     g.transition()
       .duration(750)
-      .attr('transform', 'translate(' + slice.width() / 2 + ',' + slice.height() / 2 + ')scale(' + k + ')translate(' + -x + ',' + -y + ')');
+      .attr('transform', `translate(${halfWidth},${halfHeight})scale(${k})translate(${-x},${-y})`);
+    textLayer
+        .style('opacity', 0)
+        .attr('transform', `translate(0,0)translate(${x},${hasCenter ? (y - 5) : 45})`)
+      .transition()
+        .duration(750)
+        .style('opacity', 1);
     bigText.transition()
       .duration(750)
-      .attr('transform', 'translate(0,0)translate(' + bigTextX + ',' + bigTextY + ')')
-      .style('font-size', bigTextSize);
+      .style('font-size', hasCenter ? 6 : 16);
     resultText.transition()
       .duration(750)
-      .attr('transform', 'translate(0,0)translate(' + resultTextX + ',' + resultTextY + ')');
+      .style('font-size', hasCenter ? 16 : 24);
   };
 
+  backgroundRect.on('click', clicked);
+
   const selectAndDisplayNameOfRegion = function (feature) {
     let name = '';
     if (feature && feature.properties) {
@@ -114,44 +143,29 @@ function countryMapChart(slice, payload) {
     resultText.text('');
   };
 
-  div.append('rect')
-    .attr('class', 'background')
-    .attr('width', slice.width())
-    .attr('height', slice.height())
-    .on('click', clicked);
-
-  g = div.append('g');
-  const mapLayer = g.append('g')
-    .classed('map-layer', true);
-  bigText = g.append('text')
-    .classed('big-text', true)
-    .attr('x', 20)
-    .attr('y', 45);
-  resultText = g.append('text')
-    .classed('result-text', true)
-    .attr('x', 20)
-    .attr('y', 60);
-
-  const url = `/static/assets/src/visualizations/countries/${fd.select_country.toLowerCase()}.geojson`;
-  d3.json(url, function (error, mapData) {
+  function drawMap(mapData) {
     const features = mapData.features;
     const center = d3.geo.centroid(mapData);
-    let scale = 150;
-    let offset = [slice.width() / 2, slice.height() / 2];
-    let projection = d3.geo.mercator().scale(scale).center(center)
-      .translate(offset);
-
-    path = path.projection(projection);
-
+    const scale = 100;
+    const projection = d3.geo.mercator()
+      .scale(scale)
+      .center(center)
+      .translate([width / 2, height / 2]);
+    path.projection(projection);
+
+    // Compute scale that fits container.
     const bounds = path.bounds(mapData);
-    const hscale = scale * slice.width() / (bounds[1][0] - bounds[0][0]);
-    const vscale = scale * slice.height() / (bounds[1][1] - bounds[0][1]);
-    scale = (hscale < vscale) ? hscale : vscale;
-    const offsetWidth = slice.width() - (bounds[0][0] + bounds[1][0]) / 2;
-    const offsetHeigth = slice.height() - (bounds[0][1] + bounds[1][1]) / 2;
-    offset = [offsetWidth, offsetHeigth];
-    projection = d3.geo.mercator().center(center).scale(scale).translate(offset);
-    path = path.projection(projection);
+    const hscale = scale * width / (bounds[1][0] - bounds[0][0]);
+    const vscale = scale * height / (bounds[1][1] - bounds[0][1]);
+    const newScale = (hscale < vscale) ? hscale : vscale;
+
+    // Compute bounds and offset using the updated scale.
+    projection.scale(newScale);
+    const newBounds = path.bounds(mapData);
+    projection.translate([
+      width - (newBounds[0][0] + newBounds[1][0]) / 2,
+      height - (newBounds[0][1] + newBounds[1][1]) / 2,
+    ]);
 
     // Draw each province as a path
     mapLayer.selectAll('path')
@@ -164,8 +178,43 @@ function countryMapChart(slice, payload) {
       .on('mouseenter', mouseenter)
       .on('mouseout', mouseout)
       .on('click', clicked);
+  }
+
+  const countryKey = country.toLowerCase();
+  const map = maps[countryKey];
+  if (map) {
+    drawMap(map);
+  } else {
+    const url = `${mapBaseUrl}/${countryKey}.geojson`;
+    d3.json(url, function (error, mapData) {
+      if (!error) {
+        maps[countryKey] = mapData;
+        drawMap(mapData);
+      }
+    });
+  }
+
+}
+
+CountryMap.propTypes = propTypes;
+
+function adaptor(slice, payload) {
+  const { selector, formData } = slice;
+  const {
+    linear_color_scheme: linearColorScheme,
+    number_format: numberFormat,
+    select_country: country,
+  } = formData;
+  const element = document.querySelector(selector);
+
+  return CountryMap(element, {
+    data: payload.data,
+    width: slice.width(),
+    height: slice.height(),
+    country,
+    linearColorScheme,
+    numberFormat,
   });
-  container.show();
 }
 
-module.exports = countryMapChart;
+export default adaptor;