Skip to content

Commit

Permalink
refactor(dia.CellView)!: early evaluation of calc attributes (#2459)
Browse files Browse the repository at this point in the history
  • Loading branch information
kumilingus authored Jan 16, 2024
1 parent 1d392a1 commit da5000b
Show file tree
Hide file tree
Showing 19 changed files with 796 additions and 780 deletions.
2 changes: 1 addition & 1 deletion examples/fta-js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ const IntermediateEvent = Event.define(
},
{
attributes: {
gateType: {
'gate-type': {
set: function(type) {
const data = this.model.gateTypes[type];
return { d: data ? data + ' M 0 -30 0 -80' : 'M 0 0 0 0' };
Expand Down
8 changes: 4 additions & 4 deletions packages/joint-core/demo/custom-shapes/src/custom-shapes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ var paper = new joint.dia.Paper({
});

// Global special attributes
joint.dia.attributes.lineStyle = {
joint.dia.attributes['line-style'] = {
set: function(lineStyle, refBBox, node, attrs) {

var n = attrs['strokeWidth'] || attrs['stroke-width'] || 1;
var n = attrs['stroke-width'] || 1;
var dasharray = {
'dashed': (4*n) + ',' + (2*n),
'dotted': n + ',' + n
Expand All @@ -27,7 +27,7 @@ joint.dia.attributes.lineStyle = {
}
};

joint.dia.attributes.fitRef = {
joint.dia.attributes['fit-ref'] = {
set: function(fitRef, refBBox, node) {
switch (node.tagName.toUpperCase()) {
case 'ELLIPSE':
Expand Down Expand Up @@ -509,7 +509,7 @@ var Progress = joint.dia.Element.define('progress', {
}
}, {
attributes: {
progressD: {
'progress-d': {
set: function(value, bbox) {

function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
Expand Down
6 changes: 3 additions & 3 deletions packages/joint-core/demo/rough/src/rough.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
},

attributes: {
rough: {
'rough': {
set: function(opt, bbox) {
var r = this.paper.rough;
if (!r) return;
Expand All @@ -248,7 +248,7 @@
return { d: r.opsToPath(sets[opt.fillSketch ? 0 : 1]) };
}
},
pointerShape: {
'pointer-shape': {
set: function(type, bbox) {
var vel;
var width = bbox.width;
Expand Down Expand Up @@ -314,7 +314,7 @@
}]
}, {
attributes: {
rough: {
'rough': {
set: function(opt) {
var r = this.paper.rough;
if (!r) return;
Expand Down
3 changes: 3 additions & 0 deletions packages/joint-core/demo/shapes/src/standard.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ var util = joint.util;
var standard = joint.shapes.standard;
var elementTools = joint.elementTools;

// Do not translate the following attribute names.
V.attributeNames['placeholderURL'] = 'placeholderURL';

// Custom attribute for retrieving image placeholder with specific size
dia.attributes.placeholderURL = {
qualify: function(url) {
Expand Down
3 changes: 3 additions & 0 deletions packages/joint-core/docs/demo/shapes/shapes.standard.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ var dia = joint.dia;
var util = joint.util;
var standard = joint.shapes.standard;

// Do not translate the following attribute names.
V.attributeNames['placeholderURL'] = 'placeholderURL';

// Custom attribute for retrieving image placeholder with specific size
dia.attributes.placeholderURL = {
qualify: function(url) {
Expand Down
10 changes: 10 additions & 0 deletions packages/joint-core/src/V/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1445,6 +1445,16 @@ const V = (function() {
'zoomAndPan' // deprecated
].forEach((name) => _attributeNames[name] = name);

_attributeNames['xlinkShow'] = 'xlink:show';
_attributeNames['xlinkRole'] = 'xlink:role';
_attributeNames['xlinkActuate'] = 'xlink:actuate';
_attributeNames['xlinkHref'] = 'xlink:href';
_attributeNames['xlinkType'] = 'xlink:type';
_attributeNames['xlinkTitle'] = 'xlink:title';
_attributeNames['xmlBase'] = 'xml:base';
_attributeNames['xmlLang'] = 'xml:lang';
_attributeNames['xmlSpace'] = 'xml:space';

const attributeNames = new Proxy(_attributeNames, {
get(cache, name) {
// The cache is a dictionary of attribute names. See `_attributeNames` above.
Expand Down
43 changes: 27 additions & 16 deletions packages/joint-core/src/dia/CellView.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
isBoolean,
isEmpty,
isString,
toKebabCase,
result,
sortedIndex,
merge,
Expand All @@ -21,6 +20,7 @@ import { Point, Rect } from '../g/index.mjs';
import V from '../V/index.mjs';
import $ from '../mvc/Dom/index.mjs';
import { HighlighterView } from './HighlighterView.mjs';
import { evalAttributes, evalAttribute } from './attributes/eval.mjs';

const HighlightingTypes = {
DEFAULT: 'default',
Expand Down Expand Up @@ -544,12 +544,17 @@ export const CellView = View.extend({
var attrName, attrVal, def, i, n;
var normalAttrs, setAttrs, positionAttrs, offsetAttrs;
var relatives = [];
// divide the attributes between normal and special
const rawAttrs = {};
for (attrName in attrs) {
if (!attrs.hasOwnProperty(attrName)) continue;
attrVal = attrs[attrName];
rawAttrs[V.attributeNames[attrName]] = attrs[attrName];
}
// divide the attributes between normal and special
for (attrName in rawAttrs) {
if (!rawAttrs.hasOwnProperty(attrName)) continue;
attrVal = rawAttrs[attrName];
def = this.getAttributeDefinition(attrName);
if (def && (!isFunction(def.qualify) || def.qualify.call(this, attrVal, node, attrs, this))) {
if (def && (!isFunction(def.qualify) || def.qualify.call(this, attrVal, node, rawAttrs, this))) {
if (isString(def.set)) {
normalAttrs || (normalAttrs = {});
normalAttrs[def.set] = attrVal;
Expand All @@ -559,7 +564,7 @@ export const CellView = View.extend({
}
} else {
normalAttrs || (normalAttrs = {});
normalAttrs[toKebabCase(attrName)] = attrVal;
normalAttrs[attrName] = attrVal;
}
}

Expand All @@ -584,7 +589,7 @@ export const CellView = View.extend({
}

return {
raw: attrs,
raw: rawAttrs,
normal: normalAttrs,
set: setAttrs,
position: positionAttrs,
Expand All @@ -597,19 +602,22 @@ export const CellView = View.extend({
opt || (opt = {});

var attrName, attrVal, def;
var rawAttrs = attrs.raw || {};
var evalAttrs = evalAttributes(attrs.raw || {}, refBBox);
var nodeAttrs = attrs.normal || {};
for (const nodeAttrName in nodeAttrs) {
nodeAttrs[nodeAttrName] = evalAttrs[nodeAttrName];
}
var setAttrs = attrs.set;
var positionAttrs = attrs.position;
var offsetAttrs = attrs.offset;

for (attrName in setAttrs) {
attrVal = setAttrs[attrName];
attrVal = evalAttrs[attrName];
def = this.getAttributeDefinition(attrName);
// SET - set function should return attributes to be set on the node,
// which will affect the node dimensions based on the reference bounding
// box. e.g. `width`, `height`, `d`, `rx`, `ry`, `points
var setResult = def.set.call(this, attrVal, refBBox.clone(), node, rawAttrs, this);
var setResult = def.set.call(this, attrVal, refBBox.clone(), node, evalAttrs, this);
if (isObject(setResult)) {
assign(nodeAttrs, setResult);
} else if (setResult !== undefined) {
Expand Down Expand Up @@ -645,13 +653,13 @@ export const CellView = View.extend({

var positioned = false;
for (attrName in positionAttrs) {
attrVal = positionAttrs[attrName];
attrVal = evalAttrs[attrName];
def = this.getAttributeDefinition(attrName);
// POSITION - position function should return a point from the
// reference bounding box. The default position of the node is x:0, y:0 of
// the reference bounding box or could be further specify by some
// SVG attributes e.g. `x`, `y`
translation = def.position.call(this, attrVal, refBBox.clone(), node, rawAttrs, this);
translation = def.position.call(this, attrVal, refBBox.clone(), node, evalAttrs, this);
if (translation) {
nodePosition.offset(Point(translation).scale(sx, sy));
positioned || (positioned = true);
Expand All @@ -669,12 +677,12 @@ export const CellView = View.extend({
if (nodeBoundingRect.width > 0 && nodeBoundingRect.height > 0) {
var nodeBBox = V.transformRect(nodeBoundingRect, nodeMatrix).scale(1 / sx, 1 / sy);
for (attrName in offsetAttrs) {
attrVal = offsetAttrs[attrName];
attrVal = evalAttrs[attrName];
def = this.getAttributeDefinition(attrName);
// OFFSET - offset function should return a point from the element
// bounding box. The default offset point is x:0, y:0 (origin) or could be further
// specify with some SVG attributes e.g. `text-anchor`, `cx`, `cy`
translation = def.offset.call(this, attrVal, nodeBBox, node, rawAttrs, this);
translation = def.offset.call(this, attrVal, nodeBBox, node, evalAttrs, this);
if (translation) {
nodePosition.offset(Point(translation).scale(sx, sy));
offseted || (offseted = true);
Expand Down Expand Up @@ -874,9 +882,9 @@ export const CellView = View.extend({
node = nodeData.node;
processedAttrs = this.processNodeAttributes(node, nodeAttrs);

if (!processedAttrs.set && !processedAttrs.position && !processedAttrs.offset) {
if (!processedAttrs.set && !processedAttrs.position && !processedAttrs.offset && !processedAttrs.raw.ref) {
// Set all the normal attributes right on the SVG/HTML element.
this.setNodeAttributes(node, processedAttrs.normal);
this.setNodeAttributes(node, evalAttributes(processedAttrs.normal, opt.rootBBox));

} else {

Expand Down Expand Up @@ -1280,7 +1288,10 @@ export const CellView = View.extend({
if (typeof b === 'string') b = [b];
if (Array.isArray(a) && Array.isArray(b)) return uniq(a.concat(b));
});
}
},

evalAttribute,

});

// TODO: Move to Vectorizer library.
Expand Down
75 changes: 75 additions & 0 deletions packages/joint-core/src/dia/attributes/connection.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Point } from '../../g/index.mjs';

function atConnectionWrapper(method, opt) {
var zeroVector = new Point(1, 0);
return function(value) {
var p, angle;
var tangent = this[method](value);
if (tangent) {
angle = (opt.rotate) ? tangent.vector().vectorAngle(zeroVector) : 0;
p = tangent.start;
} else {
p = this.path.start;
angle = 0;
}
if (angle === 0) return { transform: 'translate(' + p.x + ',' + p.y + ')' };
return { transform: 'translate(' + p.x + ',' + p.y + ') rotate(' + angle + ')' };
};
}

function isLinkView() {
return this.model.isLink();
}

const connectionAttributesNS = {

'connection': {
qualify: isLinkView,
set: function({ stubs = 0 }) {
let d;
if (isFinite(stubs) && stubs !== 0) {
let offset;
if (stubs < 0) {
offset = (this.getConnectionLength() + stubs) / 2;
} else {
offset = stubs;
}
const path = this.getConnection();
const segmentSubdivisions = this.getConnectionSubdivisions();
const sourceParts = path.divideAtLength(offset, { segmentSubdivisions });
const targetParts = path.divideAtLength(-offset, { segmentSubdivisions });
if (sourceParts && targetParts) {
d = `${sourceParts[0].serialize()} ${targetParts[1].serialize()}`;
}
}

return { d: d || this.getSerializedConnection() };
}
},

'at-connection-length-keep-gradient': {
qualify: isLinkView,
set: atConnectionWrapper('getTangentAtLength', { rotate: true })
},

'at-connection-length-ignore-gradient': {
qualify: isLinkView,
set: atConnectionWrapper('getTangentAtLength', { rotate: false })
},

'at-connection-ratio-keep-gradient': {
qualify: isLinkView,
set: atConnectionWrapper('getTangentAtRatio', { rotate: true })
},

'at-connection-ratio-ignore-gradient': {
qualify: isLinkView,
set: atConnectionWrapper('getTangentAtRatio', { rotate: false })
}

};

connectionAttributesNS['at-connection-length'] = connectionAttributesNS['at-connection-length-keep-gradient'];
connectionAttributesNS['at-connection-ratio'] = connectionAttributesNS['at-connection-ratio-keep-gradient'];

export default connectionAttributesNS;
76 changes: 76 additions & 0 deletions packages/joint-core/src/dia/attributes/defs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { assign, isPlainObject } from '../../util/util.mjs';

function contextMarker(context) {
var marker = {};
// Stroke
// The context 'fill' is disregarded here. The usual case is to use the marker with a connection
// (for which 'fill' attribute is set to 'none').
var stroke = context.stroke;
if (typeof stroke === 'string') {
marker['stroke'] = stroke;
marker['fill'] = stroke;
}
// Opacity
// Again the context 'fill-opacity' is ignored.
var strokeOpacity = context['stroke-opacity'];
if (strokeOpacity === undefined) strokeOpacity = context.opacity;
if (strokeOpacity !== undefined) {
marker['stroke-opacity'] = strokeOpacity;
marker['fill-opacity'] = strokeOpacity;
}
return marker;
}

function setPaintURL(def) {
const { paper } = this;
const url = (def.type === 'pattern')
? paper.definePattern(def)
: paper.defineGradient(def);
return `url(#${url})`;
}

const defsAttributesNS = {

'source-marker': {
qualify: isPlainObject,
set: function(marker, refBBox, node, attrs) {
marker = assign(contextMarker(attrs), marker);
return { 'marker-start': 'url(#' + this.paper.defineMarker(marker) + ')' };
}
},

'target-marker': {
qualify: isPlainObject,
set: function(marker, refBBox, node, attrs) {
marker = assign(contextMarker(attrs), { 'transform': 'rotate(180)' }, marker);
return { 'marker-end': 'url(#' + this.paper.defineMarker(marker) + ')' };
}
},

'vertex-marker': {
qualify: isPlainObject,
set: function(marker, refBBox, node, attrs) {
marker = assign(contextMarker(attrs), marker);
return { 'marker-mid': 'url(#' + this.paper.defineMarker(marker) + ')' };
}
},

'fill': {
qualify: isPlainObject,
set: setPaintURL
},

'stroke': {
qualify: isPlainObject,
set: setPaintURL
},

'filter': {
qualify: isPlainObject,
set: function(filter) {
return 'url(#' + this.paper.defineFilter(filter) + ')';
}
},
};

export default defsAttributesNS;
Loading

0 comments on commit da5000b

Please sign in to comment.