Plot feature attributes on a dynamic chart that updates as users pan and zoom, and respond to chart interactions by modifying feature layer contents. This demo relies on Chart.js to render an interactive scatterplot.
<html>
<head>
<meta charset="utf-8" />
<title>Add a dynamic chart (Chart.js)</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" crossorigin=""></script>
<!-- Load Esri Leaflet from CDN -->
<script src="https://unpkg.com/esri-leaflet@3.0.10/dist/esri-leaflet.js"></script>
<!-- Load Esri Leaflet Vector from CDN -->
<script src="https://unpkg.com/esri-leaflet-vector@4.2.5/dist/esri-leaflet-vector.js" crossorigin=""></script>
<style>
html,
body,
#map {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
color: #323232;
}
</style>
</head>
<body>
<!-- Load Chart.js from CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.0/dist/chart.min.js"></script>
<style>
.chart-container {
height: 245px;
width: 245px;
}
#info-pane {
position: absolute;
top: 10px;
right: 10px;
z-index: 400;
padding: 1em;
background: white;
}
</style>
<div id="map"></div>
<div id="info-pane" class="leaflet-bar chart-container">
<canvas id="chartCanvas"></canvas>
</div>
<script>
const accessToken = "YOUR_ACCESS_TOKEN";
const map = L.map("map").setView([45.526, -122.667], 13);
L.esri.Vector.vectorBasemapLayer("arcgis/community", {
token: accessToken
}).addTo(map);
//Features from this layer will appear in the Chart.js scatterplot
const treesFeatureLayer = L.esri.featureLayer({
url: "https://www.portlandmaps.com/arcgis/rest/services/Public/Parks_Misc/MapServer/21/"
});
treesFeatureLayer.addTo(map);
const initialChartData = { //Define a chart
datasets: [
{
label: "Portland Heritage Trees",
data: [] //Data will be update dynamically below
}
]
};
const chartOptions = {
scales: {
x: {
title: {
display: true,
text: "tree diameter"
},
beginAtZero: true,
max: 250,
ticks: {
stepSize: 50
}
},
y: {
title: {
display: true,
text: "tree height"
},
beginAtZero: true,
max: 250,
ticks: {
stepSize: 50
}
}
},
maintainAspectRatio: false, // Turn off animations during chart data updates
animation: false,
onHover: handleChartHover
};
const chart = new Chart("chartCanvas", {
type: "scatter",
data: initialChartData,
options: chartOptions
});
map.on("zoom move", updateChart); //Show in the chart only the features in the map's current extent
treesFeatureLayer.on("load", updateChart);
function updateChart() {
// reformat the features' attributes of interest into
// the data array format required by the Chart.js scatterplot
const scatterPlotDataArray = [];
treesFeatureLayer.eachActiveFeature(function (e) {
scatterPlotDataArray.push({
x: e.feature.properties.DIAMETER,
y: e.feature.properties.HEIGHT,
featureId: e.feature.id
});
});
// assign the new scatterPlotDataArray to the chart's data property
chart.data.datasets[0].data = scatterPlotDataArray;
chart.update();
}
function handleChartHover(event, elements, chart) {
if (!elements.length) {
treesFeatureLayer.eachFeature(function (e) {
e.setOpacity(1);
e.setZIndexOffset(0);
});
return;
}
const hoverFeatureIds = elements.map(function (datum) {
return chart.data.datasets[datum.datasetIndex].data[datum.index].featureId;
});
treesFeatureLayer.eachFeature(function (e, idx) {
if (hoverFeatureIds.indexOf(e.feature.id) > -1) {
e.setOpacity(1);
e.setZIndexOffset(10000);
} else {
e.setOpacity(0.1);
}
});
}
</script>
</body>
</html>