diff --git a/.env b/.env
index d616f95f..dedacd3d 100644
--- a/.env
+++ b/.env
@@ -6,3 +6,4 @@
//VITE_PENNSIEVE_API_LOCATION=https://api.pennsieve.io
#VITE_API_LOCATION=https://sparc-api.herokuapp.com/
VITE_SPARC_API=https://alan-wu-portal-api.herokuapp.com/
+VITE_APP_CELL_CARDS_API=http://localhost:8787/cellCardsData.json
diff --git a/src/App.vue b/src/App.vue
index 3977e04e..0fa4c9ab 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -161,6 +161,7 @@ export default {
ElIconSetting: shallowRef(ElIconSetting),
routerIsReady: false,
showLongLabel: true,
+ isMapLoaded: false,
}
},
computed: {
@@ -177,6 +178,7 @@ export default {
algoliaId: import.meta.env.VITE_ALGOLIA_ID,
pennsieveApi: import.meta.env.VITE_PENNSIEVE_API_LOCATION,
flatmapAPI: this.$route.query.flatmapserver ? this.$route.query.flatmapserver : import.meta.env.VITE_FLATMAPAPI_LOCATION,
+ cellCardsApi: import.meta.env.VITE_APP_CELL_CARDS_API,
rootUrl: import.meta.env.VITE_ROOT_URL,
}
}
@@ -251,7 +253,6 @@ export default {
}
getShareLink(1)
});
-
},
setFlatmap: function() {
this.$refs.map.setCurrentEntry(
@@ -317,6 +318,7 @@ export default {
},
mapIsLoaded: function(map) {
console.log("map is loaded", map)
+ this.isMapLoaded = true;
// map.changeViewingMode('Annotation')
},
viewerIsReady: function() {
diff --git a/src/components.d.ts b/src/components.d.ts
index 95232a3c..0ebd6d22 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -19,6 +19,8 @@ declare module 'vue' {
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol']
+ ElCollapse: typeof import('element-plus/es')['ElCollapse']
+ ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@@ -28,8 +30,11 @@ declare module 'vue' {
ElIconArrowDown: typeof import('@element-plus/icons-vue')['ArrowDown']
ElIconArrowUp: typeof import('@element-plus/icons-vue')['ArrowUp']
ElIconCompass: typeof import('@element-plus/icons-vue')['Compass']
+ ElIconCpu: typeof import('@element-plus/icons-vue')['Cpu']
+ ElIconDocument: typeof import('@element-plus/icons-vue')['Document']
ElIconEditPen: typeof import('@element-plus/icons-vue')['EditPen']
ElIconMoreFilled: typeof import('@element-plus/icons-vue')['MoreFilled']
+ ElIconSGrid: typeof import('@element-plus/icons-vue')['SGrid']
ElIconShare: typeof import('@element-plus/icons-vue')['Share']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
diff --git a/src/components/ContentVuer.vue b/src/components/ContentVuer.vue
index 7b44eb3f..28e595d5 100644
--- a/src/components/ContentVuer.vue
+++ b/src/components/ContentVuer.vue
@@ -146,6 +146,9 @@ export default {
onShowConnectivityTooltips: function(payload) {
this.$refs.viewer?.showConnectivityTooltips(payload);
},
+ onShowFeatureInFlatmap: function(payload) {
+ this.$refs.viewer?.showFeatureInFlatmap(payload);
+ },
onShowReferenceConnectivity: function(payload) {
this.$refs.viewer?.showConnectivitiesByReference(payload);
},
diff --git a/src/components/DialogToolbarContent.vue b/src/components/DialogToolbarContent.vue
index 6d386815..ce9ed18a 100644
--- a/src/components/DialogToolbarContent.vue
+++ b/src/components/DialogToolbarContent.vue
@@ -29,6 +29,9 @@
+
+
+
{{ globalSettings.viewingMode }}
@@ -56,6 +59,9 @@
+
+
+
{{ key }}
@@ -268,7 +274,7 @@
-
@@ -435,6 +441,7 @@ import {
ElButton as Button,
ElCheckbox as Checkbox,
ElCol as Col,
+ ElIcon,
ElIcon as Icon,
ElInput as Input,
ElPopover as Popover,
@@ -520,7 +527,8 @@ export default {
viewingModes: {
'Exploration': 'Find relevant research and view detail of neural pathways by selecting a pathway to view its connections and data sources',
'Neuron Connection': 'Discover neuron connections by selecting a feature and viewing its associated network connections',
- 'Annotation': ['View feature annotations', 'Add, comment on and view feature annotations']
+ 'Annotation': ['View feature annotations', 'Add, comment on and view feature annotations'],
+ 'Cell Type': 'Explore cell cards for different cell types and their associated data',
},
authorisedUser: false,
mapLoaded: false,
@@ -546,12 +554,19 @@ export default {
} else if (value === 'Annotation') {
this.globalSettings.displayMarkers = false;
this.globalSettings.interactiveMode = 'dataset';
+ } else if (value === 'Cell Type') {
+ this.globalSettings.displayMarkers = true;
+ this.globalSettings.interactiveMode = 'connectivity';
} else {
this.globalSettings.displayMarkers = false;
this.globalSettings.interactiveMode = 'connectivity';
}
this.updateGlobalSettings('viewingMode');
+
+ if (value === 'Cell Type') {
+ this.$emit('open-cell-card-explorer');
+ }
}
},
updateGlobalSettings: function(changedKey) {
@@ -559,7 +574,7 @@ export default {
this.settingsStore.updateGlobalSettings(this.globalSettings);
// display marker update
- if (updatedSettings.includes('displayMarkers')) {
+ if (updatedSettings.includes('displayMarkers') || updatedSettings.includes('interactiveMode')) {
EventBus.emit('markerUpdate');
}
if (updatedSettings.includes('interactiveMode')) {
diff --git a/src/components/MapContent.vue b/src/components/MapContent.vue
index 79b078e5..f326a55b 100644
--- a/src/components/MapContent.vue
+++ b/src/components/MapContent.vue
@@ -411,6 +411,7 @@ export default {
this.options.algoliaId ? this.settingsStore.updateAlgoliaId(this.options.algoliaId) : null;
this.options.pennsieveApi ? this.settingsStore.updatePennsieveApi(this.options.pennsieveApi) : null;
this.options.flatmapAPI ? this.settingsStore.updateFlatmapAPI(this.options.flatmapAPI) : null;
+ this.options.cellCardsApi ? this.settingsStore.updateCellCardsApi(this.options.cellCardsApi) : null;
this.options.rootUrl ? this.settingsStore.updateRootUrl(this.options.rootUrl) : null;
}
this.settingsStore.updateAllClosable(this.allClosable);
diff --git a/src/components/SplitDialog.vue b/src/components/SplitDialog.vue
index 3d8cf817..5ddfecd4 100644
--- a/src/components/SplitDialog.vue
+++ b/src/components/SplitDialog.vue
@@ -173,6 +173,10 @@ export default {
const wholeBodyScaffoldIDs = [307, '307'];
const sckanVersion = Object.keys(this.connectivitiesStore.globalConnectivities)
.find(key => key.includes('sckan'));
+ // Filter active entries and emit their resources (species)
+ const activeEntries = this.entries.filter((entry) => activePaneIDs.includes(entry.id));
+ const activeSpecies = activeEntries.map((entry) => entry.resource);
+ this.$emit('update-active-species', activeSpecies);
const uuids = Array.from(
new Set(
this.entries
@@ -639,6 +643,12 @@ export default {
content.onShowConnectivityTooltips(payload);
});
});
+ EventBus.on('soma-location-hovered', (payload) => {
+ const contents = this.getActiveContents();
+ contents.forEach((content) => {
+ content.onShowFeatureInFlatmap(payload);
+ });
+ });
EventBus.on('connectivity-source-change', (payload) => {
const contents = this.getActiveContents();
//Use ongoingSource array to make sure, the knowledge
diff --git a/src/components/SplitFlow.vue b/src/components/SplitFlow.vue
index 72ae6c1e..50ed6375 100644
--- a/src/components/SplitFlow.vue
+++ b/src/components/SplitFlow.vue
@@ -11,6 +11,7 @@
@onFullscreen="onFullscreen"
@local-search="onDisplaySearch"
@fetch-suggestions="fetchSuggestions"
+ @open-cell-card-explorer="openCellCardExplorerFromToolbar"
ref="dialogToolbar"
/>
@@ -31,6 +32,7 @@
:filterOptions="filterOptions"
:showVisibilityFilter="showVisibilityFilter"
:showLongLabel="showLongLabel"
+ :showCellCards="showCellCards"
@tabClicked="onSidebarTabClicked"
@tabClosed="onSidebarTabClosed"
@actionClick="actionClick"
@@ -52,6 +54,8 @@
@show-connectivity-graph="onShowConnectivityGraph"
@filter-visibility="onFilterVisibility"
@connectivity-item-close="onConnectivityItemClose"
+ @soma-location-hovered="showSomaLocation"
+ @soma-locations-ready="onSomaLocationsReady"
@trackEvent="trackEvent"
/>
@@ -186,6 +191,8 @@ export default {
filterVisibility: true,
filterOptions: [],
annotationHighlight: [],
+ showCellCards: false,
+ cellCardSomaLocations: [],
}
},
watch: {
@@ -212,6 +219,20 @@ export default {
},
},
methods: {
+ openCellCardExplorerFromToolbar: function () {
+ this.openCellCardExplorer();
+ },
+ openCellCardExplorer: function (payload = {}) {
+ this.showCellCards = true;
+
+ if (this.$refs.sideBar) {
+ this.$refs.sideBar.tabClicked({ id: 4, type: 'cellCardExplorer' });
+ this.$refs.sideBar.setDrawerOpen(true);
+ if (payload && (payload.filters?.length || payload.query)) {
+ this.$refs.sideBar.openCellCardExplorerSearch(payload.filters || [], payload.query || '');
+ }
+ }
+ },
onFilterVisibility: function (state) {
this.filterVisibility = state;
const filterExpression = {
@@ -321,6 +342,8 @@ export default {
});
this.filterTriggered = true;
}
+ } else if (action.type === "OpenCellCardExplorer") {
+ this.openCellCardExplorer(action);
} else if (action.type == "URL") {
window.open(action.resource, "_blank");
} else if (action.type == "Facet") {
@@ -595,6 +618,25 @@ export default {
onConnectivityHovered: function (data) {
EventBus.emit('connectivity-hovered', data);
},
+ showSomaLocation: function (name) {
+ EventBus.emit('soma-location-hovered', name);
+ },
+ onSomaLocationsReady: function (somaLocations) {
+ const normalizedSomaLocations = (Array.isArray(somaLocations) ? somaLocations : [])
+ .map((item) => {
+ return {
+ label: String(item?.label || '').trim(),
+ curie: String(item?.curie || '').trim(),
+ count: Number(item?.count || 0),
+ };
+ })
+ .filter((item) => item.label);
+
+ this.cellCardSomaLocations = [...new Map(
+ normalizedSomaLocations.map((item) => [item.label.toLowerCase(), item])
+ ).values()];
+ this.updateSomaLocationMarkers(this.cellCardSomaLocations);
+ },
onConnectivitySourceChange: function (data) {
this.connectivityExplorerClicked.push(true);
EventBus.emit('connectivity-source-change', data);
@@ -727,8 +769,35 @@ export default {
});
}
}
+ } else if (data.tabType === 'cellType') {
+ const filterValues = data.filter.filter(f => (f.facet && f.facet.toLowerCase() !== 'show all'))
+ .map((f) => f.tagLabel)
+ .join(', ');
+ const searchValue = data.query;
+
+ if (filterValues) {
+ Tagging.sendEvent({
+ 'event': 'interaction_event',
+ 'event_name': 'portal_maps_action_filter',
+ 'category': filterValues,
+ 'location': 'map_sidebar_cell_card_filter'
+ });
+ }
+
+ if (searchValue) {
+ Tagging.sendEvent({
+ 'event': 'interaction_event',
+ 'event_name': 'portal_maps_action_search',
+ 'category': searchValue,
+ 'location': 'map_sidebar_cell_card_search'
+ });
+ }
}
},
+ updateSomaLocationMarkers: function (data) {
+ this.settingsStore.updateCellCardSomaLocations(data);
+ EventBus.emit("markerUpdate");
+ },
updateMarkers: function (data) {
this.settingsStore.updateMarkers(data);
EventBus.emit("markerUpdate");
@@ -818,6 +887,10 @@ export default {
// - connectivity knowledge is loaded
// - if sidebar state is not restored yet
const sidebarState = state?.sidebar;
+ // Restore Cell Card Explorer
+ if (sidebarState?.activeTabId === 4) {
+ this.showCellCards = true;
+ }
if (!this.sidebarStateRestored && sidebarState && this.$refs.sideBar && this.connectivityKnowledge?.length) {
if (sidebarState.connectivityEntries?.length) {
this.restoreConnectivityEntries(sidebarState.connectivityEntries);
@@ -890,6 +963,11 @@ export default {
})
}
},
+ updateActiveSpeciesForEntries: function (activeSpecies) {
+ if (this.$refs.sideBar) {
+ this.$refs.sideBar.updateActiveSpeciesForEntries(activeSpecies);
+ }
+ },
contextUpdate: function (payload) {
EventBus.emit("contextUpdate", payload);
},
@@ -1089,6 +1167,7 @@ export default {
PENNSIEVE_API_LOCATION: this.settingsStore.pennsieveApi,
ROOT_URL: this.settingsStore.rootUrl,
FLATMAPAPI_LOCATION: this.settingsStore.flatmapAPI,
+ CELL_CARDS_API: this.settingsStore.cellCardsApi,
};
},
entries: function() {
diff --git a/src/components/viewers/Flatmap.vue b/src/components/viewers/Flatmap.vue
index 909e1879..746ad8f1 100644
--- a/src/components/viewers/Flatmap.vue
+++ b/src/components/viewers/Flatmap.vue
@@ -166,6 +166,28 @@ export default {
flatmap.showConnectivityTooltips(payload);
}
},
+ showFeatureInFlatmap: function (payload) {
+ if (this?.alive && this.flatmapReady) {
+ const flatmap = this.$refs.flatmap;
+
+ if (payload) {
+ const searchResults = flatmap.searchSuggestions(payload);
+ let geoJSONID = undefined;
+
+ if (searchResults?.results.length) {
+ const featureId = searchResults?.results[0].featureId;
+ geoJSONID = featureId;
+ }
+
+ if (geoJSONID) {
+ flatmap.showPopup(geoJSONID, payload);
+ }
+ } else {
+ flatmap.closeTooltip();
+ this.showConnectivityTooltips({connectivityInfo: null, data: []});
+ }
+ }
+ },
showConnectivitiesByReference: function (payload) {
if (this?.alive) {
const currentFlatmap = this.$refs.flatmap;
diff --git a/src/components/viewers/MultiFlatmap.vue b/src/components/viewers/MultiFlatmap.vue
index 3ddacd8c..f6e5a783 100644
--- a/src/components/viewers/MultiFlatmap.vue
+++ b/src/components/viewers/MultiFlatmap.vue
@@ -200,7 +200,7 @@ export default {
updateProvCard: function() {
const imp = this.getFlatmapImp();
if (imp) {
- let provClone = {id: this.entry.id, prov: imp.mapMetadata};
+ let provClone = {id: this.entry.id, prov: imp.mapMetadata, species: this.activeSpecies};
EventBus.emit("mapImpProv", provClone);
this.$emit("flatmap-provenance-ready", provClone);
}
@@ -355,6 +355,28 @@ export default {
flatmap.showConnectivityTooltips(payload);
}
},
+ showFeatureInFlatmap: function (payload) {
+ if (this?.alive && this.flatmapReady) {
+ const flatmap = this.$refs.multiflatmap.getCurrentFlatmap();
+
+ if (payload) {
+ const searchResults = flatmap.searchSuggestions(payload);
+ let geoJSONID = undefined;
+
+ if (searchResults?.results.length) {
+ const featureId = searchResults?.results[0].featureId;
+ geoJSONID = featureId;
+ }
+
+ if (geoJSONID) {
+ flatmap.showPopup(geoJSONID, payload);
+ }
+ } else {
+ flatmap.closeTooltip();
+ this.showConnectivityTooltips({connectivityInfo: null, data: []});
+ }
+ }
+ },
showConnectivitiesByReference: function (payload) {
if (this?.alive && this.flatmapReady && this.$refs.multiflatmap) {
const currentFlatmap = this.$refs.multiflatmap.getCurrentFlatmap();
diff --git a/src/mixins/ContentMixin.js b/src/mixins/ContentMixin.js
index b08052d0..a654ffe6 100644
--- a/src/mixins/ContentMixin.js
+++ b/src/mixins/ContentMixin.js
@@ -184,6 +184,20 @@ export default {
if (resource.eventType == "click") {
result.eventType = "selected";
if (resource.feature.type == "marker") {
+ // Cell card explorer - cell type markers
+ if (!resource.feature['dataset-terms']) {
+ returnedAction = {
+ type: "OpenCellCardExplorer",
+ label: resource.feature.label,
+ models: resource.feature.models,
+ query: '',
+ filters: [{
+ facet: resource.feature.label,
+ term: 'Soma location',
+ }],
+ };
+ fireResourceSelected = true;
+ } else {
let label = result.internalName;
// `resource.feature.id` is the marker identifier (not featureId or models)
if (this.settingsStore.isFeaturedMarkerIdentifier(resource.feature.id)) {
@@ -236,6 +250,7 @@ export default {
const flatmap = this.$refs.multiflatmap.getCurrentFlatmap().mapImp;
flatmap.clearSearchResults();
}
+ }
}
} else if (resource.eventType == "mouseenter") {
result.eventType = "highlighted";
@@ -574,6 +589,9 @@ export default {
showConnectivityTooltips: function () {
return;
},
+ showFeatureInFlatmap: function () {
+ return;
+ },
setVisibilityFilter: function() {
return;
},
diff --git a/src/mixins/DynamicMarkerMixin.js b/src/mixins/DynamicMarkerMixin.js
index 59a487da..880f7caf 100644
--- a/src/mixins/DynamicMarkerMixin.js
+++ b/src/mixins/DynamicMarkerMixin.js
@@ -9,7 +9,17 @@ const removeDuplicates = function (arrayOfAnything) {
JSON.parse(e)
)
}
-
+
+const getCellTypeSomaLocations = function (somaLocations) {
+ return (Array.isArray(somaLocations) ? somaLocations : [])
+ .map((location) => ({
+ label: String(location?.label || '').trim(),
+ curie: String(location?.curie || '').trim(),
+ count: Number(location?.count || 0),
+ }))
+ .filter((location) => location.curie && location.count > 0)
+}
+
/* eslint-disable no-alert, no-console */
export default {
computed: {
@@ -34,17 +44,30 @@ export default {
flatmapMarkerUpdate(flatmap) {
if (!this.flatmapReady) return;
- let flatmapImp = flatmap;
- if (!flatmapImp)
- flatmapImp = this.getFlatmapImp();
+ const flatmapImp = flatmap ?? this.getFlatmapImp();
if (flatmapImp) {
- // Set the dataset markers
- let markers = this.settingsStore.globalSettings.displayMarkers ? this.settingsStore.markers : [];
- markers = removeDuplicates(markers);
+ const displayMarkers = this.settingsStore.globalSettings.displayMarkers;
+ const isCellTypeViewingMode = this.settingsStore.globalSettings.viewingMode === "Cell Type";
+ const markers = !displayMarkers || isCellTypeViewingMode
+ ? []
+ : removeDuplicates(this.settingsStore.markers);
+ const somaLocations = !displayMarkers || !isCellTypeViewingMode
+ ? []
+ : getCellTypeSomaLocations(this.settingsStore.cellCardSomaLocations);
+
flatmapImp.clearMarkers();
flatmapImp.clearDatasetMarkers();
- flatmapImp.addDatasetMarkers(markers);
+
+ if (typeof flatmapImp.clearSomaLocationMarkers === "function") {
+ flatmapImp.clearSomaLocationMarkers();
+ }
+
+ if (isCellTypeViewingMode && typeof flatmapImp.addSomaLocationMarkers === "function") {
+ flatmapImp.addSomaLocationMarkers(somaLocations);
+ } else {
+ flatmapImp.addDatasetMarkers(markers);
+ }
// Set the featured markers
if (this.entry.type === "MultiFlatmap") {
diff --git a/src/stores/entries.js b/src/stores/entries.js
index 0cddf703..e401b68e 100644
--- a/src/stores/entries.js
+++ b/src/stores/entries.js
@@ -41,6 +41,9 @@ export const useEntriesStore = defineStore('entries', {
const sckanVersion = getKnowledgeSourceFromProvenance(prov.prov);
entry['uuid'] = prov.prov.uuid;
entry['sckanVersion'] = sckanVersion;
+ if (entry.type === 'MultiFlatmap' && prov.species) {
+ entry['resource'] = prov.species;
+ }
}
},
updateLabelForEntry(targetEntry, label) {
diff --git a/src/stores/settings.js b/src/stores/settings.js
index 510ee4b7..bdfec547 100644
--- a/src/stores/settings.js
+++ b/src/stores/settings.js
@@ -12,12 +12,14 @@ export const useSettingsStore = defineStore('settings', {
algoliaId: undefined,
pennsieveApi: undefined,
flatmapAPI: undefined,
+ cellCardsApi: undefined,
mapManager: undefined,
rootUrl: undefined,
facets: { species: [], gender: [], organ: [] },
appliedFacets: [],
numberOfDatasetsForFacets: [],
markers: [],
+ cellCardSomaLocations: [],
hoverAnatomies: [],
hoverOrgans: [],
hoverDOI: '',
@@ -110,6 +112,9 @@ export const useSettingsStore = defineStore('settings', {
updateFlatmapAPI(flatmapAPI) {
this.flatmapAPI = flatmapAPI;
},
+ updateCellCardsApi(cellCardsApi) {
+ this.cellCardsApi = cellCardsApi;
+ },
updateMapManager(mapManager) {
this.mapManager = mapManager;
},
@@ -119,6 +124,9 @@ export const useSettingsStore = defineStore('settings', {
updateMarkers(markers) {
this.markers = markers;
},
+ updateCellCardSomaLocations(somaLocations) {
+ this.cellCardSomaLocations = somaLocations;
+ },
updateHoverFeatures(anatomies, organs, doi, connectivity) {
this.hoverAnatomies = anatomies;
this.hoverOrgans = organs;