Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(dia.CellView)!: early evaluation of calc attributes #2459

Merged
merged 5 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/fta-js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,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