@itk-viewer/element
Version:
Web Component for multi-dimensional viewer
168 lines • 7.91 kB
JavaScript
import { SelectorController } from 'xstate-lit';
import { TransferFunctionEditor } from '@itk-viewer/transfer-function-editor/TransferFunctionEditor.js';
import { ColorTransferFunction } from '@itk-viewer/transfer-function-editor/ColorTransferFunction.js';
const equalFloats = (a, b) => Math.abs(a - b) < 1e-6;
export class ViewControls {
constructor(host) {
this.selectedComponent = 0;
this.view = '2d';
this.colorTransferFunctions = new Map(); // component -> colorTransferFunction
this.onSlice = (event) => {
const target = event.target;
const slice = Number(target.value);
this.actor.send({
type: 'setSlice',
slice,
});
};
this.onAxis = (event) => {
const target = event.target;
const axis = target.value;
this.actor.send({
type: 'setAxis',
axis,
});
};
this.onScale = (event) => {
const target = event.target;
const scale = Number(target.value);
this.actor.send({ type: 'setScale', scale });
};
this.onSelectedComponent = (component) => {
this.selectedComponent = component;
this.updateTransferFunctionEditor();
};
this.onColorMap = (colorMap) => {
this.imageActor?.send({
type: 'colorMap',
component: this.selectedComponent,
colorMap,
});
};
this.setTransferFunctionContainer = (container) => {
if (container) {
this.transferFunctionEditor = new TransferFunctionEditor(container);
this.transferFunctionEditor.setColorTransferFunction(new ColorTransferFunction());
this.updateTransferFunctionEditor();
this.transferFunctionEditor.eventTarget.addEventListener('colorRange', (e) => {
const range = e.detail;
this.imageActor?.send({
type: 'normalizedColorRange',
range,
component: this.selectedComponent,
});
});
this.transferFunctionEditor.eventTarget.addEventListener('updated', (e) => {
const points = e.detail;
this.imageActor?.send({
type: 'normalizedOpacityPoints',
points,
component: this.selectedComponent,
});
});
}
else {
this.transferFunctionEditor?.remove();
this.transferFunctionEditor = undefined;
}
};
this.onViewSnapshot = (snapshot) => {
const { imageActor, spawned } = snapshot.context;
if (this.imageActor !== imageActor) {
this.imageSubscription?.unsubscribe();
this.imageSubscription = undefined;
}
this.imageActor = imageActor;
// If imageActor exists and there's no subscription, subscribe to it.
if (this.imageActor && !this.imageSubscription) {
this.imageSubscription = this.imageActor.subscribe(this.onImageActorSnapshot.bind(this));
this.onImageActorSnapshot(this.imageActor.getSnapshot());
this.imageDimension = new SelectorController(this.host, this.imageActor, (state) => state.context.image?.imageType.dimension ?? 0);
this.colorMaps = new SelectorController(this.host, this.imageActor, (state) => state.context.colorMaps);
this.componentCount = new SelectorController(this.host, this.imageActor, (state) => state.context.image?.imageType.components ?? 1);
}
const renderer = Object.values(spawned)?.[0];
if (this.renderer !== renderer) {
this.rendererSubscription?.unsubscribe();
this.rendererSubscription = undefined;
}
this.renderer = renderer;
if (this.renderer && !this.rendererSubscription) {
this.colorMapsOptions = new SelectorController(this.host, renderer, (state) => state.context.colorMapOptions ?? {});
this.rendererSubscription = renderer.on('colorTransferFunctionApplied', this.onColorTransferFunction);
}
};
this.onImageActorSnapshot = (snapshot) => {
if (!this.transferFunctionEditor)
return;
const component = this.selectedComponent;
const { dataRanges, normalizedColorRanges, normalizedOpacityPoints } = snapshot.context;
const componentRange = dataRanges[component];
if (componentRange) {
this.transferFunctionEditor.setRange(componentRange);
}
if (normalizedColorRanges.length === 0)
return;
const currentColorRange = this.transferFunctionEditor.getColorRange();
// avoid infinite loop
const colorRange = normalizedColorRanges[component];
const changed = currentColorRange?.some((v, i) => !equalFloats(v, colorRange[i]));
if (changed) {
this.transferFunctionEditor.setColorRange(colorRange);
}
const currentPoints = this.transferFunctionEditor.getPoints();
const opacityPoints = normalizedOpacityPoints[component];
const pointsChanged = currentPoints?.some((point, i) => !equalFloats(point[0], opacityPoints[i][0]) ||
!equalFloats(point[1], opacityPoints[i][1]));
if (pointsChanged) {
this.transferFunctionEditor.setPoints(opacityPoints);
}
this.updateColorTransferFunction();
};
this.setView = (view) => {
this.view = view;
this.updateTransferFunctionEditor();
};
this.updateTransferFunctionEditor = () => {
const rangeViewOnly = this.view === '2d';
this.transferFunctionEditor?.setRangeViewOnly(rangeViewOnly);
if (this.imageActor) {
this.onImageActorSnapshot(this.imageActor.getSnapshot());
}
};
this.updateColorTransferFunction = () => {
const ct = this.colorTransferFunctions.get(this.selectedComponent);
if (!ct)
return;
this.transferFunctionEditor?.setColorTransferFunction(ct);
};
this.onColorTransferFunction = ({ colorTransferFunction, component, }) => {
this.colorTransferFunctions.set(component, colorTransferFunction);
this.updateColorTransferFunction();
};
this.host = host;
host.addController(this);
}
hostConnected() {
// no-op
}
setActor(actor) {
this.actor = actor;
this.scale = new SelectorController(this.host, this.actor, (state) => state.context.scale);
this.scaleCount = new SelectorController(this.host, this.actor, (state) => {
const image = state.context.image;
if (!image)
return 1;
return image.coarsestScale + 1;
});
this.slice = new SelectorController(this.host, this.actor, (state) => state.context.slice);
this.axis = new SelectorController(this.host, this.actor, (state) => state.context.axis);
this.host.requestUpdate(); // trigger render with selected state
// wire up Transfer Function Editor
if (this.viewSubscription)
this.viewSubscription.unsubscribe();
this.viewSubscription = this.actor.subscribe(this.onViewSnapshot.bind(this));
this.onViewSnapshot(this.actor.getSnapshot());
}
}
//# sourceMappingURL=view-controls-controller.js.map