Vico

2.2. CartesianChartModelProducer

2.2.1. Overview

A CartesianChart’s data is stored in its CartesianChartModel. Much like a CartesianChart is a collection of CartesianLayers, a CartesianChartModel is a collection of CartesianLayerModels. CartesianChartModels are created via the Transaction-based CartesianChartModelProducer.

2.2.2. CartesianChartModelProducer creation

Create a CartesianChartModelProducer via the constructor. A CartesianChart’s CartesianChartModelProducer mustn’t be replaced—data updates are performed via Transactions—so store the CartesianChartModelProducer in a place with sufficient persistence, such as a ViewModel.

2.2.3. Transaction

Transactions are run via runTransaction, which is a suspending function:

cartesianChartModelProducer.runTransaction { /* ... */ }

This function returns when the update is complete—that is, once a new CartesianChartModel has been generated, and the hosts have been notified. If there’s already an update in progress, the current coroutine is first suspended until the ongoing update’s completion.

How data is added to a Transaction depends on the CartesianLayers in use. We cover this in 2.4.2, 2.5.2, and 2.6.2.

2.2.4. Asynchrony and extras

Transactions are handled off the main thread, meaning that CartesianChartModels are generated and processed asynchronously. Moreover, during an update, two CartesianChartModels may be being dealt with at once—one in the foreground and one in the background. Thus, setup tied to the CartesianChartModel must be performed based on the arguments of library callbacks and interface functions, which may receive CartesianChartModels themselves or related data. (The exact means of accessing the data mentioned differs between APIs, so refer to the API reference. Note that the data may be provided indirectly, via an instance of CartesianMeasuringContext or a subtype thereof.)

For setup derived from series-related data, this is straightforward, with the data being immediately available. However, changes that aren’t directly derived from series-related data may also need to be aligned with CartesianChartModel updates. We thus need a means of sending additional information through the same channel that the usual series-related data goes through. This is where extras come into play.

Extras are a means of adding auxiliary data to CartesianChartModels. They’re stored in ExtraStores and have typed keys (ExtraStore.Key instances), enabling you to save any kind of data in a type-safe manner. To add extras, use Transaction.extras, as shown below. (This is, of course, a simplified example—extras are used for values that change. With a static string, there are no changes and thus no need for synchronization.)

val unitKey = ExtraStore.Key<String>()
cartesianChartModelProducer.runTransaction {
    extras { extraStore ->
        extraStore[unitKey] = "Ω"
        // ...
    }
    // ...
}

Just like series-related data, extras can be read via function parameters. The ExtraStore may be provided directly, or you may have to use CartesianChartModel.extraStore.

ExtraStore.Keys are compared by instance. Given the asynchronous context in which they’re used, it’s important not to recreate or swap them more often than appropriate. As a rule of thumb, all ExtraStore.Key instances used with a particular CartesianChartModelProducer should be persisted at least as long as the CartesianChartModelProducer, and any given property’s ExtraStore.Key should remain unchanged over this period. For the former requirement, a universal approach is to store ExtraStore.Key statically—at the top level, in companion objects, and so on.

For the kind of setup considered in this section, external mechanisms should be avoided:

Such solutions don’t have the tight coupling with the Transaction mechanism that is required for reliable behavior and may produce improper, unpredictable results of varying significance.

2.2.5. Manual CartesianChartModel creation

CartesianChartModelProducer is recommended because it offers performance benefits and supports animations. However, you can create CartesianChartModels manually via the constructor, which takes a list of CartesianLayerModels. When a host receives a CartesianChartModel, it handles it synchronously, so extras are unneeded.