Skip welcome & menu and move to editor
Welcome to JS Bin
Load cached copy from
 
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>Client-side queries with Cedar - 4.9</title>
  <link rel="stylesheet" href="https://js.arcgis.com/4.9/esri/css/main.css">
  <link rel="stylesheet" href="https://js.arcgis.com/4.9/dijit/themes/claro/claro.css">
  <style>
    html,
    body,
    #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
      overflow: hidden;
      font-family: "Avenir Next W00", "Helvetica Neue", Helvetica, Arial, sans-serif;
    }
    #cedarPanel {
      background: #fff;
      font: "Avenir Next W00";
      line-height: 1.5em;
      overflow: auto;
      padding: 10px;
      width: 400px;
      height: 250px;
    }
    .chart {
      height: 300px;
    }
    #instructions {
      width: 300px;
      padding: 15px;
    }
  </style>
  <!-- Load the Chart.js library -->
  <script src="https://www.amcharts.com/lib/3/amcharts.js"></script>
  <!-- in this case, we only need bar charts, so we'll load the appropriate amCharts script -->
  <script src="https://www.amcharts.com/lib/3/serial.js"></script>
  <!-- load the arcgis-rest-js scripts -->
  <script src="https://unpkg.com/@esri/arcgis-rest-request@1.7.1/dist/umd/request.umd.min.js"></script>
  <script src="https://unpkg.com/@esri/arcgis-rest-feature-service@1.7.1/dist/umd/feature-service.umd.min.js"></script>
  <!-- optionally load an amcharts theme; cedar provides a calcite theme -->
  <script src="https://unpkg.com/@esri/cedar/dist/umd/themes/amCharts/calcite.js"></script>
  <!-- load cedar -->
  <script src="https://unpkg.com/@esri/cedar/dist/umd/cedar.js"></script>
  <script src="https://js.arcgis.com/4.9/"></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/watchUtils"
    ], function(
      SketchViewModel, Polyline, Point,
      Graphic, Map, MapView, FeatureLayer, GraphicsLayer,
      geometryEngine, Expand, Legend, Search, watchUtils
    ) {
      // App 'globals'
      let sketchViewModel, featureLayerView, pausableWatchHandle,
        centerGraphic, edgeGraphic, polylineGraphic,
        bufferGraphic, labelGraphic, chartExpand, cedarExpand, activeGraphic;
      const unit = "kilometers";
      // Create layers
      const graphicsLayer = new GraphicsLayer();
      const featureLayer = new FeatureLayer({
        portalItem: {
          id: "83c37666a059480bb8a7cb73f449ff52"
        },
        outFields: ["*"]
      });
      // Create map
      const map = new Map({
        basemap: "dark-gray",
        layers: [featureLayer, graphicsLayer]
      });
      // Create view
      const view = new MapView({
        container: "viewDiv",
        map: map,
        zoom: 12,
        center: [-122.083, 37.3069],
        constraints: {
          maxScale: 0,
          minScale: 300000
        }
      });
      // Set up statistics definition for client-side query
      // Total popultion of age groups by gender in census tracts
      const statDefinitions = [
        "FEM85C10", "FEM80C10", "FEM75C10", "FEM70C10", "FEM65C10",
        "FEM60C10",
        "FEM55C10", "FEM50C10", "FEM45C10", "FEM40C10", "FEM35C10",
        "FEM30C10",
        "FEM25C10", "FEM20C10", "FEM15C10", "FEM10C10", "FEM5C10",
        "FEM0C10",
        "MALE85C10", "MALE80C10", "MALE75C10", "MALE70C10", "MALE65C10",
        "MALE60C10",
        "MALE55C10", "MALE50C10", "MALE45C10", "MALE40C10", "MALE35C10",
        "MALE30C10",
        "MALE25C10", "MALE20C10", "MALE15C10", "MALE10C10", "MALE5C10",
        "MALE0C10"
      ].map(function(fieldName) {
        return {
          onStatisticField: fieldName,
          outStatisticFieldName: fieldName + "_TOTAL",
          statisticType: "sum"
        };
      });
      // Update UI
      setUpViewHandles();
      setUpSketch();
      setUpGraphicClickHandle();
      /*****************************************************************
       * Wire up handlers for different view states
       *****************************************************************/
      function setUpViewHandles() {
        // When layer is loaded, create a watcher to trigger drawing of the buffer polygon
        view.whenLayerView(featureLayer).then(function(layerView) {
          featureLayerView = layerView;
          pausableWatchHandle = watchUtils.pausable(layerView,
            "updating",
            function(val) {
              if (!val) {
                drawBufferPolygon();
              }
            });
          // Display directions when the layerView is loading
          watchUtils.whenFalseOnce(layerView, "updating", function() {
            view.popup.open({
              title: "Center point",
              content: "Click on this point and then drag it to move the buffer.<br/> " +
                "Or click on the <b>Edge</b> point and then drag it to resize the buffer.",
              location: centerGraphic.geometry
            });
          });
        });
        view.when(function() {
          // Display the chart in an Expand widget
          cedarExpand = new Expand({
            expandIconClass: "esri-icon-chart",
            expandTooltip: "the illest",
            expanded: true,
            view: view,
            content: document.getElementById("cedarPanel")
          });
          const search = new Search({
            view: view,
            resultGraphicEnabled: false,
            popupEnabled: false
          });
          // Resume drawBufferPolygon() function; user searched for a new location
          // Must update the buffer polygon and re-run the stats query
          search.on("search-complete", function() {
            pausableWatchHandle.resume();
          });
          // Legend widget
          const legend = new Legend({
            view: view,
            layerInfos: [{
              layer: featureLayer,
              title: "2010 Population Density by Census tracts"
            }]
          });
          // Display the Legend in an Expand widget
          const legendExpand = new Expand({
            expandTooltip: "Show Legend",
            expanded: false,
            view: view,
            content: legend
          });
          // Display Instructions in an Expand widget
          const instructionsExpand = new Expand({
            expandIconClass: "esri-icon-question",
            expandTooltip: "How to use this sample",
            expanded: false,
            view: view,
            content: document.getElementById("instructions")
          });
          // Add our components to the UI
          view.ui.add(cedarExpand, "bottom-left");
          view.ui.add(search, "top-right");
          view.ui.add(legendExpand, "bottom-right");
          view.ui.add(instructionsExpand, "top-left");
        });
        // Close the 'help' popup when view is focused
        view.watch("focused", function(newValue) {
          if (newValue) {
            view.popup.close();
          }
        });
      }
      /*****************************************************************
       * Create SketchViewModel and wire up event listeners
       *****************************************************************/
      function setUpSketch() {
        sketchViewModel = new SketchViewModel({
          view: view,
          layer: graphicsLayer
        });
        // Listen to SketchViewModel's move events so that population pyramid chart
        // is updated as the graphics are updated
        sketchViewModel.on("move-start", onMove);
        sketchViewModel.on("move", onMove);
        sketchViewModel.on("move-complete", onMove);
      }
      /*****************************************************************
       * Listen to view.click event to update the center or edge graphic
       *****************************************************************/
      function setUpGraphicClickHandle() {
        view.on("click", function(event) {
          view.hitTest(event).then(function(response) {
            const results = response.results;
            // Loop through results returned from hitTest
            results.forEach(function(result) {
              // Call sketchViewModel.update method if point graphic is clicked
              if (result.graphic === edgeGraphic || result.graphic ===
                centerGraphic) {
                // Save a reference to the current graphic being updated
                activeGraphic = result.graphic;
                sketchViewModel.update(result.graphic);
              }
            });
          });
        });
      }
      /*********************************************************************
       * Edge or center graphics are being moved. Recalculate the buffer with
       * updated geometry information and run the query stats again.
       *********************************************************************/
      function onMove(event) {
        // User is moving the 'center' graphic.
        // Adjust other graphics based on the movement of the center graphic
        if (activeGraphic === centerGraphic) {
          // Update the reference graphic's geometry
          centerGraphic.geometry = event.geometry;
          // Calculate location of edgeGraphic
          // dx/dy values are in pixels
          const edgeScreenPoint = view.toScreen(edgeGraphic.geometry);
          const edgeX = edgeScreenPoint.x + event.dx;
          const edgeY = edgeScreenPoint.y + event.dy;
          edgeGraphic.geometry = view.toMap(edgeX, edgeY);
        }
        // User is moving on the 'edge' graphic.
        // Resize the polyline graphic and recalculate the buffer polygon
        else if (activeGraphic === edgeGraphic) {
          // Update the reference graphic's geometry
          edgeGraphic.geometry = event.geometry;
        }
        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);
      }
      /*********************************************************************
       * 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;
        // Query female and male age groups of the census tracts that intersect
        // the buffer polygon on the client
        queryLayerViewAgeStats(buffer).then(function(newData) {
          // Create a population pyramid chart from the returned result
          // updateChart(newData);
        });
        // Update label graphic to show the length of the polyline
        labelGraphic.geometry = edgeGraphic.geometry;
        labelGraphic.symbol.text = length.toFixed(2) + " kilometers";
      }
      /*********************************************************************
       * Spatial query the census tracts feature layer view for statistics
       * using the updated buffer polygon.
       *********************************************************************/
      function queryLayerViewAgeStats(buffer) {
        // Data storage for the chart
        let femaleAgeData = [],
          maleAgeData = [];
        // Client-side spatial query:
        // Get a sum of age groups for census tracts that intersect the polygon buffer
        const query = featureLayerView.layer.createQuery();
        query.outStatistics = statDefinitions;
        query.geometry = buffer;
        // Query the features on the client using FeatureLayerView.queryFeatures
        return featureLayerView.queryFeatures(query).then(function(results) {
          updateCedarChart(results.features);
          // Statistics query returns a feature with 'stats' as attributes
            const attributes = results.features[0].attributes;
            // Loop through attributes and save the values for use in the population pyramid.
            for (var key in attributes) {
              if (key.includes("FEM")) {
                femaleAgeData.push(attributes[key]);
              } else {
                // Make 'all male age group population' total negative so that
                // data will be displayed to the left of female age group
                maleAgeData.push(-Math.abs(attributes[key]));
              }
            }
            // Return information, seperated by gender
            return [femaleAgeData, maleAgeData];
          })
          .catch(function(error) {
            console.log(error);
          });
      }
      /***************************************************
       * Draw the buffer polygon when application loads or
       * when user searches for a new location
       **************************************************/
      // Function is called for the first time in app load.
      // Function is also called from search widget's search-complete event
      function drawBufferPolygon() {
        // 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();
        // Initial location for the center, edge and polylines on the view
        const viewCenter = view.center.clone();
        const centerScreenPoint = view.toScreen(viewCenter);
        const centerPoint = view.toMap(centerScreenPoint.x + 120,
          centerScreenPoint.y - 120);
        const edgePoint = view.toMap(centerScreenPoint.x + 240,
          centerScreenPoint.y - 120);
        // 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
          centerGraphic = createGraphic(centerPoint, "center");
          edgeGraphic = createGraphic(edgePoint, "handle");
          polylineGraphic = createGraphic(polyline, "line");
          bufferGraphic = createGraphic(buffer, "buffer");
          labelGraphic = labelLength(edgePoint, length);
          // Add graphics to layer
          graphicsLayer.addMany([bufferGraphic, polylineGraphic,
            centerGraphic, edgeGraphic, labelGraphic
          ]);
        }
        // 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);
      }
      // Create an population pyramid chart for the census tracts that intersect the buffer polygon
      function updateCedarChart (features) {
        var cedarFeatures = []
        for (var key in features[0].attributes) {
          if (key.includes("FEM")) {
            cedarFeatures.push({
              attributes: {
                // extract the age group from the indecipherable census attribute name
                category: key.slice(3).slice(0,-9),
                total: features[0].attributes[key]
              }
            });
          }
        }
        // youngest to oldest
        cedarFeatures.reverse();
        var definition = {
          type: "bar",
          title: "tom is cool",
          style: {
            colors: ["#66eeea"]
          },
          datasets: [
            {
              data: {
                features: cedarFeatures
              }
            }
          ],
          series: [
            {
              category: {
                field: "category",
                label: "Age Group (Female)"
              },
              value: {
                field: "total",
                label: "Total Population"
              }
            }
          ]
        };
        var cedarChart = new cedar.Chart("cedarPanel", definition);
        cedarChart.show();
      }
      // Helper function for creating graphics based on symbol type
      function createGraphic(geometry, symbolType) {
        let graphic = new Graphic(geometry);
        switch (symbolType) {
          case "handle":
            graphic.attributes = {
              edge: "edge"
            };
            graphic.symbol = {
              type: "simple-marker",
              style: "circle",
              size: 12,
              color: [255, 0, 255],
              outline: {
                color: [255, 255, 255],
                width: 1
              }
            };
            break;
          case "center":
            graphic.attributes = {
              center: "center"
            };
            graphic.symbol = {
              type: "simple-marker",
              style: "circle",
              size: 12,
              color: "#e7903c",
              outline: {
                color: [255, 255, 255],
                width: 1
              }
            };
            break;
          case "line":
            graphic.symbol = {
              type: "simple-line",
              color: [254, 254, 254, 1],
              width: 2.5
            };
            break;
          default:
            graphic.symbol = {
              type: "simple-fill",
              color: [150, 150, 150, 0.2],
              outline: {
                color: "#FFEB00",
                width: 2
              }
            };
            break;
        }
        return graphic;
      }
      // Helper function for formatting number labels with commas
      function numberWithCommas(value) {
        value = value || 0;
        return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
      }
      // 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: 13,
            yoffset: 3,
            font: { // autocast as Font
              size: 14,
              family: "sans-serif"
            }
          }
        });
      }
    });
  </script>
</head>
<body>
  <div id="viewDiv"></div>
  <div id="cedarPanel" class="esri-widget">
  </div>
  <div id="instructions" class="esri-widget">
    Click on the
    <b>center</b> point and drag it to move the buffer. Click on the
    <b>edge</b> point and drag it to resize the buffer.
  </div>
</body>
</html>
Output

You can jump to the latest bin by adding /latest to your URL

Dismiss x
public
Bin info
jgravoispro
0viewers