Skip to content
This repository has been archived by the owner on Jan 22, 2024. It is now read-only.

Commit

Permalink
Remove all custom marker logic from Map and support circle props
Browse files Browse the repository at this point in the history
  • Loading branch information
Bo-Duke authored Jun 30, 2023
1 parent f30b038 commit 79f4533
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 173 deletions.
103 changes: 103 additions & 0 deletions src/modules/Map/Map/CustomMarkers/customMarker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Marker } from 'mapbox-gl';
import { getLayerOpacity } from '../../services/mapUtils';
import piechart from './piechartMarker';


const STYLE_TYPE_MATCH = {
piechart,
};

/**
* Function used to create a Markers that are synced to a default hidden layer
*/
export default (type, layer, map) => {
if (!layer || Object.keys(layer).length === 0) return;
const markers = {};
let markersOnScreen = {};
let opacity = 1;
const customMarker = STYLE_TYPE_MATCH[type];

// Get Paint from layer but update properties to use them for "fake" layer
const newPaint = Object.fromEntries(
Object.entries(layer.paint || {}).map(([key, value]) => {
const transformedKey = key.replace(`${type}-`, `${customMarker.targetType}-`);
return [transformedKey, value];
}),
);

const newLayer = {
type: customMarker.targetType,
id: layer.id,
paint: {
...customMarker.defaultPaint,
...newPaint,
},
source: layer.source,
...(layer['source-layer'] ? { 'source-layer': layer['source-layer'] } : {}),
};

// Add "fake" layer to map
map.addLayer(newLayer);

/**
* Function used to update markers, add new ones and remove those no longer visible
*/
const updateMarkers = updateParameters => {
if (!map.getLayer(layer.id) || map.getLayer(layer.id).visibility === 'none') {
Object.values(markersOnScreen).forEach(marker => marker.remove());
markersOnScreen = {};
return;
}
const newMarkers = {};
const features = map.queryRenderedFeatures({ layers: [layer.id] });

const layerOpacity = getLayerOpacity(map, layer.id);

// for every feature on the screen, create an HTML marker for it (if we didn't yet),
// and add it to the map if it's not there already
features.forEach(feature => {
const coords = feature.geometry.coordinates;
const idCoords = feature.geometry.coordinates.join('');
const props = feature.properties;

const { paint: layerPaint = {} } = feature.layer;

let marker = markers[idCoords];
if (!marker) {
// createMarkers using the customMarker type
const el = customMarker.createMarker(
props,
updateParameters,
layerPaint,
);
marker = new Marker({
element: el,
}).setLngLat(coords);
markers[idCoords] = marker;
}
newMarkers[idCoords] = marker;

if (!markersOnScreen[idCoords]) marker.addTo(map);
});
// for every marker we've added previously, remove those that are no longer visible
Object.keys(markersOnScreen).forEach(markerid => {
if (!newMarkers[markerid]) markersOnScreen[markerid].remove();
});
markersOnScreen = newMarkers;

// change opacity of markers without having to recreate them;
if (layerOpacity !== opacity) {
Object.values(markers).forEach(e => {
customMarker.updateOpacity(e, layerOpacity);
});
opacity = layerOpacity;
}
};

const updateParameters = customMarker.getUpdateParameters(layer);

map.on('render', () => {
if (!map.isSourceLoaded(layer.source)) return;
updateMarkers(updateParameters);
});
};
98 changes: 98 additions & 0 deletions src/modules/Map/Map/CustomMarkers/piechartMarker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const getUpdateParameters = layer => {
const { fields, ...advancedStyle } = layer.advanced_style;
const usableFields = fields.filter(field => field.use);
return { fields: usableFields, advancedStyle };
};

const donutSegment = (start, pEnd, r, color, r0 = 0) => {
let end = pEnd;
if (end - start === 1) end -= 0.00001;
const a0 = 2 * Math.PI * (start - 0.25);
const a1 = 2 * Math.PI * (end - 0.25);
const x0 = Math.cos(a0);
const y0 = Math.sin(a0);
const x1 = Math.cos(a1);
const y1 = Math.sin(a1);
const largeArc = end - start > 0.5 ? 1 : 0;

// draw an SVG path
const draw = [
`M ${r + r0 * x0} ${r + r0 * y0}`,
`L ${r + r * x0} ${r + r * y0}`,
`A ${r} ${r} 0 ${largeArc} 1 ${r + r * x1} ${r + r * y1}`,
`L ${r + r0 * x1} ${r + r0 * y1}`,
`A ${r0} ${r0} 0 ${largeArc} 0 ${r + r0 * x0} ${r + r0 * y0}`,
].join(' ');
// draw an SVG path
return `<path d="${draw}" fill="${color}" />`;
};

const createMarker = (
props,
{
fields = [],
advancedStyle: { show_total: showTotal },
},
{ 'circle-radius': circleRadius = 30, 'circle-opacity': circleOpacity = 1 },
) => {
const offsets = [];
let total = 0;
fields.forEach(field => {
offsets.push(total);
total += props[field.name];
});

const r = circleRadius === 0 ? 30 : circleRadius;
const fontSize = 15;
const w = r * 2;

let html = `<div style="z-index:${10 - Math.round(r / 10)}">
<svg
opacity=${circleOpacity}
width="${w}"
height="${w}"
viewbox="0 0 ${w} ${w}"
text-anchor="middle"
style="font: ${fontSize}px sans-serif; display: block"
>`;

for (let i = 0; i < fields.length; i += 1) {
html += donutSegment(
offsets[i] / total,
(offsets[i] + props[fields[i].name]) / total,
r,
fields[i].color,
);
}
if (showTotal) {
html += `
<text dominant-baseline="central" transform="translate(${r}, ${r})">
${total.toLocaleString()}
</text>`;
}

html += `
</svg>
</div>`;

const el = document.createElement('div');
el.innerHTML = html;
return el.firstChild;
};

const updateLayerOpacity = (marker, opacity) => {
marker.getElement().children[0].setAttribute('opacity', opacity);
};

export default {
type: 'piechart',
targetType: 'circle',
defaultPaint: {
'circle-opacity': 1,
'circle-color': '#ffffff',
'circle-radius': 0,
},
getUpdateParameters,
createMarker,
updateLayerOpacity,
};
Loading

0 comments on commit 79f4533

Please sign in to comment.