Select to view content in your preferred language

Migrate an application that uses the D3.js library, from API JS for ArcGIS 3 to API JS for ArcGIS 4

490
2
06-16-2023 10:41 AM
Med-Karim-Rouissi
Emerging Contributor

I am trying to migrate this application which uses the API JS for ArcGIS 3 and the D3.js library, to the API JS for ArcGIS 4 version, the code is retrieved from Github (https://github.com/chelm/esri-d3),
I run into this error:
"Uncaught TypeError: Cannot read properties of undefined (reading 'properties')", apparently it can't read "new d3Layer" on the main code.
If anyone has an idea on a solution or a suggestion, I thank him in advance.


Here is the code for the main file:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=7,IE=9" />
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
<title>Esri ❤️ d3js</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.11/esri/themes/light/main.css">
<style type="text/css">
html, body {
height: 100%; width: 100%;
margin: 0; padding: 0;
}
body{
background-color: #fff; overflow:hidden;
font-family: sans-serif;
}
path {
fill: #08c;
stroke: #fff;
stroke-width:1.5px;
opacity: 0.5;
}
path#Arizona {
fill: #FF0;
}
path#Colorado {
fill: #F49;
}
#map {
position:absolute;
width: 100%;
height: 100%;
}
</style>
<script>
var dojoConfig = {
parseOnLoad: true,
packages: [{
"name": "modules",
"location": location.origin + location.pathname.replace(/\/[^/]+$/, '')
}]
};
</script>
<script src="https://js.arcgis.com/4.11/"></script>
<script>

require([
"esri/Map",
"esri/views/MapView",
"modules/d3Layer",
"dojo/parser",
"dojo/domReady!"
], function(
Map,
MapView,
d3Layer,
parser) {

parser.parse();

var map = new Map({
basemap: "gray"
});

var view = new MapView({
container: "map",
map: map,
center: [-98, 39],
zoom: 4
});
console.log("d3Layer", d3Layer);
layer = new d3Layer('us-states.json', {
styles: [
//{ key: 'fill', value: '#555'}
],
attrs: [{
key: 'id',
value: function(d) {
return d.properties.name;
}
}]
});
console.log("layer", layer);
map.addLayer(layer);
});
</script>
</head>
<body>
<div id="map"></div>
</body>
</html>


Here is the code for d3Layer.js:

define([
    "dojo/_base/declare",
    "dojo/_base/lang",
    "dojo/_base/array",
    "dojo/on",
    "esri/geometry/Point",
    "esri/geometry/support/webMercatorUtils",
    "esri/layers/GraphicsLayer",
    "https://d3js.org/d3.v3.min.js"
  ],
  function(
    declare,
    lang,
    array,
    on,
    Point,
    webMercatorUtils,
    GraphicsLayer,
    d3) {

    var d3Layer = declare("d3Layer", [GraphicsLayer], {

      constructor: function(url, options) {
        var self = this;
        this.url = url;
        this.type = options.type || 'path';
        this.selector = this.type;

        if (options.projection) this._project = options.projection;

        this._styles = options.styles || [];
        this._attrs = options.attrs || [];
        this._events = options.events || [];

        this._path = options.path || d3.geo.path();
        this.path = this._path.projection(lang.hitch(this, self._project));
      },

      _load: function() {
        var self = this;
        d3.json(self.url, function(geojson) {
          self.geojson = geojson;
          self.bounds = d3.geo.bounds(self.geojson);
          self.loaded = true;
          self._render();
          self.onLoad(self);
        });
      },

      //called once the layer's been added to the map
      _setMap: function(map, surface) {
        this._load();

        this._zoomEnd = map.on("zoom-end", lang.hitch(this, function() {
          this._reset();
        }));
        return this.inherited(arguments);
      },

      _unsetMap: function() {
        this.inherited(arguments);
        this._zoomEnd.remove();
      },

      _project: function(x) {
        var p = new Point(x[0], x[1]);
        var point = this._map.toScreen(webMercatorUtils.geographicToWebMercator(p))
        return [point.x, point.y];
      },

      _render: function() {
        var self = this;
        var p = this._paths();
        if (this.type == 'circle') {

          p.data(this.geojson.features)
            .enter().append(this.type)
            .attr("cx", function(d, i) {
              return self._project(d.geometry.coordinates)[0];
            })
            .attr("cy", function(d, i) {
              return self._project(d.geometry.coordinates)[1];
            })
            .attr('r', 10)
            .on('click', function(d) {
              self.select(d, this)
            })
            .on('mouseover', function(d) {
              self.hover(d, this);
            })
            .on('mouseout', function(d) {
              self.exit(d, this);
            });
        } else {

          p.data(this.geojson.features)
            .enter().append(this.type)
            .attr('d', this.path);
        }

        this._styles.forEach(function(s, i) {
          self.style(s);
        });

        this._attrs.forEach(function(s, i) {
          self.attr(s);
        });

        this._events.forEach(function(s, i) {
          self.event(s);
        });

        // assign a class to each feature element that is the ID of the layer
        // this makes it possible to select all primary features, and have secondary ones
        this._paths().attr('class', function(d, el) {
          return d3.select(this).attr('class') + " " + self.id;
        });

        // selector needs to respect the layer id classname we just gave each element
        this.selector += "." + this.id;
      },

      style: function(s) {
        this._paths().style(s.key, s.value);
      },

      attr: function(a) {
        /* at, 3.9+, this method fires with 'data-suspended' as the sole argument before the graphics have drawn for the first time

        it seems like it would be sufficient to check layer.suspended, but it is returning false

        https://developers.arcgis.com/javascript/jsapi/layer-amd.html#suspended
        */
        if (a != "data-suspended" || this.suspended) {
          this._paths().attr(a.key, a.value);
        }

        return this.inherited(arguments);
      },

      event: function(e) {
        this._paths().on(e.type, e.fn);
      },

      _reset: function() {
        var self = this;
        if (this.type == 'circle') {
          this._paths()
            .attr("cx", function(d, i) {
              return self._project(d.geometry.coordinates)[0];
            })
            .attr("cy", function(d, i) {
              return self._project(d.geometry.coordinates)[1];
            })
        } else {
          this._paths().attr('d', this.path)
        }
      },

      _element: function() {
        return d3.select("g#" + this.id + "_layer");
      },

      _paths: function(selector) {
        return this._element().selectAll(selector || this.selector);
      },

      hover: function() {},
      exit: function() {},
      select: function() {}
    });
    return d3Layer;
  });

 

0 Kudos
2 Replies
ReneRubalcava
Honored Contributor

That d3Layer is written for a very old version of the 3x JavaScript library. It would need to be rewritten for 4x without all the dojo modules. As far as I know, no one has undertaken this task. If you are interested, you can look at this Custom WebGL LayerView sample that shows how to extend a GraphicsLayer to create the custom LayerView.

https://developers.arcgis.com/javascript/latest/sample-code/custom-gl-visuals/

Med-Karim-Rouissi
Emerging Contributor

Thanks Rene for your answer, it is important for me to work with D3.js, because I am using solutions based on D3.js version 3. is there a way?

0 Kudos