// namespaces
var dwv = dwv || {};
dwv.draw = dwv.draw || {};
dwv.ctrl = dwv.ctrl || {};
/**
* The Konva namespace.
*
* @external Konva
* @see https://konvajs.org/
*/
var Konva = Konva || {};
/**
* Get the draw group id for a given position.
*
* @param {dwv.math.Point} currentPosition The current position.
* @returns {string} The group id.
* @deprecated Use the index.toStringId instead.
*/
dwv.draw.getDrawPositionGroupId = function (currentPosition) {
var sliceNumber = currentPosition.get(2);
var frameNumber = currentPosition.length() === 4
? currentPosition.get(3) : 0;
return 'slice-' + sliceNumber + '_frame-' + frameNumber;
};
/**
* Get the slice and frame position from a group id.
*
* @param {string} groupId The group id.
* @returns {object} The slice and frame number.
* @deprecated Use the dwv.math.getVectorFromStringId instead.
*/
dwv.draw.getPositionFromGroupId = function (groupId) {
var sepIndex = groupId.indexOf('_');
if (sepIndex === -1) {
dwv.logger.warn('Badly formed PositionGroupId: ' + groupId);
}
return {
sliceNumber: groupId.substring(6, sepIndex),
frameNumber: groupId.substring(sepIndex + 7)
};
};
/**
* Is an input node's name 'shape'.
*
* @param {object} node A Konva node.
* @returns {boolean} True if the node's name is 'shape'.
*/
dwv.draw.isNodeNameShape = function (node) {
return node.name() === 'shape';
};
/**
* Is a node an extra shape associated with a main one.
*
* @param {object} node A Konva node.
* @returns {boolean} True if the node's name starts with 'shape-'.
*/
dwv.draw.isNodeNameShapeExtra = function (node) {
return node.name().startsWith('shape-');
};
/**
* Is an input node's name 'label'.
*
* @param {object} node A Konva node.
* @returns {boolean} True if the node's name is 'label'.
*/
dwv.draw.isNodeNameLabel = function (node) {
return node.name() === 'label';
};
/**
* Is an input node a position node.
*
* @param {object} node A Konva node.
* @returns {boolean} True if the node's name is 'position-group'.
*/
dwv.draw.isPositionNode = function (node) {
return node.name() === 'position-group';
};
/**
* Get a lambda to check a node's id.
*
* @param {string} id The id to check.
* @returns {Function} A function to check a node's id.
*/
dwv.draw.isNodeWithId = function (id) {
return function (node) {
return node.id() === id;
};
};
/**
* Is the input node a node that has the 'stroke' method.
*
* @param {object} node A Konva node.
* @returns {boolean} True if the node's name is 'anchor' and 'label'.
*/
dwv.draw.canNodeChangeColour = function (node) {
return node.name() !== 'anchor' && node.name() !== 'label';
};
/**
* Debug function to output the layer hierarchy as text.
*
* @param {object} layer The Konva layer.
* @param {string} prefix A display prefix (used in recursion).
* @returns {string} A text representation of the hierarchy.
*/
dwv.draw.getHierarchyLog = function (layer, prefix) {
if (typeof prefix === 'undefined') {
prefix = '';
}
var kids = layer.getChildren();
var log = prefix + '|__ ' + layer.name() + ': ' + layer.id() + '\n';
for (var i = 0; i < kids.length; ++i) {
log += dwv.draw.getHierarchyLog(kids[i], prefix + ' ');
}
return log;
};
/**
* Draw controller.
*
* @class
* @param {object} konvaLayer The draw layer.
*/
dwv.ctrl.DrawController = function (konvaLayer) {
// current position group id
var currentPosGroupId = null;
/**
* Get the current position group.
*
* @returns {object} The Konva.Group.
*/
this.getCurrentPosGroup = function () {
// get position groups
var posGroups = konvaLayer.getChildren(function (node) {
return node.id() === currentPosGroupId;
});
// if one group, use it
// if no group, create one
var posGroup = null;
if (posGroups.length === 1) {
posGroup = posGroups[0];
} else if (posGroups.length === 0) {
posGroup = new Konva.Group();
posGroup.name('position-group');
posGroup.id(currentPosGroupId);
posGroup.visible(true); // dont inherit
// add new group to layer
konvaLayer.add(posGroup);
} else {
dwv.logger.warn('Unexpected number of draw position groups.');
}
// return
return posGroup;
};
/**
* Reset: clear the layers array.
*/
this.reset = function () {
konvaLayer = null;
};
/**
* Activate the current draw layer.
*
* @param {dwv.math.Index} index The current position.
* @param {number} scrollIndex The scroll index.
*/
this.activateDrawLayer = function (index, scrollIndex) {
// TODO: add layer info
// get and store the position group id
var dims = [scrollIndex];
for (var j = 3; j < index.length(); ++j) {
dims.push(j);
}
currentPosGroupId = index.toStringId(dims);
// get all position groups
var posGroups = konvaLayer.getChildren(dwv.draw.isPositionNode);
// reset or set the visible property
var visible;
for (var i = 0, leni = posGroups.length; i < leni; ++i) {
visible = false;
if (posGroups[i].id() === currentPosGroupId) {
visible = true;
}
// group members inherit the visible property
posGroups[i].visible(visible);
}
// show current draw layer
konvaLayer.draw();
};
/**
* Get a list of drawing display details.
*
* @returns {Array} A list of draw details as
* {id, position, type, color, meta}
*/
this.getDrawDisplayDetails = function () {
var list = [];
var groups = konvaLayer.getChildren();
for (var j = 0, lenj = groups.length; j < lenj; ++j) {
var position = dwv.math.getIndexFromStringId(groups[j].id());
var collec = groups[j].getChildren();
for (var i = 0, leni = collec.length; i < leni; ++i) {
var shape = collec[i].getChildren(dwv.draw.isNodeNameShape)[0];
var label = collec[i].getChildren(dwv.draw.isNodeNameLabel)[0];
var text = label.getChildren()[0];
var type = shape.className;
if (type === 'Line') {
var shapeExtrakids = collec[i].getChildren(
dwv.draw.isNodeNameShapeExtra);
if (shape.closed()) {
type = 'Roi';
} else if (shapeExtrakids.length !== 0) {
if (shapeExtrakids[0].name().indexOf('triangle') !== -1) {
type = 'Arrow';
} else {
type = 'Ruler';
}
}
}
if (type === 'Rect') {
type = 'Rectangle';
}
list.push({
id: collec[i].id(),
position: position.toString(),
type: type,
color: shape.stroke(),
meta: text.meta
});
}
}
return list;
};
/**
* Get a list of drawing store details.
*
* @returns {object} A list of draw details including id, text, quant...
* TODO Unify with getDrawDisplayDetails?
*/
this.getDrawStoreDetails = function () {
var drawingsDetails = {};
// get all position groups
var posGroups = konvaLayer.getChildren(dwv.draw.isPositionNode);
var posKids;
var group;
for (var i = 0, leni = posGroups.length; i < leni; ++i) {
posKids = posGroups[i].getChildren();
for (var j = 0, lenj = posKids.length; j < lenj; ++j) {
group = posKids[j];
// remove anchors
var anchors = group.find('.anchor');
for (var a = 0; a < anchors.length; ++a) {
anchors[a].remove();
}
// get text
var texts = group.find('.text');
if (texts.length !== 1) {
dwv.logger.warn('There should not be more than one text per shape.');
}
// get meta (non konva vars)
drawingsDetails[group.id()] = {
meta: texts[0].meta
};
}
}
return drawingsDetails;
};
/**
* Set the drawings on the current stage.
*
* @param {Array} drawings An array of drawings.
* @param {Array} drawingsDetails An array of drawings details.
* @param {object} cmdCallback The DrawCommand callback.
* @param {object} exeCallback The callback to call once the
* DrawCommand has been executed.
*/
this.setDrawings = function (
drawings, drawingsDetails, cmdCallback, exeCallback) {
// regular Konva deserialize
var stateLayer = Konva.Node.create(drawings);
// get all position groups
var statePosGroups = stateLayer.getChildren(dwv.draw.isPositionNode);
for (var i = 0, leni = statePosGroups.length; i < leni; ++i) {
var statePosGroup = statePosGroups[i];
// Get or create position-group if it does not exist and
// append it to konvaLayer
var posGroup = konvaLayer.getChildren(
dwv.draw.isNodeWithId(statePosGroup.id()))[0];
if (typeof posGroup === 'undefined') {
posGroup = new Konva.Group({
id: statePosGroup.id(),
name: 'position-group',
visible: false
});
konvaLayer.add(posGroup);
}
var statePosKids = statePosGroup.getChildren();
for (var j = 0, lenj = statePosKids.length; j < lenj; ++j) {
// shape group (use first one since it will be removed from
// the group when we change it)
var stateGroup = statePosKids[0];
// add group to posGroup (switches its parent)
posGroup.add(stateGroup);
// shape
var shape = stateGroup.getChildren(dwv.draw.isNodeNameShape)[0];
// create the draw command
var cmd = new dwv.tool.DrawGroupCommand(
stateGroup, shape.className, konvaLayer);
// draw command callbacks
cmd.onExecute = cmdCallback;
cmd.onUndo = cmdCallback;
// details
if (drawingsDetails) {
var details = drawingsDetails[stateGroup.id()];
var label = stateGroup.getChildren(dwv.draw.isNodeNameLabel)[0];
var text = label.getText();
// store details
text.meta = details.meta;
// reset text (it was not encoded)
text.setText(dwv.utils.replaceFlags(
text.meta.textExpr, text.meta.quantification
));
}
// execute
cmd.execute();
exeCallback(cmd);
}
}
};
/**
* Update a drawing from its details.
*
* @param {object} drawDetails Details of the drawing to update.
*/
this.updateDraw = function (drawDetails) {
// get the group
var group = konvaLayer.findOne('#' + drawDetails.id);
if (typeof group === 'undefined') {
dwv.logger.warn(
'[updateDraw] Cannot find group with id: ' + drawDetails.id
);
return;
}
// shape
var shapes = group.getChildren(dwv.draw.isNodeNameShape);
for (var i = 0; i < shapes.length; ++i) {
shapes[i].stroke(drawDetails.color);
}
// shape extra
var shapesExtra = group.getChildren(dwv.draw.isNodeNameShapeExtra);
for (var j = 0; j < shapesExtra.length; ++j) {
if (typeof shapesExtra[j].stroke() !== 'undefined') {
shapesExtra[j].stroke(drawDetails.color);
} else if (typeof shapesExtra[j].fill() !== 'undefined') {
// for example text
shapesExtra[j].fill(drawDetails.color);
}
}
// label
var label = group.getChildren(dwv.draw.isNodeNameLabel)[0];
var shadowColor = dwv.utils.getShadowColour(drawDetails.color);
var kids = label.getChildren();
for (var k = 0; k < kids.length; ++k) {
var kid = kids[k];
kid.fill(drawDetails.color);
if (kids[k].className === 'Text') {
var text = kids[k];
text.shadowColor(shadowColor);
if (typeof drawDetails.meta !== 'undefined') {
text.meta = drawDetails.meta;
text.setText(dwv.utils.replaceFlags(
text.meta.textExpr, text.meta.quantification
));
label.setVisible(text.meta.textExpr.length !== 0);
}
}
}
// udpate current layer
konvaLayer.draw();
};
/**
* Check the visibility of a given group.
*
* @param {object} drawDetails Details of the group to check.
* @returns {boolean} True if the group is visible.
*/
this.isGroupVisible = function (drawDetails) {
// get the group
var group = konvaLayer.findOne('#' + drawDetails.id);
if (typeof group === 'undefined') {
dwv.logger.warn(
'[isGroupVisible] Cannot find node with id: ' + drawDetails.id
);
return false;
}
// get visibility
return group.isVisible();
};
/**
* Toggle the visibility of a given group.
*
* @param {object} drawDetails Details of the group to update.
* @returns {boolean} False if the group cannot be found.
*/
this.toogleGroupVisibility = function (drawDetails) {
// get the group
var group = konvaLayer.findOne('#' + drawDetails.id);
if (typeof group === 'undefined') {
dwv.logger.warn(
'[toogleGroupVisibility] Cannot find node with id: ' + drawDetails.id
);
return false;
}
// toggle visible
group.visible(!group.isVisible());
// udpate current layer
konvaLayer.draw();
};
/**
* Delete a Draw from the stage.
*
* @param {number} groupId The group id of the group to delete.
* @param {object} cmdCallback The DeleteCommand callback.
* @param {object} exeCallback The callback to call once the
* DeleteCommand has been executed.
*/
this.deleteDrawGroupId = function (groupId, cmdCallback, exeCallback) {
var groups = konvaLayer.getChildren();
var groupToDelete = groups.getChildren(function (node) {
return node.id() === groupId;
});
if (groupToDelete.length === 1) {
this.deleteDrawGroup(groupToDelete[0], cmdCallback, exeCallback);
} else if (groupToDelete.length === 0) {
dwv.logger.warn('Can\'t delete group with id:\'' + groupId +
'\', cannot find it.');
} else {
dwv.logger.warn('Can\'t delete group with id:\'' + groupId +
'\', too many with the same id.');
}
};
/**
* Delete a Draw from the stage.
*
* @param {object} group The group to delete.
* @param {object} cmdCallback The DeleteCommand callback.
* @param {object} exeCallback The callback to call once the
* DeleteCommand has been executed.
*/
this.deleteDrawGroup = function (group, cmdCallback, exeCallback) {
var shape = group.getChildren(dwv.draw.isNodeNameShape)[0];
var shapeDisplayName = dwv.tool.GetShapeDisplayName(shape);
var delcmd = new dwv.tool.DeleteGroupCommand(
group, shapeDisplayName, konvaLayer);
delcmd.onExecute = cmdCallback;
delcmd.onUndo = cmdCallback;
delcmd.execute();
exeCallback(delcmd);
};
/**
* Delete all Draws from the stage.
*
* @param {object} cmdCallback The DeleteCommand callback.
* @param {object} exeCallback The callback to call once the
* DeleteCommand has been executed.
*/
this.deleteDraws = function (cmdCallback, exeCallback) {
var groups = konvaLayer.getChildren();
while (groups.length) {
this.deleteDrawGroup(groups[0], cmdCallback, exeCallback);
}
};
}; // class DrawController