// namespaces
var dwv = dwv || {};
dwv.gui = dwv.gui || {};
/**
* Get the layer group div id.
*
* @param {number} groupId The layer group id.
* @param {number} layerId The lyaer id.
* @returns {string} A string id.
*/
dwv.gui.getLayerGroupDivId = function (groupId, layerId) {
return 'layer-' + groupId + '-' + layerId;
};
/**
* Get the layer details from a div id.
*
* @param {string} idString The layer group id.
* @returns {object} The layer details as {groupId, layerId}.
*/
dwv.gui.getLayerDetailsFromLayerDivId = function (idString) {
var posHyphen = idString.lastIndexOf('-');
var groupId = null;
var layerId = null;
if (posHyphen !== -1) {
groupId = parseInt(idString.substring(6, posHyphen), 10);
layerId = parseInt(idString.substring(posHyphen + 1), 10);
}
return {
groupId: groupId,
layerId: layerId
};
};
/**
* Get the layer details from a mouse event.
*
* @param {object} event The event to get the layer div id from. Expecting
* an event origininating from a canvas inside a layer HTML div
* with the 'layer' class and id generated with `dwv.gui.getLayerGroupDivId`.
* @returns {object} The layer details as {groupId, layerId}.
*/
dwv.gui.getLayerDetailsFromEvent = function (event) {
var res = null;
// get the closest element from the event target and with the 'layer' class
var layerDiv = event.target.closest('.layer');
if (layerDiv && typeof layerDiv.id !== 'undefined') {
res = dwv.gui.getLayerDetailsFromLayerDivId(layerDiv.id);
}
return res;
};
/**
* Get a view orientation according to an image geometry (with its orientation)
* and target orientation.
*
* @param {dwv.image.Geometry} imageGeometry The image geometry.
* @param {dwv.math.Matrix33} targetOrientation The target orientation.
* @returns {dwv.math.Matrix33} The view orientation.
*/
dwv.gui.getViewOrientation = function (imageGeometry, targetOrientation) {
var viewOrientation = dwv.math.getIdentityMat33();
if (typeof targetOrientation !== 'undefined') {
// image orientation as one and zeros
// -> view orientation is one and zeros
var imgOrientation = imageGeometry.getOrientation().asOneAndZeros();
// imgOrientation * viewOrientation = targetOrientation
// -> viewOrientation = inv(imgOrientation) * targetOrientation
viewOrientation =
imgOrientation.getInverse().multiply(targetOrientation);
}
return viewOrientation;
};
/**
* Layer group.
*
* Display position: {x,y}
* Plane position: Index (access: get(i))
* (world) Position: Point3D (access: getX, getY, getZ)
*
* Display -> World:
* planePos = viewLayer.displayToPlanePos(displayPos)
* -> compensate for layer scale and offset
* pos = viewController.getPositionFromPlanePoint(planePos)
*
* World -> display
* planePos = viewController.getOffset3DFromPlaneOffset(pos)
* no need yet for a planePos to displayPos...
*
* @param {object} containerDiv The associated HTML div.
* @param {number} groupId The group id.
* @class
*/
dwv.gui.LayerGroup = function (containerDiv, groupId) {
// closure to self
var self = this;
// list of layers
var layers = [];
/**
* The layer scale as {x,y}.
*
* @private
* @type {object}
*/
var scale = {x: 1, y: 1, z: 1};
/**
* The base scale as {x,y}: all posterior scale will be on top of this one.
*
* @private
* @type {object}
*/
var baseScale = {x: 1, y: 1, z: 1};
/**
* The layer offset as {x,y}.
*
* @private
* @type {object}
*/
var offset = {x: 0, y: 0, z: 0};
/**
* Active view layer index.
*
* @private
* @type {number}
*/
var activeViewLayerIndex = null;
/**
* Active draw layer index.
*
* @private
* @type {number}
*/
var activeDrawLayerIndex = null;
/**
* Listener handler.
*
* @type {object}
* @private
*/
var listenerHandler = new dwv.utils.ListenerHandler();
/**
* The target orientation matrix.
*
* @type {object}
* @private
*/
var targetOrientation;
/**
* Get the target orientation.
*
* @returns {dwv.math.Matrix33} The orientation matrix.
*/
this.getTargetOrientation = function () {
return targetOrientation;
};
/**
* Set the target orientation.
*
* @param {dwv.math.Matrix33} orientation The orientation matrix.
*/
this.setTargetOrientation = function (orientation) {
targetOrientation = orientation;
};
/**
* Get the Id of the container div.
*
* @returns {string} The id of the div.
*/
this.getElementId = function () {
return containerDiv.id;
};
/**
* Get the layer group id.
*
* @returns {number} The id.
*/
this.getGroupId = function () {
return groupId;
};
/**
* Get the layer scale.
*
* @returns {object} The scale as {x,y,z}.
*/
this.getScale = function () {
return scale;
};
/**
* Get the base scale.
*
* @returns {object} The scale as {x,y,z}.
*/
this.getBaseScale = function () {
return baseScale;
};
/**
* Get the added scale: the scale added to the base scale
*
* @returns {object} The scale as {x,y,z}.
*/
this.getAddedScale = function () {
return {
x: scale.x / baseScale.x,
y: scale.y / baseScale.y,
z: scale.z / baseScale.z
};
};
/**
* Get the layer offset.
*
* @returns {object} The offset as {x,y,z}.
*/
this.getOffset = function () {
return offset;
};
/**
* Get the number of layers handled by this class.
*
* @returns {number} The number of layers.
*/
this.getNumberOfLayers = function () {
return layers.length;
};
/**
* Get the active image layer.
*
* @returns {object} The layer.
*/
this.getActiveViewLayer = function () {
return layers[activeViewLayerIndex];
};
/**
* Get the view layers associated to a data index.
*
* @param {number} index The data index.
* @returns {Array} The layers.
*/
this.getViewLayersByDataIndex = function (index) {
var res = [];
for (var i = 0; i < layers.length; ++i) {
if (layers[i] instanceof dwv.gui.ViewLayer &&
layers[i].getDataIndex() === index) {
res.push(layers[i]);
}
}
return res;
};
/**
* Get the active draw layer.
*
* @returns {object} The layer.
*/
this.getActiveDrawLayer = function () {
return layers[activeDrawLayerIndex];
};
/**
* Get the draw layers associated to a data index.
*
* @param {number} index The data index.
* @returns {Array} The layers.
*/
this.getDrawLayersByDataIndex = function (index) {
var res = [];
for (var i = 0; i < layers.length; ++i) {
if (layers[i] instanceof dwv.gui.DrawLayer &&
layers[i].getDataIndex() === index) {
res.push(layers[i]);
}
}
return res;
};
/**
* Set the active view layer.
*
* @param {number} index The index of the layer to set as active.
*/
this.setActiveViewLayer = function (index) {
activeViewLayerIndex = index;
};
/**
* Set the active view layer with a data index.
*
* @param {number} index The data index.
*/
this.setActiveViewLayerByDataIndex = function (index) {
for (var i = 0; i < layers.length; ++i) {
if (layers[i] instanceof dwv.gui.ViewLayer &&
layers[i].getDataIndex() === index) {
this.setActiveViewLayer(i);
break;
}
}
};
/**
* Set the active draw layer.
*
* @param {number} index The index of the layer to set as active.
*/
this.setActiveDrawLayer = function (index) {
activeDrawLayerIndex = index;
};
/**
* Set the active draw layer with a data index.
*
* @param {number} index The data index.
*/
this.setActiveDrawLayerByDataIndex = function (index) {
for (var i = 0; i < layers.length; ++i) {
if (layers[i] instanceof dwv.gui.DrawLayer &&
layers[i].getDataIndex() === index) {
this.setActiveDrawLayer(i);
break;
}
}
};
/**
* Add a view layer.
*
* @returns {object} The created layer.
*/
this.addViewLayer = function () {
// layer index
var viewLayerIndex = layers.length;
// create div
var div = getNextLayerDiv();
// prepend to container
containerDiv.append(div);
// view layer
var layer = new dwv.gui.ViewLayer(div);
// add layer
layers.push(layer);
// mark it as active
this.setActiveViewLayer(viewLayerIndex);
// bind view layer events
bindViewLayer(layer);
// return
return layer;
};
/**
* Add a draw layer.
*
* @returns {object} The created layer.
*/
this.addDrawLayer = function () {
// store active index
activeDrawLayerIndex = layers.length;
// create div
var div = getNextLayerDiv();
// prepend to container
containerDiv.append(div);
// draw layer
var layer = new dwv.gui.DrawLayer(div);
// add layer
layers.push(layer);
// return
return layer;
};
/**
* Bind view layer events to this.
*
* @param {object} viewLayer The view layer to bind.
*/
function bindViewLayer(viewLayer) {
// listen to position change to update other group layers
viewLayer.addEventListener(
'positionchange', self.updateLayersToPositionChange);
// propagate view viewLayer-layer events
for (var j = 0; j < dwv.image.viewEventNames.length; ++j) {
viewLayer.addEventListener(dwv.image.viewEventNames[j], fireEvent);
}
// propagate viewLayer events
viewLayer.addEventListener('renderstart', fireEvent);
viewLayer.addEventListener('renderend', fireEvent);
}
/**
* Get the next layer DOM div.
*
* @returns {HTMLElement} A DOM div.
*/
function getNextLayerDiv() {
var div = document.createElement('div');
div.id = dwv.gui.getLayerGroupDivId(groupId, layers.length);
div.className = 'layer';
div.style.pointerEvents = 'none';
return div;
}
/**
* Empty the layer list.
*/
this.empty = function () {
layers = [];
// reset active indices
activeViewLayerIndex = null;
activeDrawLayerIndex = null;
// clean container div
var previous = containerDiv.getElementsByClassName('layer');
if (previous) {
while (previous.length > 0) {
previous[0].remove();
}
}
};
/**
* Update layers (but not the active view layer) to a position change.
*
* @param {object} event The position change event.
*/
this.updateLayersToPositionChange = function (event) {
// pause positionchange listeners
for (var j = 0; j < layers.length; ++j) {
if (layers[j] instanceof dwv.gui.ViewLayer) {
layers[j].removeEventListener(
'positionchange', self.updateLayersToPositionChange);
layers[j].removeEventListener('positionchange', fireEvent);
}
}
var index = new dwv.math.Index(event.value[0]);
var position = new dwv.math.Point(event.value[1]);
// update position for all layers except the source one
for (var i = 0; i < layers.length; ++i) {
if (layers[i].getId() !== event.srclayerid) {
layers[i].setCurrentPosition(position, index);
}
}
// re-start positionchange listeners
for (var k = 0; k < layers.length; ++k) {
if (layers[k] instanceof dwv.gui.ViewLayer) {
layers[k].addEventListener(
'positionchange', self.updateLayersToPositionChange);
layers[k].addEventListener('positionchange', fireEvent);
}
}
};
/**
* Fit the display to the size of the container.
* To be called once the image is loaded.
*/
this.fitToContainer = function () {
// check container size
if (containerDiv.offsetWidth === 0 &&
containerDiv.offsetHeight === 0) {
throw new Error('Cannot fit to zero sized container.');
}
// find best fit
var fitScales = [];
for (var i = 0; i < layers.length; ++i) {
var fullSize = layers[i].getFullSize();
fitScales.push(containerDiv.offsetWidth / fullSize.x);
fitScales.push(containerDiv.offsetHeight / fullSize.y);
}
var fitScale = Math.min.apply(null, fitScales);
// apply to layers
for (var j = 0; j < layers.length; ++j) {
layers[j].fitToContainer(fitScale);
}
};
/**
* Add scale to the layers. Scale cannot go lower than 0.1.
*
* @param {number} scaleStep The scale to add.
* @param {dwv.math.Point3D} center The scale center Point3D.
*/
this.addScale = function (scaleStep, center) {
var newScale = {
x: scale.x * (1 + scaleStep),
y: scale.y * (1 + scaleStep),
z: scale.z * (1 + scaleStep)
};
var centerPlane = {
x: (center.getX() - offset.x) * scale.x,
y: (center.getY() - offset.y) * scale.y,
z: (center.getZ() - offset.z) * scale.z
};
// center should stay the same:
// center / newScale + newOffset = center / oldScale + oldOffset
// => newOffset = center / oldScale + oldOffset - center / newScale
var newOffset = {
x: (centerPlane.x / scale.x) + offset.x - (centerPlane.x / newScale.x),
y: (centerPlane.y / scale.y) + offset.y - (centerPlane.y / newScale.y),
z: (centerPlane.z / scale.z) + offset.z - (centerPlane.z / newScale.z)
};
this.setOffset(newOffset);
this.setScale(newScale);
};
/**
* Set the layers' scale.
*
* @param {object} newScale The scale to apply as {x,y,z}.
* @fires dwv.ctrl.LayerGroup#zoomchange
*/
this.setScale = function (newScale) {
scale = newScale;
// apply to layers
for (var i = 0; i < layers.length; ++i) {
layers[i].setScale(scale);
}
/**
* Zoom change event.
*
* @event dwv.ctrl.LayerGroup#zoomchange
* @type {object}
* @property {Array} value The changed value.
*/
fireEvent({
type: 'zoomchange',
value: [scale.x, scale.y, scale.z],
});
};
/**
* Add translation to the layers.
*
* @param {object} translation The translation as {x,y,z}.
*/
this.addTranslation = function (translation) {
this.setOffset({
x: offset.x - translation.x,
y: offset.y - translation.y,
z: offset.z - translation.z
});
};
/**
* Set the layers' offset.
*
* @param {object} newOffset The offset as {x,y,z}.
* @fires dwv.ctrl.LayerGroup#offsetchange
*/
this.setOffset = function (newOffset) {
// store
offset = newOffset;
// apply to layers
for (var i = 0; i < layers.length; ++i) {
layers[i].setOffset(offset);
}
/**
* Offset change event.
*
* @event dwv.ctrl.LayerGroup#offsetchange
* @type {object}
* @property {Array} value The changed value.
*/
fireEvent({
type: 'offsetchange',
value: [offset.x, offset.y, offset.z],
});
};
/**
* Reset the stage to its initial scale and no offset.
*/
this.reset = function () {
this.setScale(baseScale);
this.setOffset({x: 0, y: 0, z: 0});
};
/**
* Draw the layer.
*/
this.draw = function () {
for (var i = 0; i < layers.length; ++i) {
layers[i].draw();
}
};
/**
* Display the layer.
*
* @param {boolean} flag Whether to display the layer or not.
*/
this.display = function (flag) {
for (var i = 0; i < layers.length; ++i) {
layers[i].display(flag);
}
};
/**
* Add an event listener to this class.
*
* @param {string} type The event type.
* @param {object} callback The method associated with the provided
* event type, will be called with the fired event.
*/
this.addEventListener = function (type, callback) {
listenerHandler.add(type, callback);
};
/**
* Remove an event listener from this class.
*
* @param {string} type The event type.
* @param {object} callback The method associated with the provided
* event type.
*/
this.removeEventListener = function (type, callback) {
listenerHandler.remove(type, callback);
};
/**
* Fire an event: call all associated listeners with the input event object.
*
* @param {object} event The event to fire.
* @private
*/
function fireEvent(event) {
listenerHandler.fireEvent(event);
}
}; // LayerGroup class