PanelExtensionContext
type PanelExtensionContext = object;
The PanelExtensionContext
exposes properties and methods for writing a custom panel. The
context has methods to subscribe for messages, receive updates, configure your panel's settings,
and render your panel to the UI.
The initPanel function used in
registerPanel accepts a PanelExtensionContext
argument. This argument contains properties and methods for accessing panel data and rendering UI
updates. The initPanel
function also returns an optional cleanup function to run when the
extension panelElement
unmounts.
See the Creating a custom panel guide for more details.
Properties
panelElement
readonly panelElement: HTMLDivElement;
The root element for the panel. Add your panel elements as children under this element.
initialState
readonly initialState: unknown;
Initial panel state
layout
readonly layout: LayoutActions;
Actions the panel may perform related to the user's current layout. See LayoutActions for details.
dataSourceProfile?
readonly optional dataSourceProfile: string;
Identifies the semantics of the data being played back, such as which topics or parameters are semantically meaningful or normalization conventions to use. This typically maps to a shorthand identifier for a robotics framework such as "ros1", "ros2", or "ulog". See the MCAP profiles concept at https://github.com/foxglove/mcap/blob/main/docs/specification/appendix.md#well-known-profiles.
onRender()?
optional onRender: (renderState, done) => void;
Set this property to a function during your panel's initialization.
Foxglove will run context.onRender
whenever your panel needs to re-render during playback.
The function accepts renderState
and a done
callback as its arguments. Render events occur
frequently (60hz, 30hz, etc).
Note: Your onRender
function must call done
after rendering to indicate that the
panel is ready to render the next set of data. The exact placement of this done
invocation
will vary between frameworks and different extensions' logic.
context.onRender = (renderState, done) => {
// Render your UI updates with fields from RenderState
// Call done when you've rendered all the UI for this renderState.
// If your UI framework delays rendering, call done when rendering has actually happened.
done();
};
Parameters
Parameter | Type |
---|---|
renderState | Immutable <RenderState > |
done | () => void |
Returns
void
subscribeMessageRange()?
optional subscribeMessageRange: (args) => () => void;
Subscribe to receive the entire time range of messages for a given topic for the current data source.
See SubscribeMessageRangeArgs for more information on behavior.
Note: This will not read messages for live sources, like foxglove_bridge, rosbridge, or ROS 1
native connections. For those messages you will still need to use context.subscribe()
and
watch("currentFrame")
.
Parameters
Parameter | Type |
---|---|
args | SubscribeMessageRangeArgs |
Returns
Function
A function that will unsubscribe from the topic, cancel the active async iterator, and prevent onNewRangeIterator from being called again.
Returns
void
UNSTABLE_subscribeMessageRange()?
optional UNSTABLE_subscribeMessageRange: (args) => () => void;
Parameters
Parameter | Type |
---|---|
args | SubscribeMessageRangeArgs |
Returns
Function
Returns
void
Deprecated
Renamed to subscribeMessageRange
. Please use that method instead.
Methods
watch()
Call Signature
watch(field): void
Subscribe to updates on this field within the render state. Render will only be invoked when this field changes.
Use context.watch
to indicate which fields in RenderState (e.g. currentFrame
,
currentTime
, previewTime
, parameters
, topics
) should trigger panel re-renders when
their contained values change.
context.watch("topics");
context.watch("currentFrame");
context.watch("parameters");
context.watch("currentTime");
Parameters
Parameter | Type |
---|---|
field | keyof RenderState |
Returns
void
Call Signature
watch(field): void
Subscribe to updates on this field within the render state. Render will only be invoked when this field changes.
Parameters
Parameter | Type |
---|---|
field | "allFrames" |
Returns
void
Deprecated
Calling watch
with allFrames
is deprecated. Use PanelExtensionContext.subscribeMessageRange instead.
saveState()
saveState(state): void
Use context.saveState
to save an arbitrary object as persisted panel state (also known as
panel settings) in the current layout. You can view the current panel state using
Import/export settings.
context.initialState = undefined; // your panel's initial state
context.saveState({ myNum: 2, myBool: false, myStr: "abc" });
Parameters
Parameter | Type | Description |
---|---|---|
state | Partial <unknown > | The state to save. This value should be JSON serializable. |
Returns
void
setParameter()
setParameter(name, value): void
Use context.setParameter
to set a parameter name
to any valid value
(i.e. primitives, dates, Uint8Array
s, and arrays or objects containing these values).
context.setParameter("/param1", "value1");
Parameters
Parameter | Type | Description |
---|---|---|
name | string | The name of the parameter to set. |
value | ParameterValue | The new value of the parameter. |
Returns
void
setSharedPanelState()
setSharedPanelState(state): void
Set the transient state shared by panels of the same type as the caller of this function. This will not be persisted in the layout.
Parameters
Parameter | Type |
---|---|
state | undefined | Record <string , unknown > |
Returns
void
setVariable()
setVariable(name, value): void
Use context.setVariable
to set a
variable name
to any valid variable
value
.
context.setVariable("myVar", 55);
context.onRender = (renderState: RenderState, done) => {
// Read variable values from the renderState
const variableValues = renderState.variables;
const myVarValue = variableValues.myVar;
// Call done when you've rendered all the UI for this renderState. If your UI framework delays rendering, call done when rendering has actaully happened.
done();
};
Parameters
Parameter | Type | Description |
---|---|---|
name | string | The name of the variable to set. |
value | VariableValue | The new value of the variable. |
Returns
void
setPreviewTime()
setPreviewTime(time): void
Set the active preview time. Setting the preview time to undefined clears the preview time.
Parameters
Parameter | Type |
---|---|
time | undefined | number |
Returns
void
seekPlayback()?
optional seekPlayback(time): void
Seek playback to the given time. Behaves as if the user had clicked the playback bar to seek.
Clients can pass a number or alternatively a Time object for greater precision.
This property may be undefined
if the current data source does not support seeking.
Parameters
Parameter | Type |
---|---|
time | number | Time |
Returns
void
subscribe()
Call Signature
subscribe(subscriptions): void
Use context.subscribe
to indicate the topics your panel wants to receive messages for. The
messages are provided during render in RenderState.currentFrame.
Parameters
Parameter | Type |
---|---|
subscriptions | Subscription [] |
Returns
void
Remarks
This method will update the current subscriptions to the new list of Subscriptions and unsubscribe from any previously subscribed topics no longer in the Subscription list. Passing an empty array will unsubscribe from all topics.
context.subscribe([{ topic: "/some/topic" }, { topic: "/another/topic" }]);
context.subscribe([])
will unsubscribe from all topics, and is equivalent to
unsubscribeAll
.
Range loading
Most panels display data from the current frame; examples of built-in panels that display the current frame are 3D, Image, and Raw Message, however some panels can display data for multiple messages or even the entire dataset duration (Plot, Map, State Transitions).
Subscriptions will provide only the messages for the current frame. If your panel would like to process all the available messages on a topic, use @PanelExtensionContext.subscribeMessageRange | subscribeMessageRange instead.
NOTE: Message range loading is done on a best-effort basis. If your range-loaded messages exceed available memory limits for the browser or desktop app, then the data may not represent the full dataset range. Range loading results in more data transfer and memory use and is recommended only for panels which require access to the entire dataset.
Message converters
Message converters can convert messages from one schema to another – for example, a user might
convert custom GPS message into
foxglove.LocationFix
messages for visualization in the Map
panel. Users may have one or more
message converters registered.
If your panel expects messages with specific schema names, you can leverage registered message converters to convert from one schema to another.
Specify the convertTo
option to enable message conversion on a topic. When conversion is
enabled for a subscription, the MessageEvents will contain message
entries with the
converted message rather than the original message on the topic. The original message is
available in the originalMessageEvent
field in the message event.
context.subscribe([{ topic: "/some/topic", convertTo: "foxglove.LocationFix" }]);
The convertibleTo field within RenderState.topics will contain the names of schemas you can convert this topic into.
Call Signature
subscribe(topics): void
Parameters
Parameter | Type |
---|---|
topics | string [] |
Returns
void
Deprecated
Use subscribe
with an array of Subscription objects instead.
unsubscribeAll()
unsubscribeAll(): void
Unsubscribe from all topics.
Note: This is analagous to calling subscribe([])
with an empty array of topics.
Returns
void
subscribeAppSettings()
subscribeAppSettings(settings): void
Subscribe to any changes in application settings for an array of setting keys.
The keys and their corresponding values are not currently documented and are subject to change.
Parameters
Parameter | Type |
---|---|
settings | string [] |
Returns
void
advertise()?
optional advertise(
topic,
schemaName,
options?): void
Use context.advertise
to indicate an intent to publish a specific datatype on a topic. A
panel must call context.advertise
before being able to publish on the topic
(context.publish
). Options are specific to the data source - some make use of options; others
do not.
This property may be undefined
if the current data source does not support publishing.
context.advertise("/my_image_topic", "sensor_msgs/Image");
options
are specific to each data source - see documentation below for supported data
sources.
Parameters
Parameter | Type | Description |
---|---|---|
topic | string | The topic on which the extension will publish messages. |
schemaName | string | The name of the schema that the published messages will conform to. |
options ? | Record <string , unknown > | Options passed to the current data source for additional configuration. |
Returns
void
Remarks
Native (ROS 1)
field | type | description |
---|---|---|
datatypes | Map<string, T> | JavaScript map of datatype names and MessageDefinition definitions for a given topic |
Common datatype definitions are available in the @foxglove/rosmsg-msgs-common
package.
import { ros1 } from "@foxglove/rosmsg-msgs-common";
context.advertise?.(currentTopic, "sensor_msgs/Joy", {
datatypes: new Map([
["std_msgs/Header", ros1["std_msgs/Header"]],
["std_msgs/Float32", ros1["std_msgs/Float32"]],
["std_msgs/Int32", ros1["std_msgs/Int32"]],
["sensor_msgs/Joy", ros1["sensor_msgs/Joy"]],
]),
});
Foxglove WebSocket
options
depend on the server implementation.
Foxglove Bridge
When using the Foxglove Bridge with ROS data, use context.dataSourceProfile
to determine the
ROS version.
For ROS 1 data, pass datatypes the same way you would for a native ROS 1 connection.
For ROS 2 data, pass datatypes using the following fields:
field | type | description |
---|---|---|
datatypes | Map<string, T> | JavaScript map of datatype names and MessageDefinition definitions for a given topic |
Common datatype definitions are available in the @foxglove/rosmsg-msgs-common
package.
import { ros2humble as ros2 } from "@foxglove/rosmsg-msgs-common";
// For Galactic and lower, use:
// import { ros2galactic as ros2 } from "@foxglove/rosmsg-msgs-common";
context.advertise?.(currentTopic, "sensor_msgs/Joy", {
datatypes: new Map([
["std_msgs/Header", ros2["std_msgs/Header"]],
["std_msgs/Float32", ros2["std_msgs/Float32"]],
["std_msgs/Int32", ros2["std_msgs/Int32"]],
["sensor_msgs/Joy", ros2["sensor_msgs/Joy"]],
]),
});
Rosbridge (ROS 1, ROS 2)
No options
required. Simply publish a JSON message with fields conforming to the advertised
datatype, and the bridge node will serialize it according to the datatype.
unadvertise()?
optional unadvertise(topic): void
Indicate that you no longer want to advertise on this topic.
context.unadvertise("/my_image_topic");
This property may be undefined
if the current data source does not support publishing.
Parameters
Parameter | Type |
---|---|
topic | string |
Returns
void
publish()?
optional publish(topic, message): void
Use context.publish
to publish a message on a previously advertised topic. (You must first
call advertise to advertise the topic before
publishing.) If the topic is not advertised or otherwise malformed, the function will throw an
error.
context.advertise("/my_color_topic", "std_msgs/ColorRGBA");
context.publish("/my_color_topic", { r: 0, g: 1, b: 0, a: 1 });
This property may be undefined
if the current data source does not support publishing.
Parameters
Parameter | Type | Description |
---|---|---|
topic | string | The name of the topic to publish the message on |
message | unknown | The message to publish |
Returns
void
callService()?
optional callService(service, request): Promise<unknown>
Use context.callService
to make a service call to the specified service
with a request payload.
context.callService("my_service", { foo: "bar" });
This property may be undefined
if the current data source does not support services.
Parameters
Parameter | Type | Description |
---|---|---|
service | string | The name of the service to call |
request | unknown | The request payload for the service call |
Returns
Promise
<unknown
>
A promise that resolves when the result is available or rejected with an error
updatePanelSettingsEditor()
updatePanelSettingsEditor(settings): void
Call the updatePanelSettingsEditor
method on your panel's PanelExtensionContext
instance to define or update its settings.
const panelSettings: SettingsTree = {
nodes: { ... },
actionHandler: (action: SettingsTreeAction) => { ... }
};
context.updatePanelSettingsEditor(panelSettings);
The settings
argument must be a valid SettingsTree and include 2 mandatory properties
– nodes
and actionHandler
:
nodes
- Hierarchical structure where each node can contain input fields, display fields, or even other nodesactionHandler
- Function that is invoked when the user interacts with the settings UI; contains logic to process the interactions and update the panel or settings tree
It can also include the following optional properties:
enableFilter
– Whether the settings should show the filter controlfocusedPath
– Node to scroll to (transient one-time effect)
The example tree below has a title
text input field inside a General
section along with an
actionHandler
to respond to updates for the title
field.
const panelSettings: SettingsTree = {
nodes: {
general: {
label: "General",
fields: {
title: "{
label: "Title",
input: "string",
// `panelTitle` refers to a value in your extension panel's config
value: panelTitle,
},
},
},
},
actionHandler: (action: SettingsTreeAction) => {
switch (action.action) {
case "perform-node-action":
// Handle user-defined actions for nodes in the settings tree
break;
case "update":
if (action.payload.path[0] === "general" && action.payload.path[1] === "title") {
// Read action.payload.value for the new panel title value
panelTitle = action.payload.value;
// Update your panel's state accordingly
}
break;
}
},
}
context.updatePanelSettingsEditor(panelSettings);
SettingsTreeAction
A SettingsTreeAction describes how the settings UI should update when a user interacts with its fields.
Each SettingsTreeAction
has a payload
with a path
to the settings field to update (e.g.
["general", "title"]
).
The update
action corresponds to a user setting a new value for a field (e.g. "My new
title").
Special node properties
There are two special SettingsTreeNode properties, label
and visibility
. The value
you specify for label
will control the label displayed in the settings editor. If you set the
renamable
node property to true
, the user can edit the node label
– you will receive a
SettingsTreeAction
of update
with a path ending in label
.
In addition, if you specify a boolean value for visibility
of the node then the settings
editor will provide a button to toggle the visibility of the node and you will receive an
update
action with visibility
as the final element in the path.
For an example of how to use these special properties, check out the panel settings example extension.
Input types
In addition to the string
input type in the example above, the panel API provides a wide
array of types for your extension panel input fields.
Each input type has different properties that you can configure:
autocomplete
boolean
rgb
rgba
gradient
messagepath
select
string
toggle
vec3
vec2
Parameters
Parameter | Type | Description |
---|---|---|
settings | { actionHandler : (action ) => void ; enableFilter : boolean ; focusedPath : readonly string []; nodes : {}; } | - |
settings.actionHandler | (action ) => void | Handler to process all actions on the settings tree initiated by the UI. |
settings.enableFilter ? | boolean | True if the settings editor should show the filter control. |
settings.focusedPath ? | readonly string [] | Setting this will have a one-time effect of scrolling the editor to the node at the path and highlighting it. This is a transient effect so it is not necessary to subsequently unset this. |
settings.nodes | {} | The settings tree root nodes. Updates to these will automatically be reflected in the editor UI. |
Returns
void
setDefaultPanelTitle()
setDefaultPanelTitle(defaultTitle): void
Use context.setDefaultPanelTitle
to override a panel's default title. Users can always
override the default title by editing it manually. If no override or default title is set, the
panel will simply display its type (e.g. "Image").
// Override the default panel title
context.setDefaultPanelTitle(`Plot of ${config.topicName}`);
Parameters
Parameter | Type |
---|---|
defaultTitle | undefined | string |
Returns
void