Update the orientation of a graphic using expressions based on its attributes.
Use case
Instead of reading the attribute and changing the rotation on the symbol for a single graphic (a manual CPU operation), you can bind the rotation to an expression that applies to the whole overlay (an automatic GPU operation). This usually results in a noticeable performance boost (smooth rotations).
How to use the sample
Adjust the heading and pitch sliders to rotate the cone.
How it works
- Create a new graphics overlay.
- Create a simple renderer and set its scene properties.
- Set the heading expression to
[HEADING]
. - Set the pitch expression to
[PITCH]
. - Apply the renderer to the graphics overlay.
- Create a graphic and add it to the overlay.
- To update the graphic's rotation, update the
HEADING
orPITCH
property in the graphic's attributes.
Relevant API
- Graphic.Attributes
- GraphicsOverlay
- SceneProperties
- SceneProperties.headingExpression
- SceneProperties.pitchExpression
- SimpleRenderer
- SimpleRenderer.SceneProperties
Tags
3D, expression, graphics, heading, pitch, rotation, scene, symbology
Sample Code
/* Copyright 2025 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.esri.arcgismaps.sample.applyscenepropertyexpressions.components
import android.app.Application
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.arcgismaps.Color
import com.arcgismaps.geometry.Point
import com.arcgismaps.geometry.SpatialReference
import com.arcgismaps.mapping.ArcGISScene
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.mapping.view.SurfacePlacement
import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbol
import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbolStyle
import com.arcgismaps.mapping.symbology.SimpleRenderer
import com.arcgismaps.mapping.view.Camera
import com.arcgismaps.mapping.view.Graphic
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
import kotlinx.coroutines.launch
class ApplyScenePropertyExpressionsViewModel(app: Application) : AndroidViewModel(app) {
// The ArcGISScene shown in the SceneView
val arcGISScene = ArcGISScene(BasemapStyle.ArcGISImageryStandard).apply {
// Set initial viewpoint with a camera looking at the cone
val point = Point(x = 83.9, y = 28.4, z = 1000.0, spatialReference = SpatialReference.wgs84())
initialViewpoint = Viewpoint(
latitude = 0.0,
longitude = 0.0,
scale = 1.0,
camera = Camera(lookAtPoint = point, distance = 1000.0, heading = 0.0, pitch = 65.0, roll = 0.0)
)
}
// UI state for heading and pitch sliders
var heading by mutableDoubleStateOf(180.0)
private set
var pitch by mutableDoubleStateOf(45.0)
private set
// Create the cone symbol
private val coneSymbol = SimpleMarkerSceneSymbol(
style = SimpleMarkerSceneSymbolStyle.Cone, color = Color.red, height = 100.0, width = 100.0, depth = 100.0
)
// The cone graphic, with mutable heading and pitch attributes
private val coneGraphic = Graphic(
geometry = Point(x = 83.9, y = 28.42, z = 200.0, spatialReference = SpatialReference.wgs84()),
attributes = mapOf(
"HEADING" to heading, "PITCH" to pitch
),
symbol = coneSymbol
)
// GraphicsOverlay with heading and pitch expressions
val graphicsOverlay: GraphicsOverlay = GraphicsOverlay().apply {
sceneProperties.surfacePlacement = SurfacePlacement.Relative
// Set up renderer with heading and pitch expressions
renderer = SimpleRenderer().apply {
sceneProperties.headingExpression = "[HEADING]"
sceneProperties.pitchExpression = "[PITCH]"
}
graphics.add(coneGraphic)
}
// Message dialog for error handling
val messageDialogVM = MessageDialogViewModel()
init {
// Load the scene and handle errors
viewModelScope.launch {
arcGISScene.load().onFailure { messageDialogVM.showMessageDialog(it) }
}
}
// Called when the user changes the heading slider
fun updateHeading(newHeading: Double) {
heading = newHeading
coneGraphic.attributes["HEADING"] = heading
}
// Called when the user changes the pitch slider
fun updatePitch(newPitch: Double) {
pitch = newPitch
coneGraphic.attributes["PITCH"] = pitch
}
}