mapshaper 0.7.65 → 0.7.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mapshaper-gui +3 -2
- package/mapshaper.js +907 -33
- package/package.json +1 -1
- package/www/index.html +6 -0
- package/www/mapshaper-gui.js +2633 -399
- package/www/mapshaper.js +907 -33
- package/www/page.css +177 -11
package/www/mapshaper.js
CHANGED
|
@@ -7549,19 +7549,19 @@
|
|
|
7549
7549
|
pointN = [CK.lengthMG, CK.lengthMG * Math.tan(30 * D2R$5)];
|
|
7550
7550
|
CK.pointA = [CK.lengthMA, 0];
|
|
7551
7551
|
CK.pointB = lineIntersection(CK.pointM, 30, CK.pointA, 45);
|
|
7552
|
-
CK.lengthAG = distance(CK.pointA, CK.pointG);
|
|
7553
|
-
CK.lengthAB = distance(CK.pointA, CK.pointB);
|
|
7554
|
-
lengthMB = distance(CK.pointM, CK.pointB);
|
|
7555
|
-
lengthMN = distance(CK.pointM, pointN);
|
|
7556
|
-
lengthNG = distance(pointN, CK.pointG);
|
|
7552
|
+
CK.lengthAG = distance$1(CK.pointA, CK.pointG);
|
|
7553
|
+
CK.lengthAB = distance$1(CK.pointA, CK.pointB);
|
|
7554
|
+
lengthMB = distance$1(CK.pointM, CK.pointB);
|
|
7555
|
+
lengthMN = distance$1(CK.pointM, pointN);
|
|
7556
|
+
lengthNG = distance$1(pointN, CK.pointG);
|
|
7557
7557
|
CK.pointD = interpolate(lengthMB, lengthMN, pointN, CK.pointM);
|
|
7558
7558
|
CK.pointF = [CK.lengthMG, lengthNG - lengthMB];
|
|
7559
7559
|
CK.pointE = [
|
|
7560
7560
|
pointN[0] - CK.lengthMA * Math.sin(30 * D2R$5),
|
|
7561
7561
|
pointN[1] - CK.lengthMA * Math.cos(30 * D2R$5)
|
|
7562
7562
|
];
|
|
7563
|
-
CK.lengthGF = distance(CK.pointG, CK.pointF);
|
|
7564
|
-
CK.lengthBD = distance(CK.pointB, CK.pointD);
|
|
7563
|
+
CK.lengthGF = distance$1(CK.pointG, CK.pointF);
|
|
7564
|
+
CK.lengthBD = distance$1(CK.pointB, CK.pointD);
|
|
7565
7565
|
CK.lengthGFE = CK.lengthGF + CK.lengthAB;
|
|
7566
7566
|
CK.deltaMEq = CK.lengthGFE / 45;
|
|
7567
7567
|
CK.lengthAP75 = 15 * CK.lengthParallel73to90At0;
|
|
@@ -7588,7 +7588,7 @@
|
|
|
7588
7588
|
k * CK.pointD[0] - CK.pointD[1])
|
|
7589
7589
|
);
|
|
7590
7590
|
CK.pointC[0] = k * CK.pointC[1];
|
|
7591
|
-
CK.radius = distance(CK.pointC, CK.pointD);
|
|
7591
|
+
CK.radius = distance$1(CK.pointC, CK.pointD);
|
|
7592
7592
|
}
|
|
7593
7593
|
|
|
7594
7594
|
function ll2mp(lon, lat) {
|
|
@@ -7619,12 +7619,12 @@
|
|
|
7619
7619
|
);
|
|
7620
7620
|
var l15;
|
|
7621
7621
|
if (hit[0]) {
|
|
7622
|
-
l15 = lT + distance(jointT(m), hit[1]);
|
|
7622
|
+
l15 = lT + distance$1(jointT(m), hit[1]);
|
|
7623
7623
|
} else {
|
|
7624
7624
|
hit = circleLineIntersection(
|
|
7625
7625
|
CK.pointC, CK.radius, jointE(m), jointT(m)
|
|
7626
7626
|
);
|
|
7627
|
-
l15 = lT - distance(jointT(m), hit[1]);
|
|
7627
|
+
l15 = lT - distance$1(jointT(m), hit[1]);
|
|
7628
7628
|
}
|
|
7629
7629
|
return p <= 15 ? zoneK(m, p, l15) : zoneL(m, p, l15);
|
|
7630
7630
|
}
|
|
@@ -7660,8 +7660,8 @@
|
|
|
7660
7660
|
function zoneH(m, p) {
|
|
7661
7661
|
var p75 = parallel75(45);
|
|
7662
7662
|
var p73 = parallel73(m).parallel73;
|
|
7663
|
-
var lF = distance(CK.pointT, CK.pointB);
|
|
7664
|
-
var lF75 = distance(CK.pointB, p75);
|
|
7663
|
+
var lF = distance$1(CK.pointT, CK.pointB);
|
|
7664
|
+
var lF75 = distance$1(CK.pointB, p75);
|
|
7665
7665
|
var l = (75 - p) * (lF75 + lF) / 2;
|
|
7666
7666
|
return l <= lF75 ?
|
|
7667
7667
|
interpolate(l, lF75, p75, CK.pointB) :
|
|
@@ -7688,7 +7688,7 @@
|
|
|
7688
7688
|
function zoneJ(m, p) {
|
|
7689
7689
|
var p75 = parallel75(m);
|
|
7690
7690
|
var p73a = parallel73(m);
|
|
7691
|
-
var lF75 = distance(jointF(m), p75);
|
|
7691
|
+
var lF75 = distance$1(jointF(m), p75);
|
|
7692
7692
|
var l = (75 - p) * (lF75 - p73a.lengthParallel73) / 2;
|
|
7693
7693
|
return l <= lF75 ?
|
|
7694
7694
|
interpolate(l, lF75, p75, jointF(m)) :
|
|
@@ -7745,11 +7745,11 @@
|
|
|
7745
7745
|
}
|
|
7746
7746
|
|
|
7747
7747
|
function lengthTorridSegment(m) {
|
|
7748
|
-
return distance(jointE(m), jointT(m));
|
|
7748
|
+
return distance$1(jointE(m), jointT(m));
|
|
7749
7749
|
}
|
|
7750
7750
|
|
|
7751
7751
|
function lengthMiddleSegment(m) {
|
|
7752
|
-
return distance(jointT(m), jointF(m));
|
|
7752
|
+
return distance$1(jointT(m), jointF(m));
|
|
7753
7753
|
}
|
|
7754
7754
|
|
|
7755
7755
|
function parallel73(m) {
|
|
@@ -7757,15 +7757,15 @@
|
|
|
7757
7757
|
var jF = jointF(m);
|
|
7758
7758
|
if (m <= 30) {
|
|
7759
7759
|
p73 = radialPoint(CK.pointA, CK.lengthAP73, m);
|
|
7760
|
-
lF = distance(jF, p73);
|
|
7760
|
+
lF = distance$1(jF, p73);
|
|
7761
7761
|
} else {
|
|
7762
7762
|
p73 = lineIntersection(CK.pointT, -60, jF, m);
|
|
7763
|
-
lF = distance(jF, p73);
|
|
7763
|
+
lF = distance$1(jF, p73);
|
|
7764
7764
|
if (m > 44) {
|
|
7765
7765
|
xy = lineIntersection(CK.pointT, -60, jF, 2 * m / 3);
|
|
7766
7766
|
if (xy[0] > p73[0]) {
|
|
7767
7767
|
p73 = xy;
|
|
7768
|
-
lF = -distance(jF, p73);
|
|
7768
|
+
lF = -distance$1(jF, p73);
|
|
7769
7769
|
}
|
|
7770
7770
|
}
|
|
7771
7771
|
}
|
|
@@ -7838,7 +7838,7 @@
|
|
|
7838
7838
|
return createCahillKeyesTransform(mg, true);
|
|
7839
7839
|
}
|
|
7840
7840
|
|
|
7841
|
-
function distance(a, b) {
|
|
7841
|
+
function distance$1(a, b) {
|
|
7842
7842
|
return Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
7843
7843
|
}
|
|
7844
7844
|
|
|
@@ -26647,6 +26647,10 @@
|
|
|
26647
26647
|
'font-style': null,
|
|
26648
26648
|
'font-stretch': null,
|
|
26649
26649
|
'font-weight': null,
|
|
26650
|
+
// a stroke around a label's text, painted beneath it -- see svg-label-halo.mjs
|
|
26651
|
+
'halo-color': 'color',
|
|
26652
|
+
'halo-opacity': 'number',
|
|
26653
|
+
'halo-width': 'number',
|
|
26650
26654
|
icon: null,
|
|
26651
26655
|
'icon-color': 'color',
|
|
26652
26656
|
// opacity of the symbol at a label's anchor, apart from the text's. Needed
|
|
@@ -26664,6 +26668,20 @@
|
|
|
26664
26668
|
// where the text starts along its path; a length or a percentage
|
|
26665
26669
|
'label-start-offset': null,
|
|
26666
26670
|
'label-text': null, // leaving this null
|
|
26671
|
+
// width of a fixed-width text block, whose lines the GUI wraps and stores
|
|
26672
|
+
// with <wbr> soft breaks -- see docs/development/text-annotation-design.md
|
|
26673
|
+
'label-width': 'number',
|
|
26674
|
+
// a line from a label's anchor to its text -- see svg-label-callout.mjs
|
|
26675
|
+
callout: 'callout',
|
|
26676
|
+
'callout-end': 'calloutend',
|
|
26677
|
+
'callout-end-size': 'number',
|
|
26678
|
+
'callout-via': 'pointpair',
|
|
26679
|
+
'callout-attach': 'pointpair',
|
|
26680
|
+
'callout-gap': 'number',
|
|
26681
|
+
'callout-padding': 'number',
|
|
26682
|
+
'callout-color': 'color',
|
|
26683
|
+
'callout-width': 'number',
|
|
26684
|
+
'callout-opacity': 'number',
|
|
26667
26685
|
'letter-spacing': 'measure',
|
|
26668
26686
|
'line-height': 'measure',
|
|
26669
26687
|
opacity: 'number',
|
|
@@ -26971,6 +26989,13 @@
|
|
|
26971
26989
|
val = parseLabelPosition(strVal);
|
|
26972
26990
|
} else if (type == 'labelalign') {
|
|
26973
26991
|
val = parseLabelAlign(strVal);
|
|
26992
|
+
} else if (type == 'callout') {
|
|
26993
|
+
val = parseCalloutType(strVal);
|
|
26994
|
+
} else if (type == 'calloutend') {
|
|
26995
|
+
val = parseCalloutEnd(strVal);
|
|
26996
|
+
} else if (type == 'pointpair') {
|
|
26997
|
+
val = parsePointPair(strVal);
|
|
26998
|
+
val = val ? formatPointPair(val) : null;
|
|
26974
26999
|
}
|
|
26975
27000
|
// else {
|
|
26976
27001
|
// // unknown type -- assume literal value
|
|
@@ -27007,6 +27032,29 @@
|
|
|
27007
27032
|
return /^(n|s|e|w|ne|se|nw|sw|c)$/i.test(pos) ? pos : null;
|
|
27008
27033
|
}
|
|
27009
27034
|
|
|
27035
|
+
// 'none' is kept rather than rejected, so that a data field or an expression
|
|
27036
|
+
// can switch a callout off for some features; it renders as no callout.
|
|
27037
|
+
function parseCalloutType(str) {
|
|
27038
|
+
var type = String(str).trim().toLowerCase();
|
|
27039
|
+
return /^(line|elbow|curve|none)$/.test(type) ? type : null;
|
|
27040
|
+
}
|
|
27041
|
+
|
|
27042
|
+
function parseCalloutEnd(str) {
|
|
27043
|
+
var end = String(str).trim().toLowerCase();
|
|
27044
|
+
return /^(arrow|open-arrow|none)$/.test(end) ? end : null;
|
|
27045
|
+
}
|
|
27046
|
+
|
|
27047
|
+
// "x,y" -> [x, y], or null. Stored as a string so that a pair is set and
|
|
27048
|
+
// cleared as one value and reads plainly in a table.
|
|
27049
|
+
function parsePointPair(str) {
|
|
27050
|
+
var match = /^\s*(-?[0-9]*\.?[0-9]+)\s*,\s*(-?[0-9]*\.?[0-9]+)\s*$/.exec(String(str));
|
|
27051
|
+
return match ? [Number(match[1]), Number(match[2])] : null;
|
|
27052
|
+
}
|
|
27053
|
+
|
|
27054
|
+
function formatPointPair(p) {
|
|
27055
|
+
return p[0] + ',' + p[1];
|
|
27056
|
+
}
|
|
27057
|
+
|
|
27010
27058
|
function getLabelPositionStyle(pos) {
|
|
27011
27059
|
pos = parseLabelPosition(pos);
|
|
27012
27060
|
if (!pos) return null;
|
|
@@ -27095,6 +27143,7 @@
|
|
|
27095
27143
|
applyStyleAttributes: applyStyleAttributes,
|
|
27096
27144
|
emptyValueUnsetsProperty: emptyValueUnsetsProperty,
|
|
27097
27145
|
findStylePropertiesBySymbolGeom: findStylePropertiesBySymbolGeom,
|
|
27146
|
+
formatPointPair: formatPointPair,
|
|
27098
27147
|
getLabelPositionAnchor: getLabelPositionAnchor,
|
|
27099
27148
|
getLabelPositionStyle: getLabelPositionStyle,
|
|
27100
27149
|
getPropertyAccessor: getPropertyAccessor,
|
|
@@ -27111,17 +27160,179 @@
|
|
|
27111
27160
|
labelPositionFields: labelPositionFields,
|
|
27112
27161
|
mightBeExpression: mightBeExpression,
|
|
27113
27162
|
parseBoolean: parseBoolean,
|
|
27163
|
+
parseCalloutEnd: parseCalloutEnd,
|
|
27164
|
+
parseCalloutType: parseCalloutType,
|
|
27114
27165
|
parseLabelPosition: parseLabelPosition,
|
|
27166
|
+
parsePointPair: parsePointPair,
|
|
27115
27167
|
parseStyleLiteral: parseStyleLiteral,
|
|
27116
27168
|
parseSvgMeasure: parseSvgMeasure,
|
|
27117
27169
|
resolveLabelPosition: resolveLabelPosition
|
|
27118
27170
|
});
|
|
27119
27171
|
|
|
27172
|
+
// A halo is a stroke around a label's glyphs, painted underneath their fill so
|
|
27173
|
+
// that it widens the letters' outline without eating into them. Three
|
|
27174
|
+
// properties describe one, named after the icon-* properties they sit beside
|
|
27175
|
+
// in the panel:
|
|
27176
|
+
//
|
|
27177
|
+
// halo-width how far the halo reaches past the edge of the glyphs, in px.
|
|
27178
|
+
// A halo is drawn when this is above 0 and not otherwise.
|
|
27179
|
+
// halo-color defaults to white, the halo nearly every map wants
|
|
27180
|
+
// halo-opacity 0-1, apart from the text's own opacity
|
|
27181
|
+
//
|
|
27182
|
+
// Not stroke, stroke-width and stroke-opacity, which labels already accept and
|
|
27183
|
+
// which would then mean two things at once: a label's record styles its icon
|
|
27184
|
+
// too (see getIconStyleData()), so a halo stored as a stroke would ring the
|
|
27185
|
+
// symbol as well as the text.
|
|
27186
|
+
//
|
|
27187
|
+
// The stroke is twice halo-width because a stroke is centred on the outline:
|
|
27188
|
+
// the inner half is hidden under the fill, and only the outer half shows.
|
|
27189
|
+
//
|
|
27190
|
+
// In the GUI the halo is the text element's own stroke, with paint-order
|
|
27191
|
+
// putting it underneath. Export draws it as a second copy of the text instead
|
|
27192
|
+
// -- see splitLabelHalos() -- because Illustrator and Figma ignore paint-order
|
|
27193
|
+
// when they import SVG and would draw the stroke over the letters.
|
|
27194
|
+
|
|
27195
|
+
var DEFAULT_HALO_COLOR = '#ffffff';
|
|
27196
|
+
var HALO_PAINT_ORDER = 'stroke fill';
|
|
27197
|
+
|
|
27198
|
+
// The properties of the text element that belong to the halo once it has one.
|
|
27199
|
+
// The label's own stroke properties are among them: a halo is the text's
|
|
27200
|
+
// stroke, and a dash pattern or a second colour meant for the letters has
|
|
27201
|
+
// nowhere left to go.
|
|
27202
|
+
var HALO_STROKE_PROPERTIES = ['stroke', 'stroke-width', 'stroke-opacity',
|
|
27203
|
+
'stroke-dasharray', 'stroke-linecap', 'stroke-linejoin', 'paint-order'];
|
|
27204
|
+
|
|
27205
|
+
function labelHasHalo(rec) {
|
|
27206
|
+
return !!rec && rec['halo-width'] > 0;
|
|
27207
|
+
}
|
|
27208
|
+
|
|
27209
|
+
// Paints @rec's halo on @o, a rendered <text> element, and returns it.
|
|
27210
|
+
// Leaves @o alone when the label has no halo, so that labels without one
|
|
27211
|
+
// render exactly as they did before halos existed.
|
|
27212
|
+
function applyLabelHalo(o, rec) {
|
|
27213
|
+
var props, opacity;
|
|
27214
|
+
if (!labelHasHalo(rec)) return o;
|
|
27215
|
+
props = o.properties || (o.properties = {});
|
|
27216
|
+
HALO_STROKE_PROPERTIES.forEach(function(k) {
|
|
27217
|
+
delete props[k];
|
|
27218
|
+
});
|
|
27219
|
+
props.stroke = rec['halo-color'] || DEFAULT_HALO_COLOR;
|
|
27220
|
+
props['stroke-width'] = rec['halo-width'] * 2;
|
|
27221
|
+
opacity = rec['halo-opacity'];
|
|
27222
|
+
if (isSvgNumber(opacity) && Number(opacity) < 1) {
|
|
27223
|
+
props['stroke-opacity'] = Number(opacity);
|
|
27224
|
+
}
|
|
27225
|
+
// Round, because the default miter join puts spikes on the sharp corners of
|
|
27226
|
+
// letters like M and V, which read as blemishes at any halo width.
|
|
27227
|
+
props['stroke-linejoin'] = 'round';
|
|
27228
|
+
props['paint-order'] = HALO_PAINT_ORDER;
|
|
27229
|
+
return o;
|
|
27230
|
+
}
|
|
27231
|
+
|
|
27232
|
+
// Rewrites every halo'd <text> in @o as a group of two: a stroked copy of the
|
|
27233
|
+
// text underneath, and the text itself on top with no stroke. Returns @o, or
|
|
27234
|
+
// the group that replaces it if @o is itself a halo'd label.
|
|
27235
|
+
//
|
|
27236
|
+
// The two copies are identical apart from their paint, so they sit in exactly
|
|
27237
|
+
// the same place whatever the label's position, alignment or path.
|
|
27238
|
+
//
|
|
27239
|
+
// The halo copy is unfilled. That matches paint-order, where the fill covers
|
|
27240
|
+
// the stroke's inner half and nothing else, and it keeps a translucent fill
|
|
27241
|
+
// from showing a second colour through it.
|
|
27242
|
+
//
|
|
27243
|
+
// Things that apply to the label as a whole move to the group: its transform,
|
|
27244
|
+
// which places it, and its opacity, which would otherwise fade each copy on its
|
|
27245
|
+
// own and let the halo show through the letters. That is also how opacity
|
|
27246
|
+
// reads on a single element with paint-order, which is faded as one image.
|
|
27247
|
+
function splitLabelHalos(o) {
|
|
27248
|
+
if (!o) return o;
|
|
27249
|
+
if (o.tag == 'text' && isHaloText(o)) return splitHalo(o);
|
|
27250
|
+
if (o.children) {
|
|
27251
|
+
for (var i = 0; i < o.children.length; i++) {
|
|
27252
|
+
o.children[i] = splitLabelHalos(o.children[i]);
|
|
27253
|
+
}
|
|
27254
|
+
}
|
|
27255
|
+
return o;
|
|
27256
|
+
}
|
|
27257
|
+
|
|
27258
|
+
function isHaloText(o) {
|
|
27259
|
+
return !!o.properties && o.properties['paint-order'] == HALO_PAINT_ORDER;
|
|
27260
|
+
}
|
|
27261
|
+
|
|
27262
|
+
function splitHalo(text) {
|
|
27263
|
+
var halo = cloneSvgObject(text);
|
|
27264
|
+
var group = {tag: 'g', properties: {}, children: [halo, text]};
|
|
27265
|
+
moveProperty(text.properties, group.properties, 'transform');
|
|
27266
|
+
moveProperty(text.properties, group.properties, 'opacity');
|
|
27267
|
+
delete halo.properties.transform;
|
|
27268
|
+
delete halo.properties.opacity;
|
|
27269
|
+
delete halo.properties['paint-order'];
|
|
27270
|
+
delete halo.properties['fill-opacity'];
|
|
27271
|
+
halo.properties.fill = 'none';
|
|
27272
|
+
HALO_STROKE_PROPERTIES.forEach(function(k) {
|
|
27273
|
+
delete text.properties[k];
|
|
27274
|
+
});
|
|
27275
|
+
return group;
|
|
27276
|
+
}
|
|
27277
|
+
|
|
27278
|
+
function moveProperty(src, dest, k) {
|
|
27279
|
+
if (k in src) {
|
|
27280
|
+
dest[k] = src[k];
|
|
27281
|
+
delete src[k];
|
|
27282
|
+
}
|
|
27283
|
+
}
|
|
27284
|
+
|
|
27285
|
+
function cloneSvgObject(o) {
|
|
27286
|
+
var copy = {tag: o.tag};
|
|
27287
|
+
if ('value' in o) copy.value = o.value;
|
|
27288
|
+
if (o.properties) copy.properties = Object.assign({}, o.properties);
|
|
27289
|
+
if (o.children) copy.children = o.children.map(cloneSvgObject);
|
|
27290
|
+
return copy;
|
|
27291
|
+
}
|
|
27292
|
+
|
|
27293
|
+
var SvgLabelHalo = /*#__PURE__*/Object.freeze({
|
|
27294
|
+
__proto__: null,
|
|
27295
|
+
DEFAULT_HALO_COLOR: DEFAULT_HALO_COLOR,
|
|
27296
|
+
applyLabelHalo: applyLabelHalo,
|
|
27297
|
+
labelHasHalo: labelHasHalo,
|
|
27298
|
+
splitLabelHalos: splitLabelHalos
|
|
27299
|
+
});
|
|
27300
|
+
|
|
27120
27301
|
// Accepting \n (two chars) as an alternative to the newline character
|
|
27121
27302
|
// (sometimes, '\n' is not converted to newline, e.g. in a Makefile)
|
|
27122
27303
|
// Also accepting <br>
|
|
27123
27304
|
var labelNewlineRxp = /\n|\\n|<br>/i;
|
|
27124
27305
|
|
|
27306
|
+
// A line break that the GUI's wrapper put in a fixed-width text block, as
|
|
27307
|
+
// against one the user typed. It consumes no character: the space or hyphen
|
|
27308
|
+
// the line broke at stays in the text before it, so removing every soft break
|
|
27309
|
+
// gives back exactly what was typed. See docs/development/text-annotation-design.md.
|
|
27310
|
+
var LABEL_SOFT_BREAK = '<wbr>';
|
|
27311
|
+
|
|
27312
|
+
// Every form of line break, captured, so that a split keeps the delimiters and
|
|
27313
|
+
// a soft break can be told from a hard one.
|
|
27314
|
+
var anyLabelBreakRxp = /(\n|\\n|<br>|<wbr>)/i;
|
|
27315
|
+
var softBreakRxp = /^<wbr>$/i;
|
|
27316
|
+
|
|
27317
|
+
// The lines a label's text is drawn as. Whitespace before a soft break is
|
|
27318
|
+
// dropped: a trailing space would move an end- or middle-justified line by a
|
|
27319
|
+
// space's width.
|
|
27320
|
+
function splitLabelLines(text) {
|
|
27321
|
+
var parts = String(text).split(anyLabelBreakRxp);
|
|
27322
|
+
var lines = [parts[0]];
|
|
27323
|
+
for (var i = 1; i < parts.length; i += 2) {
|
|
27324
|
+
if (softBreakRxp.test(parts[i])) {
|
|
27325
|
+
lines[lines.length - 1] = lines[lines.length - 1].replace(/\s+$/, '');
|
|
27326
|
+
}
|
|
27327
|
+
lines.push(parts[i + 1]);
|
|
27328
|
+
}
|
|
27329
|
+
return lines;
|
|
27330
|
+
}
|
|
27331
|
+
|
|
27332
|
+
function removeSoftBreaks(text) {
|
|
27333
|
+
return String(text).replace(/<wbr>/gi, '');
|
|
27334
|
+
}
|
|
27335
|
+
|
|
27125
27336
|
// The size a label is drawn at when it carries none of its own, which is the
|
|
27126
27337
|
// size its layer's group supplies (see getLabelTextDefaults()). Shared with
|
|
27127
27338
|
// that function rather than written twice: the em offsets the label positions
|
|
@@ -27143,7 +27354,7 @@
|
|
|
27143
27354
|
var rec = resolveLabelPosition(recArg);
|
|
27144
27355
|
var o = renderLabel$1(rec);
|
|
27145
27356
|
applyStyleAttributes(o, 'label', rec);
|
|
27146
|
-
return o;
|
|
27357
|
+
return applyLabelHalo(o, rec);
|
|
27147
27358
|
}
|
|
27148
27359
|
|
|
27149
27360
|
function renderLabel$1(recArg) {
|
|
@@ -27151,9 +27362,9 @@
|
|
|
27151
27362
|
// already been through it, so calling it here as well costs nothing and means
|
|
27152
27363
|
// every way into the renderer draws a label in the position it is stored in.
|
|
27153
27364
|
var rec = resolveLabelPosition(recArg);
|
|
27154
|
-
var
|
|
27155
|
-
var morelines
|
|
27156
|
-
var
|
|
27365
|
+
var morelines = splitLabelLines(toLabelString(rec['label-text']));
|
|
27366
|
+
var line = morelines.shift();
|
|
27367
|
+
var obj;
|
|
27157
27368
|
var dx = applyAlignmentShift(rec);
|
|
27158
27369
|
var dy = rec.dy || 0;
|
|
27159
27370
|
var properties = {
|
|
@@ -27162,16 +27373,12 @@
|
|
|
27162
27373
|
y: dy,
|
|
27163
27374
|
x: dx
|
|
27164
27375
|
};
|
|
27165
|
-
if (newline.test(line)) {
|
|
27166
|
-
morelines = line.split(newline);
|
|
27167
|
-
line = morelines.shift();
|
|
27168
|
-
}
|
|
27169
27376
|
obj = {
|
|
27170
27377
|
tag: 'text',
|
|
27171
27378
|
value: line,
|
|
27172
27379
|
properties: properties
|
|
27173
27380
|
};
|
|
27174
|
-
if (morelines) {
|
|
27381
|
+
if (morelines.length > 0) {
|
|
27175
27382
|
// multiline label
|
|
27176
27383
|
obj.children = [];
|
|
27177
27384
|
morelines.forEach(function(line) {
|
|
@@ -27274,11 +27481,598 @@
|
|
|
27274
27481
|
var SvgLabels = /*#__PURE__*/Object.freeze({
|
|
27275
27482
|
__proto__: null,
|
|
27276
27483
|
DEFAULT_LABEL_FONT_SIZE: DEFAULT_LABEL_FONT_SIZE,
|
|
27484
|
+
LABEL_SOFT_BREAK: LABEL_SOFT_BREAK,
|
|
27277
27485
|
getDrawnLabelOffset: getDrawnLabelOffset,
|
|
27278
27486
|
labelNewlineRxp: labelNewlineRxp,
|
|
27487
|
+
removeSoftBreaks: removeSoftBreaks,
|
|
27279
27488
|
renderLabel: renderLabel$1,
|
|
27280
27489
|
renderStyledLabel: renderStyledLabel,
|
|
27281
|
-
|
|
27490
|
+
splitLabelLines: splitLabelLines,
|
|
27491
|
+
toLabelString: toLabelString,
|
|
27492
|
+
toPixels: toPixels
|
|
27493
|
+
});
|
|
27494
|
+
|
|
27495
|
+
// A callout: a line from a label's anchor to its text, straight, elbowed or
|
|
27496
|
+
// curved, optionally ending in an arrowhead at the anchor. A dot at the anchor
|
|
27497
|
+
// is the label's icon, not a callout marker.
|
|
27498
|
+
//
|
|
27499
|
+
// Everything here is in the label's own space -- origin at the anchor, y down,
|
|
27500
|
+
// in px -- which is the space dx/dy are in and the one the GUI scales with the
|
|
27501
|
+
// frame. Nothing derived is stored: the shape is worked out from the anchor,
|
|
27502
|
+
// the text box and the callout-* fields every time the label is drawn.
|
|
27503
|
+
//
|
|
27504
|
+
// See docs/development/text-annotation-design.md.
|
|
27505
|
+
|
|
27506
|
+
var DEFAULT_PADDING$1 = 3;
|
|
27507
|
+
var DEFAULT_LINE_WIDTH = 1;
|
|
27508
|
+
var DEFAULT_LINE_HEIGHT = '1.1em';
|
|
27509
|
+
// Space left between the anchor's symbol and the end of the line
|
|
27510
|
+
var SYMBOL_CLEARANCE = 2;
|
|
27511
|
+
// How far an automatic curve bows out, as a fraction of its chord
|
|
27512
|
+
var CURVE_BEND = 0.2;
|
|
27513
|
+
// The shortest first leg an automatic elbow into the top or bottom of the text
|
|
27514
|
+
// is drawn with, px
|
|
27515
|
+
var MIN_LEG = 8;
|
|
27516
|
+
// The angle at an arrowhead's point, degrees. The open one is wider, since a
|
|
27517
|
+
// narrow chevron drawn with a line reads as a thickened line more than as an
|
|
27518
|
+
// arrow.
|
|
27519
|
+
var ARROW_ANGLE = 44;
|
|
27520
|
+
var OPEN_ARROW_ANGLE = 70;
|
|
27521
|
+
// How far into a solid arrowhead the line reaches, as a fraction of the
|
|
27522
|
+
// head's length: far enough that its cap is hidden, short of the tip.
|
|
27523
|
+
var ARROW_LINE_OVERLAP = 0.7;
|
|
27524
|
+
// Where glyphs sit relative to their baseline, in ems, for estimating the
|
|
27525
|
+
// height of a block of text that is measured only for its width
|
|
27526
|
+
var ASCENT = 0.8;
|
|
27527
|
+
var DESCENT = 0.2;
|
|
27528
|
+
var MIDLINE = 0.35;
|
|
27529
|
+
|
|
27530
|
+
function labelHasCallout(rec) {
|
|
27531
|
+
var type = rec && rec.callout ? parseCalloutType(rec.callout) : null;
|
|
27532
|
+
return !!type && type != 'none';
|
|
27533
|
+
}
|
|
27534
|
+
|
|
27535
|
+
// The callout for @rec as an SVG object, or null if it has none or there is no
|
|
27536
|
+
// room to draw one.
|
|
27537
|
+
// @symbolRadius: radius of the symbol drawn at the anchor, or 0; the default
|
|
27538
|
+
// gap keeps the line clear of it
|
|
27539
|
+
function renderLabelCallout(rec, symbolRadius) {
|
|
27540
|
+
var shape = getLabelCalloutShape(rec, symbolRadius);
|
|
27541
|
+
return shape ? renderCalloutShape(shape, rec) : null;
|
|
27542
|
+
}
|
|
27543
|
+
|
|
27544
|
+
// The shape renderLabelCallout() draws for @rec, from getCalloutShape(), or
|
|
27545
|
+
// null. The GUI places its handles on this, so they sit where the line is.
|
|
27546
|
+
function getLabelCalloutShape(rec, symbolRadius) {
|
|
27547
|
+
if (!labelHasCallout(rec)) return null;
|
|
27548
|
+
return getCalloutShape({
|
|
27549
|
+
type: parseCalloutType(rec.callout),
|
|
27550
|
+
end: getCalloutEndType(rec),
|
|
27551
|
+
box: getLabelTextBox(rec),
|
|
27552
|
+
via: parsePointPair(rec['callout-via'] || ''),
|
|
27553
|
+
attach: parsePointPair(rec['callout-attach'] || ''),
|
|
27554
|
+
padding: getNumber(rec['callout-padding'], DEFAULT_PADDING$1),
|
|
27555
|
+
gap: getCalloutGap(rec, symbolRadius),
|
|
27556
|
+
width: getLineWidth(rec),
|
|
27557
|
+
endSize: getNumber(rec['callout-end-size'], 0)
|
|
27558
|
+
});
|
|
27559
|
+
}
|
|
27560
|
+
|
|
27561
|
+
// An arrowhead's size when callout-end-size does not say, in px: 10 for the
|
|
27562
|
+
// default line, and growing with the line, so that a heavier line does not end
|
|
27563
|
+
// in a head too small to read.
|
|
27564
|
+
function getDefaultCalloutEndSize(end, lineWidth) {
|
|
27565
|
+
var w = lineWidth > 0 ? lineWidth : DEFAULT_LINE_WIDTH;
|
|
27566
|
+
return 7 + 3 * w;
|
|
27567
|
+
}
|
|
27568
|
+
|
|
27569
|
+
// 'arrow', 'open-arrow' or 'none'
|
|
27570
|
+
function getCalloutEndType(rec) {
|
|
27571
|
+
return rec && parseCalloutEnd(rec['callout-end'] || '') || 'none';
|
|
27572
|
+
}
|
|
27573
|
+
|
|
27574
|
+
// How far short of the anchor the line stops. Unset, an arrowhead stops clear
|
|
27575
|
+
// of the symbol there, so that its point reads against the background; a
|
|
27576
|
+
// plain line runs to the symbol's edge, so that the two read as one object.
|
|
27577
|
+
// The line's round cap then reaches under the symbol, which is drawn over it.
|
|
27578
|
+
function getCalloutGap(rec, symbolRadius) {
|
|
27579
|
+
if (isSvgNumber(rec['callout-gap'])) return Math.max(0, Number(rec['callout-gap']));
|
|
27580
|
+
if (!(symbolRadius > 0)) return 0;
|
|
27581
|
+
return getCalloutEndType(rec) == 'none' ? symbolRadius : symbolRadius + SYMBOL_CLEARANCE;
|
|
27582
|
+
}
|
|
27583
|
+
|
|
27584
|
+
// The block of text a label draws, in the label's space: {xmin, ymin, xmax,
|
|
27585
|
+
// ymax, midline}, where midline is the middle of the first line's capitals.
|
|
27586
|
+
//
|
|
27587
|
+
// The width is measured, since it depends on the font, and the height is
|
|
27588
|
+
// estimated from the font size and line height, since only the width is. With
|
|
27589
|
+
// no measurement the box takes the label's width if it is a text block, and is
|
|
27590
|
+
// otherwise a vertical line through the text's origin -- a callout then meets
|
|
27591
|
+
// the text where it starts rather than not being drawn.
|
|
27592
|
+
function getLabelTextBox(rec) {
|
|
27593
|
+
var fontSize = getFontSizeInPx$1(rec);
|
|
27594
|
+
var offset = getDrawnLabelOffset(rec);
|
|
27595
|
+
var lines = splitLabelLines(toLabelString(rec['label-text']));
|
|
27596
|
+
var lineHeight = measureToPx(rec['line-height'] || DEFAULT_LINE_HEIGHT, fontSize);
|
|
27597
|
+
var width = getMeasuredTextWidth(rec) || getNumber(rec['label-width'], 0);
|
|
27598
|
+
// An anchor the record does not set is inherited from the layer's group,
|
|
27599
|
+
// which is 'middle' -- see getLabelTextDefaults().
|
|
27600
|
+
var anchor = offset['text-anchor'] || 'middle';
|
|
27601
|
+
var xmin = offset.dx - width * (anchor == 'middle' ? 0.5 : anchor == 'end' ? 1 : 0);
|
|
27602
|
+
var baseline = offset.dy + getBaselineShift(rec['dominant-baseline']) * fontSize;
|
|
27603
|
+
if (!(lineHeight > 0)) lineHeight = fontSize * 1.1;
|
|
27604
|
+
return {
|
|
27605
|
+
xmin: xmin,
|
|
27606
|
+
xmax: xmin + width,
|
|
27607
|
+
ymin: baseline - ASCENT * fontSize,
|
|
27608
|
+
ymax: baseline + (lines.length - 1) * lineHeight + DESCENT * fontSize,
|
|
27609
|
+
midline: baseline - MIDLINE * fontSize
|
|
27610
|
+
};
|
|
27611
|
+
}
|
|
27612
|
+
|
|
27613
|
+
// Where a line meets a callout's text and how it gets there from the anchor.
|
|
27614
|
+
//
|
|
27615
|
+
// o.type 'line', 'elbow' or 'curve'
|
|
27616
|
+
// o.end 'arrow' (filled), 'open-arrow' (stroked) or 'none'
|
|
27617
|
+
// o.box the text box, from getLabelTextBox()
|
|
27618
|
+
// o.via [x, y] the elbow's corner or a point the curve passes through, or null
|
|
27619
|
+
// o.attach [fx, fy] where the line meets the text, as fractions of the padded
|
|
27620
|
+
// box, or null
|
|
27621
|
+
// o.padding clearance around the text, px
|
|
27622
|
+
// o.gap how far short of the anchor the line stops, px
|
|
27623
|
+
// o.width line width, px
|
|
27624
|
+
// o.endSize an arrowhead's size, px, or 0 for the default, which grows with
|
|
27625
|
+
// the line width. The size is the length of the head's sides, as
|
|
27626
|
+
// drawn, stroke and all: the two styles are different shapes, and
|
|
27627
|
+
// what makes one look the size of the other is sides the same
|
|
27628
|
+
// length, not the same length along the line.
|
|
27629
|
+
//
|
|
27630
|
+
// Returns {kind, coords, head, openHead, box, attach, via, tip} with coords
|
|
27631
|
+
// from the anchor end to the text end -- a polyline's vertices, or a quadratic
|
|
27632
|
+
// Bezier's three points -- or null when there is nothing to draw: the anchor
|
|
27633
|
+
// inside the padded box, or a gap that leaves no line. head is a filled
|
|
27634
|
+
// triangle, openHead the three points of a stroked chevron, wing to tip to
|
|
27635
|
+
// wing. The rest is what the shape was worked out from: the padded box, the
|
|
27636
|
+
// point where the line meets it, the elbow's corner or the curve's midpoint
|
|
27637
|
+
// (null for a straight line), and where the line stops short of the anchor.
|
|
27638
|
+
function getCalloutShape(o) {
|
|
27639
|
+
var box = padBox(o.box, o.padding || 0);
|
|
27640
|
+
var a = [0, 0];
|
|
27641
|
+
var size = o.endSize > 0 ? o.endSize : getDefaultCalloutEndSize(o.end, o.width);
|
|
27642
|
+
var t, v, path, tip, dir, out, lineWidth, headLen, curved;
|
|
27643
|
+
if (boxContains(box, a)) return null;
|
|
27644
|
+
t = o.attach ? getBoxPoint(box, o.attach) :
|
|
27645
|
+
getAutoAttachment(o.type, box, o.via || null);
|
|
27646
|
+
// A leg too short to read as one, or to hold the marker, is a jog: the line
|
|
27647
|
+
// goes straight up or down to the text instead.
|
|
27648
|
+
if (o.type == 'elbow' && !o.attach && !o.via && isOnTopOrBottom(box, t) &&
|
|
27649
|
+
Math.abs(t[0]) < getMinimumLeg(o.end, size)) {
|
|
27650
|
+
t = [0, t[1]];
|
|
27651
|
+
}
|
|
27652
|
+
if (o.type == 'elbow') {
|
|
27653
|
+
v = o.via || getAutoElbow(a, t, box);
|
|
27654
|
+
// A corner on either end is no corner -- straight under the text, say --
|
|
27655
|
+
// and a leg of no length has no direction for an arrowhead to take.
|
|
27656
|
+
path = {kind: 'polyline', coords: distance(a, v) > 0 && distance(v, t) > 0 ?
|
|
27657
|
+
[a, v, t] : [a, t]};
|
|
27658
|
+
} else if (o.type == 'curve') {
|
|
27659
|
+
v = o.via || getAutoBend(a, t);
|
|
27660
|
+
path = {kind: 'bezier', coords: [a, getControlPoint(a, v, t), t]};
|
|
27661
|
+
} else {
|
|
27662
|
+
path = {kind: 'polyline', coords: [a, t]};
|
|
27663
|
+
}
|
|
27664
|
+
path = trimPath(path, o.gap || 0);
|
|
27665
|
+
if (!path) return null;
|
|
27666
|
+
tip = path.coords[0];
|
|
27667
|
+
out = {kind: path.kind, coords: path.coords, head: null, openHead: null,
|
|
27668
|
+
box: box, attach: t, via: v || null, tip: tip};
|
|
27669
|
+
if (o.end == 'arrow') {
|
|
27670
|
+
headLen = getArrowHeadLength(size, ARROW_ANGLE);
|
|
27671
|
+
curved = path.kind == 'bezier' ? getCurveIntoArrowHead(path, headLen) : null;
|
|
27672
|
+
dir = curved ? curved.dir : getStartDirection(path);
|
|
27673
|
+
if (!dir) return null;
|
|
27674
|
+
out.head = getArrowHead(tip, dir, size, ARROW_ANGLE);
|
|
27675
|
+
// The line stops inside the head, so that its cap does not show past the
|
|
27676
|
+
// tip. A line too short to reach past the head is drawn as the head alone.
|
|
27677
|
+
path = curved ? curved.path : trimPath(path, headLen * ARROW_LINE_OVERLAP);
|
|
27678
|
+
out.coords = path ? path.coords : null;
|
|
27679
|
+
} else if (o.end == 'open-arrow') {
|
|
27680
|
+
// Stroked with the line's round join, which reaches half a line width past
|
|
27681
|
+
// the chevron's point: the point is set back by that much, so that the
|
|
27682
|
+
// arrow ends where a filled one would. The join and the round cap at the
|
|
27683
|
+
// far end each add half a line width to an arm, which is taken off it.
|
|
27684
|
+
lineWidth = o.width > 0 ? o.width : DEFAULT_LINE_WIDTH;
|
|
27685
|
+
path = trimPath(path, lineWidth / 2) || path;
|
|
27686
|
+
dir = getStartDirection(path);
|
|
27687
|
+
if (!dir) return null;
|
|
27688
|
+
out.coords = path.coords;
|
|
27689
|
+
out.openHead = getArrowHead(path.coords[0], dir,
|
|
27690
|
+
Math.max(size - lineWidth, size / 2), OPEN_ARROW_ANGLE);
|
|
27691
|
+
out.openHead = [out.openHead[1], out.openHead[0], out.openHead[2]];
|
|
27692
|
+
}
|
|
27693
|
+
return out;
|
|
27694
|
+
}
|
|
27695
|
+
|
|
27696
|
+
function renderCalloutShape(shape, rec) {
|
|
27697
|
+
var color = rec['callout-color'] || rec.fill || 'black';
|
|
27698
|
+
var opacity = isSvgNumber(rec['callout-opacity']) ? Number(rec['callout-opacity']) :
|
|
27699
|
+
isSvgNumber(rec.opacity) ? Number(rec.opacity) : 1;
|
|
27700
|
+
var props = {class: 'label-callout'};
|
|
27701
|
+
var children = [];
|
|
27702
|
+
if (shape.coords) {
|
|
27703
|
+
children.push({
|
|
27704
|
+
tag: 'path',
|
|
27705
|
+
properties: {
|
|
27706
|
+
d: getPathData(shape),
|
|
27707
|
+
fill: 'none',
|
|
27708
|
+
stroke: color,
|
|
27709
|
+
'stroke-width': getLineWidth(rec),
|
|
27710
|
+
'stroke-linecap': 'round',
|
|
27711
|
+
'stroke-linejoin': 'round'
|
|
27712
|
+
}
|
|
27713
|
+
});
|
|
27714
|
+
}
|
|
27715
|
+
if (shape.head) {
|
|
27716
|
+
children.push({
|
|
27717
|
+
tag: 'path',
|
|
27718
|
+
properties: {d: getPolygonData(shape.head), fill: color}
|
|
27719
|
+
});
|
|
27720
|
+
}
|
|
27721
|
+
if (shape.openHead) {
|
|
27722
|
+
children.push({
|
|
27723
|
+
tag: 'path',
|
|
27724
|
+
properties: {
|
|
27725
|
+
d: 'M ' + shape.openHead.map(formatPoint).join(' L '),
|
|
27726
|
+
fill: 'none',
|
|
27727
|
+
stroke: color,
|
|
27728
|
+
'stroke-width': getLineWidth(rec),
|
|
27729
|
+
'stroke-linecap': 'round',
|
|
27730
|
+
'stroke-linejoin': 'round'
|
|
27731
|
+
}
|
|
27732
|
+
});
|
|
27733
|
+
}
|
|
27734
|
+
if (opacity < 1) props.opacity = opacity;
|
|
27735
|
+
return {tag: 'g', properties: props, children: children};
|
|
27736
|
+
}
|
|
27737
|
+
|
|
27738
|
+
// Drawn from the text to the anchor, which is the direction a leader is read in
|
|
27739
|
+
// and the one a dash pattern starts from. A quadratic's control point is the
|
|
27740
|
+
// same in both directions.
|
|
27741
|
+
function getPathData(shape) {
|
|
27742
|
+
var c = shape.coords;
|
|
27743
|
+
if (shape.kind == 'bezier') {
|
|
27744
|
+
return 'M ' + formatPoint(c[2]) + ' Q ' + formatPoint(c[1]) + ' ' + formatPoint(c[0]);
|
|
27745
|
+
}
|
|
27746
|
+
return 'M ' + c.slice().reverse().map(formatPoint).join(' L ');
|
|
27747
|
+
}
|
|
27748
|
+
|
|
27749
|
+
function getPolygonData(coords) {
|
|
27750
|
+
return 'M ' + coords.map(formatPoint).join(' L ') + ' Z';
|
|
27751
|
+
}
|
|
27752
|
+
|
|
27753
|
+
function formatPoint(p) {
|
|
27754
|
+
return roundToTenths(p[0]) + ' ' + roundToTenths(p[1]);
|
|
27755
|
+
}
|
|
27756
|
+
|
|
27757
|
+
// Attachment
|
|
27758
|
+
|
|
27759
|
+
function getBoxPoint(box, f) {
|
|
27760
|
+
var fx = Math.max(0, Math.min(1, f[0]));
|
|
27761
|
+
var fy = Math.max(0, Math.min(1, f[1]));
|
|
27762
|
+
return [box.xmin + fx * (box.xmax - box.xmin), box.ymin + fy * (box.ymax - box.ymin)];
|
|
27763
|
+
}
|
|
27764
|
+
|
|
27765
|
+
// An elbow meets the side of the text facing the way it comes from, level with
|
|
27766
|
+
// the first line, which is the usual form of an elbowed leader. A straight or
|
|
27767
|
+
// curved line is aimed at the middle of the block and stops at its edge.
|
|
27768
|
+
//
|
|
27769
|
+
// An elbow coming from directly above or below the text cannot reach a side
|
|
27770
|
+
// without crossing the block, so it meets the facing edge instead: in the
|
|
27771
|
+
// middle when it comes from the anchor, and straight above or below a via
|
|
27772
|
+
// point, whose landing is then vertical.
|
|
27773
|
+
function getAutoAttachment(type, box, via) {
|
|
27774
|
+
var cx = (box.xmin + box.xmax) / 2;
|
|
27775
|
+
var cy = (box.ymin + box.ymax) / 2;
|
|
27776
|
+
var from = via || [0, 0];
|
|
27777
|
+
if (type == 'elbow' && isAboveOrBelow(box, from)) {
|
|
27778
|
+
return [via ? from[0] : cx, from[1] > box.ymax ? box.ymax : box.ymin];
|
|
27779
|
+
}
|
|
27780
|
+
if (type == 'elbow') {
|
|
27781
|
+
return [from[0] < cx ? box.xmin : box.xmax,
|
|
27782
|
+
Math.max(box.ymin, Math.min(box.ymax, box.midline))];
|
|
27783
|
+
}
|
|
27784
|
+
return clipToBox(from, [cx, cy], box);
|
|
27785
|
+
}
|
|
27786
|
+
|
|
27787
|
+
function getMinimumLeg(end, size) {
|
|
27788
|
+
return end == 'none' ? MIN_LEG : Math.max(MIN_LEG, size * 1.5);
|
|
27789
|
+
}
|
|
27790
|
+
|
|
27791
|
+
function isAboveOrBelow(box, p) {
|
|
27792
|
+
return p[0] >= box.xmin && p[0] <= box.xmax && (p[1] > box.ymax || p[1] < box.ymin);
|
|
27793
|
+
}
|
|
27794
|
+
|
|
27795
|
+
// Whether @t is on the top or bottom edge of @box, away from its corners
|
|
27796
|
+
function isOnTopOrBottom(box, t) {
|
|
27797
|
+
return t[0] > box.xmin && t[0] < box.xmax && (t[1] == box.ymin || t[1] == box.ymax);
|
|
27798
|
+
}
|
|
27799
|
+
|
|
27800
|
+
// Where the segment from @p (outside the box) to @c (inside it) enters the box.
|
|
27801
|
+
function clipToBox(p, c, box) {
|
|
27802
|
+
var dx = c[0] - p[0];
|
|
27803
|
+
var dy = c[1] - p[1];
|
|
27804
|
+
var s;
|
|
27805
|
+
if (boxContains(box, p)) return c;
|
|
27806
|
+
s = Math.max(0, getEntry(p[0], dx, box.xmin, box.xmax),
|
|
27807
|
+
getEntry(p[1], dy, box.ymin, box.ymax));
|
|
27808
|
+
return [p[0] + s * dx, p[1] + s * dy];
|
|
27809
|
+
}
|
|
27810
|
+
|
|
27811
|
+
function getEntry(p, d, min, max) {
|
|
27812
|
+
if (d === 0) return -Infinity;
|
|
27813
|
+
return Math.min((min - p) / d, (max - p) / d);
|
|
27814
|
+
}
|
|
27815
|
+
|
|
27816
|
+
// Shapes
|
|
27817
|
+
|
|
27818
|
+
// The segment from the anchor rises at 45 degrees to meet a horizontal landing
|
|
27819
|
+
// into the side of the text, or goes straight up or down when the text is too
|
|
27820
|
+
// close horizontally for that.
|
|
27821
|
+
//
|
|
27822
|
+
// Into the top or bottom of the text, the landing is vertical and the line
|
|
27823
|
+
// leaves the anchor horizontally, which keeps the corner clear of the block.
|
|
27824
|
+
function getAutoElbow(a, t, box) {
|
|
27825
|
+
var dx = t[0] - a[0];
|
|
27826
|
+
var dy = Math.abs(t[1] - a[1]);
|
|
27827
|
+
if (isOnTopOrBottom(box, t)) return [t[0], a[1]];
|
|
27828
|
+
if (Math.abs(dx) <= dy) return [a[0], t[1]];
|
|
27829
|
+
return [a[0] + (dx > 0 ? dy : -dy), t[1]];
|
|
27830
|
+
}
|
|
27831
|
+
|
|
27832
|
+
// The chord's midpoint, pushed to its left as seen from the anchor -- which
|
|
27833
|
+
// bows a callout to text on its right upward.
|
|
27834
|
+
function getAutoBend(a, t) {
|
|
27835
|
+
var dx = t[0] - a[0];
|
|
27836
|
+
var dy = t[1] - a[1];
|
|
27837
|
+
return [(a[0] + t[0]) / 2 + dy * CURVE_BEND, (a[1] + t[1]) / 2 - dx * CURVE_BEND];
|
|
27838
|
+
}
|
|
27839
|
+
|
|
27840
|
+
// The control point of the quadratic from @a to @t that passes through @v at
|
|
27841
|
+
// its midpoint. That is also the curve's farthest point from its chord, so a
|
|
27842
|
+
// handle at @v sits where the bend visibly is.
|
|
27843
|
+
function getControlPoint(a, v, t) {
|
|
27844
|
+
return [2 * v[0] - (a[0] + t[0]) / 2, 2 * v[1] - (a[1] + t[1]) / 2];
|
|
27845
|
+
}
|
|
27846
|
+
|
|
27847
|
+
// Trimming
|
|
27848
|
+
|
|
27849
|
+
// @path with its start moved to where it leaves a circle of radius @r around
|
|
27850
|
+
// that start, or null if it never does. A circle rather than a length along the
|
|
27851
|
+
// path, because what the gap has to clear is a symbol drawn at the anchor.
|
|
27852
|
+
function trimPath(path, r) {
|
|
27853
|
+
if (!(r > 0)) return hasLength(path) ? path : null;
|
|
27854
|
+
if (path.kind == 'bezier') return trimBezier(path.coords, r);
|
|
27855
|
+
return trimPolyline(path.coords, r);
|
|
27856
|
+
}
|
|
27857
|
+
|
|
27858
|
+
function hasLength(path) {
|
|
27859
|
+
var c = path.coords;
|
|
27860
|
+
for (var i = 1; i < c.length; i++) {
|
|
27861
|
+
if (distance(c[0], c[i]) > 0) return true;
|
|
27862
|
+
}
|
|
27863
|
+
return false;
|
|
27864
|
+
}
|
|
27865
|
+
|
|
27866
|
+
function trimPolyline(coords, r) {
|
|
27867
|
+
var p0 = coords[0];
|
|
27868
|
+
for (var i = 1; i < coords.length; i++) {
|
|
27869
|
+
if (distance(p0, coords[i]) > r) {
|
|
27870
|
+
return {
|
|
27871
|
+
kind: 'polyline',
|
|
27872
|
+
coords: [getCircleExit(p0, r, coords[i - 1], coords[i])].concat(coords.slice(i))
|
|
27873
|
+
};
|
|
27874
|
+
}
|
|
27875
|
+
}
|
|
27876
|
+
return null;
|
|
27877
|
+
}
|
|
27878
|
+
|
|
27879
|
+
// The point where segment @p-@q, which starts inside the circle and ends
|
|
27880
|
+
// outside it, crosses it.
|
|
27881
|
+
function getCircleExit(c, r, p, q) {
|
|
27882
|
+
var dx = q[0] - p[0];
|
|
27883
|
+
var dy = q[1] - p[1];
|
|
27884
|
+
var ex = p[0] - c[0];
|
|
27885
|
+
var ey = p[1] - c[1];
|
|
27886
|
+
var a = dx * dx + dy * dy;
|
|
27887
|
+
var b = 2 * (dx * ex + dy * ey);
|
|
27888
|
+
var k = ex * ex + ey * ey - r * r;
|
|
27889
|
+
var s = (-b + Math.sqrt(Math.max(0, b * b - 4 * a * k))) / (2 * a);
|
|
27890
|
+
s = Math.max(0, Math.min(1, s));
|
|
27891
|
+
return [p[0] + s * dx, p[1] + s * dy];
|
|
27892
|
+
}
|
|
27893
|
+
|
|
27894
|
+
function trimBezier(c, r) {
|
|
27895
|
+
var steps = 64;
|
|
27896
|
+
var lo = 0, hi = -1, mid, i;
|
|
27897
|
+
for (i = 1; i <= steps; i++) {
|
|
27898
|
+
if (distance(c[0], getBezierPoint(c, i / steps)) > r) {
|
|
27899
|
+
hi = i / steps;
|
|
27900
|
+
lo = (i - 1) / steps;
|
|
27901
|
+
break;
|
|
27902
|
+
}
|
|
27903
|
+
}
|
|
27904
|
+
if (hi < 0) return null;
|
|
27905
|
+
for (i = 0; i < 24; i++) {
|
|
27906
|
+
mid = (lo + hi) / 2;
|
|
27907
|
+
if (distance(c[0], getBezierPoint(c, mid)) > r) hi = mid;
|
|
27908
|
+
else lo = mid;
|
|
27909
|
+
}
|
|
27910
|
+
return {kind: 'bezier', coords: splitBezier(c, hi)};
|
|
27911
|
+
}
|
|
27912
|
+
|
|
27913
|
+
function getBezierPoint(c, t) {
|
|
27914
|
+
var u = 1 - t;
|
|
27915
|
+
return [
|
|
27916
|
+
u * u * c[0][0] + 2 * u * t * c[1][0] + t * t * c[2][0],
|
|
27917
|
+
u * u * c[0][1] + 2 * u * t * c[1][1] + t * t * c[2][1]
|
|
27918
|
+
];
|
|
27919
|
+
}
|
|
27920
|
+
|
|
27921
|
+
// The part of the quadratic @c from @t to its end
|
|
27922
|
+
function splitBezier(c, t) {
|
|
27923
|
+
return [getBezierPoint(c, t), lerp(c[1], c[2], t), c[2]];
|
|
27924
|
+
}
|
|
27925
|
+
|
|
27926
|
+
// End markers
|
|
27927
|
+
|
|
27928
|
+
// Unit vector from the start of @path into it, or null for a path with no
|
|
27929
|
+
// direction there.
|
|
27930
|
+
function getStartDirection(path) {
|
|
27931
|
+
var c = path.coords;
|
|
27932
|
+
var next = c[1];
|
|
27933
|
+
if (path.kind == 'bezier' && distance(c[0], c[1]) === 0) next = c[2];
|
|
27934
|
+
return getUnitVector(c[0], next);
|
|
27935
|
+
}
|
|
27936
|
+
|
|
27937
|
+
// A solid arrowhead on a curve, and the curve that runs into it:
|
|
27938
|
+
// {dir, path}, or null if the curve does not reach past the head.
|
|
27939
|
+
//
|
|
27940
|
+
// Pointed along the curve's tangent at the tip, the head faces off to one side
|
|
27941
|
+
// of where the line goes on a bend, and the line cut back along the curve then
|
|
27942
|
+
// meets the back of the head off-centre and at a slant. So the head points
|
|
27943
|
+
// along the chord from its tip to where the curve leaves it, which puts that
|
|
27944
|
+
// point at the middle of its base, and the curve is re-fitted to leave the
|
|
27945
|
+
// head along the same line: it starts at the middle of the base, heading
|
|
27946
|
+
// along the head's axis, and keeps its own direction into the text. The
|
|
27947
|
+
// control point that does both is where those two lines cross. Starting on
|
|
27948
|
+
// the base rather than inside the head keeps the line centred exactly, since
|
|
27949
|
+
// a curve bends away from its starting direction; its round cap reaches back
|
|
27950
|
+
// into the head, where it is hidden.
|
|
27951
|
+
//
|
|
27952
|
+
// path: a quadratic Bezier from the anchor end; len: the head's length
|
|
27953
|
+
function getCurveIntoArrowHead(path, len) {
|
|
27954
|
+
var tip = path.coords[0];
|
|
27955
|
+
var rest = trimBezier(path.coords, len);
|
|
27956
|
+
var dir, start, end, ctrl;
|
|
27957
|
+
if (!rest) return null;
|
|
27958
|
+
start = rest.coords[0];
|
|
27959
|
+
dir = getUnitVector(tip, start);
|
|
27960
|
+
if (!dir) return null;
|
|
27961
|
+
end = rest.coords[2];
|
|
27962
|
+
ctrl = intersectRays(start, dir, end, getUnitVector(end, rest.coords[1]));
|
|
27963
|
+
// A curve that doubles back, or runs straight on: a control point on the
|
|
27964
|
+
// head's axis still takes the line out of the head straight.
|
|
27965
|
+
if (!ctrl) ctrl = [start[0] + dir[0] * distance(start, end) / 2,
|
|
27966
|
+
start[1] + dir[1] * distance(start, end) / 2];
|
|
27967
|
+
return {dir: dir, path: {kind: 'bezier', coords: [start, ctrl, end]}};
|
|
27968
|
+
}
|
|
27969
|
+
|
|
27970
|
+
// Where the ray from @p along @d meets the ray from @q along @e, or null if
|
|
27971
|
+
// they do not meet ahead of both
|
|
27972
|
+
function intersectRays(p, d, q, e) {
|
|
27973
|
+
var cross, s, u, qx, qy;
|
|
27974
|
+
if (!d || !e) return null;
|
|
27975
|
+
cross = d[0] * e[1] - d[1] * e[0];
|
|
27976
|
+
if (Math.abs(cross) < 1e-9) return null;
|
|
27977
|
+
qx = q[0] - p[0];
|
|
27978
|
+
qy = q[1] - p[1];
|
|
27979
|
+
s = (qx * e[1] - qy * e[0]) / cross;
|
|
27980
|
+
u = (qx * d[1] - qy * d[0]) / cross;
|
|
27981
|
+
return s > 0 && u > 0 ? [p[0] + d[0] * s, p[1] + d[1] * s] : null;
|
|
27982
|
+
}
|
|
27983
|
+
|
|
27984
|
+
// [tip, wing, wing], for a head with sides @side long meeting at @angle degrees
|
|
27985
|
+
function getArrowHead(tip, dir, side, angle) {
|
|
27986
|
+
var len = getArrowHeadLength(side, angle);
|
|
27987
|
+
var half = side * Math.sin(angle / 2 * Math.PI / 180);
|
|
27988
|
+
var bx = tip[0] + dir[0] * len;
|
|
27989
|
+
var by = tip[1] + dir[1] * len;
|
|
27990
|
+
return [tip, [bx - dir[1] * half, by + dir[0] * half], [bx + dir[1] * half, by - dir[0] * half]];
|
|
27991
|
+
}
|
|
27992
|
+
|
|
27993
|
+
// How far along the line a head with sides @side long reaches
|
|
27994
|
+
function getArrowHeadLength(side, angle) {
|
|
27995
|
+
return side * Math.cos(angle / 2 * Math.PI / 180);
|
|
27996
|
+
}
|
|
27997
|
+
|
|
27998
|
+
// Utilities
|
|
27999
|
+
|
|
28000
|
+
function padBox(box, pad) {
|
|
28001
|
+
return {
|
|
28002
|
+
xmin: box.xmin - pad,
|
|
28003
|
+
xmax: box.xmax + pad,
|
|
28004
|
+
ymin: box.ymin - pad,
|
|
28005
|
+
ymax: box.ymax + pad,
|
|
28006
|
+
midline: box.midline
|
|
28007
|
+
};
|
|
28008
|
+
}
|
|
28009
|
+
|
|
28010
|
+
function boxContains(box, p) {
|
|
28011
|
+
return p[0] > box.xmin && p[0] < box.xmax && p[1] > box.ymin && p[1] < box.ymax;
|
|
28012
|
+
}
|
|
28013
|
+
|
|
28014
|
+
function getUnitVector(p, q) {
|
|
28015
|
+
var dx = q[0] - p[0];
|
|
28016
|
+
var dy = q[1] - p[1];
|
|
28017
|
+
var len = Math.sqrt(dx * dx + dy * dy);
|
|
28018
|
+
return len > 0 ? [dx / len, dy / len] : null;
|
|
28019
|
+
}
|
|
28020
|
+
|
|
28021
|
+
function distance(p, q) {
|
|
28022
|
+
var dx = q[0] - p[0];
|
|
28023
|
+
var dy = q[1] - p[1];
|
|
28024
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
28025
|
+
}
|
|
28026
|
+
|
|
28027
|
+
function lerp(p, q, t) {
|
|
28028
|
+
return [p[0] + (q[0] - p[0]) * t, p[1] + (q[1] - p[1]) * t];
|
|
28029
|
+
}
|
|
28030
|
+
|
|
28031
|
+
function getNumber(val, defaultVal) {
|
|
28032
|
+
return isSvgNumber(val) && Number(val) >= 0 ? Number(val) : defaultVal;
|
|
28033
|
+
}
|
|
28034
|
+
|
|
28035
|
+
function getLineWidth(rec) {
|
|
28036
|
+
var w = getNumber(rec['callout-width'], DEFAULT_LINE_WIDTH);
|
|
28037
|
+
return w > 0 ? w : DEFAULT_LINE_WIDTH;
|
|
28038
|
+
}
|
|
28039
|
+
|
|
28040
|
+
function getFontSizeInPx$1(rec) {
|
|
28041
|
+
var val = rec['font-size'];
|
|
28042
|
+
var px = val === undefined || val === null || val === '' ? null :
|
|
28043
|
+
measureToPx(val, DEFAULT_LABEL_FONT_SIZE);
|
|
28044
|
+
return px > 0 ? px : DEFAULT_LABEL_FONT_SIZE;
|
|
28045
|
+
}
|
|
28046
|
+
|
|
28047
|
+
// px for a number, a px value or an em value, or null
|
|
28048
|
+
function measureToPx(val, emBasis) {
|
|
28049
|
+
var px = toPixels(val, emBasis);
|
|
28050
|
+
var match;
|
|
28051
|
+
if (px !== null) return px;
|
|
28052
|
+
match = /^(-?[.0-9]+)px$/.exec(String(val).trim());
|
|
28053
|
+
return match ? Number(match[1]) : null;
|
|
28054
|
+
}
|
|
28055
|
+
|
|
28056
|
+
// Where the first baseline sits below the text's y, in ems
|
|
28057
|
+
function getBaselineShift(baseline) {
|
|
28058
|
+
var val = String(baseline || '').toLowerCase();
|
|
28059
|
+
if (val == 'central' || val == 'middle') return MIDLINE;
|
|
28060
|
+
if (val == 'hanging' || val == 'text-before-edge') return ASCENT;
|
|
28061
|
+
if (val == 'text-after-edge' || val == 'ideographic') return -DESCENT;
|
|
28062
|
+
return 0;
|
|
28063
|
+
}
|
|
28064
|
+
|
|
28065
|
+
var SvgLabelCallout = /*#__PURE__*/Object.freeze({
|
|
28066
|
+
__proto__: null,
|
|
28067
|
+
getCalloutEndType: getCalloutEndType,
|
|
28068
|
+
getCalloutGap: getCalloutGap,
|
|
28069
|
+
getCalloutShape: getCalloutShape,
|
|
28070
|
+
getControlPoint: getControlPoint,
|
|
28071
|
+
getDefaultCalloutEndSize: getDefaultCalloutEndSize,
|
|
28072
|
+
getLabelCalloutShape: getLabelCalloutShape,
|
|
28073
|
+
getLabelTextBox: getLabelTextBox,
|
|
28074
|
+
labelHasCallout: labelHasCallout,
|
|
28075
|
+
renderLabelCallout: renderLabelCallout
|
|
27282
28076
|
});
|
|
27283
28077
|
|
|
27284
28078
|
// convert data records (properties like svg-symbol, label-text, fill, r) to svg symbols
|
|
@@ -27307,7 +28101,13 @@
|
|
|
27307
28101
|
// render label and/or point symbol
|
|
27308
28102
|
function renderPoint(rec) {
|
|
27309
28103
|
var children = [];
|
|
28104
|
+
var callout;
|
|
27310
28105
|
// var halfSize = rec.r || 0; // radius or half of symbol size
|
|
28106
|
+
// Beneath the symbol and the text, which it runs between.
|
|
28107
|
+
if (featureHasLabel(rec) && labelHasCallout(rec)) {
|
|
28108
|
+
callout = renderLabelCallout(rec, getAnchorSymbolRadius(rec));
|
|
28109
|
+
if (callout) children.push(callout);
|
|
28110
|
+
}
|
|
27311
28111
|
if (featureHasSvgSymbol(rec)) {
|
|
27312
28112
|
children.push(renderSymbol(rec));
|
|
27313
28113
|
}
|
|
@@ -27333,6 +28133,15 @@
|
|
|
27333
28133
|
return empty();
|
|
27334
28134
|
}
|
|
27335
28135
|
|
|
28136
|
+
// How far the symbol at a label's anchor reaches from it, which a callout's
|
|
28137
|
+
// default gap clears. A composite svg-symbol has no single radius, and gets no
|
|
28138
|
+
// clearance.
|
|
28139
|
+
function getAnchorSymbolRadius(d) {
|
|
28140
|
+
if (!featureHasSvgSymbol(d) || d['svg-symbol']) return 0;
|
|
28141
|
+
if (featureHasIcon(d)) return getIconRadius(d, d.icon || 'circle');
|
|
28142
|
+
return d.r > 0 ? d.r : 0;
|
|
28143
|
+
}
|
|
28144
|
+
|
|
27336
28145
|
function featureHasIcon(d) {
|
|
27337
28146
|
return !!(d && (d.icon || d['icon-size'] || (d['icon-color'] && d.r > 0)));
|
|
27338
28147
|
}
|
|
@@ -27557,6 +28366,7 @@
|
|
|
27557
28366
|
|
|
27558
28367
|
var SvgSymbols = /*#__PURE__*/Object.freeze({
|
|
27559
28368
|
__proto__: null,
|
|
28369
|
+
getAnchorSymbolRadius: getAnchorSymbolRadius,
|
|
27560
28370
|
getTransform: getTransform,
|
|
27561
28371
|
renderPoint: renderPoint,
|
|
27562
28372
|
symbolRenderers: symbolRenderers
|
|
@@ -28518,7 +29328,9 @@
|
|
|
28518
29328
|
}
|
|
28519
29329
|
cls = LABEL_OVERFLOW_CLASS;
|
|
28520
29330
|
}
|
|
28521
|
-
text
|
|
29331
|
+
// A soft break is wrapping for a width, which text along a path does not
|
|
29332
|
+
// have, and removing it leaves the space it broke at.
|
|
29333
|
+
text = removeSoftBreaks(toLabelString(rec['label-text']));
|
|
28522
29334
|
if (labelNewlineRxp.test(text)) {
|
|
28523
29335
|
// a <tspan> inside a <textPath> advances along the path instead of
|
|
28524
29336
|
// stacking below it, so the lines are joined rather than dropping
|
|
@@ -28546,6 +29358,7 @@
|
|
|
28546
29358
|
if (rec.dy) textPath.properties.dy = rec.dy;
|
|
28547
29359
|
o = {tag: 'text', properties: {}, children: [textPath]};
|
|
28548
29360
|
applyStyleAttributes(o, 'label', rec);
|
|
29361
|
+
applyLabelHalo(o, rec);
|
|
28549
29362
|
if (cls) {
|
|
28550
29363
|
o.properties.class = o.properties.class ? o.properties.class + ' ' + cls : cls;
|
|
28551
29364
|
}
|
|
@@ -28842,6 +29655,11 @@
|
|
|
28842
29655
|
// are applied to the <g> container, 'r' property is applied to circle
|
|
28843
29656
|
applyStyleAttributes(svgObj, msType, d, simpleCircleFilter);
|
|
28844
29657
|
} else ;
|
|
29658
|
+
// Only here, where SVG is written for other programs to read: the GUI
|
|
29659
|
+
// draws the same labels with paint-order and has no use for the copies.
|
|
29660
|
+
if (labelHasHalo(d)) {
|
|
29661
|
+
svgObj = splitLabelHalos(svgObj);
|
|
29662
|
+
}
|
|
28845
29663
|
if ('id' in obj) {
|
|
28846
29664
|
if (!svgObj.properties) {
|
|
28847
29665
|
svgObj.properties = {};
|
|
@@ -40585,6 +41403,20 @@ ${svg}
|
|
|
40585
41403
|
dy: {describe: 'vertical offset from the anchor'},
|
|
40586
41404
|
fill: {describe: 'text color'},
|
|
40587
41405
|
opacity: {describe: 'text opacity'},
|
|
41406
|
+
'halo-width': {describe: 'width of a halo around the text in px'},
|
|
41407
|
+
'halo-color': {describe: 'halo color (default is white)'},
|
|
41408
|
+
'halo-opacity': {describe: 'halo opacity, 0-1'},
|
|
41409
|
+
'label-width': {describe: 'width of a fixed-width text block in px'},
|
|
41410
|
+
callout: {describe: 'line from the anchor to the text: line, elbow or curve'},
|
|
41411
|
+
'callout-end': {describe: 'arrowhead at the anchor end: arrow, open-arrow or none'},
|
|
41412
|
+
'callout-end-size': {describe: 'length of an arrowhead\'s sides in px'},
|
|
41413
|
+
'callout-via': {describe: 'x,y of an elbow\'s corner or a point on a curve, in px'},
|
|
41414
|
+
'callout-attach': {describe: 'where the callout meets the text, as x,y fractions'},
|
|
41415
|
+
'callout-gap': {describe: 'px between the callout and the anchor'},
|
|
41416
|
+
'callout-padding': {describe: 'px between the callout and the text'},
|
|
41417
|
+
'callout-color': {describe: 'callout color (defaults to the text color)'},
|
|
41418
|
+
'callout-width': {describe: 'callout line width in px'},
|
|
41419
|
+
'callout-opacity': {describe: 'callout opacity, 0-1'},
|
|
40588
41420
|
css: {describe: 'inline css style'},
|
|
40589
41421
|
class: {describe: 'name of CSS class or classes (space-separated)'}
|
|
40590
41422
|
},
|
|
@@ -42612,6 +43444,15 @@ ${svg}
|
|
|
42612
43444
|
.option('label-align', {
|
|
42613
43445
|
describe: 'alignment of the lines of multi-line labels; left, center or right'
|
|
42614
43446
|
})
|
|
43447
|
+
.option('halo-width', {
|
|
43448
|
+
describe: 'width of a halo around label text in px (default is 0, no halo)'
|
|
43449
|
+
})
|
|
43450
|
+
.option('halo-color', {
|
|
43451
|
+
describe: 'color of label halos (default is white)'
|
|
43452
|
+
})
|
|
43453
|
+
.option('halo-opacity', {
|
|
43454
|
+
describe: 'opacity of label halos, 0-1'
|
|
43455
|
+
})
|
|
42615
43456
|
.option('dx', {
|
|
42616
43457
|
describe: 'x offset of labels (default is 0)'
|
|
42617
43458
|
})
|
|
@@ -42648,6 +43489,39 @@ ${svg}
|
|
|
42648
43489
|
.option('label-start-offset', {
|
|
42649
43490
|
describe: 'where label text starts along its path, e.g. 50%'
|
|
42650
43491
|
})
|
|
43492
|
+
.option('label-width', {
|
|
43493
|
+
describe: 'width of a fixed-width text block in px (lines are wrapped in the web UI)'
|
|
43494
|
+
})
|
|
43495
|
+
.option('callout', {
|
|
43496
|
+
describe: 'line from a label\'s anchor to its text: line, elbow or curve'
|
|
43497
|
+
})
|
|
43498
|
+
.option('callout-end', {
|
|
43499
|
+
describe: 'arrowhead at the anchor end of a callout: arrow (solid), open-arrow or none'
|
|
43500
|
+
})
|
|
43501
|
+
.option('callout-end-size', {
|
|
43502
|
+
describe: 'length of an arrowhead\'s sides in px (default 10, more for a thicker line)'
|
|
43503
|
+
})
|
|
43504
|
+
.option('callout-via', {
|
|
43505
|
+
describe: 'x,y of an elbow\'s corner or a point on a curve, in px from the anchor'
|
|
43506
|
+
})
|
|
43507
|
+
.option('callout-attach', {
|
|
43508
|
+
describe: 'where a callout meets its text, as x,y fractions of the text box'
|
|
43509
|
+
})
|
|
43510
|
+
.option('callout-gap', {
|
|
43511
|
+
describe: 'px between a callout and its anchor (default: a line meets the icon, an arrow clears it)'
|
|
43512
|
+
})
|
|
43513
|
+
.option('callout-padding', {
|
|
43514
|
+
describe: 'px between a callout and its text (default is 3)'
|
|
43515
|
+
})
|
|
43516
|
+
.option('callout-color', {
|
|
43517
|
+
describe: 'callout color (defaults to the text color)'
|
|
43518
|
+
})
|
|
43519
|
+
.option('callout-width', {
|
|
43520
|
+
describe: 'callout line width in px (default is 1)'
|
|
43521
|
+
})
|
|
43522
|
+
.option('callout-opacity', {
|
|
43523
|
+
describe: 'callout opacity, 0-1'
|
|
43524
|
+
})
|
|
42651
43525
|
.option('target', targetOpt);
|
|
42652
43526
|
|
|
42653
43527
|
parser.command('repel')
|
|
@@ -82789,7 +83663,7 @@ ${svg}
|
|
|
82789
83663
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
82790
83664
|
}
|
|
82791
83665
|
|
|
82792
|
-
var version = "0.7.
|
|
83666
|
+
var version = "0.7.67";
|
|
82793
83667
|
|
|
82794
83668
|
// Parse command line args into commands and run them
|
|
82795
83669
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -83613,7 +84487,7 @@ ${svg}
|
|
|
83613
84487
|
if (!(fontSize > 0)) return null;
|
|
83614
84488
|
// The widest line, which is what the block of a multi-line label is as wide
|
|
83615
84489
|
// as, and what the browser's getBBox() reports for the same text.
|
|
83616
|
-
width = text
|
|
84490
|
+
width = splitLabelLines(text).reduce(function(max, line) {
|
|
83617
84491
|
var w = measureLine(font, line, fontSize, spacing);
|
|
83618
84492
|
return w > max ? w : max;
|
|
83619
84493
|
}, 0);
|
|
@@ -85391,7 +86265,7 @@ ${svg}
|
|
|
85391
86265
|
internal.svg = Object.assign({}, SvgStringify, SvgPathUtils, GeojsonToSvg,
|
|
85392
86266
|
SvgFeatureUtils,
|
|
85393
86267
|
SvgLabels, SvgSymbols, SvgLabelPaths, SvgLabelFit, SvgLabelAlign,
|
|
85394
|
-
SvgLabelMetrics);
|
|
86268
|
+
SvgLabelMetrics, SvgLabelHalo, SvgLabelCallout);
|
|
85395
86269
|
|
|
85396
86270
|
// Reached through the bundle rather than imported from source, unlike most of
|
|
85397
86271
|
// what tests use, because these modules load fs and fontkit through the
|