in superset-frontend/plugins/legacy-plugin-chart-world-map/src/WorldMap.js [53:279]
function WorldMap(element, props) {
const {
countryFieldtype,
entity,
data,
width,
height,
maxBubbleSize,
showBubbles,
linearColorScheme,
color,
colorBy,
colorScheme,
sliceId,
theme,
onContextMenu,
setDataMask,
inContextMenu,
filterState,
emitCrossFilters,
formatter,
} = props;
const div = d3.select(element);
div.classed('superset-legacy-chart-world-map', true);
div.selectAll('*').remove();
// Ignore XXX's to get better normalization
const filteredData = data.filter(d => d.country && d.country !== 'XXX');
const extRadius = d3.extent(filteredData, d => Math.sqrt(d.m2));
const radiusScale = d3.scale
.linear()
.domain([extRadius[0], extRadius[1]])
.range([1, maxBubbleSize]);
let processedData;
let colorFn;
if (colorBy === ColorBy.Country) {
colorFn = CategoricalColorNamespace.getScale(colorScheme);
processedData = filteredData.map(d => ({
...d,
radius: radiusScale(Math.sqrt(d.m2)),
fillColor: colorFn(d.name, sliceId),
}));
} else {
colorFn = getSequentialSchemeRegistry()
.get(linearColorScheme)
.createLinearScale(d3Extent(filteredData, d => d.m1));
processedData = filteredData.map(d => ({
...d,
radius: radiusScale(Math.sqrt(d.m2)),
fillColor: colorFn(d.m1),
}));
}
const mapData = {};
processedData.forEach(d => {
mapData[d.country] = d;
});
const getCrossFilterDataMask = source => {
const selected = Object.values(filterState.selectedValues || {});
const key = source.id || source.country;
const country =
countryFieldtype === 'name' ? mapData[key]?.name : mapData[key]?.country;
if (!country) {
return undefined;
}
let values;
if (selected.includes(key)) {
values = [];
} else {
values = [country];
}
return {
dataMask: {
extraFormData: {
filters: values.length
? [
{
col: entity,
op: 'IN',
val: values,
},
]
: [],
},
filterState: {
value: values.length ? values : null,
selectedValues: values.length ? [key] : null,
},
},
isCurrentValueSelected: selected.includes(key),
};
};
const handleClick = source => {
if (!emitCrossFilters) {
return;
}
const pointerEvent = d3.event;
pointerEvent.preventDefault();
getCrossFilterDataMask(source);
const dataMask = getCrossFilterDataMask(source)?.dataMask;
if (dataMask) {
setDataMask(dataMask);
}
};
const handleContextMenu = source => {
const pointerEvent = d3.event;
pointerEvent.preventDefault();
const key = source.id || source.country;
const val =
countryFieldtype === 'name' ? mapData[key]?.name : mapData[key]?.country;
let drillToDetailFilters;
let drillByFilters;
if (val) {
drillToDetailFilters = [
{
col: entity,
op: '==',
val,
formattedVal: val,
},
];
drillByFilters = [
{
col: entity,
op: '==',
val,
},
];
}
onContextMenu(pointerEvent.clientX, pointerEvent.clientY, {
drillToDetail: drillToDetailFilters,
crossFilter: getCrossFilterDataMask(source),
drillBy: { filters: drillByFilters, groupbyFieldName: 'entity' },
});
};
const map = new Datamap({
element,
width,
height,
data: processedData,
fills: {
defaultFill: theme.colors.grayscale.light2,
},
geographyConfig: {
popupOnHover: !inContextMenu,
highlightOnHover: !inContextMenu,
borderWidth: 1,
borderColor: theme.colors.grayscale.light5,
highlightBorderColor: theme.colors.grayscale.light5,
highlightFillColor: color,
highlightBorderWidth: 1,
popupTemplate: (geo, d) =>
`<div class="hoverinfo"><strong>${d.name}</strong><br>${formatter(
d.m1,
)}</div>`,
},
bubblesConfig: {
borderWidth: 1,
borderOpacity: 1,
borderColor: color,
popupOnHover: !inContextMenu,
radius: null,
popupTemplate: (geo, d) =>
`<div class="hoverinfo"><strong>${d.name}</strong><br>${formatter(
d.m2,
)}</div>`,
fillOpacity: 0.5,
animate: true,
highlightOnHover: !inContextMenu,
highlightFillColor: color,
highlightBorderColor: theme.colors.grayscale.dark2,
highlightBorderWidth: 2,
highlightBorderOpacity: 1,
highlightFillOpacity: 0.85,
exitDelay: 100,
key: JSON.stringify,
},
done: datamap => {
datamap.svg
.selectAll('.datamaps-subunit')
.on('contextmenu', handleContextMenu)
.on('click', handleClick);
},
});
map.updateChoropleth(mapData);
if (showBubbles) {
map.bubbles(processedData);
div
.selectAll('circle.datamaps-bubble')
.style('fill', color)
.style('stroke', color)
.on('contextmenu', handleContextMenu)
.on('click', handleClick);
}
if (filterState.selectedValues?.length > 0) {
d3.selectAll('path.datamaps-subunit')
.filter(
countryFeature =>
!filterState.selectedValues.includes(countryFeature.id),
)
.style('fill-opacity', theme.opacity.mediumLight);
// hack to ensure that the clicked country's color is preserved
// sometimes the fill color would get default grey value after applying cross filter
filterState.selectedValues.forEach(value => {
d3.select(`path.datamaps-subunit.${value}`).style(
'fill',
mapData[value]?.fillColor,
);
});
}
}