Smooth hover/mouseover tooltips in JSAPI v4

4135
5
Jump to solution
06-16-2020 01:25 PM
ChrisSmith7
Frequent Contributor

Hey guys,

We've used the JSAPI since the early days of v3, and while we have workable mouseover tooltips (opening the pop-up), one aspect we wish we could change is the smoothness of the hover/tooltip. I do not have a link to our application directly, but to give you an idea, please see the following sample:

Feature widget in a side panel | ArcGIS API for JavaScript 4.15 

You'll notice that every time the pointer moves, the graphic is removed and re-added to the map (causing flickering), and then the panel is subsequently refreshed. This is what we experience with our pop-up tooltip... flickering tooltips and constant symbol "redraws." It makes sense based on how it's coded, but I haven't found a better way to do this. It distracts somewhat from the user experience - we've always hoped for something more graceful, e.g.:

https://www.highcharts.com/maps/demo/color-axis 

Has anyone had similar experiences and found success in developing smoother tooltips?

1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Emeritus

Chris,

   Here is a smooth mouse over example. The big takeaway is to check if you are still over the same graphic in the pointer-move event and if you are then do nothing.

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
  <title>PopupTemplate - use functions to set content - 4.15</title>

  <link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />
  <style>
    html,
     body,
     #viewDiv {
       padding: 0;
       margin: 0;
       height: 100%;
       width: 100%;
     }
  </style>
  <script src="https://js.arcgis.com/4.15/"></script>
  <script>
    var populationChange;
    require([
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/Layer",
      "esri/widgets/Feature"
    ], function (Map, MapView, Layer, Feature) {
      var map = new Map({
        basemap: "dark-gray"
      });

      // Create the MapView
      var view = new MapView({
        container: "viewDiv",
        map: map,
        zoom: 7,
        center: [-87, 34]
      });

      Layer.fromPortalItem({
        portalItem: {
          // autocasts as new PortalItem()
          id: "e8f85b4982a24210b9c8aa20ba4e1bf7"
        }
      }).then(function (layer) {
        // add the layer to the map
        layer.outFields = ["*"];
        map.add(layer);

        view.when().then((_) => {
          const graphic = {
            geometry: view.center,
            popupTemplate: {
              content: "Mouse over features to show details..."
            }
          };

          const feature = new Feature({
            graphic,
            view
          });
          view.ui.add(feature, "top-right");

          view.whenLayerView(layer).then((layerView) => {
            let highlight, lResult = {attributes:{NAME:null}};
            view.on("pointer-move", (event) => {
              view.hitTest(event).then(({
                results
              }) => {
                const result = results[0];
                if (result) {
                  if(result.graphic.layer.id === layer.id){
                    if(lResult.attributes.NAME !== result.graphic.attributes.NAME){
                      highlight && highlight.remove();
                      highlight = layerView.highlight(result.graphic);
                      feature.graphic = result.graphic;
                      lResult = result.graphic;
                    }
                  }
                }else{
                  highlight && highlight.remove();
                  feature.graphic = graphic
                }
              });
            });
          });
        });

        // Create a new popupTemplate for the layer and
        // format the numeric field values using the FieldInfoFormat properties. Call the custom populationChange()
        // function to calculate percent change for the county.
        var popupTemplate = {
          // autocasts as new PopupTemplate()
          title: "Population in {NAME}",
          outFields: ["*"],
          content: populationChange,
          fieldInfos: [{
              fieldName: "POP2010",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "POP10_SQMI",
              format: {
                digitSeparator: true,
                places: 2
              }
            },
            {
              fieldName: "POP2013",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "POP13_SQMI",
              format: {
                digitSeparator: true,
                places: 2
              }
            }
          ]
        };

        layer.popupTemplate = popupTemplate;

        function populationChange(feature) {
          var div = document.createElement("div");
          var upArrow =
            '<svg width="16" height="16" ><polygon points="14.14 7.07 7.07 0 0 7.07 4.07 7.07 4.07 16 10.07 16 10.07 7.07 14.14 7.07" style="fill:green"/></svg>';
          var downArrow =
            '<svg width="16" height="16"><polygon points="0 8.93 7.07 16 14.14 8.93 10.07 8.93 10.07 0 4.07 0 4.07 8.93 0 8.93" style="fill:red"/></svg>';

          // Calculate the population percent change from 2010 to 2013.
          var diff =
            feature.graphic.attributes.POP2013 - feature.graphic.attributes.POP2010;
          var pctChange = (diff * 100) / feature.graphic.attributes.POP2010;
          var arrow = diff > 0 ? upArrow : downArrow;

          // Add green arrow if the percent change is positive and a red arrow for negative percent change.
          div.innerHTML =
            "As of 2010, the total population in this area was <b>" +
            feature.graphic.attributes.POP2010 +
            "</b> and the density was <b>" +
            feature.graphic.attributes.POP10_SQMI +
            "</b> sq mi. As of 2013, the total population was <b>" +
            feature.graphic.attributes.POP2013 +
            "</b> and the density was <b>" +
            feature.graphic.attributes.POP13_SQMI +
            "</b> sq mi. <br/> <br/>" +
            "Percent change is " +
            arrow +
            "<span style='color: " +
            (pctChange < 0 ? "red" : "green") +
            ";'>" +
            pctChange.toFixed(3) +
            "%</span>";
          return div;
        }
      });
    });
  </script>


