I'm building a custom drawing component with SketchViewModel. I'm able to update graphics well. Drawing an interactive circle with radius. Whenever I delete the graphics. I always get the below error.
{
"name": "sketch:invalid-parameter",
"message": "Parameter 'graphics' contains one or more graphics missing from the supplied GraphicsLayer."
}
@ReneRubalcava @ReneRubalcava3 @ReneRubalcava2 @ReneRab,@AndyGup could you help?
https://developers.arcgis.com/javascript/latest/sample-code/featurelayerview-query-geometry/
The error could be reproducible from the below html file.
A click on the map draws a circle with radius that's interactive.
Delete button (trash icon button on the top right will delete graphics added to the sketchviewmodel).
Check the debug console on Chrome browser for errors.
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,
maximum-scale=1,user-scalable=no"
/>
<title>
FeatureLayerView - query statistics by geometry | Sample | ArcGIS API for
JavaScript 4.24
</title>
<link
rel="stylesheet"
href="https://js.arcgis.com/4.24/esri/themes/light/main.css"
/>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
</style>
<!-- Load the Chart.js library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"></script>
<script src="https://js.arcgis.com/4.24/"></script>
<script>
require([
"esri/widgets/Sketch/SketchViewModel",
"esri/geometry/Polyline",
"esri/geometry/Point",
"esri/Graphic",
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/layers/GraphicsLayer",
"esri/geometry/geometryEngine",
"esri/widgets/Expand",
"esri/widgets/Legend",
"esri/widgets/Search",
"esri/core/reactiveUtils",
"esri/core/promiseUtils",
"esri/core/reactiveUtils"
], function(
SketchViewModel,
Polyline,
Point,
Graphic,
Map,
MapView,
FeatureLayer,
GraphicsLayer,
geometryEngine,
Expand,
Legend,
Search,
reactiveUtils,
promiseUtils,
reactiveUtils
) {
// App 'globals'
let sketchViewModel;
let count = 0,
centerGraphic,
edgeGraphic,
polylineGraphic,
bufferGraphic,
centerGeometryAtStart,
labelGraphic;
const unit = "kilometers";
// Create layers
const graphicsLayer = new GraphicsLayer();
const bufferLayer = new GraphicsLayer({
blendMode: "color-burn"
});
// Create map
const map = new Map({
basemap: "dark-gray-vector",
layers: [bufferLayer, graphicsLayer]
});
// Create view
const view = new MapView({
container: "viewDiv",
map: map,
zoom: 11,
center: [-122.083, 37.3069],
constraints: {
maxScale: 0,
minScale: 300000
}
});
view.on("click", function(evt) {
drawBufferPolygon(evt.screenPoint);
});
/*********************************************************************
* Edge or center graphics are being moved. Recalculate the buffer with
* updated geometry information and run the query stats again.
*********************************************************************/
const onMove = promiseUtils.debounce((event) => {
// If the edge graphic is moving, keep the center graphic
// at its initial location. Only move edge graphic
if (
event.toolEventInfo &&
event.toolEventInfo.mover.attributes.edge
) {
const toolType = event.toolEventInfo.type;
if (toolType === "move-start") {
centerGeometryAtStart = centerGraphic.geometry;
}
// keep the center graphic at its initial location when edge point is moving
else if (toolType === "move" || toolType === "move-stop") {
centerGraphic.geometry = centerGeometryAtStart;
}
}
// the center or edge graphic is being moved, recalculate the buffer
const vertices = [
[centerGraphic.geometry.x, centerGraphic.geometry.y],
[edgeGraphic.geometry.x, edgeGraphic.geometry.y]
];
// client-side stats query of features that intersect the buffer
calculateBuffer(vertices);
// user is clicking on the view... call update method with the center and edge graphics
if (event.state === "complete") {
sketchViewModel.update([edgeGraphic, centerGraphic], {
tool: "move"
});
}
});
/*****************************************************************
* Create SketchViewModel and wire up event listeners
*****************************************************************/
sketchViewModel = new SketchViewModel({
view: view,
layer: graphicsLayer
});
// Listen to SketchViewModel's update event so that population pyramid chart
// is updated as the graphics are updated
sketchViewModel.on("update", onMove);
/*********************************************************************
* Edge or center point is being updated. Recalculate the buffer with
* updated geometry information.
*********************************************************************/
function calculateBuffer(vertices) {
// Update the geometry of the polyline based on location of edge and center points
polylineGraphic.geometry = new Polyline({
paths: vertices,
spatialReference: view.spatialReference
});
// Recalculate the polyline length and buffer polygon
const length = geometryEngine.geodesicLength(
polylineGraphic.geometry,
unit
);
const buffer = geometryEngine.geodesicBuffer(
centerGraphic.geometry,
length,
unit
);
// Update the buffer polygon
bufferGraphic.geometry = buffer;
// Update label graphic to show the length of the polyline
labelGraphic.geometry = edgeGraphic.geometry;
labelGraphic.symbol = {
type: "text",
color: "#FFEB00",
text: length.toFixed(2) + " kilometers",
xoffset: 50,
yoffset: 10,
font: {
// autocast as Font
size: 14,
family: "sans-serif"
}
};
}
var element = document.createElement('div');
element.className = "esri-icon-trash esri-widget--button esri-widget esri-interactive";
element.addEventListener('click', function(evt) {
if (sketchViewModel) {
sketchViewModel.complete();
sketchViewModel.delete();
graphicsLayer.removeAll();
bufferLayer.removeAll();
view.graphics.removeAll();
}
})
view.ui.add(element, "top-right");
/***************************************************
* Draw the buffer polygon when application loads or
* when user searches for a new location
**************************************************/
function drawBufferPolygon(screenPoint) {
// When pause() is called on the watch handle, the callback represented by the
// watch is no longer invoked, but is still available for later use
// this watch handle will be resumed when user searches for a new location
// pausableWatchHandle.pause();
paused = false;
// Initial location for the center, edge and polylines on the view
//const viewCenter = mapPoint.clone();
const centerScreenPoint = screenPoint;
const centerPoint = view.toMap({
x: centerScreenPoint.x,
y: centerScreenPoint.y
});
const edgePoint = view.toMap({
x: centerScreenPoint.x + 24,
y: centerScreenPoint.y - 12
});
// Store updated vertices
const vertices = [
[centerPoint.x, centerPoint.y],
[edgePoint.x, edgePoint.y]
];
// Create center, edge, polyline and buffer graphics for the first time
if (!centerGraphic) {
const polyline = new Polyline({
paths: vertices,
spatialReference: view.spatialReference
});
// get the length of the initial polyline and create buffer
const length = geometryEngine.geodesicLength(polyline, unit);
const buffer = geometryEngine.geodesicBuffer(
centerPoint,
length,
unit
);
// Create the graphics representing the line and buffer
const pointSymbol = {
type: "simple-marker",
style: "circle",
size: 10,
color: [0, 255, 255, 0.5]
};
centerGraphic = new Graphic({
geometry: centerPoint,
symbol: pointSymbol,
attributes: {
center: "center"
}
});
edgeGraphic = new Graphic({
geometry: edgePoint,
symbol: pointSymbol,
attributes: {
edge: "edge"
}
});
polylineGraphic = new Graphic({
geometry: polyline,
symbol: {
type: "simple-line",
color: [254, 254, 254, 1],
width: 2.5
}
});
bufferGraphic = new Graphic({
geometry: buffer,
symbol: {
type: "simple-fill",
color: [150, 150, 150],
outline: {
color: "#FFEB00",
width: 2
}
}
});
labelGraphic = labelLength(edgePoint, length);
// Add graphics to layer used with sketchVM
graphicsLayer.addMany([
centerGraphic,
edgeGraphic,
polylineGraphic
]);
// Add label to view graphics
view.graphics.add(labelGraphic);
// once center and edge point graphics are added to the layer,
// call sketch's update method pass in the graphics so that users
// can just drag these graphics to adjust the buffer
sketchViewModel.update([edgeGraphic, centerGraphic], {
tool: "move"
});
bufferLayer.addMany([bufferGraphic]);
}
// Move the center and edge graphics to the new location returned from search
else {
centerGraphic.geometry = centerPoint;
edgeGraphic.geometry = edgePoint;
}
// Query features that intersect the buffer
calculateBuffer(vertices);
}
// Label polyline with its length
function labelLength(geom, length) {
return new Graphic({
geometry: geom,
symbol: {
type: "text",
color: "#FFEB00",
text: length.toFixed(2) + " kilometers",
xoffset: 50,
yoffset: 10,
font: {
// autocast as Font
size: 14,
family: "sans-serif"
}
}
});
}
});
</script>
</head>
<body>
<div id="viewDiv">
</div>
</body>
</html>
Solved! Go to Solution.
Ok, I think you need to simplify your sample a bit to get to the bare minimum and work up from there.
Looks like I was incorrect about removing graphics from the graphicsLayer used by SketchViewModel. If you no longer want to use them in a Sketch, you can remove them, but if you still want to see them, you need move to another graphicsLayer not used by Sketch.
There's some snippets using this pattern for when you use sketch.create(), but you are sketching your own graphics with sketch.update(), so it's slightly different, as you provided the graphic for the SketchViewModel.
Here is a super simple sample showing how to sketch a given graphic, and finish the sketch.
https://codepen.io/odoe/pen/abYWVaK?editors=0010
I think in your previous example, you are trying to delete things before completing the Sketch action and that's causing issues.
// on click, stop the sketch actions
sketchBtn.addEventListener("click", () => {
// if the sketch is currently active
// we can complete the action
if (sketchViewModel.state === "active") {
sketchViewModel.complete();
}
});
sketchViewModel.on("update", (event) => {
// this fires after sketchViewModel.complete()
if (event.state === "complete") {
// you do need to remove graphics from
// the grapicsLayer so they are not part
// sketch updates
graphicsLayer.removeMany(event.graphics);
// move it to another graphics layer
// so I can still see it, but not sketch it
// anymore
view.graphics.addMany(event.graphics);
}
});
const clickHandle = view.on("click", ({ mapPoint }) => {
clickHandle.remove();
const graphic = new Graphic({
geometry: mapPoint
});
graphicsLayer.add(graphic);
sketchViewModel.update(graphic, {
tool: "move"
});
});
Here's how you can make this work.
Those two things will fix the error (Update, this is not a correct sample).
https://codepen.io/odoe/pen/OJvpVKL?editors=1000
Still get the two vertices there, not sure how to clean that up. I think you need some more logic around starting the sketch workflow and finishing it.
I'm wondering how to fix the two vertices issue.
Anyone can help with the issue? Unable to delete graphics from SketchViewModel once added.
https://codepen.io/odoe/pen/OJvpVKL?editors=1000
Or, possibly is it a bug within ArcGIS for JS? I think delete is not properly documented within the help documents either.
https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Sketch-SketchViewModel.ht...
Ok, I think you need to simplify your sample a bit to get to the bare minimum and work up from there.
Looks like I was incorrect about removing graphics from the graphicsLayer used by SketchViewModel. If you no longer want to use them in a Sketch, you can remove them, but if you still want to see them, you need move to another graphicsLayer not used by Sketch.
There's some snippets using this pattern for when you use sketch.create(), but you are sketching your own graphics with sketch.update(), so it's slightly different, as you provided the graphic for the SketchViewModel.
Here is a super simple sample showing how to sketch a given graphic, and finish the sketch.
https://codepen.io/odoe/pen/abYWVaK?editors=0010
I think in your previous example, you are trying to delete things before completing the Sketch action and that's causing issues.
// on click, stop the sketch actions
sketchBtn.addEventListener("click", () => {
// if the sketch is currently active
// we can complete the action
if (sketchViewModel.state === "active") {
sketchViewModel.complete();
}
});
sketchViewModel.on("update", (event) => {
// this fires after sketchViewModel.complete()
if (event.state === "complete") {
// you do need to remove graphics from
// the grapicsLayer so they are not part
// sketch updates
graphicsLayer.removeMany(event.graphics);
// move it to another graphics layer
// so I can still see it, but not sketch it
// anymore
view.graphics.addMany(event.graphics);
}
});
const clickHandle = view.on("click", ({ mapPoint }) => {
clickHandle.remove();
const graphic = new Graphic({
geometry: mapPoint
});
graphicsLayer.add(graphic);
sketchViewModel.update(graphic, {
tool: "move"
});
});