diff --git a/src/components/FlatmapVuer.vue b/src/components/FlatmapVuer.vue index 13cf43b2..47ba0b1e 100644 --- a/src/components/FlatmapVuer.vue +++ b/src/components/FlatmapVuer.vue @@ -634,6 +634,7 @@ import { capitalise, normaliseAlertToStringArray } from './utilities.js' import yellowstar from '../icons/yellowstar' import ResizeSensor from 'css-element-queries/src/ResizeSensor' import flatmap from '../services/flatmapLoader.js' +import { attachSomaLocationMarkerMethods } from '../services/somaLocationMarkers.js' import { AnnotationService } from '@abi-software/sparc-annotation' import { mapState } from 'pinia' import { useMainStore } from '@/store/index' @@ -2845,7 +2846,7 @@ export default { } ) promise1.then((returnedObject) => { - this.mapImp = returnedObject + this.mapImp = attachSomaLocationMarkerMethods(returnedObject) this.serverURL = this.mapImp.makeServerUrl('').slice(0, -1) let mapVersion = this.mapImp.details.version this.setFlightPathInfo(mapVersion) diff --git a/src/services/somaLocationMarkers.js b/src/services/somaLocationMarkers.js new file mode 100644 index 00000000..5eb23f96 --- /dev/null +++ b/src/services/somaLocationMarkers.js @@ -0,0 +1,125 @@ +const SOMA_MARKER_COLOUR = '#1b7f6b' +const SOMA_MARKER_SECONDARY_COLOUR = '#f4f1de' +const SOMA_MARKER_TEXT_COLOUR = '#12312b' +const SOMA_MARKER_MAX_COUNT_LABEL = '99+' + +const normalizeSomaLocations = function (somaLocations) { + return (Array.isArray(somaLocations) ? somaLocations : []) + .map((item) => ({ + label: String(item?.label || '').trim(), + curie: String(item?.curie || '').trim(), + count: Number(item?.count || 0), + })) + .filter((item) => item.curie && item.count > 0) +} + +const formatCount = function (count) { + if (count > 99) return SOMA_MARKER_MAX_COUNT_LABEL + return String(count) +} + +const createSomaLocationMarkerElement = function (somaLocation) { + const wrapper = document.createElement('div') + wrapper.className = 'flatmapvuer-soma-location-marker' + wrapper.title = somaLocation.label || somaLocation.curie + wrapper.style.width = '34px' + wrapper.style.height = '48px' + wrapper.style.display = 'flex' + wrapper.style.alignItems = 'center' + wrapper.style.justifyContent = 'center' + wrapper.style.transform = 'translate(-50%, -100%)' + + const countLabel = formatCount(somaLocation.count) + wrapper.innerHTML = `` + + return wrapper +} + +const resolveMarkerPlacement = function (mapImp, curie) { + const featureIds = Array.isArray(mapImp?.modelFeatureIds?.(curie)) ? mapImp.modelFeatureIds(curie) : [] + const placements = [] + const seenFeatureUris = new Set() + + for (const featureId of featureIds) { + const annotation = mapImp.annotation(featureId) + const featureUri = String(annotation?.uri || '').trim() + if (featureUri && !seenFeatureUris.has(featureUri)) { + seenFeatureUris.add(featureUri) + placements.push({ + type: 'feature-uri', + value: featureUri, + }) + } + } + + // Fall back to model placement only when no feature URI can be resolved. + if (!placements.length && featureIds.length) { + placements.push({ + type: 'model', + value: curie, + }) + } + + return placements +} + +export const attachSomaLocationMarkerMethods = function (mapImp) { + if (!mapImp || mapImp.addSomaLocationMarkers) { + return mapImp + } + + let markerIds = [] + + mapImp.clearSomaLocationMarkers = function () { + markerIds.forEach((markerId) => { + mapImp.removeMarker(markerId) + }) + markerIds = [] + } + + mapImp.addSomaLocationMarkers = function (somaLocations) { + const normalizedSomaLocations = normalizeSomaLocations(somaLocations) + const placedSomaLocations = [] + + mapImp.clearSomaLocationMarkers() + + normalizedSomaLocations.forEach((somaLocation) => { + const placements = resolveMarkerPlacement(mapImp, somaLocation.curie) + if (!placements.length) return + + let somaLocationPlaced = false + placements.forEach((placement) => { + const markerElement = createSomaLocationMarkerElement(somaLocation) + let addedMarkerIds = [] + + if (placement.type === 'feature-uri') { + addedMarkerIds = mapImp.addMarkerByFeatureUri(placement.value, { element: markerElement }) + } else if (placement.type === 'model') { + const markerId = mapImp.addMarker(placement.value, { element: markerElement }) + addedMarkerIds = markerId > -1 ? [markerId] : [] + } + + if (!addedMarkerIds.length) return + + markerIds.push(...addedMarkerIds) + somaLocationPlaced = true + }) + + if (somaLocationPlaced) { + placedSomaLocations.push(somaLocation) + } + }) + + return placedSomaLocations + } + + return mapImp +}