src/renderer/canvas/symbols/ShapeSymbolCanvasRenderer.js
import { rendererLogger as logger } from '../../../configuration/LoggerConfig';
/**
* @type {{table: String, shape: String, recognizedShape: String, ellipse: String, line: String}}
*/
export const ShapeSymbols = {
table: 'table',
shape: 'shape',
recognizedShape: 'recognizedShape',
ellipse: 'ellipse',
line: 'line'
};
function phi(angle) {
let returnedAngle = ((angle + Math.PI) % (Math.PI * 2)) - Math.PI;
if (returnedAngle < -Math.PI) {
returnedAngle += Math.PI * 2;
}
return returnedAngle;
}
function drawEllipseArc(context, centerPoint, maxRadius, minRadius, orientation, startAngle, sweepAngle) {
const angleStep = 0.02; // angle delta between interpolated
let z1 = Math.cos(orientation);
let z3 = Math.sin(orientation);
let z2 = z1;
let z4 = z3;
z1 *= maxRadius;
z2 *= minRadius;
z3 *= maxRadius;
z4 *= minRadius;
const n = Math.floor(Math.abs(sweepAngle) / angleStep);
const boundariesPoints = [];
context.save();
try {
context.beginPath();
for (let i = 0; i <= n; i++) {
const angle = startAngle + ((i / n) * sweepAngle); // points on the arc, in radian
const alpha = Math.atan2(Math.sin(angle) / minRadius, Math.cos(angle) / maxRadius);
const cosAlpha = Math.cos(alpha);
const sinAlpha = Math.sin(alpha);
// current point
const x = (centerPoint.x + (z1 * cosAlpha)) - (z4 * sinAlpha);
const y = (centerPoint.y + (z2 * sinAlpha)) + (z3 * cosAlpha);
if (i === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
if (i === 0 || i === n) {
boundariesPoints.push({ x, y });
}
}
context.stroke();
} finally {
context.restore();
}
return boundariesPoints;
}
function drawArrowHead(context, headPoint, angle, length) {
const alpha = phi(angle + (Math.PI * (7 / 8)));
const beta = phi(angle - (Math.PI * (7 / 8)));
const contextReference = context;
contextReference.save();
try {
contextReference.fillStyle = contextReference.strokeStyle;
contextReference.moveTo(headPoint.x, headPoint.y);
contextReference.beginPath();
contextReference.lineTo(headPoint.x + (length * Math.cos(alpha)), headPoint.y + (length * Math.sin(alpha)));
contextReference.lineTo(headPoint.x + (length * Math.cos(beta)), headPoint.y + (length * Math.sin(beta)));
contextReference.lineTo(headPoint.x, headPoint.y);
contextReference.fill();
} finally {
contextReference.restore();
}
}
function drawShapeEllipse(context, shapeEllipse) {
const points = drawEllipseArc(
context,
shapeEllipse.center,
shapeEllipse.maxRadius,
shapeEllipse.minRadius,
shapeEllipse.orientation,
shapeEllipse.startAngle,
shapeEllipse.sweepAngle);
if (shapeEllipse.beginDecoration && shapeEllipse.beginDecoration === 'ARROW_HEAD') {
drawArrowHead(context, points[0], shapeEllipse.beginTangentAngle, 12.0);
}
if (shapeEllipse.endDecoration && shapeEllipse.endDecoration === 'ARROW_HEAD') {
drawArrowHead(context, points[1], shapeEllipse.endTangentAngle, 12.0);
}
}
/**
* Draw a line
* @param {Object} context Current rendering context
* @param {{x: Number, y: Number}} p1 Origin point
* @param {{x: Number, y: Number}} p2 Destination point
*/
export function drawLine(context, p1, p2) {
context.save();
try {
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
context.stroke();
} finally {
context.restore();
}
}
function drawShapeLine(context, shapeLine) {
drawLine(context, shapeLine.firstPoint, shapeLine.lastPoint);
if (shapeLine.beginDecoration === 'ARROW_HEAD') {
drawArrowHead(context, shapeLine.firstPoint, shapeLine.beginTangentAngle, 12.0);
}
if (shapeLine.endDecoration === 'ARROW_HEAD') {
drawArrowHead(context, shapeLine.lastPoint, shapeLine.endTangentAngle, 12.0);
}
}
/**
* Draw a shape symbol
* @param {Object} context Current rendering context
* @param {Object} symbol Symbol to draw
*/
export function drawShapeSymbol(context, symbol) {
logger.debug(`draw ${symbol.type} symbol`);
const contextReference = context;
contextReference.save();
try {
contextReference.lineWidth = symbol.width;
contextReference.strokeStyle = symbol.color;
if (symbol.elementType) {
switch (symbol.elementType) {
case ShapeSymbols.shape:
drawShapeSymbol(contextReference, symbol.candidates[symbol.selectedCandidateIndex]);
break;
case ShapeSymbols.table:
symbol.lines.forEach(line => drawShapeSymbol(contextReference, line));
break;
case ShapeSymbols.line:
drawLine(contextReference, symbol.data.p1, symbol.data.p2);
break;
default:
logger.error(`${symbol.elementType} not implemented`);
break;
}
} else {
switch (symbol.type) {
case ShapeSymbols.ellipse:
drawShapeEllipse(contextReference, symbol);
break;
case ShapeSymbols.line:
drawShapeLine(contextReference, symbol);
break;
case ShapeSymbols.recognizedShape:
symbol.primitives.forEach(primitive => drawShapeSymbol(contextReference, primitive));
break;
default:
logger.error(`${symbol.type} not implemented`);
break;
}
}
} finally {
contextReference.restore();
}
}