UNPKG

@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
// 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> ` }