Change Feature Layer Symbol on Hover Event

3903
6
Jump to solution
06-19-2019 12:59 PM
ParkerWelch
New Contributor III

BACKGROUND

I am not a web developer. I am sure I violate all sorts of best practices and code conventions. Feel free to suggest code optimizations if you're so inclined.

I am trying to simply add a building 'highlight' and change the cursor to pointer on hover. I don't want to change the fill inside the building like in the example below. I just want to add a highlight outline.

QUESTIONS

  1. Any idea what may be causing my feature layer to 're-render' on hover?
    • all other features in the layer turn off then on again during the unique value renderer change that is triggered by the hover event
    •  I'm hoping to avoid the 'blinking' effect. Just want to change the unique value renderer on the building that was hovered over without adjusting the rest of the layer symbology
    • something like, "if hittest returns an item in the buildings layer, add a graphic on top of the existing layer with custom symbology without reloading the entire layer"
  2. How can I get the cursor to change to the pointer on the polygon fill as well? Currently, the arrow only changes to pointer when it is over the building outline.
    • This might be due to my custom symbology

CONSIDERATIONS

  • I have many layers in this map. The filter method on line 43 wasn't working, so I made a conditional on line 41 to only return hits from my buildings_lyr. I am sure this can be optimized if you have suggestions

CODE

This code was taken from this thread and adapted to my particular use.

It's possible that I unknowingly removed bits of the code that is causing the weird functionality.

Please pardon the indentation errors, this is just how it pastes into the syntax highlighter.

function changeCursor(response){
        if (response.results.length > 0){
          document.getElementById("viewDiv").style.cursor = "pointer";
        } else {
          document.getElementById("viewDiv").style.cursor = "default";
        }
    }
  
  	function getGraphics(response) {
  		// the topmost graphic from the click location
  		// and display select attribute values from the
  		// graphic to the user
  		var graphic = response.results[0].graphic;
  		var attributes = graphic.attributes;
  		var name = attributes.BUILDINGNAME;

  		dom.byId("info").style.visibility = "visible";
  		dom.byId("name").innerHTML = name;

  		// symbolize all line segments with the given
  		// storm name with the same symbol
  		var renderer = new UniqueValueRenderer({
  			field: "BUILDINGNAME",
  			defaultSymbol: buildings_lyr.renderer.symbol || buildings_lyr.renderer.defaultSymbol,
  			uniqueValueInfos: [{
  				value: name,
  				symbol: new SimpleFillSymbol({ 
  					color:blue,
						outline: {
							color: orange,
							width: 1
						}
  				})
				}]
  		});
  		buildings_lyr.renderer = renderer;
  	}

  	view.on("pointer-move", function (evt) {
  		var screenPoint = {
  			x: evt.x,
  			y: evt.y
  		};

  		// the hitTest() checks to see if any graphics in the view
  		// intersect the given screen x, y coordinates
  		view.hitTest(screenPoint)
  			.then(function (response) {
  				if (response.results.length > 0) {
  					if (response.results[0].graphic.layer.title == 'buildings_app') {
  						const graphic = response.results.filter(function (result) {
  							return result.graphic.layer === buildings_lyr;
  						})
  						changeCursor(response);
  						//getGraphics(response); //!!Uncomment this when getGraphics is fixed
  					}
  				} else {
  					changeCursor(response);
  				}
  			});
				
  	});
	  
		buildings_lyr.then(function() {
			var renderer = buildings_lyr.renderer.clone();
			renderer.symbol.color = [255,255,255,1.0];
			buildings_lyr.renderer = renderer;
		});
0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Emeritus

