@abi-software/flatmapvuer 1.9.0-beta.0 → 1.9.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -592,6 +592,7 @@ Please use `const` to assign meaningful names to them...
592
592
  :tooltipEntry="tooltipEntry"
593
593
  :annotationDisplay="viewingMode === 'Annotation'"
594
594
  @annotation="commitAnnotationEvent"
595
+ @onActionClick="onActionClick"
595
596
  />
596
597
  </div>
597
598
  </div>
@@ -641,6 +642,7 @@ import { mapState } from 'pinia'
641
642
  import { useMainStore } from '@/store/index'
642
643
  import { DrawToolbar, Tooltip, TreeControls } from '@abi-software/map-utilities'
643
644
  import '@abi-software/map-utilities/dist/style.css'
645
+ import EventBus from './EventBus.js'
644
646
 
645
647
  const ERROR_MESSAGE = 'cannot be found on the map.';
646
648
 
@@ -696,18 +698,6 @@ const processFTUs = (parent, key) => {
696
698
  return ftus
697
699
  }
698
700
 
699
- const createUnfilledTooltipData = function () {
700
- return {
701
- destinations: [],
702
- origins: [],
703
- components: [],
704
- destinationsWithDatasets: [],
705
- originsWithDatasets: [],
706
- componentsWithDatasets: [],
707
- resource: undefined,
708
- }
709
- }
710
-
711
701
  /**
712
702
  * A vue component of the flatmap viewer.
713
703
  */
