AnsweredAssumed Answered

Visualize data with class breaks - joining JSON data in v4

Question asked by csgeosol1 Champion on Oct 30, 2018
Latest reply on Nov 6, 2018 by rscheitlin

Years ago, there was a sample to visualize dynamic JSON by "joining" to a static FeatureLayer "template" by a shared pk (state, in this case). It is no longer available as the data source went offline, but here's the sample code to give you an idea of what it was doing:

 

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
    <title></title>
    <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css">
    <link rel="stylesheet" href="css/styles.css">
    <script>var dojoConfig = {
      packages: [{
        name: "extras",
        location: location.pathname.replace(/\/[^/]+$/, "") + "/extras"
      }]
    };
    </script>
    <script src="http://js.arcgis.com/3.11/"></script>
    <script>
      require([
        "dojo/parser",
        "dojo/json",
        "dojo/_base/array",
        "dojo/_base/connect",
        "esri/Color",
        "dojo/number",
        "dojo/dom-construct",
       
        "esri/map",
        "esri/geometry/Extent",
        "esri/symbols/SimpleLineSymbol",
        "esri/symbols/SimpleFillSymbol",
        "esri/renderers/SimpleRenderer",
        "esri/renderers/ClassBreaksRenderer",

        "esri/layers/FeatureLayer",
        "esri/dijit/Legend",
        "esri/request",
        "extras/Tip",
       
        "dijit/layout/BorderContainer",
        "dijit/layout/ContentPane",
        "dojo/domReady!"
      ], function(
        parser, JSON, arr, conn, Color, number, domConstruct,
        Map, Extent, SimpleLineSymbol, SimpleFillSymbol, SimpleRenderer, ClassBreaksRenderer,
        FeatureLayer, Legend, esriRequest, Tip) {
          // call the parser to create the dijit layout dijits
          parser.parse();

          var bounds = new Extent({"xmin":-2332499,"ymin":-1530060,"xmax":2252197,"ymax":1856904,"spatialReference":{"wkid":102003}});
          window.map = new Map("map", {
            extent: bounds,
            lods: [{"level":0, "resolution": 3966, "scale": 15000000}],
            slider: false
          });

          window.fl = new FeatureLayer("http://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer/3", {
            maxAllowableOffset: window.map.extent.getWidth() / window.map.width,
            mode: FeatureLayer.MODE_SNAPSHOT,
            outFields: ["STATE_NAME"],
            visible: true
          });
          // override default renderer so that states aren't drawn
          // until the gas price data has been loaded and is joined
          fl.setRenderer(new SimpleRenderer(null));

          var template = "<strong>${STATE_NAME}:  $${GAS_DISPLAY}</strong>";
          window.tip = new Tip({
            "format": template,
            "node": "legend"
          });

          var updateEnd = fl.on("update-end", function() {
            // get gas price data
            // using apify:  http://apify.heroku.com/resources
            // edit the apify thing:  http://apify.heroku.com/resources/53b34e28d804760002000023/edit
            updateEnd.remove();
            var prices = esriRequest({
              // url: "http://apify.heroku.com/api/aaagasprices.json",
              // Alternatively, fallback to a local file, use this if APIfy is unavailable.
              url: "fallback-gas-price-data.json",
              callbackParamName: "callback"
            });
            prices.then(drawFeatureLayer, pricesError);
           
            // wire up the tip
            fl.on("mouse-over", window.tip.showInfo);
            fl.on("mouse-out", window.tip.hideInfo);
          });

          window.map.addLayer(fl);

          function drawFeatureLayer(data) {
            // If data comes back as text (which it does when coming from apify), parse it.
            var gas = (typeof data === "string" ) ? JSON.parse(data) : data;
            console.log("join prices, number of graphics: ", fl.graphics.length);

            // loop through gas price data, find min/max and populate an object
            // to keep track of the price of regular in each state
            window.statePrices = {};
            var gasMin = Infinity;
            var gasMax = -Infinity;
            arr.forEach(gas, function(g) {
              if ( g.state !== "State" ) {
                var price = parseFloat(parseFloat(g.regular.replace("$", "")).toFixed(2));
                statePrices[g.state] = price;
                if ( price < gasMin ) {
                  gasMin = price;
                }
                if ( price > gasMax ) {
                  gasMax = price;
                }
              }
            });
            // format max gas price with two decimal places
            gasMax = formatDollars(gasMax);
            // add an attribute to each attribute so gas price is displayed
            // on mouse over below the legend
            arr.forEach(fl.graphics, function(g) {
              var displayValue = statePrices[g.attributes.STATE_NAME].toFixed(2);
              g.attributes.GAS_DISPLAY = displayValue;
            });

            // create a class breaks renderer
            var breaks = calcBreaks(gasMin, gasMax, 4);
            // console.log("gas price breaks: ", breaks);
            var SFS = SimpleFillSymbol;
            var SLS = SimpleLineSymbol;
            var outline = new SLS("solid", new Color("#444"), 1);
            var br = new ClassBreaksRenderer(null, findGasPrice);
            br.setMaxInclusive(true);
            br.addBreak(breaks[0], breaks[1], new SFS("solid", outline, new Color([255, 255, 178, 0.75])));
            br.addBreak(breaks[1], breaks[2], new SFS("solid", outline, new Color([254, 204, 92, 0.75])));
            br.addBreak(breaks[2], breaks[3], new SFS("solid", outline, new Color([253, 141, 60, 0.75])));
            br.addBreak(breaks[3], gasMax, new SFS("solid", outline, new Color([227, 26, 28, 0.75])));

            fl.setRenderer(br);
            fl.redraw();

            var legend = new Legend({
              map: window.map,
              layerInfos: [{ "layer": fl, "title": "Regular Gas" }]
            },"legend");
            legend.startup();

            // remove the loading div
            domConstruct.destroy("loading");
          }
         
          // function used by the class breaks renderer to get the
          // value used to symbolize each state
          function findGasPrice(graphic) {
            var state = graphic.attributes.STATE_NAME;
            return statePrices[state];
          }

          function calcBreaks(min, max, numberOfClasses) {
            var range = (max - min) / numberOfClasses;
            var breakValues = [];
            for ( var i = 0; i < numberOfClasses; i++ ) {
              breakValues[i] = formatDollars(min + ( range * i ));
            }
            // console.log("break values: ", breakValues);
            return breakValues;
          }

          function formatDollars(num) {
            return number.format(num, { "places": 2 });
          }
         
          function pricesError(e) {
            console.log("error getting gas price data: ", e);
          }
        }
      );
    </script>
  </head>
 
  <body>

    <div id="loading" class="shadow loading">
      Getting Latest Gas Price Data...
      <img src="http://dl.dropbox.com/u/2654618/loading_gray_circle.gif">
    </div>

    <div id="legend" class="shadow info"></div>

    <div data-dojo-type="dijit.layout.BorderContainer"
         data-dojo-props="design:'headline',gutters:false"
         style="width: 100%; height: 100%; margin: 0;">
      <div id="map"
           data-dojo-type="dijit.layout.ContentPane"
           data-dojo-props="region:'center'">
       
        <div id="title" class="shadow info">Current Gas Prices by State</div>

      </div>
    </div>
  </body>
</html>

 

Back in 2014, we ran with this idea for our application in v3 since our maps were a tertiary consideration for a mature product, and it was the easiest way to add choropleths within an existing framework... we simply formatted the data returned by a SQL stored procedure as JSON and "joined" it to a static "template" FeatureLayer map service on the front end, manually calculating breaks along the way (we found a nice JS stats library for this), setting tooltips, etc.

 

Revisiting this in v4, we'd like to do something similar to Visualize data with class breaks | ArcGIS API for JavaScript 4.9  - what's the best way to accomplish this? I don't see any specific samples regarding JSON... it seems most samples either have the data in the map service, or join to tables in the workspace. This, unfortunately, is not an option for us.

 

Thanks!

Outcomes