AbstractWidgetFactory

The factory that is used to construct widget instances of type
vtkAbstractWidget. These widget instances are known as “view widgets”, since
these widget instances are tied to a particular view. The factory class should
be subclassed to implement specific widgets.

(abstract) model.behavior(widgetPublicAPI, widgetModel)

This should implement widget-specific functionality. See PolyLineWidget
behavior.js for an example.

(abstract) model.widgetState

Needs to be set to a vtkWidgetState. See vtkStateBuilder for constructing
vtkWidgetState.

(abstract) publicAPI.getRepresentationsForViewType(viewType)

A function that should return a list of representations to construct for a
given viewType. Valid view types can be found in =WidgetManager/Constants.js=.
The return type should be [ { builder: vtkRepresentationClass, labels: [string,...] }, ... ].

(abstract, optional) model.methodsToLink

A list of method and property names to proxy from the
representations to the widget. If the method or property is available on any of
the widget’s representations, it will be made available on the widget. Type
structure: =[string, …]=

getWidgetForView({ viewId, renderer?, viewType?, initialValues? })

Will return the widget associated with the view with Id =viewId=. If there is
no widget associated with the view, a new widget will be constructed, provided
that the renderer, viewType, and optionally initialValues are also provided.

setVisibility(bool)

Sets visibility for all associated view widgets.

setPickable(bool)

Sets pickable flag for all associated view widgets.

setContextVisibility(bool)

Sets context visibility for all associated view widgets.

setHandleVisibility(bool)

Sets handle visibility for all associated view widgets.

Source

index.js
import macro from 'vtk.js/Sources/macro';
import vtkAbstractWidget from 'vtk.js/Sources/Widgets/Core/AbstractWidget';
import { extractRenderingComponents } from 'vtk.js/Sources/Widgets/Core/WidgetManager';

function NoOp() {}

// ----------------------------------------------------------------------------

function vtkAbstractWidgetFactory(publicAPI, model) {
model.classHierarchy.push('vtkAbstractWidgetFactory');

// DO NOT share on the model ------------------------------------------------
const viewToWidget = {};
// DO NOT share on the model ------------------------------------------------

// Can be called with just ViewId after the widget has been registered
publicAPI.getWidgetForView = ({
viewId,
renderer,
viewType,
initialValues,
}) => {
if (!viewToWidget[viewId]) {
if (!renderer) {
return null;
}

const {
interactor,
openGLRenderWindow,
camera,
} = extractRenderingComponents(renderer);
const widgetModel = {};
const widgetPublicAPI = {
onWidgetChange: publicAPI.onWidgetChange,
};
Object.assign(widgetModel, model, {
viewType,
renderer,
camera,
openGLRenderWindow,
});
macro.safeArrays(widgetModel);
vtkAbstractWidget.extend(widgetPublicAPI, widgetModel, initialValues);

// Create representations for that view
/* eslint-disable no-shadow */
widgetModel.representations = publicAPI
.getRepresentationsForViewType(viewType)
.map(({ builder, labels, initialValues }) =>
builder.newInstance(Object.assign({ labels }, initialValues))
);
/* eslint-enable no-shadow */

widgetModel.representations.forEach((r) => {
r.setInputData(widgetModel.widgetState);
r.getActors().forEach((actor) => {
widgetModel.actorToRepresentationMap.set(actor, r);
});
});

model.behavior(widgetPublicAPI, widgetModel);

// Forward representation methods
if (model.methodsToLink) {
model.methodsToLink.forEach((methodName) => {
const set = `set${macro.capitalize(methodName)}`;
const get = `get${macro.capitalize(methodName)}`;
const methods = {
[methodName]: [],
[set]: [],
[get]: [],
};
widgetModel.representations.forEach((representation) => {
if (representation[methodName]) {
methods[methodName].push(representation[methodName]);
}
if (representation[set]) {
methods[set].push(representation[set]);
}
if (representation[get]) {
methods[get].push(representation[get]);
}
});

Object.keys(methods).forEach((name) => {
const calls = methods[name];
if (calls.length === 1) {
widgetPublicAPI[name] = calls[0];
} else if (calls.length > 1) {
widgetPublicAPI[name] = macro.chain(...calls);
}
});
});
}

// Custom delete to detatch from parent
widgetPublicAPI.delete = macro.chain(() => {
delete viewToWidget[viewId];
}, widgetPublicAPI.delete);

widgetPublicAPI.setInteractor(interactor);
const viewWidget = Object.freeze(widgetPublicAPI);
viewToWidget[viewId] = viewWidget;
return viewWidget;
}
return viewToWidget[viewId];
};

// --------------------------------------------------------------------------
// Widget visibility / enable
// --------------------------------------------------------------------------
// Call methods on all its view widgets

publicAPI.setVisibility = (value) => {
const viewIds = Object.keys(viewToWidget);
for (let i = 0; i < viewIds.length; i++) {
viewToWidget[viewIds[i]].setVisibility(value);
}
};

publicAPI.setPickable = (value) => {
const viewIds = Object.keys(viewToWidget);
for (let i = 0; i < viewIds.length; i++) {
viewToWidget[viewIds[i]].setPickable(value);
}
};

publicAPI.setContextVisibility = (value) => {
const viewIds = Object.keys(viewToWidget);
for (let i = 0; i < viewIds.length; i++) {
viewToWidget[viewIds[i]].setContextVisibility(value);
}
};

publicAPI.setHandleVisibility = (value) => {
const viewIds = Object.keys(viewToWidget);
for (let i = 0; i < viewIds.length; i++) {
viewToWidget[viewIds[i]].setHandleVisibility(value);
}
};

// --------------------------------------------------------------------------
// Place Widget API
// --------------------------------------------------------------------------

publicAPI.placeWidget = (bounds) => model.widgetState.placeWidget(bounds);
publicAPI.getPlaceFactor = () => model.widgetState.getPlaceFactor();
publicAPI.setPlaceFactor = (factor) =>
model.widgetState.setPlaceFactor(factor);

// --------------------------------------------------------------------------
// Event Widget API
// --------------------------------------------------------------------------
let unsubscribe = NoOp;
publicAPI.delete = macro.chain(publicAPI.delete, () => unsubscribe());

// Defer after object instantiation so model.widgetState actually exist
setTimeout(() => {
unsubscribe = model.widgetState.onModified(() =>
publicAPI.invokeWidgetChange(model.widgetState)
).unsubscribe;
}, 0);
}

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
macro.obj(publicAPI, model);
macro.get(publicAPI, model, ['widgetState']);
macro.event(publicAPI, model, 'WidgetChange');
vtkAbstractWidgetFactory(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkAbstractWidget');

// ----------------------------------------------------------------------------

export default { newInstance, extend };