Skip to content
This repository has been archived by the owner on Nov 3, 2023. It is now read-only.

Commit

Permalink
[SIP-5] Repair and refactor CountryMap (apache#5721)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kristw authored and williaster committed Aug 30, 2018
1 parent 9f2b502 commit f72cdc3
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 95 deletions.
32 changes: 19 additions & 13 deletions superset/assets/src/visualizations/country_map.css
Original file line number Diff line number Diff line change
@@ -1,36 +1,42 @@
.country_map svg {
background-color: #feffff;
background-color: #feffff;
}

.country_map {
position: relative;
}

.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;
}
213 changes: 131 additions & 82 deletions superset/assets/src/visualizations/country_map.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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')
Expand All @@ -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;

0 comments on commit f72cdc3

Please sign in to comment.