Skip to content

Commit

Permalink
Add support for edge-distances:endpoints
Browse files Browse the repository at this point in the history
Ref: edge-distances: endpoints #3157

Notes:

- This change allows bundled bezier edges, unbundled bezier edges, and segment edges to use `edge-distances: endpoints`.
- The documentation has been updated accordingly.
- The change is minimally invasive by making a new function for the adjustment.
  - The adjustment alters the midpoint and the inverted normal vector used for middle-point calculations (e.g. bezier control points).
  - Limitation: The adjustment must happen at the middle-point phase, rather than the endpoint phase.  The endpoint phase happens after the middle point phase, because many edges have automatic endpoints based on the middle points.  This means that the endpoint calculations would be duplicated for `edge-distances: endpoints` edges.  In future, this could be optimised if needed.
- Limitation: For a bundled bezier, this implementation assumes that all beziers in the bundle have the same endpoint.  That doesn't seem like a valid use case, anyway.
  • Loading branch information
maxkfranz committed Sep 12, 2023
1 parent 2211100 commit 1a6c407
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 10 deletions.
24 changes: 21 additions & 3 deletions documentation/md/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,13 @@ A bezier edge is bundled with all other parallel bezier edges. Each bezier edge
* **`control-point-step-size`** : Along the line perpendicular from source to target, this value specifies the distance between successive bezier edges.
* **`control-point-distance`** : A single value that overrides `control-point-step-size` with a manual value. Because it overrides the step size, bezier edges with the same value will overlap. Thus, it's best to use this as a one-off value for particular edges if need be.
* **`control-point-weight`** : A single value that weights control points along the line from source to target. The value usually ranges on [0, 1], with 0 towards the source node and 1 towards the target node --- but larger or smaller values can also be used.
* **`edge-distances`** : With value `intersection` (default), the line from source to target for `control-point-weight` is from the outside of the source node's shape to the outside of the target node's shape. With value `node-position`, the line is from the source position to the target position. The `node-position` option makes calculating edge points easier --- but it should be used carefully because you can create invalid points that `intersection` would have automatically corrected.
* **`edge-distances`** :
* With value `intersection` (default), the line from source to target for `segment-weights` is from the outside of the source node's shape to the outside of the target node's shape.
* With value `node-position`, the line is from the source position to the target position.
* The `node-position` option makes calculating edge points easier --- but it should be used carefully because you can create invalid points that `intersection` would have automatically corrected.
* With value `endpoints`, the line is from the manually-specified source endpoint (via `source-endpoint`) to the manually-specified target endpoint (via `target-endpoint`).
* A manual endpoint may be specified with a position, e.g. `source-endpoint: 20 10`.
* A manual endpoint may be alternatively specified with an angle, e.g. `target-endpoint: 90deg`.
## Loop edges
Expand Down Expand Up @@ -389,7 +395,13 @@ When two or more control points are specified for an unbundled bezier edge, each
* **`control-point-distances`** : A series of values that specify for each control point the distance perpendicular to a line formed from source to target, e.g. `-20 20 -20`.
* **`control-point-weights`** : A series of values that weights control points along a line from source to target, e.g. `0.25 0.5 0.75`. A value usually ranges on [0, 1], with 0 towards the source node and 1 towards the target node --- but larger or smaller values can also be used.
* **`edge-distances`** : With value `intersection` (default), the line from source to target for `control-point-weights` is from the outside of the source node's shape to the outside of the target node's shape. With value `node-position`, the line is from the source position to the target position. The `node-position` option makes calculating edge points easier --- but it should be used carefully because you can create invalid points that `intersection` would have automatically corrected.
* **`edge-distances`** :
* With value `intersection` (default), the line from source to target for `segment-weights` is from the outside of the source node's shape to the outside of the target node's shape.
* With value `node-position`, the line is from the source position to the target position.
* The `node-position` option makes calculating edge points easier --- but it should be used carefully because you can create invalid points that `intersection` would have automatically corrected.
* With value `endpoints`, the line is from the manually-specified source endpoint (via `source-endpoint`) to the manually-specified target endpoint (via `target-endpoint`).
* A manual endpoint may be specified with a position, e.g. `source-endpoint: 20 10`.
* A manual endpoint may be alternatively specified with an angle, e.g. `target-endpoint: 90deg`.
## Haystack edges
Expand All @@ -411,7 +423,13 @@ A segment edge is made of a series of one or more straight lines, using a co-ord
* **`segment-distances`** : A series of values that specify for each segment point the distance perpendicular to a line formed from source to target, e.g. `-20 20 -20`.
* **`segment-weights`** : A series of values that weights segment points along a line from source to target, e.g. `0.25 0.5 0.75`. A value usually ranges on [0, 1], with 0 towards the source node and 1 towards the target node --- but larger or smaller values can also be used.
* **`edge-distances`** : With value `intersection` (default), the line from source to target for `segment-weights` is from the outside of the source node's shape to the outside of the target node's shape. With value `node-position`, the line is from the source position to the target position. The `node-position` option makes calculating edge points easier --- but it should be used carefully because you can create invalid points that `intersection` would have automatically corrected.
* **`edge-distances`** :
* With value `intersection` (default), the line from source to target for `segment-weights` is from the outside of the source node's shape to the outside of the target node's shape.
* With value `node-position`, the line is from the source position to the target position.
* The `node-position` option makes calculating edge points easier --- but it should be used carefully because you can create invalid points that `intersection` would have automatically corrected.
* With value `endpoints`, the line is from the manually-specified source endpoint (via `source-endpoint`) to the manually-specified target endpoint (via `target-endpoint`).
* A manual endpoint may be specified with a position, e.g. `source-endpoint: 20 10`.
* A manual endpoint may be alternatively specified with an angle, e.g. `target-endpoint: 90deg`.
## Straight edges
Expand Down
59 changes: 53 additions & 6 deletions src/extensions/renderer/base/coord-ele-math/edge-control-points.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,57 @@ import Map from '../../../../map';