Parker,

  OK, this is what you are after then (full working sample, no flicker):

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <meta name="description" content="[Highlight features with hover events - 4.11]">
  <!--
  ArcGIS API for JavaScript, https://js.arcgis.com
  For more information about the view-hittest sample, read the original sample description at developers.arcgis.com.
  https://developers.arcgis.com/javascript/latest/view-hittest/index.html
  -->
  <title>Highlight features with hover events - 4.11</title>

  <style>
    html,
    body,
    #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }
  </style>

  <link rel="stylesheet" href="https://js.arcgis.com/4.11/esri/css/main.css">
  <link rel="stylesheet" href="https://js.arcgis.com/4.11/dijit/themes/claro/claro.css">
  <script src="https://js.arcgis.com/4.11/"></script>

  <script>
    var dialog, dRenderer;
    require([
      "esri/core/watchUtils",
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/FeatureLayer",
      "esri/renderers/UniqueValueRenderer",
      "esri/symbols/SimpleLineSymbol",
      "dojo/dom",
      "dojo/dom-style",
      "dijit/popup",
      "dojo/domReady!"
    ], function (
      watchUtils,
      Map,
      MapView,
      FeatureLayer,
      UniqueValueRenderer,
      SimpleLineSymbol,
      dom,
      domStyle,
      dijitPopup
    ) {

      var layer = new FeatureLayer({
        url: "https://services.arcgis.com/8Pc9XBTAsYuxx9Ny/arcgis/rest/services/BuildingFootprint2D_gdb/FeatureServer/0",
        outFields: ["*"]
      });

      var map = new Map({
        basemap: "dark-gray",
        layers: [layer]
      });

      var view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-80.135073, 25.774479],
        zoom: 16
      });

      function changeCursor(response) {
        if (response.results.length > 0) {
          document.getElementById("viewDiv").style.cursor = "pointer";
        } else {
          document.getElementById("viewDiv").style.cursor = "default";
        }
      }

      function getGraphics(response) {
        view.graphics.removeAll();
        if (response.results.length > 0) {
          var graphic = response.results[0].graphic;
          graphic.symbol = {
            type: "simple-fill",
            style: "none",
            outline: { // autocasts as new SimpleLineSymbol()
              color: "orange",
              width: 1
            }
          }
          view.graphics.add(graphic);
        }
      }

      view.when(function () {
        view.whenLayerView(layer).then(function (lview) {
          watchUtils.whenFalseOnce(lview, "updating", function () {
            // Set up a click event handler and retrieve the screen x, y coordinates
            view.on("pointer-move", function (evt) {
              var screenPoint = {
                x: evt.x,
                y: evt.y
              };

              // the hitTest() checks to see if any graphics in the view
              // intersect the given screen x, y coordinates
              view.hitTest(screenPoint)
                .then(function (response) {
                  changeCursor(response);
                  getGraphics(response);
                });
            });
          });
        });
      });
    });
  </script>
</head>

<body>
  <div id="viewDiv"></div>
</body>

</html>

View solution in original post

0 Kudos
6 Replies
RobertScheitlin__GISP
MVP Emeritus

Parker,

   I am missing the part in your posted code where you are changing the layers renderer... I see you have getGraphics commented out so the only thing that is happening on mouse over is the cursor change. Maybe this is not the right code you pasted?

0 Kudos
ParkerWelch
New Contributor III

Sorry for the confusion Robert. I tried to address an issue between SimpleLine vs SimpleFill and pasted a branched version of my code which I haven't been able to get working. The code pasted below resulted in the behavior present in the original GIF. 

However, I am still uncertain what you mean by the "the part in your posted code where you are changing the layers renderer". I thought that was happening on line 34?

function changeCursor(response){
        if (response.results.length > 0){
          document.getElementById("viewDiv").style.cursor = "pointer";
        } else {
          document.getElementById("viewDiv").style.cursor = "default";
        }
    }
  
  	function getGraphics(response) {
  		// the topmost graphic from the click location
  		// and display select attribute values from the
  		// graphic to the user
  		var graphic = response.results[0].graphic;
  		var attributes = graphic.attributes;
  		var name = attributes.BUILDINGNAME;

  		dom.byId("info").style.visibility = "visible";
  		dom.byId("name").innerHTML = name;

  		// symbolize all line segments with the given
  		// storm name with the same symbol
  		var renderer = new UniqueValueRenderer({
  			field: "BUILDINGNAME",
  			defaultSymbol: buildings_lyr.renderer.symbol || buildings_lyr.renderer.defaultSymbol,
  			uniqueValueInfos: [{
  				value: name,
  				symbol: new SimpleLineSymbol({
  					color: "orange",
  					width: 1,
  					cap: "round"
  				})
				}]
  		});
  		buildings_lyr.renderer = renderer;
  	}

  	view.on("pointer-move", function (evt) {
  		var screenPoint = {
  			x: evt.x,
  			y: evt.y
  		};

  		// the hitTest() checks to see if any graphics in the view
  		// intersect the given screen x, y coordinates
  		view.hitTest(screenPoint)
  			.then(function (response) {
  				if (response.results.length > 0) {
  					if (response.results[0].graphic.layer.title == 'buildings_app') {
  						const graphic = response.results.filter(function (result) {
  							return result.graphic.layer === buildings_lyr;
  						})
  						changeCursor(response);
  						getGraphics(response);
  					}
  				} else {
  					changeCursor(response);
  				}
  			});
  	});
0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Parker,

  The symbol would change at line 34 if you did not comment out the getGraphics line in your previous code.

function changeCursor(response){
  if (response.results.length > 0){
    document.getElementById("viewDiv").style.cursor = "pointer";
  } else {
    document.getElementById("viewDiv").style.cursor = "default";
  }
}
  
function getGraphics(response) {
  // the topmost graphic from the click location
  // and display select attribute values from the
  // graphic to the user
  var graphic = response.results[0].graphic;
  var attributes = graphic.attributes;
  var name = attributes.BUILDINGNAME;

  dom.byId("info").style.visibility = "visible";
  dom.byId("name").innerHTML = name;

  var renderer = new UniqueValueRenderer({
    field: "BUILDINGNAME",
    defaultSymbol: buildings_lyr.renderer.symbol || buildings_lyr.renderer.defaultSymbol,
    uniqueValueInfos: [{
      value: name,
      symbol: {
        type: "simple-fill",  // autocasts as new SimpleFillSymbol()
        color: [ 51, 51, 204, 0.9 ],
        style: "solid",
        outline: {  // autocasts as new SimpleLineSymbol()
          color: "orange",
          width: 1
        }
      }
    }]
  });
  buildings_lyr.renderer = renderer;
}

