snapshot
This commit is contained in:
@@ -114,14 +114,13 @@ class MapViewLabelController {
|
||||
.attr("rx", 4)
|
||||
.attr("ry", 4);
|
||||
|
||||
graphElem.attr("data-bbox-width", bbox.width);
|
||||
graphElem.attr("data-bbox-height", bbox.height);
|
||||
}
|
||||
|
||||
// Update d3 shapes' positions to the map's current state
|
||||
updateLabelsPosition() {
|
||||
if (this.mapLabels !== null && this.mapLabels !== undefined) {
|
||||
const lblVerticalOffset = 4;
|
||||
|
||||
// The original cloned svg elements are removed and new ones are created
|
||||
d3.selectAll("g.labelclone").remove();
|
||||
this.mapLabels.each((_, i, nodes) => {
|
||||
@@ -135,33 +134,41 @@ class MapViewLabelController {
|
||||
});
|
||||
d3.selectAll("g.label").style("display", "none");
|
||||
|
||||
d3.selectAll("g.label, g.labelclone")
|
||||
.attr("transform", (_, i, nodes) => {
|
||||
const lon = Number(nodes[i].getAttribute("data-lon"));
|
||||
const lat = Number(nodes[i].getAttribute("data-lat"));
|
||||
const p = this.projectCoordinatesToPosition([lon, lat]);
|
||||
const translateVal = `translate(${p.x}, ${p.y + lblVerticalOffset})`;
|
||||
console.log(translateVal);
|
||||
return translateVal;
|
||||
});
|
||||
// Set the map projection transformation and remove not visible nodes
|
||||
d3.selectAll("g.labelclone").each((_, i, nodes) => {
|
||||
const lon = Number(nodes[i].getAttribute("data-lon"));
|
||||
const lat = Number(nodes[i].getAttribute("data-lat"));
|
||||
const p = this.projectCoordinatesToPosition([lon, lat]);
|
||||
|
||||
// Check if the label overlaps and if it does create a grouup with the overlaping labels.
|
||||
const labelNodes = d3.selectAll("g.labelclone").nodes();
|
||||
if(p.x > 0 && p.y >0){
|
||||
const translateVal = `translate(${p.x}, ${p.y})`;
|
||||
nodes[i].setAttribute("transform", translateVal);
|
||||
}
|
||||
else {
|
||||
nodes[i].remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Check if the marker overlaps and if it does create a group merging labels
|
||||
const labelGroups = [];
|
||||
for (let i = 0; i < labelNodes.length; i++) {
|
||||
|
||||
const lNode = labelNodes[i];
|
||||
d3.selectAll("g.labelclone").each((_,i,nodes) => {
|
||||
const node = nodes[i];
|
||||
let groupFound = false;
|
||||
|
||||
for (let group of labelGroups) {
|
||||
for (let j = 0; j < group.length; j++) {
|
||||
if (this.isOverlapping(lNode, group[j])) {
|
||||
group.push(lNode);
|
||||
const recta = this.getOverlapZone(node);
|
||||
const rectb = this.getOverlapZone(group[j]);
|
||||
if (this.isOverlapping(recta, rectb)) {
|
||||
group.push(node);
|
||||
groupFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (groupFound) break;
|
||||
if (!groupFound){
|
||||
labelGroups.push([node]);
|
||||
}
|
||||
}
|
||||
|
||||
// If any label group was found then create a new one with the label node
|
||||
@@ -175,50 +182,32 @@ class MapViewLabelController {
|
||||
if (group.length === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Gather merged text and coordinates
|
||||
const names = group.map(el => el.querySelector("text")!.getAttribute("feature-name"));
|
||||
const coords = group.map(el => {
|
||||
const transform = el.getAttribute("transform")!;
|
||||
const match = /translate\(([^,]+),\s*([^)]+)\)/.exec(transform);
|
||||
return { x: +match![1], y: +match![2] };
|
||||
});
|
||||
|
||||
// Calculate the new position
|
||||
const avgX = coords.reduce((sum, c) => sum + c.x, 0) / coords.length;
|
||||
const avgY = Math.min(...coords.map(c => c.y));
|
||||
|
||||
// Create a new label with the combined text and append it to the svg elements.
|
||||
const main = d3.select(group[0]);
|
||||
const mergedLabel : GeoJSON.Feature<GeoJSON.Point> = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
name: names.join('\n')
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [avgX, avgY]
|
||||
}
|
||||
};
|
||||
this.appendLabels(mergedLabel, main);
|
||||
|
||||
// Hide others
|
||||
for (let i = 1; i < group.length; i++) {
|
||||
group[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the lat-long attributes from a svg group element
|
||||
getElementLatLon(el){
|
||||
const lat = +el.getAttribute("data-lat");
|
||||
const lon = +el.getAttribute("data-lon");
|
||||
|
||||
return { lat: lat, lon: lon };
|
||||
}
|
||||
|
||||
// Project GeoJSON coordinate to the map's current state
|
||||
projectCoordinatesToPosition(c : number[]) {
|
||||
return map.project(new mapboxgl.LngLat(+c[0], +c[1]));
|
||||
}
|
||||
|
||||
getElementTransform(el : Element){
|
||||
const transform = el.getAttribute("transform");
|
||||
const match = /translate\(([^,]+),\s*([^)]+)\)/.exec(transform);
|
||||
|
||||
return { x: +match[1], y: +match[2] };
|
||||
}
|
||||
|
||||
// Detect if two labels overlap
|
||||
isOverlapping(a : SVGElement, b : SVGElement) {
|
||||
const recta = a.getBoundingClientRect();
|
||||
const rectb = b.getBoundingClientRect();
|
||||
isOverlapping(recta, rectb) {
|
||||
|
||||
const aright_lt_bleft = recta.right < rectb.left;
|
||||
const abottom_lt_btop = recta.bottom < rectb.top;
|
||||
@@ -228,4 +217,87 @@ class MapViewLabelController {
|
||||
|
||||
return overlaping;
|
||||
}
|
||||
|
||||
createClusteredLabel(group){
|
||||
// Gather merged text and coordinates
|
||||
const names = group.map(el => el.querySelector("text")!.getAttribute("feature-name"));
|
||||
const coords = group.map(el => {
|
||||
const latlon = this.getElementLatLon(el);
|
||||
|
||||
return { lat: latlon.lat, lon: latlon.lon };
|
||||
});
|
||||
|
||||
// Calculate the new position
|
||||
const avgLat = coords.reduce((sum, c) => sum + c.lat, 0) / coords.length;
|
||||
const avgLon = Math.min(...coords.map(c => c.lon));
|
||||
|
||||
// Create a new label with the combined text and append it to the svg elements.
|
||||
const main = d3.select(group[0]);
|
||||
const mergedLabel : GeoJSON.Feature<GeoJSON.Point> = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
name: names.join('\n')
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [avgLat, avgLon]
|
||||
}
|
||||
};
|
||||
this.appendLabels(mergedLabel, main);
|
||||
|
||||
// Hide others
|
||||
for (let i = 1; i < group.length; i++) {
|
||||
group[i].remove();
|
||||
}
|
||||
}
|
||||
|
||||
getOverlapZone(el){
|
||||
const overlappingOffset = 0;
|
||||
const transEl = this.getElementTransform(el);
|
||||
|
||||
return { top: transEl.y - overlappingOffset, bottom: transEl.y + overlappingOffset, left: transEl.x - overlappingOffset, right: transEl.x + overlappingOffset };
|
||||
}
|
||||
|
||||
// Repositions the final groups
|
||||
setFinalPositioning(){
|
||||
// Sort by latitude
|
||||
let sortedLabels = d3.selectAll("g.labelclone").nodes()
|
||||
.sort((a,b) => {
|
||||
let lonA = +a.getAttribute("data-lon");
|
||||
let lonB = +a.getAttribute("data-lon");
|
||||
let result = d3.ascending(lonA, lonB);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
d3.selectAll("g.labelclone").each( (_, i, nodes) => {
|
||||
let translateVal = this.getOffsetPosition(i, node);
|
||||
|
||||
nodes[i].setAttribute("transform", translateVal);
|
||||
});
|
||||
}
|
||||
|
||||
getOffsetPosition(index, nodes){
|
||||
const el = nodes[index];
|
||||
const pos = this.getElementTransform(el);
|
||||
|
||||
let markerHeight = 22;
|
||||
let markerWidht = 14;
|
||||
const boxHeight = +el.getAttribute("data-box-height");
|
||||
const boxWidth = +el.getAttribute("data-box-width");
|
||||
|
||||
// Up
|
||||
return `translate(${pos.x}, ${pos.y - boxHeight})`;
|
||||
|
||||
/*
|
||||
// Down
|
||||
return `translate(${pos.x}, ${pos.y - markerHeight})`;
|
||||
|
||||
// Left
|
||||
return `translate(${pos.x - (boxWidth/2 + markerWidth)}, ${pos.y})`;
|
||||
|
||||
// Right
|
||||
return `translate(${pos.x + (boxWidth/2 + markerWidth)}, ${pos.y})`;
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
|
||||
"strict": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
"noUncheckedSideEffectImports": true,
|
||||
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
|
||||
Reference in New Issue
Block a user