<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Explore a VoxelLayer using isosurface | Sample | ArcGIS Maps SDK for JavaScript</title>
<!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
<script type="module" src="https://js.arcgis.com/5.0/"></script>
max-height: calc(100vh - 100px);
<arcgis-scene item-id="3a922ed0c7a8489ea4fbe8747ac560ba">
<arcgis-zoom slot="top-left"> </arcgis-zoom>
<arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
<arcgis-compass slot="top-left"> </arcgis-compass>
heading="Temperature Prediction Isosurface"
style="visibility: hidden"
<div id="dvIsosurfaceList"></div>
<calcite-block label="Additional isosurface" expanded>
<calcite-button id="addIsosurface" width="full">Add Isosurface </calcite-button>
style="visibility: hidden"
scale="s"></calcite-color-picker>
<arcgis-expand slot="bottom-left">
<arcgis-legend></arcgis-legend>
const VoxelIsosurface = await $arcgis.import("@arcgis/core/layers/voxel/VoxelIsosurface.js");
// Get references to DOM elements
const viewElement = document.querySelector("arcgis-scene");
const panelVoxelControl = document.getElementById("panelVoxelControl");
const colorPicker = document.getElementById("colorPicker");
const addIsosurface = document.getElementById("addIsosurface");
const dvIsosurfaceList = document.getElementById("dvIsosurfaceList");
// Wait for the view to fully initialize
await viewElement.viewOnReady();
const voxelLayer = viewElement.map.layers.find((layer) => layer.type === "voxel");
// Wait for the voxel layer to be in the component's view
await viewElement.whenLayerView(voxelLayer);
// Voxel isosurfaces are shown when the renderMode is set to surfaces
voxelLayer.renderMode = "surfaces";
voxelLayer.enableIsosurfaces = true;
panelVoxelControl.style.visibility = "visible";
voxelLayer.popupEnabled = true;
let currentVariableStyle = voxelLayer.getVariableStyle(null);
let isosurfaceCount = currentVariableStyle.isosurfaces.length;
let selectedIsosurface = null;
// Creates the DOM elements that updates the properties of an isosurface on a VoxelLayer
const createIsosurfaceItem = function (isosurfaceIndex, isosurface, addNew) {
const itemBlock = document.createElement("calcite-block");
itemBlock.collapsible = false;
itemBlock.expanded = true;
itemBlock.heading = isosurface.label;
itemBlock.label = "item block";
dvIsosurfaceList.appendChild(itemBlock);
const itemLabel = document.createElement("calcite-label");
const itemSwitch = document.createElement("calcite-switch");
itemLabel.layout = "inline-space-between";
itemLabel.textContent = "Color Locked";
itemSwitch.checked = isosurface.colorLocked;
itemBlock.appendChild(itemLabel);
itemLabel.appendChild(itemSwitch);
const itemSlider = document.createElement("calcite-slider");
itemSwitch.classList.add("color-locked-action");
itemLabel.classList.add("color-locked-action");
itemSlider.id = "isosurfaceValueSlider_" + isosurfaceIndex.toString();
//Isosurface can have a value less than the minimum value or greater than the maximum value of the stretch range.
const range = currentVariableStyle.transferFunction.stretchRange;
const min = Math.min(Math.round(range[0]), Math.round(range[1]));
const max = Math.max(Math.round(range[0]), Math.round(range[1]));
itemSlider.min = min > isosurface.value ? isosurface.value : min;
itemSlider.max = max < isosurface.value ? isosurface.value : max;
itemSlider.value = isosurface.value;
itemSlider.labelHandles = true;
itemSlider.step = max - min < 10 ? 0.2 : 1;
itemSlider.addEventListener("calciteSliderInput", function (e) {
const index = isosurfaceList.map((obj) => obj.sliderId).indexOf(itemSlider.id);
selectedIsosurface = currentVariableStyle.isosurfaces.getItemAt(index);
selectedIsosurface.value = itemSlider.value;
colorPicker.value = selectedIsosurface.color.toHex();
const itemDeleteAction = document.createElement("calcite-action");
itemDeleteAction.icon = "trash";
itemDeleteAction.slot = "actions-end";
itemDeleteAction.onclick = function () {
const index = isosurfaceList.map((obj) => obj.sliderId).indexOf(itemSlider.id);
currentVariableStyle.isosurfaces.removeAt(index);
isosurfaceList.splice(index, 1);
isosurfaceCount = currentVariableStyle.isosurfaces.length;
//The maximum number of isosurfaces that can be created is 4.
addIsosurface.disabled = isosurfaceCount < 4 ? false : true;
itemBlock.appendChild(itemDeleteAction);
currentVariableStyle.isosurfaces.add(isosurface);
itemBlock.appendChild(itemSlider);
isosurfaceCount = currentVariableStyle.isosurfaces.length;
addIsosurface.disabled = isosurfaceCount < 4 ? false : true;
itemSwitch.addEventListener("calciteSwitchChange", function () {
// A voxel layer can have multiple isosurface with a maximum of 4 and is stored as an array.
// If an isosurface needs to be modified or removed, it is important to always check the index because it can change (e.g. removing an isosurface in the middle of the array).
// Having a separate array variable that stores all the isosurfaces can help to specify which index to use.*/
const index = isosurfaceList.map((obj) => obj.sliderId).indexOf(itemSlider.id);
selectedIsosurface = currentVariableStyle.isosurfaces.getItemAt(index);
selectedIsosurface.colorLocked = !selectedIsosurface.colorLocked;
//If the isosurface colorLocked is true, the isosurface will use the chosen color from the color picker window.
//If the isosurface colorLocked is false, it will use the color from the colorStops.
if (selectedIsosurface.colorLocked) {
const rect = this.getBoundingClientRect();
let topValue = rect.bottom;
if (rect.bottom + colorPicker.clientHeight >= document.documentElement.clientHeight) {
topValue = rect.top - colorPicker.clientHeight;
colorPicker.style = `position:absolute;top:${topValue}px;right:${window.innerWidth - rect.right}px;z-index:999`;
colorPicker.value = selectedIsosurface.color.toHex();
colorPicker.style = "visibility:hidden";
if (!addNew && isosurfaceIndex === 0) {
selectedIsosurface = !isosurface.colorLocked ? isosurface : selectedIsosurface;
colorPicker.value = isosurface.color.toHex();
// Get the isosurfaces on a VoxelLayer
for (let i = 0; i < isosurfaceCount; i++) {
// A sample on how to update an existing isosurface
const thisIsosurface = currentVariableStyle.isosurfaces.getItemAt(i);
thisIsosurface.value = Math.round(thisIsosurface.value);
createIsosurfaceItem(i, thisIsosurface, false);
addIsosurface.addEventListener("click", function (e) {
currentVariableStyle = voxelLayer.getVariableStyle(null);
isosurfaceCount = currentVariableStyle.isosurfaces.length;
// A voxel layer can have up to 4 isosurfaces
if (isosurfaceCount < 4) {
const range = currentVariableStyle.transferFunction.stretchRange;
const min = Math.min(Math.round(range[0]), Math.round(range[1]));
const max = Math.max(Math.round(range[0]), Math.round(range[1]));
let currentValue = Math.floor((min + max) / 2);
const lockedColor = voxelLayer.getColorForContinuousDataValue(
voxelLayer.currentVariableId,
let newIsosurface = new VoxelIsosurface({
label: "Isosurface " + (isosurfaceCount + 1).toString(),
createIsosurfaceItem(isosurfaceCount, newIsosurface, true);
// Update the color of an isosurface
colorPicker.addEventListener("calciteColorPickerChange", function (e) {
selectedIsosurface.color = colorPicker.value;
// Listen to a click outside the colorPicker element
// close the color picker window
document.addEventListener("click", (e) => {
if (e.target !== colorPicker && !e.target.classList.contains("color-locked-action")) {
colorPicker.style = "visibility: hidden;";