view.on("pointer-move", function (evt) {
  var screenPoint = {
    x: evt.x,
    y: evt.y
  };

  // the hitTest() checks to see if any graphics in the view
  // intersect the given screen x, y coordinates
  view.hitTest(screenPoint)
    .then(function (response) {
      if (response.results.length > 0 && response.results[0].graphic.layer.title == 'buildings_app') {
        changeCursor(response);
        getGraphics(response);
      } else {
        changeCursor(response);
      }
  });
});‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
ParkerWelch
New Contributor III

Thanks Robert - the commenting out of the getGraphics() function was a mistake. I understand that if it's commented out, it will not fire. I have adjusted the code accordingly.

Re: your previous comment, I tested the code you supplied and created a GIF of the behavior below. I have a few issues with how it's performing...

  • Notice the 'flicker' of the buildings layer upon hover. Is there a way to make it smoother so it doesn't flash off/on? Is the layer having to render again? If so, might the flicker be caused by the size of the feature service?
  • Also, notice that the changed buildings renderer remains until a new building is hovered over.
    • Really, I'm only trying to indicate to the user with some minor visual feedback that they can click a building to get a pop-up. The change in rendering is not meant to persist throughout the session. it should be 'reset' upon the pointer leaving the building outline
  • Also, notice that the cursor remains as the pointer even after it has left the building outline. How can I adjust the script to change back to the arrow upon leaving the building?

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Parker,

  OK, this is what you are after then (full working sample, no flicker):

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <meta name="description" content="[Highlight features with hover events - 4.11]">
  <!--
  ArcGIS API for JavaScript, https://js.arcgis.com
  For more information about the view-hittest sample, read the original sample description at developers.arcgis.com.
  https://developers.arcgis.com/javascript/latest/view-hittest/index.html
  -->
  <title>Highlight features with hover events - 4.11</title>

  <style>
    html,
    body,
    #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }
  </style>

  <link rel="stylesheet" href="https://js.arcgis.com/4.11/esri/css/main.css">
  <link rel="stylesheet" href="https://js.arcgis.com/4.11/dijit/themes/claro/claro.css">
  <script src="https://js.arcgis.com/4.11/"></script>

  <script>
    var dialog, dRenderer;
    require([
      "esri/core/watchUtils",
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/FeatureLayer",
      "esri/renderers/UniqueValueRenderer",
      "esri/symbols/SimpleLineSymbol",
      "dojo/dom",
      "dojo/dom-style",
      "dijit/popup",
      "dojo/domReady!"
    ], function (
      watchUtils,
      Map,
      MapView,
      FeatureLayer,
      UniqueValueRenderer,
      SimpleLineSymbol,
      dom,
      domStyle,
      dijitPopup
    ) {

      var layer = new FeatureLayer({
        url: "https://services.arcgis.com/8Pc9XBTAsYuxx9Ny/arcgis/rest/services/BuildingFootprint2D_gdb/FeatureServer/0",
        outFields: ["*"]
      });

      var map = new Map({
        basemap: "dark-gray",
        layers: [layer]
      });

      var view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-80.135073, 25.774479],
        zoom: 16
      });

      function changeCursor(response) {
        if (response.results.length > 0) {
          document.getElementById("viewDiv").style.cursor = "pointer";
        } else {
          document.getElementById("viewDiv").style.cursor = "default";
        }
      }

      function getGraphics(response) {
        view.graphics.removeAll();
        if (response.results.length > 0) {
          var graphic = response.results[0].graphic;
          graphic.symbol = {
            type: "simple-fill",
            style: "none",
            outline: { // autocasts as new SimpleLineSymbol()
              color: "orange",
              width: 1
            }
          }
          view.graphics.add(graphic);
        }
      }

      view.when(function () {
        view.whenLayerView(layer).then(function (lview) {
          watchUtils.whenFalseOnce(lview, "updating", function () {
            // Set up a click event handler and retrieve the screen x, y coordinates
            view.on("pointer-move", function (evt) {
              var screenPoint = {
                x: evt.x,
                y: evt.y
              };

              // the hitTest() checks to see if any graphics in the view
              // intersect the given screen x, y coordinates
              view.hitTest(screenPoint)
                .then(function (response) {
                  changeCursor(response);
                  getGraphics(response);
                });
            });
          });
        });
      });
    });
  </script>
</head>

<body>
  <div id="viewDiv"></div>
</body>

</html>
0 Kudos
ParkerWelch
New Contributor III

Many thanks Robert. I really appreciate your help. I'll take a look later today at adapting that working sample into my app.

0 Kudos