</head>

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

</html>

View solution in original post

5 Replies
ChrisSmith7
Frequent Contributor

Just from thinking about this logically, I believe we could work-out something where we are testing pre and post move to see if our target changed. If it didn't, keep the graphic. That should help with the symbol flickering, but not the pop-up. I've thought about some ways I'd handle that, in just moving the pop-up instead of closing/re-opening.

But, before reinventing the wheel, I'm hoping someone in the community has some pointers - maybe there is something baked-in I am overlooking! If there isn't, I'd say this would be a good enhancement to add in the future!

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Chris,

   Here is a smooth mouse over example. The big takeaway is to check if you are still over the same graphic in the pointer-move event and if you are then do nothing.

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
  <title>PopupTemplate - use functions to set content - 4.15</title>

  <link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />
  <style>
    html,
     body,
     #viewDiv {
       padding: 0;
       margin: 0;
       height: 100%;
       width: 100%;
     }
  </style>
  <script src="https://js.arcgis.com/4.15/"></script>
  <script>
    var populationChange;
    require([
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/Layer",
      "esri/widgets/Feature"
    ], function (Map, MapView, Layer, Feature) {
      var map = new Map({
        basemap: "dark-gray"
      });

      // Create the MapView
      var view = new MapView({
        container: "viewDiv",
        map: map,
        zoom: 7,
        center: [-87, 34]
      });

      Layer.fromPortalItem({
        portalItem: {
          // autocasts as new PortalItem()
          id: "e8f85b4982a24210b9c8aa20ba4e1bf7"
        }
      }).then(function (layer) {
        // add the layer to the map
        layer.outFields = ["*"];
        map.add(layer);

        view.when().then((_) => {
          const graphic = {
            geometry: view.center,
            popupTemplate: {
              content: "Mouse over features to show details..."
            }
          };

          const feature = new Feature({
            graphic,
            view
          });
          view.ui.add(feature, "top-right");

          view.whenLayerView(layer).then((layerView) => {
            let highlight, lResult = {attributes:{NAME:null}};
            view.on("pointer-move", (event) => {
              view.hitTest(event).then(({
                results
              }) => {
                const result = results[0];
                if (result) {
                  if(result.graphic.layer.id === layer.id){
                    if(lResult.attributes.NAME !== result.graphic.attributes.NAME){
                      highlight && highlight.remove();
                      highlight = layerView.highlight(result.graphic);
                      feature.graphic = result.graphic;
                      lResult = result.graphic;
                    }
                  }
                }else{
                  highlight && highlight.remove();
                  feature.graphic = graphic
                }
              });
            });
          });
        });

        // Create a new popupTemplate for the layer and
        // format the numeric field values using the FieldInfoFormat properties. Call the custom populationChange()
        // function to calculate percent change for the county.
        var popupTemplate = {
          // autocasts as new PopupTemplate()
          title: "Population in {NAME}",
          outFields: ["*"],
          content: populationChange,
          fieldInfos: [{
              fieldName: "POP2010",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "POP10_SQMI",
              format: {
                digitSeparator: true,
                places: 2
              }
            },
            {
              fieldName: "POP2013",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "POP13_SQMI",
              format: {
                digitSeparator: true,
                places: 2
              }
            }
          ]
        };

        layer.popupTemplate = popupTemplate;

        function populationChange(feature) {
          var div = document.createElement("div");
          var upArrow =
            '<svg width="16" height="16" ><polygon points="14.14 7.07 7.07 0 0 7.07 4.07 7.07 4.07 16 10.07 16 10.07 7.07 14.14 7.07" style="fill:green"/></svg>';
          var downArrow =
            '<svg width="16" height="16"><polygon points="0 8.93 7.07 16 14.14 8.93 10.07 8.93 10.07 0 4.07 0 4.07 8.93 0 8.93" style="fill:red"/></svg>';

          // Calculate the population percent change from 2010 to 2013.
          var diff =
            feature.graphic.attributes.POP2013 - feature.graphic.attributes.POP2010;
          var pctChange = (diff * 100) / feature.graphic.attributes.POP2010;
          var arrow = diff > 0 ? upArrow : downArrow;

          // Add green arrow if the percent change is positive and a red arrow for negative percent change.
          div.innerHTML =
            "As of 2010, the total population in this area was <b>" +
            feature.graphic.attributes.POP2010 +
            "</b> and the density was <b>" +
            feature.graphic.attributes.POP10_SQMI +
            "</b> sq mi. As of 2013, the total population was <b>" +
            feature.graphic.attributes.POP2013 +
            "</b> and the density was <b>" +
            feature.graphic.attributes.POP13_SQMI +
            "</b> sq mi. <br/> <br/>" +
            "Percent change is " +
            arrow +
            "<span style='color: " +
            (pctChange < 0 ? "red" : "green") +
            ";'>" +
            pctChange.toFixed(3) +
            "%</span>";
          return div;
        }
      });
    });
  </script>


