mapshaper 0.7.71 → 0.7.72
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/mapshaper.js +367 -18
- package/package.json +1 -1
- package/www/mapshaper-gui.js +700 -126
- package/www/mapshaper.js +367 -18
- package/www/page.css +21 -2
package/www/mapshaper-gui.js
CHANGED
|
@@ -3021,7 +3021,7 @@
|
|
|
3021
3021
|
return str + repeatString(pad, size - str.length);
|
|
3022
3022
|
}
|
|
3023
3023
|
|
|
3024
|
-
function trim(str) {
|
|
3024
|
+
function trim$1(str) {
|
|
3025
3025
|
return ltrim(rtrim(str));
|
|
3026
3026
|
}
|
|
3027
3027
|
|
|
@@ -3625,7 +3625,7 @@
|
|
|
3625
3625
|
range, regexEscape, reorderArray: reorderArray$1, repeat, repeatString,
|
|
3626
3626
|
replaceArray, rpad, rtrim,
|
|
3627
3627
|
shuffle, some, sortArrayIndex, sortOn, splitLines, sum,
|
|
3628
|
-
toArray, toBuffer, trim, trimQuotes,
|
|
3628
|
+
toArray, toBuffer, trim: trim$1, trimQuotes,
|
|
3629
3629
|
uniq, uniqifyNames,
|
|
3630
3630
|
wildcardToRegExp
|
|
3631
3631
|
};
|
|
@@ -15892,6 +15892,11 @@
|
|
|
15892
15892
|
var colorPicker = El('div').addClass('label-color-picker').appendTo(parent).hide();
|
|
15893
15893
|
var sbCanvas, hueCanvas, sbMarker, hueMarker, pickerHsbInputs;
|
|
15894
15894
|
var pickerColor = {h: 0, s: 0, b: 0};
|
|
15895
|
+
// The hex value the picker was set to, until it is moved off it. HSB is
|
|
15896
|
+
// held in bytes, which cannot represent every RGB colour, so a colour that
|
|
15897
|
+
// was typed or picked from a preset would otherwise come back as a
|
|
15898
|
+
// neighbour of itself (#ff8800 as #ff8a00).
|
|
15899
|
+
var pickerHex = null;
|
|
15895
15900
|
var presetRows = opts.presetRows || [grayscaleColorPresets];
|
|
15896
15901
|
|
|
15897
15902
|
init();
|
|
@@ -15920,13 +15925,15 @@
|
|
|
15920
15925
|
|
|
15921
15926
|
this.setColor = function(color) {
|
|
15922
15927
|
if (isHexColor(color)) {
|
|
15923
|
-
setPickerColor(hexToHsb(color));
|
|
15928
|
+
setPickerColor(hexToHsb(color), color);
|
|
15924
15929
|
}
|
|
15925
15930
|
};
|
|
15926
15931
|
|
|
15927
|
-
this.getColor =
|
|
15928
|
-
|
|
15929
|
-
|
|
15932
|
+
this.getColor = getPickerHex;
|
|
15933
|
+
|
|
15934
|
+
function getPickerHex() {
|
|
15935
|
+
return pickerHex || hsbToHex(pickerColor);
|
|
15936
|
+
}
|
|
15930
15937
|
|
|
15931
15938
|
// The picker is placed in viewport coordinates (see .label-color-picker), so
|
|
15932
15939
|
// it has to be put against the field it belongs to each time it opens. The
|
|
@@ -15991,7 +15998,7 @@
|
|
|
15991
15998
|
}
|
|
15992
15999
|
|
|
15993
16000
|
function applyPreset(color) {
|
|
15994
|
-
setPickerColor(hexToHsb(color));
|
|
16001
|
+
setPickerColor(hexToHsb(color), color);
|
|
15995
16002
|
commitPickerColor();
|
|
15996
16003
|
}
|
|
15997
16004
|
|
|
@@ -16061,15 +16068,17 @@
|
|
|
16061
16068
|
};
|
|
16062
16069
|
}
|
|
16063
16070
|
|
|
16064
|
-
|
|
16071
|
+
// @hex: the exact colour, when @hsb was made from one
|
|
16072
|
+
function setPickerColor(hsb, hex) {
|
|
16065
16073
|
pickerColor = {
|
|
16066
16074
|
h: clamp$4(Math.round(hsb.h), 0, 255),
|
|
16067
16075
|
s: clamp$4(Math.round(hsb.s), 0, 255),
|
|
16068
16076
|
b: clamp$4(Math.round(hsb.b), 0, 255)
|
|
16069
16077
|
};
|
|
16078
|
+
pickerHex = hex || null;
|
|
16070
16079
|
drawColorPicker();
|
|
16071
16080
|
updatePickerFields();
|
|
16072
|
-
if (opts.onPreview) opts.onPreview(
|
|
16081
|
+
if (opts.onPreview) opts.onPreview(getPickerHex());
|
|
16073
16082
|
}
|
|
16074
16083
|
|
|
16075
16084
|
function updatePickerFields() {
|
|
@@ -16125,7 +16134,7 @@
|
|
|
16125
16134
|
}
|
|
16126
16135
|
|
|
16127
16136
|
function commitPickerColor() {
|
|
16128
|
-
if (opts.onChange) opts.onChange(
|
|
16137
|
+
if (opts.onChange) opts.onChange(getPickerHex());
|
|
16129
16138
|
}
|
|
16130
16139
|
|
|
16131
16140
|
function getMarkerColor(rgb) {
|
|
@@ -16894,7 +16903,7 @@
|
|
|
16894
16903
|
});
|
|
16895
16904
|
}
|
|
16896
16905
|
|
|
16897
|
-
// A button with a word on it rather than a glyph: Clear style, Random
|
|
16906
|
+
// A button with a word on it rather than a glyph: Clear style, Random fill,
|
|
16898
16907
|
// Create. Shaped like the panel's fields rather than like its 19px icon
|
|
16899
16908
|
// buttons, because what it does is named rather than drawn, and it is as wide
|
|
16900
16909
|
// as the name.
|
|
@@ -16952,7 +16961,10 @@
|
|
|
16952
16961
|
// split row. Fill and Stroke are each one of these.
|
|
16953
16962
|
//
|
|
16954
16963
|
// opts.label the caption over the field
|
|
16955
|
-
// opts.onColor(hex) a colour was typed, picked or previewed to a finish
|
|
16964
|
+
// opts.onColor(hex) a colour was typed, picked or previewed to a finish;
|
|
16965
|
+
// '' when the field was emptied, which unsets a colour
|
|
16966
|
+
// that can be unset and restores the default of one
|
|
16967
|
+
// that cannot
|
|
16956
16968
|
// opts.onOpacity(frac) a usable percentage was typed
|
|
16957
16969
|
// opts.revert() one that was not, so put the row back as it was
|
|
16958
16970
|
// opts.noOpacity a colour with no opacity of its own, like a pattern's,
|
|
@@ -17220,6 +17232,36 @@
|
|
|
17220
17232
|
return gui.state.label_text_session || null;
|
|
17221
17233
|
}
|
|
17222
17234
|
|
|
17235
|
+
// The label tool's armed placement tool: 'anchor', 'block', 'path' or null.
|
|
17236
|
+
// The tool owns it; the panel reads it, because an armed tool with nothing
|
|
17237
|
+
// selected is what points the panel at new labels rather than at all of them.
|
|
17238
|
+
function setLabelToolArmed(gui, mode) {
|
|
17239
|
+
var val = mode || null;
|
|
17240
|
+
if ((gui.state.label_tool_armed || null) === val) return;
|
|
17241
|
+
gui.state.label_tool_armed = val;
|
|
17242
|
+
gui.dispatchEvent('label_tool_armed_change');
|
|
17243
|
+
}
|
|
17244
|
+
|
|
17245
|
+
function getLabelToolArmed(gui) {
|
|
17246
|
+
return gui.state.label_tool_armed || null;
|
|
17247
|
+
}
|
|
17248
|
+
|
|
17249
|
+
// The "Editing:" line at the top of the style panels, which says what a
|
|
17250
|
+
// change to the panel's controls will act on. One wording for every panel.
|
|
17251
|
+
//
|
|
17252
|
+
// o.selected how many features are selected
|
|
17253
|
+
// o.total how many features the layer has
|
|
17254
|
+
// o.newLabels (label panel) a placement tool is armed with nothing
|
|
17255
|
+
// selected, so the controls set the style of the next label
|
|
17256
|
+
// o.editingText (label panel) a label is open for typing
|
|
17257
|
+
function formatEditingStatus(o) {
|
|
17258
|
+
var what = o.editingText ? 'this label' :
|
|
17259
|
+
o.selected > 0 ? o.selected + ' selected' :
|
|
17260
|
+
o.newLabels ? 'new labels' :
|
|
17261
|
+
o.total > 0 ? 'all' : 'none (empty layer)';
|
|
17262
|
+
return 'Editing: ' + what;
|
|
17263
|
+
}
|
|
17264
|
+
|
|
17223
17265
|
// Dragging an anchored label's text away from its anchor: what the drag
|
|
17224
17266
|
// writes, and which of the nine positions it ends up nearest.
|
|
17225
17267
|
//
|
|
@@ -17792,6 +17834,9 @@
|
|
|
17792
17834
|
}, {
|
|
17793
17835
|
name: 'open-arrow',
|
|
17794
17836
|
title: 'open arrowhead at the anchor'
|
|
17837
|
+
}, {
|
|
17838
|
+
name: 'ring',
|
|
17839
|
+
title: 'ring around the anchor'
|
|
17795
17840
|
}];
|
|
17796
17841
|
// The anchor is at the lower left of the shapes and at the left of the ends,
|
|
17797
17842
|
// which is where the markers go. The arrowheads are drawn to the renderer's
|
|
@@ -17803,7 +17848,8 @@
|
|
|
17803
17848
|
curve: '<path d="M3.5 12.5Q4.5 4.5 12.5 4"></path>',
|
|
17804
17849
|
none: '<path d="M3 8H13"></path>',
|
|
17805
17850
|
arrow: '<path d="M6.84 8H13"></path><path class="fill" d="M2.5 8L8.7 6.05V9.95Z"></path>',
|
|
17806
|
-
'open-arrow': '<path d="M3.2 8H13"></path><path d="M7.38 5.07L3.2 8L7.38 10.93"></path>'
|
|
17851
|
+
'open-arrow': '<path d="M3.2 8H13"></path><path d="M7.38 5.07L3.2 8L7.38 10.93"></path>',
|
|
17852
|
+
ring: '<path d="M8.4 8H13"></path><circle cx="5.6" cy="8" r="2.8"></circle>'
|
|
17807
17853
|
};
|
|
17808
17854
|
var iconButtonSymbols = {
|
|
17809
17855
|
circle: '<circle cx="8" cy="8" r="4.25"></circle>',
|
|
@@ -17854,6 +17900,11 @@
|
|
|
17854
17900
|
// a label opened for typing becomes what the controls act on
|
|
17855
17901
|
if (panel.visible()) updateControls();
|
|
17856
17902
|
});
|
|
17903
|
+
gui.on('label_tool_armed_change', function() {
|
|
17904
|
+
// arming a tool points an empty selection at new labels, and disarming
|
|
17905
|
+
// points it back at the whole layer
|
|
17906
|
+
if (panel.visible()) updateControls();
|
|
17907
|
+
});
|
|
17857
17908
|
gui.on('label_position_mode_change', function() {
|
|
17858
17909
|
// The tool sets the mode back to Fixed when it opens, and the toggle is
|
|
17859
17910
|
// the only thing on screen that says which of the two is on.
|
|
@@ -18003,12 +18054,9 @@
|
|
|
18003
18054
|
});
|
|
18004
18055
|
colorInput.on('change', function() {
|
|
18005
18056
|
var color = colorInput.node().value.trim();
|
|
18006
|
-
|
|
18007
|
-
|
|
18008
|
-
|
|
18009
|
-
}
|
|
18010
|
-
applyLabelColor(color);
|
|
18011
|
-
}
|
|
18057
|
+
// An emptied field unsets the colour.
|
|
18058
|
+
if (isHexColor(color)) colorPicker.setColor(color);
|
|
18059
|
+
applyLabelColor(color);
|
|
18012
18060
|
});
|
|
18013
18061
|
colorPicker = initColorPicker(textColorCell, colorChit, colorInput, applyLabelColor);
|
|
18014
18062
|
|
|
@@ -18075,12 +18123,8 @@
|
|
|
18075
18123
|
});
|
|
18076
18124
|
haloColorInput.on('change', function() {
|
|
18077
18125
|
var color = haloColorInput.node().value.trim();
|
|
18078
|
-
if (color)
|
|
18079
|
-
|
|
18080
|
-
haloColorPicker.setColor(color);
|
|
18081
|
-
}
|
|
18082
|
-
applyHaloColor(color);
|
|
18083
|
-
}
|
|
18126
|
+
if (isHexColor(color)) haloColorPicker.setColor(color);
|
|
18127
|
+
applyHaloColor(color);
|
|
18084
18128
|
});
|
|
18085
18129
|
haloColorPicker = initColorPicker(haloColorCell, haloColorChit, haloColorInput, applyHaloColor);
|
|
18086
18130
|
|
|
@@ -18140,12 +18184,8 @@
|
|
|
18140
18184
|
});
|
|
18141
18185
|
iconColorInput.on('change', function() {
|
|
18142
18186
|
var color = iconColorInput.node().value.trim();
|
|
18143
|
-
if (color)
|
|
18144
|
-
|
|
18145
|
-
iconColorPicker.setColor(color);
|
|
18146
|
-
}
|
|
18147
|
-
applyIconColor(color);
|
|
18148
|
-
}
|
|
18187
|
+
if (isHexColor(color)) iconColorPicker.setColor(color);
|
|
18188
|
+
applyIconColor(color);
|
|
18149
18189
|
});
|
|
18150
18190
|
iconColorPicker = initColorPicker(iconColorCell, iconColorChit, iconColorInput, applyIconColor);
|
|
18151
18191
|
|
|
@@ -18259,7 +18299,7 @@
|
|
|
18259
18299
|
var endSizeCell = El('div').addClass('label-split-cell label-spacing-row label-callout-end-size-row').appendTo(endRow);
|
|
18260
18300
|
El('span').appendTo(endSizeCell).text('Size');
|
|
18261
18301
|
calloutEndSizeInput = new SizeField(endSizeCell, {
|
|
18262
|
-
title: '
|
|
18302
|
+
title: 'Marker size in px: an arrowhead\'s sides, or a ring\'s diameter',
|
|
18263
18303
|
min: 1,
|
|
18264
18304
|
max: 60,
|
|
18265
18305
|
step: 1,
|
|
@@ -18283,12 +18323,8 @@
|
|
|
18283
18323
|
});
|
|
18284
18324
|
calloutColorInput.on('change', function() {
|
|
18285
18325
|
var color = calloutColorInput.node().value.trim();
|
|
18286
|
-
if (color)
|
|
18287
|
-
|
|
18288
|
-
calloutColorPicker.setColor(color);
|
|
18289
|
-
}
|
|
18290
|
-
applyCalloutColor(color);
|
|
18291
|
-
}
|
|
18326
|
+
if (isHexColor(color)) calloutColorPicker.setColor(color);
|
|
18327
|
+
applyCalloutColor(color);
|
|
18292
18328
|
});
|
|
18293
18329
|
calloutColorPicker = initColorPicker(colorCell, calloutColorChit, calloutColorInput, applyCalloutColor);
|
|
18294
18330
|
|
|
@@ -18410,6 +18446,16 @@
|
|
|
18410
18446
|
return !!(gui.interaction && gui.interaction.getMode() == labelMode);
|
|
18411
18447
|
}
|
|
18412
18448
|
|
|
18449
|
+
// Whether an empty selection means the next label rather than every label:
|
|
18450
|
+
// only while a placement tool is armed, or a label not yet made is being
|
|
18451
|
+
// typed. With no tool armed the panel styles the whole layer, as the line
|
|
18452
|
+
// and polygon panels do.
|
|
18453
|
+
function editingNewLabels() {
|
|
18454
|
+
var session = getLabelTextSession(gui);
|
|
18455
|
+
if (!labelModeIsOn()) return false;
|
|
18456
|
+
return !!getLabelToolArmed(gui) || !!(session && session.id == -1);
|
|
18457
|
+
}
|
|
18458
|
+
|
|
18413
18459
|
function updateVisibility() {
|
|
18414
18460
|
var enabled = activeLayerHasLabels();
|
|
18415
18461
|
textBtn.classed('disabled', !enabled);
|
|
@@ -18493,13 +18539,14 @@
|
|
|
18493
18539
|
if (session) return [];
|
|
18494
18540
|
ids = getSelectionIds();
|
|
18495
18541
|
if (ids.length > 0) return ids;
|
|
18496
|
-
return
|
|
18542
|
+
return editingNewLabels() ? [] : getAllLabelIds();
|
|
18497
18543
|
}
|
|
18498
18544
|
|
|
18499
|
-
// With nothing selected the controls edit the style that
|
|
18500
|
-
// so they stay live even when there is no label, and
|
|
18545
|
+
// With a tool armed and nothing selected the controls edit the style that
|
|
18546
|
+
// new labels are given, so they stay live even when there is no label, and
|
|
18547
|
+
// no layer, yet. With no tool armed and no labels, there is nothing to edit.
|
|
18501
18548
|
function controlsEnabled() {
|
|
18502
|
-
return getTargetIds().length > 0 ||
|
|
18549
|
+
return getTargetIds().length > 0 || editingNewLabels();
|
|
18503
18550
|
}
|
|
18504
18551
|
|
|
18505
18552
|
function getAllLabelIds() {
|
|
@@ -18729,22 +18776,21 @@
|
|
|
18729
18776
|
});
|
|
18730
18777
|
}
|
|
18731
18778
|
|
|
18779
|
+
// What the controls will act on. With a tool armed an empty selection is the
|
|
18780
|
+
// next label rather than the whole layer, and saying "all" there is what
|
|
18781
|
+
// would make the panel untrustworthy.
|
|
18732
18782
|
function updateEditingStatus(count, editingText) {
|
|
18733
|
-
editingStatus.text(
|
|
18783
|
+
editingStatus.text(formatEditingStatus({
|
|
18784
|
+
selected: count,
|
|
18785
|
+
total: getActiveLayer() ? internal.getFeatureCount(getActiveLayer()) : 0,
|
|
18786
|
+
newLabels: editingNewLabels(),
|
|
18787
|
+
editingText: editingText
|
|
18788
|
+
}));
|
|
18734
18789
|
// "deselect" is for a selection the user made; a label being typed into is
|
|
18735
18790
|
// left by clicking away or pressing Escape, not from here.
|
|
18736
18791
|
clearLink.classed('hidden', count === 0 || editingText);
|
|
18737
18792
|
}
|
|
18738
18793
|
|
|
18739
|
-
// What the controls will act on. In label mode an empty selection is the next
|
|
18740
|
-
// label rather than the whole layer, and saying "all" there is what would
|
|
18741
|
-
// make the panel untrustworthy.
|
|
18742
|
-
function describeTarget(count, editingText) {
|
|
18743
|
-
if (editingText) return 'this label';
|
|
18744
|
-
if (count > 0) return count + ' selected';
|
|
18745
|
-
return labelModeIsOn() ? 'new labels' : 'all';
|
|
18746
|
-
}
|
|
18747
|
-
|
|
18748
18794
|
function updateFontSizeControls(shown) {
|
|
18749
18795
|
fontSizeInput.setValue(shown.value || '');
|
|
18750
18796
|
fontSizeInput.setPlaceholder(shown.mixed ? MIXED_TEXT : '');
|
|
@@ -18953,7 +18999,7 @@
|
|
|
18953
18999
|
var ids = getTargetIds();
|
|
18954
19000
|
var table = getActiveTable();
|
|
18955
19001
|
if (ids.length === 0) {
|
|
18956
|
-
return internal.svg.labelHasHalo(
|
|
19002
|
+
return internal.svg.labelHasHalo(editingNewLabels() ? getNewLabelStyle(gui) : null) ? 'on' : 'off';
|
|
18957
19003
|
}
|
|
18958
19004
|
return getToggleState(ids.map(function(id) {
|
|
18959
19005
|
return internal.svg.labelHasHalo(table && table.getRecordAt(id));
|
|
@@ -19013,7 +19059,7 @@
|
|
|
19013
19059
|
var ids = getTargetIds();
|
|
19014
19060
|
var table = getActiveTable();
|
|
19015
19061
|
if (ids.length === 0) {
|
|
19016
|
-
return internal.svg.labelHasCallout(
|
|
19062
|
+
return internal.svg.labelHasCallout(editingNewLabels() ? getNewLabelStyle(gui) : null) ? 'on' : 'off';
|
|
19017
19063
|
}
|
|
19018
19064
|
return getToggleState(ids.map(function(id) {
|
|
19019
19065
|
return internal.svg.labelHasCallout(table && table.getRecordAt(id));
|
|
@@ -19155,7 +19201,7 @@
|
|
|
19155
19201
|
function updateCalloutEndSizeControl(off) {
|
|
19156
19202
|
var ids = getTargetIds().length > 0 ? getCalloutEndTargetIds() : [];
|
|
19157
19203
|
var noMarker = getTargetIds().length > 0 ? ids.length === 0 :
|
|
19158
|
-
getCalloutEnd(
|
|
19204
|
+
getCalloutEnd(editingNewLabels() ? getNewLabelStyle(gui) : null) == 'none';
|
|
19159
19205
|
var shown = off || noMarker ? {value: '', mixed: false} : getCalloutEndSizeShown(ids);
|
|
19160
19206
|
calloutEndSizeInput.setValue(shown.value);
|
|
19161
19207
|
calloutEndSizeInput.setPlaceholder(shown.mixed ? MIXED_TEXT : '');
|
|
@@ -19194,7 +19240,7 @@
|
|
|
19194
19240
|
var vals;
|
|
19195
19241
|
if (!controlsEnabled()) return {value: '', mixed: false};
|
|
19196
19242
|
if (ids.length === 0) {
|
|
19197
|
-
return {value: fn(
|
|
19243
|
+
return {value: fn(editingNewLabels() ? getNewLabelStyle(gui) : null), mixed: false};
|
|
19198
19244
|
}
|
|
19199
19245
|
vals = ids.map(function(id) {
|
|
19200
19246
|
return fn(table && table.getRecordAt(id));
|
|
@@ -19270,7 +19316,7 @@
|
|
|
19270
19316
|
var records = getActiveTable() && getActiveTable().getRecords();
|
|
19271
19317
|
var value, val;
|
|
19272
19318
|
if (ids.length === 0) {
|
|
19273
|
-
return resolveAlignment(
|
|
19319
|
+
return resolveAlignment(editingNewLabels() ? getNewLabelStyle(gui) : null);
|
|
19274
19320
|
}
|
|
19275
19321
|
for (var i = 0; i < ids.length; i++) {
|
|
19276
19322
|
val = resolveAlignment(records && records[ids[i]]);
|
|
@@ -19290,7 +19336,7 @@
|
|
|
19290
19336
|
// label will be created with, falling back to the same default an existing
|
|
19291
19337
|
// label would show.
|
|
19292
19338
|
function getNewLabelValue(field, opts) {
|
|
19293
|
-
var style =
|
|
19339
|
+
var style = editingNewLabels() ? getNewLabelStyle(gui) : null;
|
|
19294
19340
|
var val = style ? style[field] : '';
|
|
19295
19341
|
if (val || val === 0) return val;
|
|
19296
19342
|
return opts && opts.useDefault ? opts.defaultValue : '';
|
|
@@ -19381,7 +19427,7 @@
|
|
|
19381
19427
|
var records = getActiveTable() && getActiveTable().getRecords();
|
|
19382
19428
|
var ids = getTargetIds();
|
|
19383
19429
|
if (ids.length === 0) {
|
|
19384
|
-
return !!(
|
|
19430
|
+
return !!(editingNewLabels() && getNewLabelStyle(gui)[textAnchorField]);
|
|
19385
19431
|
}
|
|
19386
19432
|
return ids.some(function(id) {
|
|
19387
19433
|
var rec = records && records[id];
|
|
@@ -19921,13 +19967,13 @@
|
|
|
19921
19967
|
// no fill gets a clear one, so that the pattern is drawn over whatever is
|
|
19922
19968
|
// underneath.
|
|
19923
19969
|
function getPatternBackground(fill) {
|
|
19924
|
-
return isBlank$
|
|
19970
|
+
return isBlank$2(fill) ? 'none' : String(fill).trim();
|
|
19925
19971
|
}
|
|
19926
19972
|
|
|
19927
19973
|
// Whether the settings describe a pattern the renderer will draw. A hatch with
|
|
19928
19974
|
// no gap would be a solid fill, and the parser refuses a stripe of no width.
|
|
19929
19975
|
function isValidPatternControls(o) {
|
|
19930
|
-
if (!o || patternTypes.indexOf(o.type) == -1 || isBlank$
|
|
19976
|
+
if (!o || patternTypes.indexOf(o.type) == -1 || isBlank$2(o.color)) return false;
|
|
19931
19977
|
if (!(o.size > 0) || !isFinite(o.angle)) return false;
|
|
19932
19978
|
return o.type == 'hatches' ? o.gap > 0 : o.gap >= 0;
|
|
19933
19979
|
}
|
|
@@ -20021,7 +20067,7 @@
|
|
|
20021
20067
|
return str == 'transparent' ? 'none' : str;
|
|
20022
20068
|
}
|
|
20023
20069
|
|
|
20024
|
-
function isBlank$
|
|
20070
|
+
function isBlank$2(val) {
|
|
20025
20071
|
return val === undefined || val === null || String(val).trim() === '';
|
|
20026
20072
|
}
|
|
20027
20073
|
|
|
@@ -20038,7 +20084,7 @@
|
|
|
20038
20084
|
// opts.revert() put the panel back as the data has it
|
|
20039
20085
|
// opts.releaseFocus()
|
|
20040
20086
|
function PatternFillControl(parent, opts) {
|
|
20041
|
-
var section = makePanelSection(parent, '
|
|
20087
|
+
var section = makePanelSection(parent, 'Patterns');
|
|
20042
20088
|
var shown = {type: 'none'};
|
|
20043
20089
|
// What switching the pattern on applies when the features have none: the
|
|
20044
20090
|
// last pattern the section showed, so that off and on again is a round trip.
|
|
@@ -20083,7 +20129,7 @@
|
|
|
20083
20129
|
var code = rec && rec['fill-pattern'];
|
|
20084
20130
|
var edit = {id: id, styles: styles};
|
|
20085
20131
|
var refilled;
|
|
20086
|
-
if (refill && !isBlank(code)) {
|
|
20132
|
+
if (refill && !isBlank$1(code)) {
|
|
20087
20133
|
refilled = refillPattern(internal.parsePattern(code), rec.fill, fill);
|
|
20088
20134
|
if (refilled) edit.styles = styles.concat([['fill-pattern', refilled]]);
|
|
20089
20135
|
}
|
|
@@ -20100,7 +20146,7 @@
|
|
|
20100
20146
|
ids.forEach(function(id) {
|
|
20101
20147
|
var rec = records[id];
|
|
20102
20148
|
var code = rec && rec['fill-pattern'];
|
|
20103
|
-
var o = isBlank(code) ? null : getPatternControls(internal.parsePattern(code), rec.fill);
|
|
20149
|
+
var o = isBlank$1(code) ? null : getPatternControls(internal.parsePattern(code), rec.fill);
|
|
20104
20150
|
if (o) edits.push({id: id, styles: [['fill-pattern', formatFillPatternExpression(o)]]});
|
|
20105
20151
|
});
|
|
20106
20152
|
return edits;
|
|
@@ -20130,7 +20176,12 @@
|
|
|
20130
20176
|
label: 'Color',
|
|
20131
20177
|
noOpacity: true,
|
|
20132
20178
|
onColor: function(color) {
|
|
20133
|
-
|
|
20179
|
+
// A pattern has to have a colour, so an emptied field is its default.
|
|
20180
|
+
if (!color) {
|
|
20181
|
+
color = defaultPatternColor;
|
|
20182
|
+
colorControl.showColor(color);
|
|
20183
|
+
}
|
|
20184
|
+
applyChange({color: color});
|
|
20134
20185
|
},
|
|
20135
20186
|
revert: opts.revert
|
|
20136
20187
|
});
|
|
@@ -20258,7 +20309,7 @@
|
|
|
20258
20309
|
|
|
20259
20310
|
function hasPattern(id) {
|
|
20260
20311
|
var rec = opts.getRecords()[id];
|
|
20261
|
-
return !isBlank(rec && rec['fill-pattern']);
|
|
20312
|
+
return !isBlank$1(rec && rec['fill-pattern']);
|
|
20262
20313
|
}
|
|
20263
20314
|
|
|
20264
20315
|
function applyChange(change) {
|
|
@@ -20330,7 +20381,7 @@
|
|
|
20330
20381
|
|
|
20331
20382
|
function readPatternState(rec) {
|
|
20332
20383
|
var code = rec && rec['fill-pattern'];
|
|
20333
|
-
if (isBlank(code)) return {type: 'none'};
|
|
20384
|
+
if (isBlank$1(code)) return {type: 'none'};
|
|
20334
20385
|
return getPatternControls(internal.parsePattern(code), rec.fill) ||
|
|
20335
20386
|
{type: 'custom', code: String(code)};
|
|
20336
20387
|
}
|
|
@@ -20346,12 +20397,292 @@
|
|
|
20346
20397
|
return undefined;
|
|
20347
20398
|
}
|
|
20348
20399
|
|
|
20400
|
+
function isBlank$1(val) {
|
|
20401
|
+
return val === undefined || val === null || String(val).trim() === '';
|
|
20402
|
+
}
|
|
20403
|
+
|
|
20404
|
+
// The polygon panel's "Effects" section: an outer and an inner glow, each a
|
|
20405
|
+
// color, an opacity and a width. See svg-glow.mjs for how they are drawn.
|
|
20406
|
+
//
|
|
20407
|
+
// A glow is there when its color is set, so each glow is a color row with no
|
|
20408
|
+
// switch of its own: picking a color adds the glow, and emptying the color
|
|
20409
|
+
// field removes it. The section's switch opens the section, and switching it
|
|
20410
|
+
// off removes both glows and their settings.
|
|
20411
|
+
//
|
|
20412
|
+
// A width or opacity typed while no feature has the glow is kept, and goes
|
|
20413
|
+
// with the color when one is picked.
|
|
20414
|
+
//
|
|
20415
|
+
// opts.getRecords() the target layer's records
|
|
20416
|
+
// opts.getTargetIds() the features being styled
|
|
20417
|
+
// opts.applyEdits(edits, title) run per-feature edits: [{id, styles}]
|
|
20418
|
+
// opts.revert() put the panel back as the data has it
|
|
20419
|
+
// opts.releaseFocus()
|
|
20420
|
+
|
|
20421
|
+
var glowTypes = ['outer', 'inner'];
|
|
20422
|
+
var glowTitles = {outer: 'Outer glow', inner: 'Inner glow'};
|
|
20423
|
+
|
|
20424
|
+
function GlowEffectsControl(parent, opts) {
|
|
20425
|
+
var section = makePanelSection(parent, 'Effects');
|
|
20426
|
+
var rows = {};
|
|
20427
|
+
// Switched on, with no glow to show yet.
|
|
20428
|
+
var opened = false;
|
|
20429
|
+
var pending = {outer: {}, inner: {}};
|
|
20430
|
+
var toggle = makePanelToggle(section.findChild('.label-style-section-title'), {
|
|
20431
|
+
title: 'Add glow effects',
|
|
20432
|
+
className: 'layer-effects-toggle',
|
|
20433
|
+
onChange: function(on) {
|
|
20434
|
+
var edits;
|
|
20435
|
+
opened = on;
|
|
20436
|
+
pending = {outer: {}, inner: {}};
|
|
20437
|
+
edits = on ? [] : getEffectsOffEdits(opts.getRecords(), opts.getTargetIds());
|
|
20438
|
+
if (edits.length > 0) {
|
|
20439
|
+
opts.applyEdits(edits, 'Remove glow effects');
|
|
20440
|
+
} else {
|
|
20441
|
+
update();
|
|
20442
|
+
}
|
|
20443
|
+
}
|
|
20444
|
+
});
|
|
20445
|
+
glowTypes.forEach(function(type) {
|
|
20446
|
+
rows[type] = addGlowRow(type);
|
|
20447
|
+
});
|
|
20448
|
+
|
|
20449
|
+
this.section = section;
|
|
20450
|
+
this.update = update;
|
|
20451
|
+
this.hidePickers = hidePickers;
|
|
20452
|
+
|
|
20453
|
+
this.reset = function() {
|
|
20454
|
+
opened = false;
|
|
20455
|
+
pending = {outer: {}, inner: {}};
|
|
20456
|
+
hidePickers();
|
|
20457
|
+
};
|
|
20458
|
+
|
|
20459
|
+
// The glows every target has, for a saved style.
|
|
20460
|
+
this.addStyleValues = function(style) {
|
|
20461
|
+
var records = opts.getRecords();
|
|
20462
|
+
var ids = opts.getTargetIds();
|
|
20463
|
+
glowTypes.forEach(function(type) {
|
|
20464
|
+
if (getGlowState(records, ids, type) != 'on') return;
|
|
20465
|
+
['color', 'width', 'opacity'].forEach(function(name) {
|
|
20466
|
+
var field = getGlowField(type, name);
|
|
20467
|
+
var val = getCommonValue(records, ids, field);
|
|
20468
|
+
if (val || val === 0) style[field] = val;
|
|
20469
|
+
});
|
|
20470
|
+
});
|
|
20471
|
+
};
|
|
20472
|
+
|
|
20473
|
+
function hidePickers() {
|
|
20474
|
+
rows.outer.picker.hide();
|
|
20475
|
+
rows.inner.picker.hide();
|
|
20476
|
+
}
|
|
20477
|
+
|
|
20478
|
+
function addGlowRow(type) {
|
|
20479
|
+
var o = makeColorRow(section, {
|
|
20480
|
+
label: glowTitles[type],
|
|
20481
|
+
onColor: function(color) {
|
|
20482
|
+
applyGlowColor(type, color);
|
|
20483
|
+
},
|
|
20484
|
+
onOpacity: function(value) {
|
|
20485
|
+
applyGlowValue(type, 'opacity', value);
|
|
20486
|
+
},
|
|
20487
|
+
revert: opts.revert
|
|
20488
|
+
});
|
|
20489
|
+
o.row.addClass('layer-glow-row layer-' + type + '-glow-row');
|
|
20490
|
+
El('span').appendTo(o.aside).text('Width');
|
|
20491
|
+
o.width = new SizeField(o.aside, {
|
|
20492
|
+
min: 0.5,
|
|
20493
|
+
max: 200,
|
|
20494
|
+
step: 1,
|
|
20495
|
+
bigStep: 5,
|
|
20496
|
+
decimals: 1,
|
|
20497
|
+
title: 'How far the glow reaches from the edge, in px',
|
|
20498
|
+
onSet: function(value) {
|
|
20499
|
+
applyGlowValue(type, 'width', value);
|
|
20500
|
+
},
|
|
20501
|
+
onStep: function(delta) {
|
|
20502
|
+
var shown = Number(o.width.getValue());
|
|
20503
|
+
if (!(shown > 0)) shown = internal.svg.DEFAULT_GLOW_WIDTH;
|
|
20504
|
+
applyGlowValue(type, 'width', Math.max(0.5, Math.round(shown + delta)));
|
|
20505
|
+
},
|
|
20506
|
+
onDone: opts.releaseFocus
|
|
20507
|
+
});
|
|
20508
|
+
return o;
|
|
20509
|
+
}
|
|
20510
|
+
|
|
20511
|
+
function applyGlowColor(type, color) {
|
|
20512
|
+
var edits = getGlowColorEdits(opts.getRecords(), opts.getTargetIds(), type, color, pending[type]);
|
|
20513
|
+
if (color) pending[type] = {};
|
|
20514
|
+
if (edits.length > 0) {
|
|
20515
|
+
opts.applyEdits(edits, color ? glowTitles[type] : 'Remove ' + glowTitles[type].toLowerCase());
|
|
20516
|
+
} else {
|
|
20517
|
+
update();
|
|
20518
|
+
}
|
|
20519
|
+
}
|
|
20520
|
+
|
|
20521
|
+
// A width or opacity goes to the features that have the glow, as the label
|
|
20522
|
+
// panel's halo settings go only to the labels that have a halo.
|
|
20523
|
+
function applyGlowValue(type, name, value) {
|
|
20524
|
+
var records = opts.getRecords();
|
|
20525
|
+
var ids = opts.getTargetIds().filter(function(id) {
|
|
20526
|
+
return hasGlow(records[id], type);
|
|
20527
|
+
});
|
|
20528
|
+
if (ids.length === 0) {
|
|
20529
|
+
pending[type][name] = value;
|
|
20530
|
+
update();
|
|
20531
|
+
return;
|
|
20532
|
+
}
|
|
20533
|
+
opts.applyEdits(ids.map(function(id) {
|
|
20534
|
+
return {id: id, styles: [[getGlowField(type, name), formatGlowValue(name, value)]]};
|
|
20535
|
+
}));
|
|
20536
|
+
}
|
|
20537
|
+
|
|
20538
|
+
function update() {
|
|
20539
|
+
var records = opts.getRecords();
|
|
20540
|
+
var ids = opts.getTargetIds();
|
|
20541
|
+
var state = getEffectsState(records, ids);
|
|
20542
|
+
if (state != 'off') opened = true;
|
|
20543
|
+
toggle.setState(state == 'off' && opened ? 'on' : state);
|
|
20544
|
+
section.classed('collapsed', state == 'off' && !opened);
|
|
20545
|
+
if (state == 'off' && !opened) hidePickers();
|
|
20546
|
+
glowTypes.forEach(function(type) {
|
|
20547
|
+
updateGlowRow(type, records, ids);
|
|
20548
|
+
});
|
|
20549
|
+
}
|
|
20550
|
+
|
|
20551
|
+
function updateGlowRow(type, records, ids) {
|
|
20552
|
+
var o = rows[type];
|
|
20553
|
+
var shown = getShownGlow(records, ids, type, pending[type]);
|
|
20554
|
+
o.showColor(shown.color);
|
|
20555
|
+
o.input.attr('placeholder', shown.mixed ? 'mixed' : '');
|
|
20556
|
+
o.chit.classed('mixed', shown.mixed);
|
|
20557
|
+
o.opacity.node().value = shown.opacity === '' ? '' : formatOpacityPct(shown.opacity);
|
|
20558
|
+
o.width.setValue(shown.width === '' ? '' : String(shown.width));
|
|
20559
|
+
o.width.setPlaceholder(shown.width === '' ? 'mixed' : '');
|
|
20560
|
+
}
|
|
20561
|
+
}
|
|
20562
|
+
|
|
20563
|
+
function getGlowField(type, name) {
|
|
20564
|
+
return type + '-glow-' + name;
|
|
20565
|
+
}
|
|
20566
|
+
|
|
20567
|
+
// In the panel, a feature has a glow when the glow's color is set.
|
|
20568
|
+
function hasGlow(rec, type) {
|
|
20569
|
+
return !!rec && !isBlank(rec[getGlowField(type, 'color')]);
|
|
20570
|
+
}
|
|
20571
|
+
|
|
20572
|
+
// 'on', 'off' or 'mixed', over the features @ids
|
|
20573
|
+
function getGlowState(records, ids, type) {
|
|
20574
|
+
var n = ids.filter(function(id) { return hasGlow(records[id], type); }).length;
|
|
20575
|
+
return n === 0 ? 'off' : n < ids.length ? 'mixed' : 'on';
|
|
20576
|
+
}
|
|
20577
|
+
|
|
20578
|
+
function getEffectsState(records, ids) {
|
|
20579
|
+
var n = ids.filter(function(id) {
|
|
20580
|
+
return hasGlow(records[id], 'outer') || hasGlow(records[id], 'inner');
|
|
20581
|
+
}).length;
|
|
20582
|
+
return n === 0 ? 'off' : n < ids.length ? 'mixed' : 'on';
|
|
20583
|
+
}
|
|
20584
|
+
|
|
20585
|
+
// Per-feature edits for a color typed or picked for one glow. A color adds the
|
|
20586
|
+
// glow to every feature, with the @pending width and opacity where a feature
|
|
20587
|
+
// has none of its own; an empty color removes the glow's color.
|
|
20588
|
+
function getGlowColorEdits(records, ids, type, color, pending) {
|
|
20589
|
+
var colorField = getGlowField(type, 'color');
|
|
20590
|
+
var edits = [];
|
|
20591
|
+
ids.forEach(function(id) {
|
|
20592
|
+
var rec = records[id] || {};
|
|
20593
|
+
var styles = [];
|
|
20594
|
+
if (!color) {
|
|
20595
|
+
if (!isBlank(rec[colorField])) styles.push([colorField, '']);
|
|
20596
|
+
} else {
|
|
20597
|
+
if (String(rec[colorField]).trim() != color) styles.push([colorField, color]);
|
|
20598
|
+
['width', 'opacity'].forEach(function(name) {
|
|
20599
|
+
var field = getGlowField(type, name);
|
|
20600
|
+
if (pending && name in pending && isBlank(rec[field])) {
|
|
20601
|
+
var val = formatGlowValue(name, pending[name]);
|
|
20602
|
+
if (val !== '') styles.push([field, val]);
|
|
20603
|
+
}
|
|
20604
|
+
});
|
|
20605
|
+
}
|
|
20606
|
+
if (styles.length > 0) edits.push({id: id, styles: styles});
|
|
20607
|
+
});
|
|
20608
|
+
return edits;
|
|
20609
|
+
}
|
|
20610
|
+
|
|
20611
|
+
// Switching Effects off removes every glow setting, which mean nothing
|
|
20612
|
+
// without a color.
|
|
20613
|
+
function getEffectsOffEdits(records, ids) {
|
|
20614
|
+
var edits = [];
|
|
20615
|
+
ids.forEach(function(id) {
|
|
20616
|
+
var rec = records[id] || {};
|
|
20617
|
+
var styles = internal.svg.glowFields.filter(function(field) {
|
|
20618
|
+
return !isBlank(rec[field]);
|
|
20619
|
+
}).map(function(field) {
|
|
20620
|
+
return [field, ''];
|
|
20621
|
+
});
|
|
20622
|
+
if (styles.length > 0) edits.push({id: id, styles: styles});
|
|
20623
|
+
});
|
|
20624
|
+
return edits;
|
|
20625
|
+
}
|
|
20626
|
+
|
|
20627
|
+
// What a glow's row shows. The color is blank where no feature has the glow,
|
|
20628
|
+
// and blank and mixed where only some do or their colors differ. The width
|
|
20629
|
+
// and opacity are those the glowing features agree on, '' where they differ;
|
|
20630
|
+
// with no glowing features, they are the @pending ones, the default width and
|
|
20631
|
+
// no opacity.
|
|
20632
|
+
function getShownGlow(records, ids, type, pending) {
|
|
20633
|
+
var glowIds = ids.filter(function(id) { return hasGlow(records[id], type); });
|
|
20634
|
+
var defaultWidth = internal.svg.DEFAULT_GLOW_WIDTH;
|
|
20635
|
+
var color, mixed;
|
|
20636
|
+
pending = pending || {};
|
|
20637
|
+
if (glowIds.length === 0) {
|
|
20638
|
+
return {
|
|
20639
|
+
color: '',
|
|
20640
|
+
mixed: false,
|
|
20641
|
+
width: 'width' in pending ? pending.width : defaultWidth,
|
|
20642
|
+
opacity: 'opacity' in pending ? pending.opacity : ''
|
|
20643
|
+
};
|
|
20644
|
+
}
|
|
20645
|
+
color = getCommonValue(records, glowIds, getGlowField(type, 'color'), undefined, trim);
|
|
20646
|
+
mixed = glowIds.length < ids.length || color === '';
|
|
20647
|
+
return {
|
|
20648
|
+
color: mixed ? '' : color,
|
|
20649
|
+
mixed: mixed,
|
|
20650
|
+
width: getCommonValue(records, glowIds, getGlowField(type, 'width'), defaultWidth, Number),
|
|
20651
|
+
opacity: getCommonValue(records, glowIds, getGlowField(type, 'opacity'), 1, Number)
|
|
20652
|
+
};
|
|
20653
|
+
}
|
|
20654
|
+
|
|
20655
|
+
// Full opacity is the default, and stored as no opacity at all.
|
|
20656
|
+
function formatGlowValue(name, value) {
|
|
20657
|
+
return name == 'opacity' && value >= 1 ? '' : value;
|
|
20658
|
+
}
|
|
20659
|
+
|
|
20660
|
+
// The value of @field that the features @ids share, or '' if they differ.
|
|
20661
|
+
function getCommonValue(records, ids, field, defaultValue, convert) {
|
|
20662
|
+
var value, val;
|
|
20663
|
+
for (var i=0; i<ids.length; i++) {
|
|
20664
|
+
val = records[ids[i]] && records[ids[i]][field];
|
|
20665
|
+
if (isBlank(val)) val = defaultValue === undefined ? '' : defaultValue;
|
|
20666
|
+
if (convert && val !== '' && val !== null) val = convert(val);
|
|
20667
|
+
if (i === 0) {
|
|
20668
|
+
value = val;
|
|
20669
|
+
} else if (val !== value) {
|
|
20670
|
+
return '';
|
|
20671
|
+
}
|
|
20672
|
+
}
|
|
20673
|
+
return ids.length > 0 ? value : '';
|
|
20674
|
+
}
|
|
20675
|
+
|
|
20676
|
+
function trim(val) {
|
|
20677
|
+
return String(val).trim();
|
|
20678
|
+
}
|
|
20679
|
+
|
|
20349
20680
|
function isBlank(val) {
|
|
20350
20681
|
return val === undefined || val === null || String(val).trim() === '';
|
|
20351
20682
|
}
|
|
20352
20683
|
|
|
20353
20684
|
var savedStylesKey = 'layer_style_presets';
|
|
20354
|
-
var styleFields = ['stroke', 'stroke-width', 'stroke-opacity', 'stroke-dasharray', 'line-start', 'line-end', 'line-end-size', 'fill', 'fill-opacity', 'fill-pattern'];
|
|
20685
|
+
var styleFields = ['stroke', 'stroke-width', 'stroke-opacity', 'stroke-dasharray', 'line-start', 'line-end', 'line-end-size', 'fill', 'fill-opacity', 'fill-pattern'].concat(internal.svg.glowFields);
|
|
20355
20686
|
var arrowShapes = [{
|
|
20356
20687
|
name: 'arrow',
|
|
20357
20688
|
title: 'solid arrowheads'
|
|
@@ -20386,7 +20717,7 @@
|
|
|
20386
20717
|
function LayerStyleTool(gui) {
|
|
20387
20718
|
var parent = gui.container.findChild('.mshp-main-map');
|
|
20388
20719
|
var panel = El('div').addClass('label-style-panel layer-style-panel rollover').appendTo(parent).hide();
|
|
20389
|
-
var title, editingStatus, clearLink, strokeControl, fillControl, strokeWidthField, dashControl, arrowControl, randomFillBtn, presetControl, patternControl, hit;
|
|
20720
|
+
var title, editingStatus, clearLink, strokeControl, fillControl, strokeWidthField, dashControl, arrowControl, randomFillBtn, presetControl, patternControl, glowControl, hit;
|
|
20390
20721
|
var targetLayer = null;
|
|
20391
20722
|
// What the arrowhead switch turns on, for lines that have no heads
|
|
20392
20723
|
var lastArrow = {shape: 'arrow', position: 'end'};
|
|
@@ -20445,6 +20776,7 @@
|
|
|
20445
20776
|
fillControl.picker.hide();
|
|
20446
20777
|
patternControl.hidePicker();
|
|
20447
20778
|
patternControl.reset();
|
|
20779
|
+
glowControl.reset();
|
|
20448
20780
|
targetLayer = null;
|
|
20449
20781
|
}
|
|
20450
20782
|
|
|
@@ -20474,14 +20806,15 @@
|
|
|
20474
20806
|
editingStatus = El('span').addClass('label-editing-status').appendTo(editRow);
|
|
20475
20807
|
clearLink = El('span').addClass('label-editing-clear colored-text').appendTo(editRow).text('deselect').on('click', clearSelection);
|
|
20476
20808
|
|
|
20477
|
-
fillControl = addColorControl(panel, 'Fill', 'fill', '');
|
|
20478
20809
|
strokeControl = addColorControl(panel, 'Stroke', 'stroke', '#000000');
|
|
20479
20810
|
strokeWidthField = addStrokeWidthControl(strokeControl.aside);
|
|
20811
|
+
fillControl = addColorControl(panel, 'Fill', 'fill', '');
|
|
20812
|
+
randomFillBtn = makePanelActionButton(fillControl.aside, 'Random fill', applyRandomFillColors)
|
|
20813
|
+
.addClass('layer-random-fill-btn');
|
|
20480
20814
|
dashControl = addDashArrayControl(panel);
|
|
20481
20815
|
arrowControl = addArrowControl(panel);
|
|
20482
20816
|
|
|
20483
20817
|
var buttonRow = El('div').addClass('label-style-row label-panel-button-row').appendTo(panel);
|
|
20484
|
-
randomFillBtn = makePanelActionButton(buttonRow, 'Random fills', applyRandomFillColors);
|
|
20485
20818
|
makePanelActionButton(buttonRow, 'Clear style', clearLayerStyle);
|
|
20486
20819
|
|
|
20487
20820
|
patternControl = new PatternFillControl(panel, {
|
|
@@ -20494,6 +20827,14 @@
|
|
|
20494
20827
|
releaseFocus: releaseFocus
|
|
20495
20828
|
});
|
|
20496
20829
|
|
|
20830
|
+
glowControl = new GlowEffectsControl(panel, {
|
|
20831
|
+
getRecords: getTargetRecords,
|
|
20832
|
+
getTargetIds: getTargetIds,
|
|
20833
|
+
applyEdits: runStyleEdits,
|
|
20834
|
+
revert: updateControls,
|
|
20835
|
+
releaseFocus: releaseFocus
|
|
20836
|
+
});
|
|
20837
|
+
|
|
20497
20838
|
presetControl = new StylePresetControl(panel, {
|
|
20498
20839
|
storageKey: savedStylesKey,
|
|
20499
20840
|
type: getStyleType,
|
|
@@ -20515,10 +20856,9 @@
|
|
|
20515
20856
|
var control = makeColorRow(parent, {
|
|
20516
20857
|
label: label,
|
|
20517
20858
|
onColor: function(color) {
|
|
20518
|
-
//
|
|
20519
|
-
//
|
|
20520
|
-
|
|
20521
|
-
if (color) applyColorControlStyle(control, color);
|
|
20859
|
+
// An emptied field unsets the colour: -style reads an empty value as
|
|
20860
|
+
// "remove this".
|
|
20861
|
+
applyColorControlStyle(control, color);
|
|
20522
20862
|
},
|
|
20523
20863
|
onOpacity: function(value) {
|
|
20524
20864
|
applyLayerStyle(field + '-opacity', value);
|
|
@@ -20786,6 +21126,8 @@
|
|
|
20786
21126
|
randomFillBtn.classed('hidden', geom != 'polygon');
|
|
20787
21127
|
patternControl.section.classed('hidden', geom != 'polygon');
|
|
20788
21128
|
if (geom == 'polygon') patternControl.update();
|
|
21129
|
+
glowControl.section.classed('hidden', geom != 'polygon');
|
|
21130
|
+
if (geom == 'polygon') glowControl.update();
|
|
20789
21131
|
presetControl.render();
|
|
20790
21132
|
updateSavedStyleControls();
|
|
20791
21133
|
}
|
|
@@ -20846,7 +21188,7 @@
|
|
|
20846
21188
|
|
|
20847
21189
|
function applyColorControlStyle(control, color) {
|
|
20848
21190
|
var styles = [[control.field, color]];
|
|
20849
|
-
if (control.field == 'stroke' && strokeWidthIsUnsetForTargets()) {
|
|
21191
|
+
if (color && control.field == 'stroke' && strokeWidthIsUnsetForTargets()) {
|
|
20850
21192
|
styles.push(['stroke-width', 1]);
|
|
20851
21193
|
}
|
|
20852
21194
|
if (control.field == 'fill') {
|
|
@@ -20968,6 +21310,7 @@
|
|
|
20968
21310
|
addStyleValue(style, 'fill', getControlValue(fillControl.input));
|
|
20969
21311
|
addStyleValue(style, 'fill-opacity', parseOpacityValue(fillControl.opacity.node().value));
|
|
20970
21312
|
addStyleValue(style, 'fill-pattern', patternControl.getCode(getControlValue(fillControl.input)));
|
|
21313
|
+
glowControl.addStyleValues(style);
|
|
20971
21314
|
}
|
|
20972
21315
|
return style;
|
|
20973
21316
|
}
|
|
@@ -21079,9 +21422,13 @@
|
|
|
21079
21422
|
updateControls();
|
|
21080
21423
|
}
|
|
21081
21424
|
|
|
21425
|
+
// An empty layer has nothing for the controls to act on, so they are shown
|
|
21426
|
+
// disabled rather than left to do nothing.
|
|
21082
21427
|
function updateEditingStatus(count) {
|
|
21083
|
-
|
|
21428
|
+
var total = targetLayer ? internal.getFeatureCount(targetLayer) : 0;
|
|
21429
|
+
editingStatus.text(formatEditingStatus({selected: count, total: total}));
|
|
21084
21430
|
clearLink.classed('hidden', count === 0);
|
|
21431
|
+
panel.classed('no-targets', total === 0);
|
|
21085
21432
|
}
|
|
21086
21433
|
|
|
21087
21434
|
function getActiveLayer() {
|
|
@@ -21308,8 +21655,17 @@
|
|
|
21308
21655
|
var control = makeColorRow(circlesSection, {
|
|
21309
21656
|
label: label,
|
|
21310
21657
|
// Every circle property is applied together, from what the controls are
|
|
21311
|
-
// showing: the fields are one style, not five.
|
|
21312
|
-
|
|
21658
|
+
// showing: the fields are one style, not five. An emptied colour field
|
|
21659
|
+
// unsets the colour, which applying the fields would leave alone.
|
|
21660
|
+
onColor: function(color) {
|
|
21661
|
+
if (color) {
|
|
21662
|
+
applyCircleStyles();
|
|
21663
|
+
} else if (!styleFieldIsUnset(field)) {
|
|
21664
|
+
runStyleCommand([field + '=' + quoteCommandValue('')], 'Remove circle ' + field);
|
|
21665
|
+
} else {
|
|
21666
|
+
updateControls();
|
|
21667
|
+
}
|
|
21668
|
+
},
|
|
21313
21669
|
onOpacity: applyCircleStyles,
|
|
21314
21670
|
revert: updateControls
|
|
21315
21671
|
});
|
|
@@ -21659,9 +22015,13 @@
|
|
|
21659
22015
|
updateControls();
|
|
21660
22016
|
}
|
|
21661
22017
|
|
|
22018
|
+
// As in the line and polygon panels: an empty layer's controls are shown
|
|
22019
|
+
// disabled rather than left to do nothing.
|
|
21662
22020
|
function updateEditingStatus(count) {
|
|
21663
|
-
|
|
22021
|
+
var total = targetLayer ? internal.getFeatureCount(targetLayer) : 0;
|
|
22022
|
+
editingStatus.text(formatEditingStatus({selected: count, total: total}));
|
|
21664
22023
|
clearLink.classed('hidden', count === 0);
|
|
22024
|
+
panel.classed('no-targets', total === 0);
|
|
21665
22025
|
}
|
|
21666
22026
|
|
|
21667
22027
|
function setCircleColor(control, color) {
|
|
@@ -29259,6 +29619,7 @@
|
|
|
29259
29619
|
selection.turnOff();
|
|
29260
29620
|
abandonCurve();
|
|
29261
29621
|
armed = null;
|
|
29622
|
+
setLabelToolArmed(gui, null);
|
|
29262
29623
|
updateButtons();
|
|
29263
29624
|
hideInstructions();
|
|
29264
29625
|
drag = null;
|
|
@@ -29324,6 +29685,7 @@
|
|
|
29324
29685
|
if (armed == mode) return;
|
|
29325
29686
|
if (drawingCurve()) abandonCurve();
|
|
29326
29687
|
armed = mode;
|
|
29688
|
+
setLabelToolArmed(gui, mode);
|
|
29327
29689
|
updateButtons();
|
|
29328
29690
|
showInstructions();
|
|
29329
29691
|
}
|
|
@@ -31080,6 +31442,11 @@
|
|
|
31080
31442
|
// rest, because the style object is reused from one feature to the next.
|
|
31081
31443
|
var arrowFields = getLineArrowFields(lyr);
|
|
31082
31444
|
var hasStrokeFields = fields.includes('stroke') || fields.includes('stroke-width');
|
|
31445
|
+
// Glows are drawn from the record rather than set as attributes, like the
|
|
31446
|
+
// arrowheads. An outer glow that the whole layer shares is drawn once for the
|
|
31447
|
+
// layer (see svg-glow.mjs), so the shapes are then given none of their own.
|
|
31448
|
+
var hasGlows = layerHasGlowFields(lyr);
|
|
31449
|
+
var layerOuterGlow = hasGlows ? internal.svg.getLayerOuterGlow(lyr) : null;
|
|
31083
31450
|
|
|
31084
31451
|
var styler = function(style, i) {
|
|
31085
31452
|
var rec = records[i];
|
|
@@ -31089,6 +31456,10 @@
|
|
|
31089
31456
|
style.lineEnd = rec && rec['line-end'];
|
|
31090
31457
|
style.lineEndSize = rec && rec['line-end-size'];
|
|
31091
31458
|
}
|
|
31459
|
+
if (hasGlows) {
|
|
31460
|
+
style.outerGlow = layerOuterGlow ? null : internal.svg.getPolygonGlow(rec, 'outer');
|
|
31461
|
+
style.innerGlow = internal.svg.getPolygonGlow(rec, 'inner');
|
|
31462
|
+
}
|
|
31092
31463
|
for (var j=0; j<fields.length; j++) {
|
|
31093
31464
|
fname = fields[j];
|
|
31094
31465
|
val = rec && rec[fname];
|
|
@@ -31110,6 +31481,12 @@
|
|
|
31110
31481
|
}
|
|
31111
31482
|
};
|
|
31112
31483
|
var style = {styler: styler, type: 'styled'};
|
|
31484
|
+
if (hasGlows) {
|
|
31485
|
+
style.layerOuterGlow = layerOuterGlow;
|
|
31486
|
+
// How far past its shapes the layer draws, which is how far outside the
|
|
31487
|
+
// view a shape can be and still reach into it.
|
|
31488
|
+
style.glowReach = internal.svg.getMaxGlowWidth(records) * internal.svg.GLOW_REACH;
|
|
31489
|
+
}
|
|
31113
31490
|
// A line layer styled with nothing but arrowheads is drawn the way SVG
|
|
31114
31491
|
// export draws it, with the black 1px line the layer's group gives it.
|
|
31115
31492
|
if (arrowFields && !hasStrokeFields) {
|
|
@@ -31131,7 +31508,14 @@
|
|
|
31131
31508
|
return fields.includes('fill') || fields.includes('r'); // support colored squares
|
|
31132
31509
|
}
|
|
31133
31510
|
return utils$1.difference(fields, ['opacity', 'class']).length > 0 ||
|
|
31134
|
-
!!getLineArrowFields(lyr);
|
|
31511
|
+
!!getLineArrowFields(lyr) || layerHasGlowFields(lyr);
|
|
31512
|
+
}
|
|
31513
|
+
|
|
31514
|
+
function layerHasGlowFields(lyr) {
|
|
31515
|
+
if (lyr.geometry_type != 'polygon' || !lyr.data) return false;
|
|
31516
|
+
return lyr.data.getFields().some(function(f) {
|
|
31517
|
+
return internal.svg.glowFields.includes(f);
|
|
31518
|
+
});
|
|
31135
31519
|
}
|
|
31136
31520
|
|
|
31137
31521
|
// The arrowhead fields a line layer has, or null
|
|
@@ -32048,6 +32432,102 @@
|
|
|
32048
32432
|
}
|
|
32049
32433
|
}
|
|
32050
32434
|
|
|
32435
|
+
// Canvas rendering of polygon glows, matching the SVG filters in svg-glow.mjs.
|
|
32436
|
+
//
|
|
32437
|
+
// What glows is drawn first into a scratch canvas: one shape, or a whole layer
|
|
32438
|
+
// for a layer's outer glow. The glows are then made from that image's alpha,
|
|
32439
|
+
// the way a filter works from SourceAlpha:
|
|
32440
|
+
//
|
|
32441
|
+
// outer the image's shadow, with the image itself cut out of it
|
|
32442
|
+
// inner the shadow of the image's inverse, kept only inside the image
|
|
32443
|
+
//
|
|
32444
|
+
// A shadow is only drawn with the image that casts it, so the image is drawn
|
|
32445
|
+
// off the edge of the canvas and its shadow is offset back into place. Drawn
|
|
32446
|
+
// in place and clipped, the image leaks into its own antialiased edge.
|
|
32447
|
+
//
|
|
32448
|
+
// A shadow's shadowBlur is twice the standard deviation of its blur, and a
|
|
32449
|
+
// glow's width is twice the standard deviation of the filter's, so the two
|
|
32450
|
+
// are the same number. Shadows are drawn in device pixels whatever the
|
|
32451
|
+
// transform, and the scratch canvases are never transformed when one is.
|
|
32452
|
+
|
|
32453
|
+
var scratch = {};
|
|
32454
|
+
|
|
32455
|
+
function getScratchCanvas(name, w, h) {
|
|
32456
|
+
var o = scratch[name];
|
|
32457
|
+
if (!o) {
|
|
32458
|
+
o = scratch[name] = {canvas: document.createElement('canvas')};
|
|
32459
|
+
o.ctx = o.canvas.getContext('2d');
|
|
32460
|
+
}
|
|
32461
|
+
// Grown and never shrunk: resizing a canvas reallocates it, and there can
|
|
32462
|
+
// be a resize for every shape drawn.
|
|
32463
|
+
if (o.canvas.width < w || o.canvas.height < h) {
|
|
32464
|
+
o.canvas.width = Math.max(o.canvas.width, w);
|
|
32465
|
+
o.canvas.height = Math.max(o.canvas.height, h);
|
|
32466
|
+
}
|
|
32467
|
+
o.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
32468
|
+
o.ctx.clearRect(0, 0, w, h);
|
|
32469
|
+
return o;
|
|
32470
|
+
}
|
|
32471
|
+
|
|
32472
|
+
// A canvas to draw what glows into, @w by @h device pixels. Separate from the
|
|
32473
|
+
// ones compositeGlows() uses, so a shape can be drawn into it while a layer is
|
|
32474
|
+
// being drawn into another.
|
|
32475
|
+
// @name: 'shape' or 'layer'
|
|
32476
|
+
function getGlowSourceCanvas(name, w, h) {
|
|
32477
|
+
return getScratchCanvas('source-' + name, w, h);
|
|
32478
|
+
}
|
|
32479
|
+
|
|
32480
|
+
// How far past a shape its glows reach, in device pixels.
|
|
32481
|
+
function getGlowMargin(outer, inner, pxScale) {
|
|
32482
|
+
var width = Math.max(outer ? outer.width : 0, inner ? inner.width : 0);
|
|
32483
|
+
return Math.ceil(width * pxScale * internal.svg.GLOW_REACH) + 2;
|
|
32484
|
+
}
|
|
32485
|
+
|
|
32486
|
+
// Draws @src (the top-left @w by @h pixels of a canvas) into @ctx at @x, @y,
|
|
32487
|
+
// with its glows: the outer one underneath and the inner one on top.
|
|
32488
|
+
// @pxScale: device pixels per px of glow width
|
|
32489
|
+
function compositeGlows(ctx, src, x, y, w, h, outer, inner, pxScale) {
|
|
32490
|
+
var glow, inverse;
|
|
32491
|
+
if (outer) {
|
|
32492
|
+
glow = getScratchCanvas('glow', w, h);
|
|
32493
|
+
drawShadow(glow.ctx, src, w, h, outer, pxScale);
|
|
32494
|
+
glow.ctx.globalCompositeOperation = 'destination-out';
|
|
32495
|
+
glow.ctx.drawImage(src, 0, 0, w, h, 0, 0, w, h);
|
|
32496
|
+
glow.ctx.globalCompositeOperation = 'source-over';
|
|
32497
|
+
ctx.drawImage(glow.canvas, 0, 0, w, h, x, y, w, h);
|
|
32498
|
+
}
|
|
32499
|
+
ctx.drawImage(src, 0, 0, w, h, x, y, w, h);
|
|
32500
|
+
if (inner) {
|
|
32501
|
+
inverse = getScratchCanvas('inverse', w, h);
|
|
32502
|
+
inverse.ctx.fillStyle = '#000';
|
|
32503
|
+
inverse.ctx.fillRect(0, 0, w, h);
|
|
32504
|
+
inverse.ctx.globalCompositeOperation = 'destination-out';
|
|
32505
|
+
inverse.ctx.drawImage(src, 0, 0, w, h, 0, 0, w, h);
|
|
32506
|
+
inverse.ctx.globalCompositeOperation = 'source-over';
|
|
32507
|
+
glow = getScratchCanvas('glow', w, h);
|
|
32508
|
+
drawShadow(glow.ctx, inverse.canvas, w, h, inner, pxScale);
|
|
32509
|
+
glow.ctx.globalCompositeOperation = 'destination-in';
|
|
32510
|
+
glow.ctx.drawImage(src, 0, 0, w, h, 0, 0, w, h);
|
|
32511
|
+
glow.ctx.globalCompositeOperation = 'source-over';
|
|
32512
|
+
ctx.drawImage(glow.canvas, 0, 0, w, h, x, y, w, h);
|
|
32513
|
+
}
|
|
32514
|
+
}
|
|
32515
|
+
|
|
32516
|
+
// The shadow of the top-left @w by @h pixels of @img, with no image. The glow's
|
|
32517
|
+
// opacity is globalAlpha rather than part of the shadow's color, which can be
|
|
32518
|
+
// any CSS color and would have to be parsed to take one.
|
|
32519
|
+
function drawShadow(ctx, img, w, h, glow, pxScale) {
|
|
32520
|
+
var offset = w + glow.width * pxScale * 4 + 10;
|
|
32521
|
+
ctx.save();
|
|
32522
|
+
ctx.globalAlpha = glow.opacity;
|
|
32523
|
+
ctx.shadowColor = glow.color;
|
|
32524
|
+
ctx.shadowBlur = glow.width * pxScale;
|
|
32525
|
+
ctx.shadowOffsetX = offset;
|
|
32526
|
+
ctx.shadowOffsetY = 0;
|
|
32527
|
+
ctx.drawImage(img, 0, 0, w, h, -offset, 0, w, h);
|
|
32528
|
+
ctx.restore();
|
|
32529
|
+
}
|
|
32530
|
+
|
|
32051
32531
|
// TODO: consider moving this upstream
|
|
32052
32532
|
function getArcsForRendering(lyr, ext) {
|
|
32053
32533
|
var dataset = lyr.gui.source.dataset;
|
|
@@ -32098,7 +32578,7 @@
|
|
|
32098
32578
|
}
|
|
32099
32579
|
} else {
|
|
32100
32580
|
arcs = getArcsForRendering(lyr, ext);
|
|
32101
|
-
filter = getShapeFilter(arcs, layer.shapes, ext);
|
|
32581
|
+
filter = getShapeFilter(arcs, layer.shapes, ext, style.overlay ? 0 : style.glowReach);
|
|
32102
32582
|
canv.drawStyledPaths(layer.shapes, arcs, style, filter);
|
|
32103
32583
|
if (style.vertices || style.vertex_overlay || style.pending_snip) {
|
|
32104
32584
|
canv.drawVertices(layer.shapes, arcs, style, filter);
|
|
@@ -32136,10 +32616,17 @@
|
|
|
32136
32616
|
// view. The filter takes a shape index and tests the shape's bbox against
|
|
32137
32617
|
// the viewport. At nearly full extent the test is a no-op, so we return
|
|
32138
32618
|
// null to let the caller skip it entirely.
|
|
32139
|
-
|
|
32619
|
+
// reach: how far past its shapes the layer draws, in px (e.g. a glow)
|
|
32620
|
+
function getShapeFilter(arcs, shapes, ext, reach) {
|
|
32140
32621
|
if (ext.scale() < 1.1) return null;
|
|
32141
32622
|
var view = ext.getBounds();
|
|
32142
32623
|
var b = new Bounds$1();
|
|
32624
|
+
var pad;
|
|
32625
|
+
if (reach > 0) {
|
|
32626
|
+
pad = reach * (ext.getSymbolScale() || 1) * ext.getPixelSize();
|
|
32627
|
+
view = new Bounds$1(view.toArray());
|
|
32628
|
+
view.padBounds(pad, pad, pad, pad);
|
|
32629
|
+
}
|
|
32143
32630
|
return function(i) {
|
|
32144
32631
|
var shp = shapes[i];
|
|
32145
32632
|
if (!shp) return false;
|
|
@@ -32303,52 +32790,136 @@
|
|
|
32303
32790
|
var key, item, shp;
|
|
32304
32791
|
var styler = style.styler || null;
|
|
32305
32792
|
var drawStyle = styler ? utils$1.defaults({}, style) : style;
|
|
32306
|
-
|
|
32307
|
-
|
|
32308
|
-
|
|
32309
|
-
|
|
32310
|
-
|
|
32311
|
-
|
|
32312
|
-
|
|
32313
|
-
|
|
32314
|
-
|
|
32315
|
-
|
|
32316
|
-
|
|
32317
|
-
|
|
32318
|
-
|
|
32319
|
-
|
|
32320
|
-
|
|
32321
|
-
|
|
32322
|
-
|
|
32323
|
-
|
|
32324
|
-
|
|
32325
|
-
|
|
32326
|
-
|
|
32327
|
-
|
|
32328
|
-
|
|
32329
|
-
|
|
32330
|
-
|
|
32331
|
-
|
|
32793
|
+
// Hover and selection overlays copy the layer's style; they do not glow.
|
|
32794
|
+
var glows = !style.overlay;
|
|
32795
|
+
var glowScale = getCanvasStrokeScale(GUI.getPixelRatio(), lineScale);
|
|
32796
|
+
var layerGlow = glows && style.layerOuterGlow ?
|
|
32797
|
+
startLayerGlow(style.layerOuterGlow, glowScale) : null;
|
|
32798
|
+
try {
|
|
32799
|
+
drawShapes();
|
|
32800
|
+
} finally {
|
|
32801
|
+
if (layerGlow) endLayerGlow(layerGlow);
|
|
32802
|
+
}
|
|
32803
|
+
if (layerGlow) compositeLayerGlow(layerGlow);
|
|
32804
|
+
|
|
32805
|
+
function drawShapes() {
|
|
32806
|
+
var glowPencil = null;
|
|
32807
|
+
for (var i=0; i<shapes.length; i++) {
|
|
32808
|
+
shp = shapes[i];
|
|
32809
|
+
if (!shp || filter && !filter(i)) continue;
|
|
32810
|
+
if (styler) {
|
|
32811
|
+
styler(drawStyle, i);
|
|
32812
|
+
}
|
|
32813
|
+
if (glows && (drawStyle.outerGlow || drawStyle.innerGlow)) {
|
|
32814
|
+
// Shapes before this one are drawn first, as they are in SVG, where
|
|
32815
|
+
// a shape's outer glow lies over the shapes that precede it.
|
|
32816
|
+
flushBatches();
|
|
32817
|
+
if (!glowPencil) glowPencil = getGlowShapePencil(arcs, startPath, draw, glowScale);
|
|
32818
|
+
glowPencil(shp, drawStyle);
|
|
32819
|
+
continue;
|
|
32820
|
+
}
|
|
32821
|
+
// A line with arrowheads is drawn on its own, trimmed to meet its heads;
|
|
32822
|
+
// see svg-line-arrows.mjs. Only lines styled with line-start or line-end
|
|
32823
|
+
// get here, so the batch below is untouched for everything else.
|
|
32824
|
+
if (drawStyle.lineStart || drawStyle.lineEnd) {
|
|
32825
|
+
if (!arrowPencil) arrowPencil = getArrowLinePencil(arcs, _ext, lineScale);
|
|
32826
|
+
if (arrowPencil(shp, _ctx, drawStyle, startPath, draw)) continue;
|
|
32827
|
+
}
|
|
32828
|
+
if (!drawStyle.batchOverlay && (drawStyle.overlay ||
|
|
32829
|
+
drawStyle.opacity < 1 || drawStyle.fillOpacity < 1 ||
|
|
32830
|
+
drawStyle.strokeOpacity < 1 || drawStyle.fillEffect)) {
|
|
32831
|
+
// don't batch shapes with opacity, in case they overlap
|
|
32832
|
+
drawPaths([shp], startPath, draw, drawStyle);
|
|
32833
|
+
continue;
|
|
32834
|
+
}
|
|
32835
|
+
key = getStyleKey(drawStyle);
|
|
32836
|
+
if (key in styleIndex === false) {
|
|
32837
|
+
styleIndex[key] = {
|
|
32838
|
+
style: utils$1.defaults({}, drawStyle),
|
|
32839
|
+
shapes: []
|
|
32840
|
+
};
|
|
32841
|
+
}
|
|
32842
|
+
item = styleIndex[key];
|
|
32843
|
+
item.shapes.push(shp);
|
|
32844
|
+
if (item.shapes.length >= batchSize) {
|
|
32845
|
+
drawPaths(item.shapes, startPath, draw, item.style);
|
|
32846
|
+
item.shapes = [];
|
|
32847
|
+
}
|
|
32332
32848
|
}
|
|
32333
|
-
|
|
32334
|
-
|
|
32335
|
-
|
|
32849
|
+
flushBatches();
|
|
32850
|
+
}
|
|
32851
|
+
|
|
32852
|
+
function flushBatches() {
|
|
32853
|
+
Object.keys(styleIndex).forEach(function(key) {
|
|
32854
|
+
var item = styleIndex[key];
|
|
32855
|
+
if (item.shapes.length === 0) return;
|
|
32336
32856
|
drawPaths(item.shapes, startPath, draw, item.style);
|
|
32337
32857
|
item.shapes = [];
|
|
32338
|
-
}
|
|
32858
|
+
});
|
|
32339
32859
|
}
|
|
32340
|
-
Object.keys(styleIndex).forEach(function(key) {
|
|
32341
|
-
var item = styleIndex[key];
|
|
32342
|
-
drawPaths(item.shapes, startPath, draw, item.style);
|
|
32343
|
-
});
|
|
32344
32860
|
};
|
|
32345
32861
|
|
|
32346
|
-
function drawPaths(shapes, beginPath, drawShape, style) {
|
|
32347
|
-
|
|
32862
|
+
function drawPaths(shapes, beginPath, drawShape, style, ctx) {
|
|
32863
|
+
ctx = ctx || _ctx;
|
|
32864
|
+
beginPath(ctx, style);
|
|
32348
32865
|
for (var i=0, n=shapes.length; i<n; i++) {
|
|
32349
|
-
drawShape(shapes[i],
|
|
32866
|
+
drawShape(shapes[i], ctx, style);
|
|
32350
32867
|
}
|
|
32351
|
-
endPath(
|
|
32868
|
+
endPath(ctx, style);
|
|
32869
|
+
}
|
|
32870
|
+
|
|
32871
|
+
// A layer's own outer glow is made from the whole layer, so the layer is
|
|
32872
|
+
// drawn into a canvas of its own and then onto this one with its glow. The
|
|
32873
|
+
// canvas is larger than this one by the reach of the glow, so that shapes
|
|
32874
|
+
// just outside the view still cast theirs into it.
|
|
32875
|
+
function startLayerGlow(glow, pxScale) {
|
|
32876
|
+
var margin = getGlowMargin(glow, null, pxScale);
|
|
32877
|
+
var w = _canvas.width + margin * 2;
|
|
32878
|
+
var h = _canvas.height + margin * 2;
|
|
32879
|
+
var src = getGlowSourceCanvas('layer', w, h);
|
|
32880
|
+
var o = {glow: glow, pxScale: pxScale, margin: margin, w: w, h: h,
|
|
32881
|
+
src: src, ctx: _ctx};
|
|
32882
|
+
src.ctx.setTransform(1, 0, 0, 1, margin, margin);
|
|
32883
|
+
_ctx = src.ctx;
|
|
32884
|
+
return o;
|
|
32885
|
+
}
|
|
32886
|
+
|
|
32887
|
+
function endLayerGlow(o) {
|
|
32888
|
+
o.src.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
32889
|
+
_ctx = o.ctx;
|
|
32890
|
+
}
|
|
32891
|
+
|
|
32892
|
+
function compositeLayerGlow(o) {
|
|
32893
|
+
compositeGlows(_ctx, o.src.canvas, -o.margin, -o.margin, o.w, o.h,
|
|
32894
|
+
o.glow, null, o.pxScale);
|
|
32895
|
+
}
|
|
32896
|
+
|
|
32897
|
+
// Draws one shape with its own glows, which are made from the shape alone.
|
|
32898
|
+
// The shape is drawn into a canvas the size of its bounding box and the
|
|
32899
|
+
// reach of its glows, cut down to the part that can be seen.
|
|
32900
|
+
function getGlowShapePencil(arcs, startPath, draw, pxScale) {
|
|
32901
|
+
var t = getScaledTransform(_ext);
|
|
32902
|
+
var b = new Bounds$1();
|
|
32903
|
+
return function(shp, style) {
|
|
32904
|
+
var outer = style.outerGlow, inner = style.innerGlow;
|
|
32905
|
+
var margin = getGlowMargin(outer, inner, pxScale) +
|
|
32906
|
+
Math.ceil((style.strokeWidth > 0 ? style.strokeWidth * pxScale : 0) / 2);
|
|
32907
|
+
var viewMargin = margin * 2;
|
|
32908
|
+
var x0, y0, x1, y1, src;
|
|
32909
|
+
b.empty();
|
|
32910
|
+
arcs.getMultiShapeBounds(shp, b);
|
|
32911
|
+
if (!b.hasBounds()) return;
|
|
32912
|
+
x0 = Math.floor(Math.max(b.xmin * t.mx + t.bx - margin, -viewMargin));
|
|
32913
|
+
x1 = Math.ceil(Math.min(b.xmax * t.mx + t.bx + margin, _canvas.width + viewMargin));
|
|
32914
|
+
y0 = Math.floor(Math.max(b.ymax * t.my + t.by - margin, -viewMargin));
|
|
32915
|
+
y1 = Math.ceil(Math.min(b.ymin * t.my + t.by + margin, _canvas.height + viewMargin));
|
|
32916
|
+
if (x1 <= x0 || y1 <= y0) return;
|
|
32917
|
+
src = getGlowSourceCanvas('shape', x1 - x0, y1 - y0);
|
|
32918
|
+
src.ctx.setTransform(1, 0, 0, 1, -x0, -y0);
|
|
32919
|
+
drawPaths([shp], startPath, draw, style, src.ctx);
|
|
32920
|
+
src.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
32921
|
+
compositeGlows(_ctx, src.canvas, x0, y0, x1 - x0, y1 - y0, outer, inner, pxScale);
|
|
32922
|
+
};
|
|
32352
32923
|
}
|
|
32353
32924
|
|
|
32354
32925
|
_self.drawSquareDots = function(shapes, style) {
|
|
@@ -36034,8 +36605,9 @@
|
|
|
36034
36605
|
var appearance = makePanelSection(form, 'Appearance');
|
|
36035
36606
|
backgroundControl = makeColorRow(appearance, {
|
|
36036
36607
|
label: 'Background',
|
|
36608
|
+
// An emptied colour field unsets the colour.
|
|
36037
36609
|
onColor: function(color) {
|
|
36038
|
-
|
|
36610
|
+
applyFrameStyle([['fill', color]]);
|
|
36039
36611
|
},
|
|
36040
36612
|
onOpacity: function(value) {
|
|
36041
36613
|
applyFrameStyle([['fill-opacity', value]]);
|
|
@@ -36045,9 +36617,8 @@
|
|
|
36045
36617
|
neatlineControl = makeColorRow(appearance, {
|
|
36046
36618
|
label: 'Neatline',
|
|
36047
36619
|
onColor: function(color) {
|
|
36048
|
-
if (!color) return;
|
|
36049
36620
|
var styles = [['stroke', color]];
|
|
36050
|
-
if (!getStyleValue('stroke-width')) styles.push(['stroke-width', 1]);
|
|
36621
|
+
if (color && !getStyleValue('stroke-width')) styles.push(['stroke-width', 1]);
|
|
36051
36622
|
applyFrameStyle(styles);
|
|
36052
36623
|
},
|
|
36053
36624
|
onOpacity: function(value) {
|
|
@@ -36427,7 +36998,10 @@
|
|
|
36427
36998
|
colorInput = El('input').attr('type', 'text').attr('aria-label', 'Scale bar color')
|
|
36428
36999
|
.on('change', function() {
|
|
36429
37000
|
var color = colorInput.node().value.trim();
|
|
36430
|
-
|
|
37001
|
+
var d = getScalebarRecord();
|
|
37002
|
+
// An emptied field unsets the colour, so the scalebar is drawn in
|
|
37003
|
+
// the default one.
|
|
37004
|
+
if (!color && !(d && d.color)) {
|
|
36431
37005
|
updateControls();
|
|
36432
37006
|
return;
|
|
36433
37007
|
}
|