Save drawings separately
Overview
By default, the library stores all drawings and drawing groups within the chart layout. This means that drawings are locked to a specific layout; if a user creates a new layout, they must recreate their drawings from scratch.
The library API offers an alternative approach where drawings can be stored separately from the chart layout.
Key advantages
- Per-symbol drawings: drawings can be associated with individual symbols. This allows a user to see the same drawings on, for example, "AAPL" regardless of which saved layout they open. This enables reuse and brings flexibility across different layouts or charts.
- Efficient data size management: separating drawings from the chart layout properties reduces the size of the saved objects, optimizing load times and data storage on your server.
How to enable
To enable saving drawings separately, follow the steps below:
- Add
saveload_separate_drawings_storageto theenabled_featuresarray in the Widget Constructor. - Implement additional methods for storing drawings separately based on your chosen approach:
How to migrate
If you are moving from the default "combined" storage to separate storage for drawings, you need to migrate your existing data.
The library still supports loading chart layouts with drawings even when separate storage is enabled through saveload_separate_drawings_storage.
To migrate a layout:
- Track migration status. We recommend storing a flag that signifies in which mode the layout was saved. This flag will help you understand whether the data has already been migrated. Handling the saving and loading of this optional flag falls outside the API's scope and remains a detail to be implemented within your code.
- Listen for the
chart_load_requestedevent to occur. - Invoke the
saveChartToServermethod on the widget to trigger the layout to be saved again.
Migration with low-level API
If you use the low-level API methods, saving the chart layout requires storing the following two pieces of data.
widget.save(
(chartLayoutState) => {
const drawings = widget.activeChart().getLineToolsState();
// Send or save state as required...
// save drawings
// save chartLayoutState
},
{ includeDrawings: false }
);
Save load adapter (API handlers)
If you use custom API handlers for saving and loading, implement two additional methods in the IExternalSaveLoadAdapter object:
saveLineToolsAndGroupslets you capture and store the current drawings state from your chart.loadLineToolsAndGroupsenables the loading of saved drawings back to the chart.
Consider the following example which implements these methods:
class SaveLoadAdapterWithDrawings extends SaveLoadAdapter {
constructor() {
super();
this.drawings = {};
}
async saveLineToolsAndGroups(layoutId, chartId, state) {
const drawings = state.sources;
if (!this.drawings[this._getDrawingKey(layoutId, chartId)]) {
this.drawings[this._getDrawingKey(layoutId, chartId)] = {}
}
for (let [key, state] of drawings) {
if (state === null) {
delete this.drawings[this._getDrawingKey(layoutId, chartId)][key];
} else {
this.drawings[this._getDrawingKey(layoutId, chartId)][key] = state;
}
}
}
async loadLineToolsAndGroups(layoutId, chartId) {
const rawSources = this.drawings[this._getDrawingKey(layoutId, chartId)];
if (!rawSources) return null;
const sources = new Map();
for (let [key, state] of Object.entries(rawSources)) {
sources.set(key, state);
}
return {
sources
};
}
_getDrawingKey(layoutId, chartId) {
return `${layoutId}/${chartId}`
}
}
Context and sharing mode
A single user action may trigger multiple saveLineToolsAndGroups calls.
The context argument provides metadata that explains why a particular call is being made and where the data should be routed.
The sharingMode property in the context indicates the scope of the drawings:
NotShared: the default state when no sync toggles are active. These drawings belong only to a specific chart instance.SharedInLayout: applies when the Sync drawings to all charts toggle is active. These drawings sync only between charts within the same layout.GloballyShared: applies when the New drawings sync globally toggle is active on the drawing toolbar. Drawings associated with the symbol appear on any layout where that symbol is loaded
Save/load adapters should persist each call according to the sharingMode to ensure the user's settings are respected.
Handling deletions
The library does not use a separate delete method.
Deletions are encoded as null values (tombstones) within the state.sources or state.groups maps.
Your backend should remove the corresponding records when a null value is received.
See Understanding the LineToolsAndGroupsState interface.
REST API
If you use the REST API for chart storage, you should implement the following endpoints in addition to the endpoints mentioned in the Develop your own storage section.
Save drawings
POST request: charts_storage_url/charts_storage_api_version/drawings?client=client_id&user=user_id&chart=chart_id&layout=layout_id
state:LineToolsAndGroupsStateobject
RESPONSE: JSON Object
status:okorerror
Load drawings
GET request: charts_storage_url/charts_storage_api_version/drawings?client=client_id&user=user_id&chart=chart_id&layout=layout_id
RESPONSE: JSON Object
status:okorerrordata: Objectstate:LineToolsAndGroupsStateobject
Low-level API methods
The low-level API has additional methods on the chart widget when you enable the saveload_separate_drawings_storage featureset.
getLineToolsState
This function captures the current drawings state from the chart. You can benefit from this if you need to programmatically capture and store the drawings state.
const state = widget.activeChart().getLineToolsState();
// Send or save state as required...
applyLineToolsState
Enable restoring the drawings on the chart by implementing a previously saved LineToolsAndGroupsState object.
const state = // previously saved state
widget.activeChart().applyLineToolsState(state).then(() => {
console.log('Drawings state restored!');
});
reloadLineToolsFromServer
Triggers a re-request of drawings from the server (via the Save Load Adapter or REST API depending on your implementation).
widget.activeChart().reloadLineToolsFromServer();
Customizing the chart save method
The save method on the IChartingLibraryWidget interface now includes options to adjust its behavior. There is an includeDrawings option in SaveChartOptions which determines whether to include drawings in the chart layout object returned by the save method. This can be useful in conjunction with the low-level API methods described above.
widget.save(state => {
// Handle saved state...
}, { includeDrawings: false });
Understanding the LineToolsAndGroupsState interface
The LineToolsAndGroupsState interface plays a crucial role in maintaining the state of chart drawings, providing a structure for both individual drawings and drawing groups.
The sources property is a Map, which constructs key-value pairs to represent a distinct drawing. The key, in this case, is an identifier or UUID for a drawing, and the value accompanies a state object exclusive to that drawing. The state object also encapsulates the symbol associated with the drawings, adding another defining layer to the data representation.
Similarly, the groups property is a Map accounting for groups of drawings. Each key-value pair comprises an identifier key or UUID and an array of UUIDs forming the drawing group.
For both sources and groups, a UUID associated with a null value indicates that the respective drawing or drawing group is to be removed. This signifies when a previously existing drawing has been deleted by the user and is no longer present on the chart.
The state objects (LineToolState and LineToolsGroupState) which represent the drawings' state should be treated essentially as black boxes. They are managed by the library and are not expected to be directly modified outside of it.