PipelineState

PipelineState

PipelineState represents a pipeline model which is used inside
Widgets and ImageBuilders to keep track of the configuration that should be
used to render an image for a given pipeline configuration.

The class is imported using the following:

var PipelineState = require('paraviewweb/src/Common/State/PipelineState'),
instance = new PipelineState(json);

constructor(json)

Creates an instance of a pipeline model which keeps track of the
state of the pipeline.
The provided JSON object should have the following type of structure:

{
"CompositePipeline": {
"layers": ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"],
"dimensions": [ 500, 500 ],
"fields": {
"A": "salinity",
"B": "temperature",
"C": "bottomDepth"
},
"layer_fields": {
"A": [ "C" ],
"B": [ "B", "A" ],
"C": [ "B", "A" ],
"D": [ "B", "A" ],
"E": [ "B", "A" ],
"F": [ "B", "A" ],
"G": [ "B", "A" ],
"H": [ "B", "A" ],
"I": [ "B", "A" ],
"J": [ "B", "A" ],
"K": [ "B", "A" ]
},
"offset": {
"AC": 1, "BA": 3, "BB": 2, "CA": 5, "CB": 4, "DA": 7, "DB": 6,
"EA": 9, "EB": 8, "FA": 11, "FB": 10, "GA": 13, "GB": 12, "HA": 15,
"HB": 14, "IA": 17, "IB": 16, "JA": 19, "JB": 18, "KA": 21, "KB": 20
"pipeline": [
{
"ids": [ "A" ],
"name": "Earth core",
"type": "layer"
},
{
"children": [
{
"ids": [ "B" ],
"name": "t=5.0",
"type": "layer"
},
{
"ids": [ "C" ],
"name": "t=10.0",
"type": "layer"
},
{
"ids": [ "D" ],
"name": "t=15.0",
"type": "layer"
},
{
"ids": [ "E" ],
"name": "t=20.0",
"type": "layer"
},
{
"ids": [ "F" ],
"name": "t=25.0",
"type": "layer"
}
],
"ids": [ "B", "C", "D", "E", "F" ],
"name": "Contour by temperature",
"type": "directory"
},
{
"children": [
{
"ids": [ "G" ],
"name": "s=34.0",
"type": "layer"
},
{
"ids": [ "H" ],
"name": "s=35.0",
"type": "layer"
},
{
"ids": [ "I" ],
"name": "s=35.5",
"type": "layer"
},
{
"ids": [ "J" ],
"name": "s=36.0",
"type": "layer"
},
{
"ids": [ "K" ],
"name": "s=36.5",
"type": "layer"
}
],
"ids": [ "G", "H", "I", "J", "K" ],
"name": "Contour by salinity",
"type": "directory"
}
]
}
}

onChange(callback) : subscription

Callback function will be called with the pipeline query as the first argument.
As we rely on Monologue.js, you get a subscription object as a
response which allows for easy unsubscription.

TopicChange() : ‘pipeline.change’

Returns the name of the topic used for the pipeline change notification.

triggerChange()

Triggers a change event. The event triggered is a string
that represents the pipeline encoding.

isLayerActive(layerId) : Boolean

Returns true if the given layer is set to active.
By default, every layer is active.

setLayerActive(layerId, active)

Updates the active state of a layer.
If the internal state has changed, the model will trigger a change event.

toggleLayerActive(layerId)

Toggles the active state of the given layer and trigger a change event.

isLayerVisible(layerId)

Returns true if the given layer is set to visible. By default, every layer is visible.

setLayerVisible(layerId, visible)

Updates the visible state of a layer.
If the internal state has changed, the model will trigger a change event.

toggleLayerVisible(layerId)

Toggles the visible state of the given layer and triggers a change event.

isLayerInEditMode(layerId)

Returns true if the given layer is in edit mode. By default, none of the layers are in edit mode.

toggleEditMode(layerId)

Toggles the edit mode state of the given layer and triggers a change event.

getColor(layerId) : Array

Returns the set of color codes that given layer can have.

getColor('E') => ['B', 'A']

getColorToLabel(colorCode) : String

Returns the label associated to a given color code.

getColorToLabel('B') => 'temperature'

isActiveColor(layerId, colorCode) : Boolean

Returns true if the given layer is colored by the
given color code, returns false otherwise.

setActiveColor(layerId, colorCode)

Updates which ColorBy field should be used for a given layer.

getPipelineQuery() : String

Returns the current pipeline configuration as an encoded string.

getPipelineDescription()

Returns the pipeline tree defined inside the JSON object under the path JSON.CompositePipeline.pipeline.

Source

index.js
import Monologue from 'monologue.js';

const CHANGE_TOPIC = 'pipeline.change';
const OPACITY_CHANGE_TOPIC = 'opacity.change';
const LAYER_CODE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

export default class PipelineState {
constructor(jsonData, hasOpacity = false) {
this.originalData = jsonData;
this.visibilityState = {};
this.activeState = {};
this.editMode = {};
this.activeColors = {};
this.noTrigger = true;
this.handleOpacity = hasOpacity;
this.opacityMap = {};
this.nbLayers = 0;

// Handle default pipeline if any
const pipelineQuery = jsonData.CompositePipeline.default_pipeline;
const layerFields = jsonData.CompositePipeline.layer_fields;
function isLayerVisible(layers) {
if (!pipelineQuery || layers.length > 1) {
return true;
}

const layerIdx = LAYER_CODE.indexOf(layers[0]);

return pipelineQuery[layerIdx * 2 + 1] !== '_';
}
function getColorCode(layers) {
if (!pipelineQuery || layers.length > 1) {
return layerFields[layers][0];
}

const layerIdx = LAYER_CODE.indexOf(layers[0]);
const colorCode = pipelineQuery[layerIdx * 2 + 1];

return colorCode === '_' ? layerFields[layers][0] : colorCode;
}

// Fill visibility and activate all layers
const isRoot = {};
jsonData.CompositePipeline.pipeline.forEach((item) => {
isRoot[item.ids.join('')] = true;
this.setLayerVisible(
item.ids.join(''),
isLayerVisible(item.ids.join(''))
);
});
jsonData.CompositePipeline.layers.forEach((item) => {
this.activeState[item] = isRoot[item] ? true : isLayerVisible(item);
this.activeColors[item] = getColorCode(item);

// Initialize opacity
this.opacityMap[item] = 100.0;
this.nbLayers += 1;
});

this.noTrigger = false;
this.triggerChange();
}

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

onChange(listener) {
return this.on(CHANGE_TOPIC, listener);
}

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

onOpacityChange(listener) {
return this.on(OPACITY_CHANGE_TOPIC, listener);
}

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

/* eslint-disable class-methods-use-this */
TopicChange() {
return CHANGE_TOPIC;
}

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

triggerChange() {
if (this.noTrigger) {
return;
}

const pipelineQuery = this.getPipelineQuery();
this.emit(CHANGE_TOPIC, pipelineQuery);
}

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

isLayerActive(layerId) {
return this.activeState[layerId];
}

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

setLayerActive(layerId, active) {
if (this.activeState[layerId] !== active) {
this.activeState[layerId] = active;
this.triggerChange();
}
}

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

toggleLayerActive(layerId) {
this.activeState[layerId] = !this.activeState[layerId];
this.triggerChange();
}

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

isLayerVisible(layerId) {
return this.visibilityState[layerId];
}

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

setLayerVisible(layerId, visible) {
if (this.visibilityState[layerId] !== visible) {
this.visibilityState[layerId] = visible;
let count = layerId.length;
while (count) {
count -= 1;
this.visibilityState[layerId[count]] = visible;
}
this.triggerChange();
}
}

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

toggleLayerVisible(layerId) {
this.setLayerVisible(layerId, !this.visibilityState[layerId]);
}

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

toggleEditMode(layerId) {
this.editMode[layerId] = !this.editMode[layerId];
this.triggerChange();
}

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

isLayerInEditMode(layerId) {
let found = false;
Object.keys(this.editMode).forEach((key) => {
if (this.editMode[key] && key.indexOf(layerId) !== -1) {
found = true;
}
});
return found;
}

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

getColor(layerId) {
return this.originalData.CompositePipeline.layer_fields[layerId[0]];
}

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

getColorToLabel(colorCode) {
return this.originalData.CompositePipeline.fields[colorCode];
}

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

isActiveColor(layerId, colorCode) {
return this.activeColors[layerId[0]] === colorCode;
}

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

setActiveColor(layerId, colorCode) {
let count = layerId.length;
while (count) {
count -= 1;
this.activeColors[layerId[count]] = colorCode;
}
this.triggerChange();
}

// ------------------------------------------------------------------------
// Return the encoding of the pipeline configuration

getPipelineQuery() {
let query = '';
this.originalData.CompositePipeline.layers.forEach((item) => {
const color =
this.isLayerActive(item) && this.isLayerVisible(item)
? this.activeColors[item]
: '_';
query += item;
query += color;
});
return query;
}

setPipelineQuery(query) {
const localQuery = this.getPipelineQuery();
if (query !== localQuery) {
const maxIdx = localQuery.length / 2;
for (let idx = 0; idx < maxIdx; idx++) {
const colorIdx = 2 * idx + 1;
if (localQuery[colorIdx] !== query[colorIdx]) {
if (query[colorIdx] === '_') {
this.setLayerVisible(LAYER_CODE[idx], false);
} else {
this.setLayerVisible(LAYER_CODE[idx], true);
this.setActiveColor(LAYER_CODE[idx], query[colorIdx]);
}
}
}
}
}

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

getPipelineDescription() {
return this.originalData.CompositePipeline.pipeline;
}

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

getOpacity(layerCode) {
return this.opacityMap[layerCode];
}

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

hasOpacity() {
return this.handleOpacity;
}

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

setOpacity(layerCode, alpha) {
if (this.opacityMap[layerCode] !== alpha) {
this.opacityMap[layerCode] = alpha;

const opacityArray = [];
for (let i = 0; i < this.nbLayers; ++i) {
opacityArray.push(this.opacityMap[LAYER_CODE[i]] / 100.0);
}

this.emit(OPACITY_CHANGE_TOPIC, opacityArray);
}
}

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

resetOpacity(alpha) {
Object.keys(this.opacityMap).forEach((key) => {
this.opacityMap[key] = alpha;
});

const opacityArray = [];
for (let i = 0; i < this.nbLayers; ++i) {
opacityArray.push(this.opacityMap[LAYER_CODE[i]] / 100.0);
}

this.emit(OPACITY_CHANGE_TOPIC, opacityArray);
}

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

/* eslint-disable */
bind(listOfStateToBind) {
let changeInProgress = false;
const queryChange = (query) => {
if (changeInProgress) {
return;
}
changeInProgress = true;
listOfStateToBind.forEach((other) => {
other.setPipelineQuery(query);
});
changeInProgress = false;
};

const opacityChange = (opacityArray) => {
if (changeInProgress) {
return;
}
changeInProgress = true;
listOfStateToBind.forEach((other) => {
other.opacityArray = [].concat(opacityArray);
other.emit(OPACITY_CHANGE_TOPIC, opacityArray);
});
changeInProgress = false;
};

listOfStateToBind.forEach((toMonitor) => {
toMonitor.onChange(queryChange);
toMonitor.onOpacityChange(opacityChange);
});
}
/* eslint-enable */
}

// Add Observer pattern using Monologue.js
Monologue.mixInto(PipelineState);