let BRp = {};

BRp.findMidptPtsEtc = function(edge, pairInfo) {
let { posPts, intersectionPts, vectorNormInverse } = pairInfo;

let midptPts;

// n.b. assumes all edges in bezier bundle have same endpoints specified
let srcManEndpt = edge.pstyle('source-endpoint');
let tgtManEndpt = edge.pstyle('target-endpoint');
const haveManualEndPts = srcManEndpt.units != null && tgtManEndpt.units != null;

const recalcVectorNormInverse = (x1, y1, x2, y2) => {
let dy = ( y2 - y1 );
let dx = ( x2 - x1 );
let l = Math.sqrt( dx * dx + dy * dy );

return {
x: -dy / l,
y: dx / l
};
};

const edgeDistances = edge.pstyle('edge-distances').value;

switch(edgeDistances) {
case 'node-position':
midptPts = posPts;
break;

case 'intersection':
midptPts = intersectionPts;
break;

case 'endpoints': {
if (haveManualEndPts) {
const [x1, y1] = this.manualEndptToPx( edge.source()[0], srcManEndpt );
const [x2, y2] = this.manualEndptToPx( edge.target()[0], tgtManEndpt );
const endPts = { x1, y1, x2, y2 };

vectorNormInverse = recalcVectorNormInverse(x1, y1, x2, y2);
midptPts = endPts;
} else {
util.warn(`Edge ${edge.id()} has edge-distances:endpoints specified without manual endpoints specified via source-endpoint and target-endpoint. Falling back on edge-distances:intersection (default).`);
midptPts = intersectionPts; // back to default
}
break;
}
}

return { midptPts, vectorNormInverse };
};

BRp.findHaystackPoints = function( edges ){
for( let i = 0; i < edges.length; i++ ){
let edge = edges[i];
Expand Down Expand Up @@ -64,8 +115,6 @@ BRp.findSegmentsPoints = function( edge, pairInfo ){
// Segments (multiple straight lines)

const rs = edge._private.rscratch;
const { posPts, intersectionPts, vectorNormInverse } = pairInfo;
const edgeDistances = edge.pstyle('edge-distances').value;
const segmentWs = edge.pstyle( 'segment-weights' );
const segmentDs = edge.pstyle( 'segment-distances' );
const segmentsN = Math.min( segmentWs.pfValue.length, segmentDs.pfValue.length );
Expand All @@ -80,7 +129,7 @@ BRp.findSegmentsPoints = function( edge, pairInfo ){
let w1 = 1 - w;
let w2 = w;

let midptPts = edgeDistances === 'node-position' ? posPts : intersectionPts;
let { midptPts, vectorNormInverse } = this.findMidptPtsEtc(edge, pairInfo);

let adjustedMidpt = {
x: midptPts.x1 * w1 + midptPts.x2 * w2,
Expand Down Expand Up @@ -192,8 +241,6 @@ BRp.findStraightEdgePoints = function( edge ){

BRp.findBezierPoints = function( edge, pairInfo, i, edgeIsUnbundled, edgeIsSwapped ){
const rs = edge._private.rscratch;
const { vectorNormInverse, posPts, intersectionPts } = pairInfo;
const edgeDistances = edge.pstyle('edge-distances').value;
const stepSize = edge.pstyle('control-point-step-size').pfValue;
const ctrlptDists = edge.pstyle('control-point-distances');
const ctrlptWs = edge.pstyle('control-point-weights');
Expand Down Expand Up @@ -230,7 +277,7 @@ BRp.findBezierPoints = function( edge, pairInfo, i, edgeIsUnbundled, edgeIsSwapp
let w1 = 1 - ctrlptWeight;
let w2 = ctrlptWeight;

let midptPts = edgeDistances === 'node-position' ? posPts : intersectionPts;
let { midptPts, vectorNormInverse } = this.findMidptPtsEtc(edge, pairInfo);

let adjustedMidpt = {
x: midptPts.x1 * w1 + midptPts.x2 * w2,
Expand Down
2 changes: 1 addition & 1 deletion src/style/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const styfn = {};
angle: { number: true, units: 'deg|rad', implicitUnits: 'rad' },
textRotation: { number: true, units: 'deg|rad', implicitUnits: 'rad', enums: [ 'none', 'autorotate' ] },
polygonPointList: { number: true, multiple: true, evenMultiple: true, min: -1, max: 1, unitless: true },
edgeDistances: { enums: ['intersection', 'node-position'] },
edgeDistances: { enums: ['intersection', 'node-position', 'endpoints'] },
edgeEndpoint: {
number: true, multiple: true, units: '%|px|em|deg|rad', implicitUnits: 'px',
enums: [ 'inside-to-node', 'outside-to-node', 'outside-to-node-or-label', 'outside-to-line', 'outside-to-line-or-label' ], singleEnum: true,
Expand Down

0 comments on commit 1a6c407

Please sign in to comment.