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) {
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
});
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() {
updateEnd.remove();
var prices = esriRequest({
url: "fallback-gas-price-data.json",
callbackParamName: "callback"
});
prices.then(drawFeatureLayer, pricesError);
fl.on("mouse-over", window.tip.showInfo);
fl.on("mouse-out", window.tip.hideInfo);
});
window.map.addLayer(fl);
function drawFeatureLayer(data) {
var gas = (typeof data === "string" ) ? JSON.parse(data) : data;
console.log("join prices, number of graphics: ", fl.graphics.length);
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;
}
}
});
gasMax = formatDollars(gasMax);
arr.forEach(fl.graphics, function(g) {
var displayValue = statePrices[g.attributes.STATE_NAME].toFixed(2);
g.attributes.GAS_DISPLAY = displayValue;
});
var breaks = calcBreaks(gasMin, gasMax, 4);
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();
domConstruct.destroy("loading");
}
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 ));
}
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!