[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
This commit is contained in:
Krist Wongsuphasawat 2018-08-30 14:43:40 -07:00 committed by Chris Williams
parent 9f2b502eb6
commit f72cdc38df
2 changed files with 148 additions and 93 deletions

View File

@ -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;
}

View File

@ -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';
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,
};
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 maps = {};
const colorScaler = colorScalerFactory(fd.linear_color_scheme, data, v => v.metric);
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);
});
container.show();
}
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);
}
});
}
}
module.exports = countryMapChart;
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,
});
}
export default adaptor;