Skip to main content

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:

  1. Add saveload_separate_drawings_storage to the enabled_features array in the Widget Constructor.
  2. 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:

  1. 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.
  2. Listen for the chart_load_requested event to occur.
  3. Invoke the saveChartToServer method 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:

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
info

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

  1. state: LineToolsAndGroupsState object

RESPONSE: JSON Object

  1. statusok or error

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

  1. statusok or error
  2. data: Object
    1. state: LineToolsAndGroupsState object

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.

caution

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.