@@ -777,10 +767,10 @@ export default {
777
767
  if (this.isValidDrawnCreated) {
778
768
  if (this.annotationSidebar) this.$emit("annotation-close")
779
769
  this.closeTooltip()
780
- this.annotationEntry = {
770
+ this.annotationEntry = [{
781
771
  ...this.drawnCreatedEvent.feature,
782
772
  resourceId: this.serverURL,
783
- }
773
+ }]
784
774
  this.rollbackAnnotationEvent()
785
775
  this.initialiseDrawing()
786
776
  }
@@ -798,7 +788,7 @@ export default {
798
788
  ? this.mapImp.featureProperties(numericId)
799
789
  : { feature: this.existDrawnFeatures.find(feature => feature.id === value.trim()) };
800
790
  let payload = { feature: featureObject }
801
- this.checkAndCreatePopups(payload)
791
+ this.checkAndCreatePopups([payload])
802
792
  } else {
803
793
  this.closeTooltip()
804
794
  }
@@ -810,12 +800,12 @@ export default {
810
800
  */
811
801
  confirmDrawnFeature: function () {
812
802
  if (this.isValidDrawnCreated) {
813
- this.checkAndCreatePopups(this.drawnCreatedEvent)
803
+ this.checkAndCreatePopups([this.drawnCreatedEvent])
814
804
  // Add connection if exist to annotationEntry
815
805
  // Connection will only be added in creating new drawn feature annotation
816
806
  // And will not be updated if move drawn features
817
807
  if (Object.keys(this.connectionEntry).length > 0) {
818
- this.annotationEntry.feature.connection = this.connectionEntry
808
+ this.annotationEntry[0].feature.connection = this.connectionEntry
819
809
  }
820
810
  this.initialiseDrawing()
821
811
  }
@@ -882,7 +872,7 @@ export default {
882
872
  target: features[features.length - 1],
883
873
  intermediates: features.filter((f, index) => index !== 0 && index !== features.length - 1),
884
874
  }
885
- this.annotationEntry.body = body
875
+ this.annotationEntry[0].body = body
886
876
  }
887
877
  },
888
878
  /**
@@ -925,10 +915,11 @@ export default {
925
915
  // For 'updated' and 'deleted' callback
926
916
  if (
927
917
  this.mapImp &&
928
- ['created', 'updated', 'deleted'].includes(this.annotationEntry.type)
918
+ this.annotationEntry.length > 0 &&
919
+ ['created', 'updated', 'deleted'].includes(this.annotationEntry[0].type)
929
920
  ) {
930
- this.mapImp.rollbackAnnotationEvent(this.annotationEntry)
931
- this.annotationEntry = {}
921
+ this.mapImp.rollbackAnnotationEvent(this.annotationEntry[0])
922
+ this.annotationEntry = []
932
923
  }
933
924
  },
934
925
  /**
@@ -941,23 +932,23 @@ export default {
941
932
  if (this.offlineAnnotationEnabled) {
942
933
  this.offlineAnnotations = JSON.parse(sessionStorage.getItem('anonymous-annotation')) || []
943
934
  this.offlineAnnotations.push(annotation)
944
- if (this.annotationEntry.type === 'deleted') {
935
+ if (this.annotationEntry[0].type === 'deleted') {
945
936
  this.offlineAnnotations = this.offlineAnnotations.filter((offline) => {
946
937
  return offline.resource !== this.serverURL || offline.item.id !== annotation.item.id
947
938
  })
948
939
  }
949
940
  sessionStorage.setItem('anonymous-annotation', JSON.stringify(this.offlineAnnotations))
950
941
  }
951
- if (['created', 'updated', 'deleted'].includes(this.annotationEntry.type)) {
942
+ if (['created', 'updated', 'deleted'].includes(this.annotationEntry[0].type)) {
952
943
  this.featureAnnotationSubmitted = true
953
- this.mapImp.commitAnnotationEvent(this.annotationEntry)
944
+ this.mapImp.commitAnnotationEvent(this.annotationEntry[0])
954
945
  if (annotation.body.comment === "Position Updated") {
955
- this.annotationEntry.positionUpdated = false
956
- } else if (this.annotationEntry.type === 'deleted') {
946
+ this.annotationEntry[0].positionUpdated = false
947
+ } else if (this.annotationEntry[0].type === 'deleted') {
957
948
  if (this.annotationSidebar) this.$emit("annotation-close")
958
949
  this.closeTooltip()
959
950
  // Only delete need, keep the annotation tooltip/sidebar open if created/updated
960
- this.annotationEntry = {}
951
+ this.annotationEntry = []
961
952
  }
962
953
  this.addAnnotationFeature()
963
954
  }
@@ -1190,7 +1181,7 @@ export default {
1190
1181
  },
1191
1182
  setInitMapState: function () {
1192
1183
  if (this.mapImp) {
1193
- const map = this.mapImp._map;
1184
+ const map = this.mapImp.map;
1194
1185
  const bounds = this.mapImp.options.bounds;
1195
1186
  const initBounds = [
1196
1187
  [bounds[0], bounds[1]],
@@ -1212,7 +1203,7 @@ export default {
1212
1203
  resetView: function () {
1213
1204
  if (this.mapImp) {
1214
1205
  // fit to window
1215
- const map = this.mapImp._map;
1206
+ const map = this.mapImp.map;
1216
1207
  const { initBounds } = this.initMapState;
1217
1208
  // reset rotation
1218
1209
  map.resetNorthPitch({
@@ -1284,52 +1275,72 @@ export default {
1284
1275
  )
1285
1276
  }
1286
1277
  },
1278
+ /**
1279
+ * Function to highlight paths and features
1280
+ * @param data
1281
+ */
1282
+ zoomToFeatures: function (data) {
1283
+ if (this.mapImp) {
1284
+ this.mapImp.zoomToFeatures(data)
1285
+ }
1286
+ },
1287
1287
  /**
1288
1288
  * @public
1289
1289
  * Function to highlight the connected paths
1290
- * by providing path model identifier, ``pathId``.
1291
- * @arg {String} `pathId`
1290
+ * by providing path model identifier, ``pathId`` or ``anatomicalId``.
1291
+ * @arg {string} `pathId` or `anatomicalId`
1292
1292
  */
1293
- highlightConnectedPaths: async function (payload) {
1293
+ retrieveConnectedPaths: async function (payload, options = {}) {
1294
1294
  if (this.mapImp) {
1295
- let paths = [...this.mapImp.pathModelNodes(payload)]
1296
-
1295
+ let connectedPaths = [];
1296
+ let connectedTarget = options.target?.length ? options.target : [];
1297
1297
  // The line below is to get the path features from the geojson ids
1298
- let pathFeatures = paths.map((p) => this.mapImp.featureProperties(p))
1299
-
1300
- // Query the flatmap knowledge graph for connectivity, we use this to grab the origins
1301
- let connectivity = await this.flatmapQueries.queryForConnectivityNew(this.mapImp, payload)
1302
-
1303
- // Check and flatten the origins node graph
1304
- let originsFlat = connectivity?.ids?.dendrites?.flat().flat()
1305
-
1306
- let toHighlight = []
1307
- let highlight = false
1308
-
1309
- // Loop through the path features and check if we have origin nodes
1310
- pathFeatures.forEach((p) => {
1311
-
1312
- // Get the nodes from each path feature
1313
- this.mapImp.nodePathModels(p.featureId).forEach((f) => {
1314
- highlight = true
1315
- // s2 here is the second level paths
1316
- let s2 = this.mapImp.pathModelNodes(f)
1317
- s2.forEach((s) => {
1318
- let s2Feature = this.mapImp.featureProperties([s]) // get the feature properties for s2
1319
- if (originsFlat.includes(s2Feature.models)) {
1320
- highlight = false // if we have an origin node, we don't want to highlight the path
1321
- return
1322
- }
1323
- })
1324
-
1325
- if (highlight) {
1326
- toHighlight.push(f)
1327
- }
1328
- })
1329
- })
1330
-
1331
- // display connected paths
1332
- this.mapImp.zoomToFeatures(toHighlight, { noZoomIn: true })
1298
+ const nodeFeatureIds = [...this.mapImp.pathModelNodes(payload)];
1299
+ const pathsOfEntities = await this.mapImp.queryPathsForFeatures(payload);
1300
+ if (nodeFeatureIds.length) {
1301
+ if (!connectedTarget.length) {
1302
+ const connectedType = options.type?.length ? options.type : ["all"];
1303
+ const connectivity = await this.flatmapQueries.queryForConnectivityNew(this.mapImp, payload[0]);
1304
+ const originsFlat = connectivity?.ids?.dendrites.flat(Infinity);
1305
+ const componentsFlat = connectivity?.ids?.components.flat(Infinity);
1306
+ const destinationsFlat = connectivity?.ids?.axons.flat(Infinity);
1307
+ let connected = [];
1308
+ if (connectedType.includes("origins")) connected.push(...originsFlat);
1309
+ if (connectedType.includes("components")) connected.push(...componentsFlat);
1310
+ if (connectedType.includes("destinations")) connected.push(...destinationsFlat);
1311
+ if (connectedType.includes("all")) connected.push(...originsFlat, ...componentsFlat, ...destinationsFlat);
1312
+ connectedTarget = [...new Set(connected)];
1313
+ }
1314
+ // Loop through the node features and check if we have certain nodes
1315
+ nodeFeatureIds.forEach((featureId) => {
1316
+ // Get the paths from each node feature
1317
+ const pathsL2 = this.mapImp.nodePathModels(featureId);
1318
+ pathsL2.forEach((path) => {
1319
+ // nodes of the second level path
1320
+ const nodeFeatureIdsL2 = this.mapImp.pathModelNodes(path);
1321
+ const nodeModelsL2 = nodeFeatureIdsL2.map((featureIdL2) => {
1322
+ return this.mapImp.featureProperties(featureIdL2).models;
1323
+ });
1324
+ const intersection = connectedTarget.filter(element => nodeModelsL2.includes(element));
1325
+ if (intersection.length && !connectedPaths.includes(path)) connectedPaths.push(path);
1326
+ });
1327
+ });
1328
+ } else if (pathsOfEntities.length) {
1329
+ if (connectedTarget.length) {
1330
+ pathsOfEntities.forEach((path) => {
1331
+ const nodeFeatureIds = this.mapImp.pathModelNodes(path);
1332
+ const nodeModels = nodeFeatureIds.map((featureId) => {
1333
+ return this.mapImp.featureProperties(featureId).models;
1334
+ });
1335
+ const intersection = connectedTarget.filter(element => nodeModels.includes(element));
1336
+ if (intersection.length && !connectedPaths.includes(path)) connectedPaths.push(path);
1337
+ });
1338
+ } else {
1339
+ connectedPaths = pathsOfEntities;
1340
+ }
1341
+ }
1342
+ connectedPaths = [...new Set([...connectedPaths, ...payload])];
1343
+ return connectedPaths;
1333
1344
  }
1334
1345
  },
1335
1346
  resetMapFilter: function() {
@@ -1567,7 +1578,7 @@ export default {
1567
1578
  // Rollback drawing when no new annotation submitted
1568
1579
  if (!this.featureAnnotationSubmitted) this.rollbackAnnotationEvent()
1569
1580
  else this.featureAnnotationSubmitted = false
1570
- this.annotationEntry = {}
1581
+ this.annotationEntry = []
1571
1582
  } else if (data.type === 'modeChanged') {
1572
1583
  if (data.feature.mode === 'direct_select') this.doubleClickedFeature = true
1573
1584
  if (this.annotationSidebar && data.feature.mode === 'simple_select' && this.activeDrawMode === 'Deleted') {
@@ -1590,10 +1601,10 @@ export default {
1590
1601
  this.annotationDrawModeEvent(payload)
1591
1602
  } else {
1592
1603
  if (this.annotationSidebar && this.previousEditEvent.type === 'updated') {
1593
- this.annotationEntry = {
1604
+ this.annotationEntry = [{
1594
1605
  ...this.previousEditEvent,
1595
1606
  resourceId: this.serverURL
1596
- }
1607
+ }]
1597
1608
  this.annotationEventCallback({}, { type: 'aborted' })
1598
1609
  }
1599
1610
  this.previousEditEvent = {}
@@ -1612,7 +1623,7 @@ export default {
1612
1623
  // Once double click mouse to confirm drawing, 'aborted' event will be triggered.
1613
1624
  // Hence disable direct popup when 'created' event, dialog will be used instead.
1614
1625
  if (data.type === 'created') this.drawnCreatedEvent = payload
1615
- else this.checkAndCreatePopups(payload)
1626
+ else this.checkAndCreatePopups([payload])
1616
1627
  }
1617
1628
  if (data.type === 'updated') this.previousEditEvent = data
1618
1629
  if (data.type === 'deleted') this.previousDeletedEvent = data
@@ -1639,6 +1650,7 @@ export default {
1639
1650
  const resource = [data.models]
1640
1651
  const taxonomy = this.entry
1641
1652
  const biologicalSex = this.biologicalSex
1653
+ const featuresAlert = data.alert
1642
1654
  let taxons = undefined
1643
1655
  if (data.taxons) {
1644
1656
  // check if data.taxons is string or array
@@ -1648,7 +1660,7 @@ export default {
1648
1660
  taxons = data.taxons
1649
1661
  }
1650
1662
  }
1651
- const payload = {
1663
+ let payload = [{
1652
1664
  dataset: data.dataset,
1653
1665
  biologicalSex: biologicalSex,
1654
1666
  taxonomy: taxonomy,
@@ -1658,31 +1670,64 @@ export default {
1658
1670
  userData: args,
1659
1671
  eventType: eventType,
1660
1672
  provenanceTaxonomy: taxons,
1661
- }
1673
+ alert: featuresAlert
1674
+ }]
1662
1675
  if (eventType === 'click') {
1663
- this.setConnectivityDataSource(this.viewingMode, data);
1664
- this.featuresAlert = data.alert
1665
- //The following will be used to track either a feature is selected
1666
- this.statesTracking.activeClick = true
1667
- this.statesTracking.activeTerm = data?.models
1676
+ const singleSelection = Object.keys(data).includes('id')
1677
+ if (!singleSelection) {
1678
+ payload = []
1679
+ const mapuuid = data.mapUUID
1680
+ for (let [key, value] of Object.entries(data)) {
1681
+ if (key !== 'mapUUID') {
1682
+ const label = value.label
1683
+ const resource = [value.models]
1684
+ let taxons = undefined
1685
+ if (value.taxons) {
1686
+ // check if data.taxons is string or array
1687
+ if (typeof value.taxons !== 'object') {
1688
+ taxons = JSON.parse(value.taxons)
1689
+ } else {
1690
+ taxons = value.taxons
1691
+ }
1692
+ }
1693
+ payload.push({
1694
+ dataset: value.dataset,
1695
+ biologicalSex: biologicalSex,
1696
+ taxonomy: taxonomy,
1697
+ resource: resource,
1698
+ label: label,
1699
+ feature: value,
1700
+ userData: args,
1701
+ eventType: eventType,
1702
+ provenanceTaxonomy: taxons,
1703
+ alert: value.alert,
1704
+ mapUUID: mapuuid
1705
+ })
1706
+ }
1707
+ }
1708
+ }
1709
+ const clickedItem = singleSelection ? data : data[0]
1668
1710
  if (this.viewingMode === 'Neuron Connection') {
1669
- this.highlightConnectedPaths([data.models])
1711
+ this.retrieveConnectedPaths([clickedItem.models]).then((paths) => {
1712
+ this.zoomToFeatures(paths)
1713
+ })
1670
1714
  } else {
1671
- this.currentActive = data.models ? data.models : ''
1672
- // Drawing connectivity between features
1715
+ this.currentActive = clickedItem.models ? clickedItem.models : '' // This is for FC map
1716
+ // This is for annotation mode - draw connectivity between features/paths
1673
1717
  if (this.activeDrawTool && !this.isValidDrawnCreated) {
1674
1718
  // Check if flatmap features or existing drawn features
1675
- const validDrawnFeature = data.featureId || this.existDrawnFeatures.find(
1676
- (feature) => feature.id === data.id
1719
+ const validDrawnFeature = clickedItem.featureId || this.existDrawnFeatures.find(
1720
+ (feature) => feature.id === clickedItem.id
1677
1721
  )
1678
1722
  // Only the linestring will have connection
1679
1723
  if (this.activeDrawTool === 'LineString' && validDrawnFeature) {
1680
- const key = data.featureId ? data.featureId : data.id
1681
- const nodeLabel = data.label ? data.label : `Feature ${data.id}`
1724
+ const key = clickedItem.featureId ? clickedItem.featureId : clickedItem.id
1725
+ const nodeLabel = clickedItem.label ? clickedItem.label : `Feature ${clickedItem.id}`
1682
1726
  // Add space before key to make sure properties follows adding order
1683
- this.connectionEntry[` ${key}`] = Object.assign({ label: nodeLabel },
1727
+ this.connectionEntry[` ${key}`] = Object.assign(
1728
+ { label: nodeLabel },
1684
1729
  Object.fromEntries(
1685
- Object.entries(data)
1730
+ Object.entries(clickedItem)
1686
1731
  .filter(([key]) => ['featureId', 'models'].includes(key))
1687
1732
  .map(([key, value]) => [(key === 'featureId') ? 'id' : key, value])))
1688
1733
  }
@@ -1711,23 +1756,6 @@ export default {
1711
1756
  }
1712
1757
  }
1713
1758
  },
1714
- /**
1715
- * The data for connectivity data source is just a placeholder data
1716
- * to check which part of the map is clicked, e.g., path or feture or empty area,
1717
- * based on the viewing mode.
1718
- * The "connectivity-info-close" event will be emitted based on this data
1719
- * when there has a click event on map.
1720
- * @param viewingMode
1721
- * @param data
1722
- */
1723
- setConnectivityDataSource: function (viewingMode, data) {
1724
- // for Exploration mode, only path click will be used as data source
1725
- this.connectivityDataSource = data.source;
1726
- // for other modes, it can be feature or path
1727
- if (viewingMode === 'Neuron Connection' || viewingMode === 'Annotation') {
1728
- this.connectivityDataSource = data.featureId;
1729
- }
1730
- },
1731
1759
  /**
1732
1760
  * @public
1733
1761
  * Function triggered by viewing mode change.
@@ -1899,6 +1927,25 @@ export default {
1899
1927
  }
1900
1928
  });
1901
1929
  },
1930
+ checkConnectivityTooltipEntry: function(tooltipEntry) {
1931
+ if (tooltipEntry?.length) {
1932
+ return undefined !== (tooltipEntry.find(entry => entry?.destinations?.length || entry?.components?.length))
1933
+ }
1934
+ return false
1935
+ },
1936
+ changeConnectivitySource: async function (payload) {
1937
+ const { featureId, connectivitySource } = payload;
1938
+ await this.flatmapQueries.queryForConnectivityNew(this.mapImp, featureId[0], connectivitySource);
1939
+ this.tooltipEntry = this.tooltipEntry.map((tooltip) => {
1940
+ if (tooltip.featureId[0] === featureId[0]) {
1941
+ return this.flatmapQueries.updateTooltipData(tooltip);
1942
+ }
1943
+ return tooltip;
1944
+ })
1945
+ if (this.checkConnectivityTooltipEntry(this.tooltipEntry)) {
1946
+ this.$emit('connectivity-info-open', this.tooltipEntry);
1947
+ }
1948
+ },
1902
1949
  /**
1903
1950
  * @public
1904
1951
  * Function to create/display tooltips from the provided ``data``.
@@ -1908,23 +1955,26 @@ export default {
1908
1955
  checkAndCreatePopups: async function (data) {
1909
1956
  // Call flatmap database to get the connection data
1910
1957
  if (this.viewingMode === 'Annotation') {
1911
- if (data.feature) {
1958
+ const features = data.filter(d => d.feature).map(d => d.feature)
1959
+ if (features.length > 0) {
1912
1960
  if (this.annotationSidebar && this.previousDeletedEvent.type === 'deleted') {
1913
- this.annotationEntry = {
1961
+ this.annotationEntry = [{
1914
1962
  ...this.previousDeletedEvent,
1915
1963
  resourceId: this.serverURL
1916
- }
1964
+ }]
1917
1965
  this.annotationEventCallback({}, { type: 'aborted' })
1918
1966
  }
1919
- this.annotationEntry = {
1920
- ...data.feature,
1921
- resourceId: this.serverURL,
1922
- offline: this.offlineAnnotationEnabled
1923
- }
1924
- if (data.feature.featureId && data.feature.models) {
1925
- this.displayTooltip(data.feature.models)
1926
- } else if (data.feature.feature) {
1927
- this.annotationEntry.featureId = data.feature.feature.id
1967
+ this.annotationEntry = []
1968
+ features.forEach(feature => {
1969
+ this.annotationEntry.push({
1970
+ ...feature,
1971
+ resourceId: this.serverURL,
1972
+ featureId: feature.featureId ? feature.featureId : feature.feature?.id,
1973
+ offline: this.offlineAnnotationEnabled
1974
+ })
1975
+ });
1976
+ // Drawn feature annotationEntry will always have length of 1
1977
+ if (features[0].feature) {
1928
1978
  // in drawing or edit/delete mode is on or valid drawn
1929
1979
  if (this.activeDrawTool || this.activeDrawMode || this.isValidDrawnCreated) {
1930
1980
  this.featureAnnotationSubmitted = false
@@ -1932,32 +1982,100 @@ export default {
1932
1982
  this.createConnectivityBody()
1933
1983
  }
1934
1984
  this.displayTooltip(
1935
- data.feature.feature.id,
1936
- centroid(data.feature.feature.geometry)
1985
+ features[0].feature.id,
1986
+ centroid(features[0].feature.geometry)
1937
1987
  )
1938
1988
  } else {
1939
1989
  this.rollbackAnnotationEvent()
1940
1990
  }
1991
+ } else {
1992
+ const featureIds = this.annotationEntry
1993
+ .filter(annotation => annotation.featureId && annotation.models)
1994
+ .map(annotation => annotation.models)
1995
+ if (featureIds.length > 0) {
1996
+ this.displayTooltip(featureIds)
1997
+ }
1941
1998
  }
1942
1999
  } else {
1943
2000
  this.annotation = {}
1944
2001
  }
1945
2002
  } else {
1946
- //require data.resource && data.feature.source
1947
- let results = await this.flatmapQueries.retrieveFlatmapKnowledgeForEvent(this.mapImp, data)
1948
2003
  // load and store knowledge
1949
2004
  loadAndStoreKnowledge(this.mapImp, this.flatmapQueries);
1950
- // The line below only creates the tooltip if some data was found on the path
1951
- // the pubmed URLs are in knowledge response.references
1952
- if (
1953
- (results && results[0]) ||
1954
- (data.feature.hyperlinks && data.feature.hyperlinks.length > 0)
1955
- ) {
1956
- this.resourceForTooltip = data.resource[0]
1957
- data.resourceForTooltip = this.resourceForTooltip
1958
- this.createTooltipFromNeuronCuration(data)
2005
+ let prom1 = []
2006
+ // When there are multiple paths, emit placeholders first.
2007
+ // This may contain invalid connectivity.
2008
+ if (data.length > 1) {
2009
+ this.tooltipEntry = data.map((tooltip) => {
2010
+ return { title: tooltip.label, featureId: tooltip.resource, ready: false }
2011
+ })
2012
+ this.$emit('connectivity-info-open', this.tooltipEntry);
2013
+ }
2014
+ // While having placeholders displayed, get details for all paths and then replace.
2015
+ for (let index = 0; index < data.length; index++) {
2016
+ prom1.push(await this.getKnowledgeTooltip(data[index]))
2017
+ }
2018
+ this.tooltipEntry = await Promise.all(prom1)
2019
+ const featureIds = this.tooltipEntry.map(tooltip => tooltip.featureId[0])
2020
+ if (featureIds.length > 0) {
2021
+ this.displayTooltip(featureIds)
2022
+ }
2023
+ }
2024
+ },
2025
+ getKnowledgeTooltip: async function (data) {
2026
+ //require data.resource && data.feature.source
2027
+ const results = await this.flatmapQueries.retrieveFlatmapKnowledgeForEvent(this.mapImp, data)
2028
+ let tooltip = await this.flatmapQueries.createTooltipData(this.mapImp, data)
2029
+ // The line below only creates the tooltip if some data was found on the path
2030
+ // the pubmed URLs are in knowledge response.references
2031
+ if ((results && results[0]) || (data.feature.hyperlinks && data.feature.hyperlinks.length > 0)) {
2032
+ tooltip['featuresAlert'] = data.alert;
2033
+ tooltip['knowledgeSource'] = getKnowledgeSource(this.mapImp);
2034
+ // Map id and uuid to load connectivity information from the map
2035
+ tooltip['mapId'] = this.mapImp.provenance.id;
2036
+ tooltip['mapuuid'] = this.mapImp.provenance.uuid;
2037
+ } else {
2038
+ tooltip = {
2039
+ ...tooltip,
2040
+ origins: [data.label],
2041
+ originsWithDatasets: [{ id: data.resource[0], name: data.label }],
2042
+ components: [],
2043
+ componentsWithDatasets: [],
2044
+ destinations: [],
2045
+ destinationsWithDatasets: [],
2046
+ }
2047
+ let featureIds = []
2048
+ const pathsOfEntities = await this.mapImp.queryPathsForFeatures(data.resource)
2049
+ if (pathsOfEntities.length) {
2050
+ pathsOfEntities.forEach((path) => {
2051
+ featureIds.push(...this.mapImp.pathModelNodes(path))
2052
+ const searchResults = this.mapImp.search(path)
2053
+ let featureId = undefined;
2054
+ for (let i = 0; i < searchResults.results.length; i++) {
2055
+ featureId = searchResults.results[i].featureId
2056
+ const annotation = this.mapImp.annotation(featureId)
2057
+ if (featureId && annotation?.label) break;
2058
+ }
2059
+ if (featureId) {
2060
+ const feature = this.mapImp.featureProperties(featureId)
2061
+ if (!tooltip.components.includes(feature.label)) {
2062
+ tooltip.components.push(feature.label)
2063
+ tooltip.componentsWithDatasets.push({ id: feature.models, name: feature.label })
2064
+ }
2065
+ }
2066
+ })
2067
+ featureIds = [...new Set(featureIds)].filter(id => id !== data.feature.featureId)
2068
+ featureIds.forEach((id) => {
2069
+ const feature = this.mapImp.featureProperties(id)
2070
+ if (!tooltip.destinations.includes(feature.label)) {
2071
+ tooltip.destinations.push(feature.label)
2072
+ tooltip.destinationsWithDatasets.push({ id: feature.models, name: feature.label })
2073
+ }
2074
+ })
1959
2075
  }
1960
2076
  }
2077
+ tooltip['ready'] = true;
2078
+ return tooltip;
1961
2079
  },
1962
2080
  /**
1963
2081
  * A hack to remove flatmap tooltips while popup is open
@@ -1990,15 +2108,6 @@ export default {
1990
2108
  item.style.display = 'none'
1991
2109
  })
1992
2110
  },
1993
- /**
1994
- * @public
1995
- * Function to create tooltip from Neuron Curation ``data``.
1996
- * @arg {Object} `data`
1997
- */
1998
- createTooltipFromNeuronCuration: async function (data) {
1999
- this.tooltipEntry = await this.flatmapQueries.createTooltipData(this.mapImp, data)
2000
- this.displayTooltip(data.resource[0])
2001
- },
2002
2111
  /**
2003
2112
  * @public
2004
2113
  * Function to show popup on map.
@@ -2203,26 +2312,21 @@ export default {
2203
2312
  featureId = feature
2204
2313
  options.annotationFeatureGeometry = geometry
2205
2314
  } else {
2206
- featureId = this.mapImp.modelFeatureIds(feature)[0]
2315
+ const entry = Array.isArray(feature) ? feature[0] : feature
2316
+ featureId = this.mapImp.modelFeatureIds(entry)[0]
2207
2317
  if (!this.activeDrawTool) {
2208
2318
  options.positionAtLastClick = true
2209
2319
  }
2210
2320
  }
2211
2321
  // If connectivityInfoSidebar is set to `true`
2212
2322
  // Connectivity info will show in sidebar
2213
- if ((this.connectivityInfoSidebar && this.hasTooltipEntry()) && this.viewingMode !== 'Annotation') {
2214
- // move the map center to highlighted area
2215
- // this method is moved to sidebar connectivity info
2216
- // const featureIds = [feature];
2217
- // this.moveMap(featureIds);
2218
- if (this.featuresAlert) {
2219
- this.tooltipEntry['featuresAlert'] = this.featuresAlert;
2220
- }
2221
- // Get connectivity knowledge source | SCKAN release
2222
- if (this.mapImp.provenance?.connectivity) {
2223
- this.tooltipEntry['knowledge-source'] = getKnowledgeSource(this.mapImp);
2323
+ if (
2324
+ (this.connectivityInfoSidebar && this.tooltipEntry.length) &&
2325
+ this.viewingMode !== 'Annotation'
2326
+ ) {
2327
+ if (this.checkConnectivityTooltipEntry(this.tooltipEntry)) {
2328
+ this.$emit('connectivity-info-open', this.tooltipEntry);
2224
2329
  }
2225
- this.$emit('connectivity-info-open', this.tooltipEntry);
2226
2330
  }
2227
2331
  if (this.annotationSidebar && this.viewingMode === 'Annotation') {
2228
2332
  this.$emit('annotation-open', {annotationEntry: this.annotationEntry, commitCallback: this.commitAnnotationEvent});
@@ -2232,16 +2336,10 @@ export default {
2232
2336
  // Provenance popup will be shown on map
2233
2337
  // Tooltip will be shown for Annotation view
2234
2338
  if (
2235
- !this.disableUI && (
2236
- (
2237
- this.viewingMode === 'Annotation' &&
2238
- !this.annotationSidebar
2239
- ) ||
2240
- (
2241
- this.viewingMode === 'Exploration' &&
2242
- !this.connectivityInfoSidebar &&
2243
- this.hasTooltipEntry()
2244
- )
2339
+ !this.disableUI &&
2340
+ (
2341
+ (this.viewingMode === 'Annotation' && !this.annotationSidebar) ||
2342
+ (this.viewingMode === 'Exploration' && !this.connectivityInfoSidebar)
2245
2343
  )
2246
2344
  ) {
2247
2345
  this.tooltipDisplay = true;
@@ -2251,23 +2349,6 @@ export default {
2251
2349
  });
2252
2350
  }
2253
2351
  },
2254
- hasTooltipEntry: function () {
2255
- const {
2256
- components,
2257
- destinations,
2258
- origins,
2259
- provenanceTaxonomy,
2260
- provenanceTaxonomyLabel
2261
- } = this.tooltipEntry;
2262
-
2263
- return Boolean(
2264
- components?.length ||
2265
- destinations?.length ||
2266
- origins?.length ||
2267
- provenanceTaxonomy?.length ||
2268
- provenanceTaxonomyLabel?.length
2269
- );
2270
- },
2271
2352
  /**
2272
2353
  * Move the map to the left side
2273
2354
  * to the visible area of the feature IDs
@@ -2277,8 +2358,8 @@ export default {
2277
2358
  moveMap: function (featureIds, options = {}) {
2278
2359
  if (this.mapImp) {
2279
2360
  const { offsetX = 0, offsetY = 0, zoom = 4 } = options;
2280
- const Map = this.mapImp._map;
2281
- const bbox = this.mapImp._bounds.toArray();
2361
+ const Map = this.mapImp.map;
2362
+ const bbox = this.mapImp.bounds.toArray();
2282
2363
 
2283
2364
  // Zoom the map to features first
2284
2365
  this.mapImp.zoomToFeatures(featureIds, { noZoomIn: true });
@@ -2403,10 +2484,10 @@ export default {
2403
2484
  state['biologicalSex'] = identifier.biologicalSex
2404
2485
  if (identifier && identifier.uuid) state['uuid'] = identifier.uuid
2405
2486
  state['viewingMode'] = this.viewingMode
2406
- state['searchTerm'] = this.statesTracking.activeTerm
2487
+ state['searchTerm'] = this.searchTerm
2407
2488
  state['flightPath3D'] = this.flightPath3DRadio
2408
2489
  state['colour'] = this.colourRadio
2409
- state['outlinesRadio'] = this.outlinesRadio
2490
+ state['outlines'] = this.outlinesRadio
2410
2491
  state['background'] = this.currentBackground
2411
2492
  if (this.offlineAnnotationEnabled) {
2412
2493
  state['offlineAnnotations'] = sessionStorage.getItem('anonymous-annotation')
@@ -2456,11 +2537,7 @@ export default {
2456
2537
  if (state.background) this.backgroundChangeCallback(state.background)
2457
2538
  if (state.searchTerm) {
2458
2539
  const searchTerm = state.searchTerm
2459
- if (state.viewingMode === "Neuron Connection") {
2460
- this.highlightConnectedPaths([searchTerm])
2461
- } else {
2462
- this.searchAndShowResult(searchTerm, true)
2463
- }
2540
+ this.searchAndShowResult(searchTerm, true)
2464
2541
  }
2465
2542
  this.setVisibilityState(state)
2466
2543
  }
@@ -2538,7 +2615,6 @@ export default {
2538
2615
 
2539
2616
  let promise1 = this.mapManagerRef.loadMap(
2540
2617
  identifier,
2541
- this.$refs.display,
2542
2618
  this.eventCallback(),
2543
2619
  {
2544
2620
  //fullscreenControl: false,
@@ -2547,6 +2623,7 @@ export default {
2547
2623
  minZoom: this.minZoom,
2548
2624
  tooltips: this.tooltips,
2549
2625
  minimap: minimap,
2626
+ container: this.$refs.display,
2550
2627
  // tooltipDelay: 15, // new feature to delay tooltips showing
2551
2628
  }
2552
2629
  )
@@ -2592,9 +2669,6 @@ export default {
2592
2669
  if (this.mapImp) {
2593
2670
  this.mapImp.resize()
2594
2671
  this.showMinimap(this.displayMinimap)
2595
- if (this.mapImp._minimap) {
2596
- this.mapImp._minimap._miniMap.resize()
2597
- }
2598
2672
  }
2599
2673
  } catch {
2600
2674
  console.error('Map resize error')
@@ -2610,7 +2684,6 @@ export default {
2610
2684
  if (this.mapImp.options?.style === 'functional') {
2611
2685
  this.isFC = true
2612
2686
  }
2613
- console.log(this.mapImp)
2614
2687
  this.mapImp.setBackgroundOpacity(1)
2615
2688
  this.backgroundChangeCallback(this.currentBackground)
2616
2689
  this.pathways = this.mapImp.pathTypes()
@@ -2624,7 +2697,6 @@ export default {
2624
2697
  this.loading = false
2625
2698
  this.computePathControlsMaximumHeight()
2626
2699
  this.mapResize()
2627
- this.handleMapClick();
2628
2700
  this.setInitMapState();
2629
2701
  /**
2630
2702
  * This is ``onFlatmapReady`` event.
@@ -2632,29 +2704,6 @@ export default {
2632
2704
  */
2633
2705
  this.$emit('ready', this)
2634
2706
  },
2635
- /**
2636
- * @public
2637
- * Function to handle mouse click on map area
2638
- * after the map is loaded.
2639
- */
2640
- handleMapClick: function () {
2641
- const _map = this.mapImp._map;
2642
- if (_map) {
2643
- _map.on('click', (e) => {
2644
- //A little logic to make sure we are keeping track
2645
- //of selected term
2646
- if (this.statesTracking.activeClick) {
2647
- this.statesTracking.activeClick = false
2648
- } else {
2649
- this.statesTracking.activeTerm = ""
2650
- }
2651
- if (!this.connectivityDataSource) {
2652
- this.$emit('connectivity-info-close');
2653
- }
2654
- this.connectivityDataSource = ''; // reset
2655
- });
2656
- }
2657
- },
2658
2707
  /**
2659
2708
  * @public
2660
2709
  * Function to show or hide the minimap
@@ -2689,12 +2738,11 @@ export default {
2689
2738
  } else if (this.viewingMode === "Annotation") {
2690
2739
  this.manualAbortedOnClose()
2691
2740
  }
2692
- this.statesTracking.activeTerm = ""
2741
+ this.searchTerm = ""
2693
2742
  return true
2694
2743
  } else {
2695
2744
  const searchResults = this.mapImp.search(term)
2696
2745
  if (searchResults?.results?.length) {
2697
- this.statesTracking.activeTerm = term
2698
2746
  this.mapImp.showSearchResults(searchResults)
2699
2747
  if (displayInfo) {
2700
2748
  let featureId = undefined;
@@ -2706,18 +2754,18 @@ export default {
2706
2754
  if (featureId) {
2707
2755
  const feature = this.mapImp.featureProperties(featureId)
2708
2756
  const data = {
2709
- resource: [feature.source],
2710
- feature: {...feature, models: feature.models || feature.source},
2757
+ resource: [feature.models],
2758
+ feature: feature,
2711
2759
  label: feature.label,
2712
2760
  provenanceTaxonomy: feature.taxons,
2761
+ alert: feature.alert,
2713
2762
  }
2714
2763
  if (this.viewingMode === "Exploration" || this.viewingMode === "Annotation") {
2715
- this.featuresAlert = feature.alert
2716
- this.checkAndCreatePopups(data)
2764
+ this.checkAndCreatePopups([data])
2717
2765
  } else if (this.viewingMode === 'Neuron Connection') {
2718
- setTimeout(() => {
2719
- this.highlightConnectedPaths(data.resource)
2720
- }, 1000)
2766
+ this.retrieveConnectedPaths(data.resource).then((paths) => {
2767
+ this.zoomToFeatures(paths)
2768
+ })
2721
2769
  }
2722
2770
  this.mapImp.showPopup(featureId, capitalise(feature.label), {
2723
2771
  className: 'custom-popup',
@@ -2726,6 +2774,7 @@ export default {
2726
2774
  })
2727
2775
  }
2728
2776
  }
2777
+ this.searchTerm = term
2729
2778
  return true
2730
2779
  } else this.mapImp.clearSearchResults()
2731
2780
  }
@@ -2742,6 +2791,9 @@ export default {
2742
2791
  if (this.mapImp) return this.mapImp.search(term)
2743
2792
  return []
2744
2793
  },
2794
+ onActionClick: function (data) {
2795
+ EventBus.emit('onActionClick', data)
2796
+ },
2745
2797
  },
2746
2798
  props: {
2747
2799
  /**
@@ -2947,7 +2999,6 @@ export default {
2947
2999
  return {
2948
3000
  flatmapAPI: this.flatmapAPI,
2949
3001
  sparcAPI: this.sparcAPI,
2950
- getFeaturesAlert: () => this.featuresAlert,
2951
3002
  userApiKey: this.userToken,
2952
3003
  }
2953
3004
  },
@@ -2956,7 +3007,7 @@ export default {
2956
3007
  sensor: null,
2957
3008
  mapManagerRef: undefined,
2958
3009
  flatmapQueries: undefined,
2959
- annotationEntry: {},
3010
+ annotationEntry: [],
2960
3011
  //tooltip display has to be set to false until it is rendered
2961
3012
  //for the first time, otherwise it may display an arrow at a
2962
3013
  //undesired location.
@@ -3001,11 +3052,10 @@ export default {
3001
3052
  availableBackground: ['white', 'lightskyblue', 'black'],
3002
3053
  loading: false,
3003
3054
  flatmapMarker: flatmapMarker,
3004
- tooltipEntry: createUnfilledTooltipData(),
3055
+ tooltipEntry: [],
3005
3056
  connectivityDataSource: '',
3006
3057
  connectivityTooltipVisible: false,
3007
3058
  drawerOpen: false,
3008
- featuresAlert: undefined,
3009
3059
  flightPath3DRadio: false,
3010
3060
  displayFlightPathOption: false,
3011
3061
  colourRadio: true,
@@ -3065,10 +3115,7 @@ export default {
3065
3115
  without: true,
3066
3116
  }
3067
3117
  }),
3068
- statesTracking: markRaw({
3069
- activeClick: false,
3070
- activeTerm: "",
3071
- }),
3118
+ searchTerm: "",
3072
3119
  taxonLeaveDelay: undefined,
3073
3120
  }
3074
3121
  },
@@ -3206,7 +3253,7 @@ export default {
3206
3253
  if (this.mapManager) {
3207
3254
  this.mapManagerRef = this.mapManager;
3208
3255
  } else {
3209
- this.mapManagerRef = markRaw(new flatmap.MapManager(this.flatmapAPI));
3256
+ this.mapManagerRef = markRaw(new flatmap.MapViewer(this.flatmapAPI, { container: undefined }));
3210
3257
  /**
3211
3258
  * The event emitted after a new mapManager is loaded.
3212
3259
  * This mapManager can be used to create new flatmaps.