# Complex Plotting

If you've chosen to have full-customization on the dashboard you're going to build, you can use our `charming_fork_zephyr` library and follow Apache Echarts' specification.&#x20;

Below is an example of how it can be implemented for the [Creating the Dashboard](/zephyr-full-customization/learn/custom-dashboards/creating-the-dashboard.md) tutorial:

***

Again as before, we can have all the logic in a single function `build_dashboard`, which takes as argument the `aggregated_data` Map we just built. Let's break down an example:

```rust
use zephyr_sdk::{
    charting::{Dashboard, DashboardEntry, Table}
use charming_fork_zephyr::{component::{Axis, Grid, Legend, Title}, element::{AreaStyle, AxisType, Color, ColorStop, Tooltip, Trigger}, series::{Bar, Line}, Chart};


pub fn build_dashboard<'a>(env: &EnvClient, aggregated_data: HashMap<&'a str, HashMap<&'a str, AggregatedData>>, collaterals: &Vec<Collateral>, borroweds: &Vec<Borrowed>) -> Dashboard {
    let mut dashboard = Dashboard::new().title(&"Blend Porotocol Dashboard").description(&"Explore the Blend protocol's mainnet activity.");
    let categories: Vec<String> = vec!["Supply".into(), "Collateral".into(), "Borrowed".into()];

    for (pool, assets) in aggregated_data {
        let auctions_table = {
            let positions_count = get_from_ledger(env, &pool);
            let table = Table::new().columns(vec!["count".into()]).row(vec![positions_count.to_string()]);
            DashboardEntry::new().title("Current Unique Users With Positions").table(table)
        };

        dashboard = dashboard.entry(auctions_table);

        let val = get_from_instance(env, pool, "Name");
        let ScVal::String(string) = val else {panic!()};
        let pool = string.to_utf8_string().unwrap();
        
        env.log().debug("Iterating over data", None);

        for (asset, data) in assets {
            let meta: StellarAssetContractMetadata = env.from_scval(&get_from_instance(env, asset, "METADATA"));
//            let asset = soroban_string_to_string(env, meta.name);
            let denom = soroban_string_to_string(env, meta.symbol);
            let asset = denom.clone();


            let bar = {
                let chart = Chart::new().legend(Legend::new().show(true).left("150px").top("3%")).tooltip(Tooltip::new().trigger(Trigger::Axis))
                .x_axis(Axis::new().type_(AxisType::Category).data(categories.clone()))
                .y_axis(Axis::new().type_(AxisType::Value)).series(Bar::new()
                .name(format!("Pool: {}, Asset {}", pool, asset))
                .data(vec![data.total_supply as i64 / STROOP as i64, data.total_collateral as i64 / STROOP as i64, data.total_borrowed as i64 / STROOP as i64]));

                DashboardEntry::new().title("Distribution all time").chart(chart)
            };
            
            dashboard = dashboard.entry(bar);
      }
      
      dashboard
}
```

Be aware that to build the charts, we need to import some types from the Zephyr SDK and Charmig. Then we create the dashboard calling `Dashboard::new()`. As you can see we can set a title and a description for the dashboard.&#x20;

Now we start iterating through our map for each pool and asset:

### Building a Table

The first element of our dashboard is going to be a table stating for each liquidity pool the current unique users with a position in it.&#x20;

```rust
for (pool, assets) in aggregated_data {
        let auctions_table = {
            let positions_count = get_from_ledger(env, &pool);
            let table = Table::new().columns(vec!["count".into()]).row(vec![positions_count.to_string()]);
            DashboardEntry::new().title("Current Unique Users With Positions").table(table)
        };

        dashboard = dashboard.entry(auctions_table);
}
```

For each pool in our `aggregated_data` Map, we count how many unique users’ positions there are with the `get_from_ledger(env, &pool)` method, we create a table with `Table::new()` and define the column `count`, while the row will be the count. To add the table to the dashboard, we create a new Dashboard Entry with `DashboardEntry::new()`, set its title, and insert our table. Finally, we can add our entry (the table) to the previously created dashboard.

### Building a Chart

Let's look at an example of how to plot a chart. Specifically, we will create multiple bar charts showing the distribution of supply, collateral, and borrowed amounts over time for each pool and asset. The chart can be defined as follows:

```rust
for (asset, data) in assets {
            let meta: StellarAssetContractMetadata = env.from_scval(&get_from_instance(env, asset, "METADATA"));
            let denom = soroban_string_to_string(env, meta.symbol);
            let asset = denom.clone();


            let bar = {
                let chart = Chart::new().legend(Legend::new().show(true).left("150px").top("3%")).tooltip(Tooltip::new().trigger(Trigger::Axis))
                .x_axis(Axis::new().type_(AxisType::Category).data(categories.clone()))
                .y_axis(Axis::new().type_(AxisType::Value)).series(Bar::new()
                .name(format!("Pool: {}, Asset {}", pool, asset))
                .data(vec![data.total_supply as i64 / STROOP as i64, data.total_collateral as i64 / STROOP as i64, data.total_borrowed as i64 / STROOP as i64]));

                DashboardEntry::new().title("Distribution all time").chart(chart)
            };
```

For each asset and each pool, we obtain the asset name as a string (from a Soroban String—more details in [Working with Data](broken://pages/eshwOk6wiTnmTcBw79xH)). We then create the chart using `Chart::new()`, define its dimensions and basic parameters, and set up the axes with `.x_axis(Axis::new())` and `.y_axis(Axis::new())`. On the x-axis, we want to display the categories: supply, collateral, and borrowed amounts. We specify that the axis type will be 'Category' using `.type_(AxisType::Category)`, and set our categories with `.data(categories.clone())`, defined as follows:

```rust
let categories: Vec<String> = vec!["Supply".into(), "Collateral".into(), "Borrowed".into()];
```

On y instead, we will have the values of the respective categories `.type_(AxisType::Value))` expressed in a bar chart: `.series(Bar::new())`.&#x20;

We then use `.name()` to specify the chart's name, including the specific pool and asset being plotted. Finally, we provide the data to be plotted using `.data()`, accessing the `total_supply`, `total_collateral`, and `total_borrowed` fields of the `AggregatedData` instances. Note that previously, we only aggregated data for `total_supply` (refer to the complete example for details on other aggregations).&#x20;

As before, we create a new entry with `DashboardEntry::new()`, set its title, and add the entry to the dashboard element. Now we are all set and set `dashboard` as the return of our function.&#x20;

***

To continue the tutorial, go back to [Creating the Dashboard](/zephyr-full-customization/learn/custom-dashboards/creating-the-dashboard.md).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mercurydata.app/zephyr-full-customization/learn/custom-dashboards/complex-plotting.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
