mirror of https://github.com/apache/superset.git
[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:
parent
9f2b502eb6
commit
f72cdc38df
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue