ProGuide Custom Pop ups - kataya/arcgis-pro-sdk GitHub Wiki

Language:      C#
Subject:       Map Exploration
Contributor:   ArcGIS Pro SDK Team <[email protected]>
Organization:  Esri, http://www.esri.com
Date:          11/24/2020
ArcGIS Pro:    2.7
Visual Studio: 2017, 2019

This guide demonstrates how to create a map tool that uses a custom pop-up. You use the tool to select a set of features and display their feature content using HTML5/JavaScript in the custom pop-up control. See ProConcepts Map Exploration, MapTool for more information on the MapTool pattern.

Prerequisites

  • Download and install the sample data required for this guide as instructed in Arcgis Pro SDK Community Samples Releases.
  • Create a new ArcGIS Pro Module Add-in, and name the project MapToolWithCustomPopup. If you are not familiar with the ArcGIS Pro SDK, you can follow the steps in the ProGuide Build your first add-in to get started.
  • Add a new ArcGIS Pro Add-ins | ArcGIS Pro Map Tool item to the add-in project, and name the item CustomPopupTool.

Step 1

Modify the Config.daml file tool item as follows:

  • Change the caption to "Pop-up (Customized)".
  • Change the tool heading to "Pop-up (Customized)" and the ToolTip text to "Shows a custom pop-up for features in the map."
...
<tool id="CustomPopup_CustomPopupTool" caption="Pop-up (Customized)" 
     className="CustomPopupTool" loadOnClick="true" 
     smallImage="Images\GenericButtonRed16.png" largeImage="Images\GenericButtonRed32.png" 
     condition="esri_mapping_mapPane" keytip="z2">
  <tooltip heading="Pop-up (Customized)">
    Shows a custom pop-up for features in the map.<disabledText /></tooltip>
</tool>
...

Build the sample and validate the UI on the ArcGIS Pro ribbon.

5MapTool.png

Step 2

The required custom pop-up functions must be implemented in the CustomPopupTool class.
First, the sketch type set in the constructor is changed to SketchGeometryType.Rectangle. Next, SketchOutputMode in the constructor is changed to SketchOutputMode.Screen. MapView.SelectFeatures and MapView.GetFeatures throw a System.ArgumentException if a 3D query is attempted in SketchOutputMode.Map, so you need to change the sketch projection to screen coordinates.

public CustomPopupTool()
{
  IsSketchTool = true;
  SketchType = SketchGeometryType.Rectangle;
  SketchOutputMode = SketchOutputMode.Screen; //required for 3D selection and identify.
}

Note: If IsSketchTool is not set to true, a sketch is not produced.

The OnSketchCompleteAsync method can be overwritten to implement the required custom pop-up functionality. Since you "await" functions within your identify implementation, you have to add the "async" keyword to the OnSketchCompleteAsync method declaration. The MapTool base class provides an ActiveMapView property that provides a link to the current active map view. The MapView, in turn, provides a method that can be used to get all features that intersect a given geometry. The resulting features' BasicFeatureLayer and object id are then used to create a list of PopupContent objects (see next step). The list of PopupContent objects is used when calling the ActiveMapView's ShowCustomPopup method.

protected override async Task<bool> OnSketchCompleteAsync(ArcGIS.Core.Geometry.Geometry 
                                                        geometry)
{
  var popupContent = await QueuedTask.Run(() =>
  {
    // Get the features that intersect the sketch geometry.
    var result = ActiveMapView.GetFeatures(geometry);

    // For each feature in the result create a new instance of our 
    // custom pop-up content class.
    List<PopupContent> popups = new List<PopupContent>();
    foreach (var kvp in result)
    {
      kvp.Value.ForEach(id => popups.Add(new DynamicPopupContent(kvp.Key, id)));
    }

    // Flash the features that intersected the sketch geometry.
    ActiveMapView.FlashFeature(result);

    // Return the collection of pop-up content object.
    return popups;
  });

  //Show the custom pop-up with the custom commands and the default pop-up commands. 
  ActiveMapView.ShowCustomPopup(popupContent, null, true);
  return true;
}

Step 3

Add the pop-up content page that is used to display the feature records. This is implemented by the DynamicPopupContent class that implements PopupContent. Add a new CSharp class file to the project, name the class DynamicPopupContent, and copy the content shown below into the class body. The ArcGIS Pro FrameworkApplication uses a list of PopupContent objects in its ShowCustomPopup method. Each PopupContent object references one feature. The DynamicPopupContent class below overrides OnCreateHtmlContent, which is called when each feature is displayed. OnCreateHtmlContent expects that the HTML content for the current selected feature is returned. The implementation queries the attributes for the selected feature, reads the template HTML page, and replaces the //data.addColumn string place holder in the template HTML string with the feature's attribute data (formatted as a JavaScript string).

internal class DynamicPopupContent : PopupContent
{
  private Dictionary<FieldDescription, double> _values = 
            new Dictionary <FieldDescription, double>();
  /// <summary>
  /// Constructor initializing the base class with the layer and object id associated 
  /// with the pop-up content.
  /// </summary>
  public DynamicPopupContent(MapMember mapMember, long id) : base(mapMember, id)
  {
    // Set property indicating the html content will be generated on demand when 
    // the content is viewed.
    IsDynamicContent = true;
  }
  /// <summary>
  /// Called the first time the pop-up content is viewed. This is good practice 
  /// when you may show a pop-up for multiple items at a time. 
  /// This allows you to delay generating the html content until 
  /// the item is actually viewed.
  /// </summary>
  protected override Task<string> OnCreateHtmlContent()
  {
    return QueuedTask.Run(() =>
    {
      var invalidPopup = "<p>Pop-up content could not be generated for this feature.</p>";
      var layer = MapMember as BasicFeatureLayer;
      if (layer == null) return invalidPopup;
      // Get all the visible numeric fields for the layer.
      var fields = layer.GetFieldDescriptions().
                Where(f => IsNumericFieldType(f.Type) && f.IsVisible);
      // Create a query filter using the fields found above and a where clause for 
      // the object id associated with this pop-up content.
      var tableDef = layer.GetTable().GetDefinition();
      var oidField = tableDef.GetObjectIDField();
      var qf = new QueryFilter() { WhereClause = $"{oidField} = {ID}", 
              SubFields = string.Join(",", fields.Select(f => f.Name)) };
      var rows = layer.Search(qf);
      // Get the first row, there should only be 1 row.
      if (!rows.MoveNext()) return invalidPopup;
      var row = rows.Current;
      // Loop through the fields, extract the value for the row, and add to a dictionary.
      foreach (var field in fields)
      {
        var val = row[field.Name];
        if (val is DBNull || val == null) continue;
        double value;
        if (!Double.TryParse(val.ToString(), out value)) continue;
        if (value < 0) continue;
        _values.Add(field, value);
      }
      if (_values.Count == 0) return invalidPopup;
      // Construct a new html string that we will use to update our html template.
      StringBuilder sb = new StringBuilder();
      sb.AppendLine("data.addColumn('string', 'Votes')");
      sb.AppendLine("data.addColumn('number', 'Count')");
      sb.AppendLine("data.addColumn('number', 'In %')");
      sb.AppendLine("data.addRows([");

      //Add each value to the html string.
      foreach (var v in _values)
      {
        var percentage = (v.Value / _values.Sum(kvp => kvp.Value)) * 100;
        sb.AppendLine(string.Format("['{0}', {{v: {1} }}, {{v: {2} }}],", 
                v.Key.Alias, v.Value, percentage));
      }
      sb.AppendLine("]);");
      //Get the html from the template file on disk that we have packaged with our add-in.
      var htmlPath = Path.Combine(Path.GetDirectoryName
            (Assembly.GetExecutingAssembly().Location), "template.html");
      var html = File.ReadAllText(htmlPath);
      // Update the template with our custom html and 
      // return it to be displayed in the pop-up window.
      html = html.Replace("//data.addColumn", sb.ToString());
      return html;
    });
  }
  private bool IsNumericFieldType(FieldType type)
  {
    switch (type)
    {
      case FieldType.Double:
      case FieldType.Integer:
      case FieldType.Single:
      case FieldType.SmallInteger:
        return true;
      default:
        return false;
    }
  }
}

Step 4

Add the pop-up content page used to display the feature records. Add a new html file to the project, and name it template.html. Paste the content below into the newly added file, and select Content as the build action for that file. Note: The //data.addColumn text is a place holder for the real data, which is substituted for each page of feature content that is displayed.

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <style type='text/css'>
        .even-row {
            background: #ffffff;
            font-weight: normal;
        }
        .odd-row {
            background: #efeeef;
            font-weight: normal;
        }
    </style>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">
        google.load("visualization", "1.1", { packages: ["table", "corechart"] });
        google.setOnLoadCallback(drawTable);

        function drawTable() {
            var data = new google.visualization.DataTable();
            //data.addColumn
            var options = {
                'showRowNumber': false, 'width': '100%', 'cssClassNames': {
                    'headerRow': 'odd-row', 
                    'oddTableRow': 'odd-row ', 
                    'tableRow': 'even-row', 
                    'hoverTableRow': 'even-row'
                }
            };

            var formatter = new google.visualization.NumberFormat({ suffix: '%', 
                                    fractionalDigits: '2' });
            formatter.format(data, 2);

            var table = new google.visualization.Table(document.
                                getElementById('table_div'));
            table.draw(data, options);

            var chart = new google.visualization.PieChart(
                                document.getElementById('chart_div'));
            chart.draw(data, { is3D: true, tooltip: { trigger: 'selection' }, 
                    chartArea: { left: 0, top: 20, 
                    width: '100%', height: '100%' }, sliceVisibilityThreshold: 0 });

            google.visualization.events.addListener(table, 'select',
			  function (e) {
			      var selection = table.getSelection();
			      chart.setSelection(selection);
			  });

            google.visualization.events.addListener(chart, 'onmouseover',
			  function (e) {
			      table.setSelection([{ row: e.row, column: null }]);
			      chart.setSelection([{ row: e.row, column: null }]);
			  });

            google.visualization.events.addListener(chart, 'onmouseout',
			  function (e) {
			      table.setSelection();
			      chart.setSelection();
			  });
        }
    </script>
</head>
<body>
    <div id="table_div"></div>
    <div id="chart_div"></div>
</body>
</html>

Step 5

Rebuild the add-in. Fix any compilation errors.

Step 6

Debug the add-in. Run the debugger and start ArcGIS Pro. Open the C:\Data\ElectionData\Election.aprx project that contains a dataset that works the custom pop-up. Try the custom pop-up tool, and select some states as shown here:

5MapTool2D.png

5MapTool2D_2.png

⚠️ **GitHub.com Fallback** ⚠️