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;