@xulab-research/vue-anatomogram
Version:
Interactive anatomical diagrams for Vue.js applications - A Vue-compatible rewrite of EBI anatomogram
220 lines (203 loc) • 5.4 kB
JavaScript
// src/AnatomogramComponent.js - Vue 3 component wrapper
import { ref, onMounted, onUnmounted, watch, inject } from 'vue'
import Main from './Main.js'
export default {
name: 'Anatomogram',
props: {
species: {
type: String,
default: 'homo_sapiens'
},
showIds: {
type: Array,
default: () => []
},
highlightIds: {
type: Array,
default: () => []
},
selectIds: {
type: Array,
default: () => []
},
showTitle: {
type: Boolean,
default: true
},
showControls: {
type: Boolean,
default: true
},
width: {
type: [String, Number],
default: '100%'
},
height: {
type: [String, Number],
default: '400px'
}
},
emits: [
'click',
'mouseover',
'mouseout',
'organ-selected',
'view-changed',
'ready',
'error'
],
setup(props, { emit }) {
const containerRef = ref()
const anatomogramInstance = ref(null)
const isReady = ref(false)
const error = ref(null)
// Inject global config if available
const globalConfig = inject('anatomogram-config', {})
// Initialize anatomogram instance
const initAnatomogram = async () => {
try {
if (!containerRef.value) return
const options = {
species: props.species,
showIds: props.showIds,
highlightIds: props.highlightIds,
selectIds: props.selectIds,
showTitle: props.showTitle,
showControls: props.showControls,
...globalConfig,
// Event handlers
onClick: (ids, event) => {
emit('click', ids, event)
emit('organ-selected', ids)
},
onMouseOver: (ids, event) => {
emit('mouseover', ids, event)
},
onMouseOut: (ids, event) => {
emit('mouseout', ids, event)
},
onViewChanged: (view) => {
emit('view-changed', view)
}
}
anatomogramInstance.value = new Main(options)
await anatomogramInstance.value.mount(containerRef.value)
isReady.value = true
emit('ready', anatomogramInstance.value)
} catch (err) {
error.value = err.message
emit('error', err)
console.error('Anatomogram initialization failed:', err)
}
}
// Update anatomogram when props change
const updateAnatomogram = () => {
if (anatomogramInstance.value && isReady.value) {
anatomogramInstance.value.update({
showIds: props.showIds,
highlightIds: props.highlightIds,
selectIds: props.selectIds,
showTitle: props.showTitle,
showControls: props.showControls
})
}
}
// Watch for prop changes
watch([
() => props.showIds,
() => props.highlightIds,
() => props.selectIds,
() => props.showTitle,
() => props.showControls
], updateAnatomogram, { deep: true })
// Watch species change (requires reinit)
watch(() => props.species, () => {
if (anatomogramInstance.value) {
anatomogramInstance.value.destroy()
anatomogramInstance.value = null
isReady.value = false
}
initAnatomogram()
})
// Lifecycle hooks
onMounted(() => {
initAnatomogram()
})
onUnmounted(() => {
if (anatomogramInstance.value) {
anatomogramInstance.value.destroy()
}
})
// Expose methods for template refs
const highlightOrgans = (ids) => {
if (anatomogramInstance.value) {
anatomogramInstance.value.update({ highlightIds: ids })
}
}
const showOrgans = (ids) => {
if (anatomogramInstance.value) {
anatomogramInstance.value.update({ showIds: ids })
}
}
const selectOrgans = (ids) => {
if (anatomogramInstance.value) {
anatomogramInstance.value.update({ selectIds: ids })
}
}
const clearAll = () => {
if (anatomogramInstance.value) {
anatomogramInstance.value.update({
showIds: [],
highlightIds: [],
selectIds: []
})
}
}
const getAnatomogramInstance = () => anatomogramInstance.value
return {
containerRef,
isReady,
error,
highlightOrgans,
showOrgans,
selectOrgans,
clearAll,
getAnatomogramInstance
}
},
template: `
<div
ref="containerRef"
class="vue-anatomogram-container"
:style="{
width: typeof width === 'number' ? width + 'px' : width,
height: typeof height === 'number' ? height + 'px' : height,
minHeight: '200px',
position: 'relative'
}"
>
<div v-if="error" class="anatomogram-error" style="
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #dc3545;
text-align: center;
padding: 20px;
">
<h4>Error loading anatomogram</h4>
<p>{{ error }}</p>
</div>
<div v-else-if="!isReady" class="anatomogram-loading" style="
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #6c757d;
text-align: center;
">
Loading anatomogram...
</div>
</div>
`
}