SketchViewModel, unable to delete graphics.

1618
4
Jump to solution
07-15-2022 08:54 PM
naveenanne
New Contributor II

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>

 

 

Tags (1)
1 Solution

Accepted Solutions
ReneRubalcava
Frequent Contributor

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"
	});
});

 

View solution in original post

4 Replies
ReneRubalcava
Frequent Contributor

Here's how you can make this work.

  • Don't call graphicsLayer.removeAll(), this is the layer used SketchViewModel and you should let it handle deletion. I was incorrect here
  • Add the polylineGraphic to the view.graphics, it's not actually part of the sketch workflow. Or add it in the update method, but this graphic is causing your error in your delete methods.

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.

naveenanne
New Contributor II

I'm wondering how to fix the two vertices issue. 

naveenanne
New Contributor II

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...

0 Kudos
ReneRubalcava
Frequent Contributor

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"
	});
});