</head>

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

</html>
ChrisSmith7
Frequent Contributor

Appreciate it Robert, I'll give this a go.

0 Kudos
Egge-Jan_Pollé
MVP Regular Contributor

Hi Chris Smith‌,

Yeah, I know what you mean. And I would, like you and probably many others, be very interested in a simple, elegant and smooth solution for hover/tooltips in the ArcGIS API for JavaScript. It does not exist, though, I am afraid.

Via the link below you will find an application which I did build some time ago, just a simple map showing railway stations as blue dots. And when you hover over them their name pops up! Brilliant! That's what I want!

I don't want to click ten times before identifying the correct railway station. I just want to see their name right away when I hover over them. TWIAV - Tips & trucs - Luchtfoto's ProRail (in EPSG:28992) 

But yeah, that's not ArcGIS API for JavaScript, it is Leaflet.

And you know, as they state on their website:

Leaflet is designed with simplicity, performance and usability in mind.

BR,

Egge-Jan

quooston
New Contributor III

I do think that the ArcGIS API for JS is super powerful, and the devs have done a great job to make it possible to do almost anything. However, what we need next are a set of abstractions that wrap up some of the inherrent complexity that comes with all that power, that enable us to do some of these simple things easily. Then we will have the best of both worlds.

Leaflet is a great example of such an API, but some of the more complex tasks it falls short on. The evolution of an API should eventually result in easier ways to do things, not just more and more complexity as more power is added. This is one of the hardest things to get right... but we can hope!