diff --git a/documentation/md/style.md b/documentation/md/style.md index 92470fe925..f43e94da3c 100644 --- a/documentation/md/style.md +++ b/documentation/md/style.md @@ -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 @@ -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 @@ -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 diff --git a/src/extensions/renderer/base/coord-ele-math/edge-control-points.js b/src/extensions/renderer/base/coord-ele-math/edge-control-points.js index 8cac08e1bc..da75bd91f7 100644 --- a/src/extensions/renderer/base/coord-ele-math/edge-control-points.js +++ b/src/extensions/renderer/base/coord-ele-math/edge-control-points.js @@ -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]; @@ -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 ); @@ -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, @@ -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'); @@ -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, diff --git a/src/style/properties.js b/src/style/properties.js index 3804852bfd..8c7588ddc4 100644 --- a/src/style/properties.js +++ b/src/style/properties.js @@ -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,