imad-hl commented on code in PR #35859:
URL: https://github.com/apache/superset/pull/35859#discussion_r2588968360
##########
superset-frontend/plugins/legacy-plugin-chart-country-map/src/CountryMap.js:
##########
@@ -83,112 +106,200 @@ function CountryMap(element, props) {
.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
+ const textLayer = svg
.append('g')
- .classed('text-layer', true)
+ .attr('class', 'text-layer')
.attr('transform', `translate(${width / 2}, 45)`);
- const bigText = textLayer.append('text').classed('big-text', true);
+ const bigText = textLayer
+ .append('text')
+ .classed('big-text', true)
+ .style('font-size', '18px');
const resultText = textLayer
.append('text')
.classed('result-text', true)
- .attr('dy', '1em');
-
- let centered;
-
- const clicked = function clicked(d) {
- const hasCenter = d && centered !== d;
- let x;
- let y;
- let k;
- const halfWidth = width / 2;
- const halfHeight = height / 2;
-
- if (hasCenter) {
- const centroid = path.centroid(d);
- [x, y] = centroid;
- k = 4;
- centered = d;
- } else {
- x = halfWidth;
- y = halfHeight;
- k = 1;
- centered = null;
- }
+ .attr('dy', '1em')
+ .style('font-size', '26px');
+
+ // Cross-filter support
+ const getCrossFilterDataMask = source => {
+ const selected = filterState.selectedValues || [];
+ const iso = source?.properties?.ISO;
+ if (!iso) return undefined;
- g.transition()
- .duration(750)
- .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)
- .style('font-size', hasCenter ? 6 : 16);
- resultText
- .transition()
- .duration(750)
- .style('font-size', hasCenter ? 16 : 24);
+ const isSelected = selected.includes(iso);
+ const values = isSelected ? [] : [iso];
+
+ return {
+ dataMask: {
+ extraFormData: {
+ filters: values.length
+ ? [{ col: entity, op: 'IN', val: values }]
+ : [],
+ },
+ filterState: {
+ value: values.length ? values : null,
+ selectedValues: values.length ? values : null,
+ },
+ },
+ isCurrentValueSelected: isSelected,
+ };
};
- backgroundRect.on('click', clicked);
+ // Handle right-click context menu
+ const handleContextMenu = feature => {
+ const pointerEvent = d3.event;
+
+ // Only prevent default if we have a context menu handler
+ if (typeof onContextMenu === 'function') {
+ pointerEvent?.preventDefault();
+ }
+
+ const iso = feature?.properties?.ISO;
+ if (!iso || typeof onContextMenu !== 'function') return;
+
+ const drillVal = iso;
+ const drillToDetailFilters = [
+ { col: entity, op: '==', val: drillVal, formattedVal: drillVal },
+ ];
+ const drillByFilters = [{ col: entity, op: '==', val: drillVal }];
- const selectAndDisplayNameOfRegion = function selectAndDisplayNameOfRegion(
- feature,
- ) {
+ onContextMenu(pointerEvent.clientX, pointerEvent.clientY, {
+ drillToDetail: drillToDetailFilters,
+ crossFilter: getCrossFilterDataMask(feature),
+ drillBy: { filters: drillByFilters, groupbyFieldName: 'entity' },
+ });
+ };
+
+ const selectAndDisplayNameOfRegion = feature => {
let name = '';
- if (feature && feature.properties) {
- if (feature.properties.ID_2) {
- name = feature.properties.NAME_2;
- } else {
- name = feature.properties.NAME_1;
- }
+ if (feature?.properties) {
+ name = feature.properties.NAME_2 || feature.properties.NAME_1 || '';
}
bigText.text(name);
};
- const updateMetrics = function updateMetrics(region) {
- if (region.length > 0) {
- resultText.text(format(region[0].metric));
- }
+ const updateMetrics = regionRows => {
+ if (regionRows?.length > 0) resultText.text(format(regionRows[0].metric));
+ else resultText.text('');
};
- const mouseenter = function mouseenter(d) {
- // Darken color
+ const mouseenter = function (d) {
let c = colorFn(d);
- if (c !== 'none') {
- c = d3.rgb(c).darker().toString();
- }
+ if (c && c !== 'none') c = d3.rgb(c).darker().toString();
d3.select(this).style('fill', c);
selectAndDisplayNameOfRegion(d);
- const result = data.filter(
- region => region.country_id === d.properties.ISO,
- );
+ const result = data.filter(r => r.country_id === d?.properties?.ISO);
updateMetrics(result);
};
- const mouseout = function mouseout() {
- d3.select(this).style('fill', colorFn);
+ const mouseout = function () {
+ d3.select(this).style('fill', d => colorFn(d));
bigText.text('');
resultText.text('');
};
+ // Zoom with panning bounds
+ const zoom = d3.behavior
+ .zoom()
+ .scaleExtent([1, 4])
+ .on('zoom', () => {
+ const { translate, scale } = d3.event; // [tx, ty]
+ let [tx, ty] = translate;
+
+ const scaledW = width * scale;
+ const scaledH = height * scale;
+ const minX = Math.min(0, width - scaledW);
+ const maxX = 0;
+ const minY = Math.min(0, height - scaledH);
+ const maxY = 0;
+
+ // clamp
+ tx = Math.max(Math.min(tx, maxX), minX);
+ ty = Math.max(Math.min(ty, maxY), minY);
+
+ g.attr('transform', `translate(${tx}, ${ty}) scale(${scale})`);
+ zoomStates[chartKey] = { scale, translate: [tx, ty] };
+ });
+
+ d3.select(svg.node()).call(zoom);
+
+ // Restore
+ if (zoomStates[chartKey]) {
+ const { scale, translate } = zoomStates[chartKey];
+ zoom.scale(scale).translate(translate);
+ g.attr(
+ 'transform',
+ `translate(${translate[0]}, ${translate[1]}) scale(${scale})`,
+ );
+ }
+
+ // Visual highlighting for selected regions
+ function highlightSelectedRegion() {
+ const selectedValues = filterState.selectedValues?.length
+ ? filterState.selectedValues
+ : [];
+
+ mapLayer
+ .selectAll('path.region')
+ .style('fill-opacity', d => {
+ const iso = d?.properties?.ISO;
+ return selectedValues.length === 0 || selectedValues.includes(iso)
+ ? 1
+ : 0.3;
+ })
+ .style('stroke', d => {
+ const iso = d?.properties?.ISO;
+ return selectedValues.includes(iso) ? '#222' : null;
+ })
+ .style('stroke-width', d => {
+ const iso = d?.properties?.ISO;
+ return selectedValues.includes(iso) ? '1.5px' : '0.5px';
+ });
+ }
+
+ // Click handler
+ const handleClick = feature => {
+ if (!emitCrossFilters || typeof setDataMask !== 'function') return;
+ const iso = feature?.properties?.ISO;
+ if (!iso) return;
+
+ const baseline = filterState.selectedValues || [];
Review Comment:
Fixed in 9651b63 : added optional chaining for 'filterState' to safely
access 'selectedValues' in 'handleClick'
( Line :
https://github.com/imad-hl/superset/blob/d818e4a41a0b1abaa92e32a2381ece98c781b00d/superset-frontend/plugins/legacy-plugin-chart-country-map/src/CountryMap.js#L279
)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]