Plotting: Simple

Learn how to easily plot dashboard data directly from your Mercury program.

Simple plotting follows a higher-level SDK helper, DashboardBuilder, that in the background relies on the same components as Complex Plotting.

The DashboardBuilder object has three main functions:

  • pub fn add_table(mut self, title: &str, columns: Vec<String>, rows: Vec<Vec<String>>) -> Self : returns a dashboard object with a new table.

  • pub fn add_bar_chart( mut self, title: &str, hover_title: &str, categories: Vec<&str>, data: Vec<i64>) -> Self : returns a dashboard with a new bar chart. Note that categories is generally the explicative x-axis and data the numeric y-axis.

  • pub fn add_line_chart( mut self, title: &str, categories: Vec<String>, series: Vec<(&str, Vec<i64>)>, ) -> Self : returns a dashboard with a new line chart. Note that categories is generally the x-axis. There can be more than one series specified, and each series ( (&str, Vec<i64> ) has a name (the first tuple element) and a set of data (the i64 vector).

Below is how it is implemented for the Creating the Dashboard tutorial:


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 = DashboardBuilder::new(
        "Blend Porotocol Dashboard",
        "Explore the Blend protocol's mainnet activity",
    );

    for (pool, assets) in aggregated_data {
        let positions_count = get_from_ledger(env, &pool);
        dashboard = dashboard.add_table(
            "Current Unique Users With Positions",
            vec!["count".into()],
            vec![vec![positions_count.to_string()]],
        );

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

        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 categories: Vec<&str> = vec!["Supply", "Collateral", "Borrowed"];

                dashboard = dashboard.add_bar_chart(
                    "All-time distribution",
                    &format!("Pool: {}, Asset {}", pool, asset),
                    categories,
                    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,
                    ],
                );
            };

            {
                let line_data: Vec<i64> = data
                    .collateral
                    .iter()
                    .map(|(_, value)| *value as i64 / STROOP as i64)
                    .collect();
                let all_ledgers: Vec<String> = data
                    .collateral
                    .iter()
                    .map(|(ledger, _)| ledger.to_string())
                    .collect();

                dashboard = dashboard.add_line_chart(
                    "Collateral supply evolution",
                    all_ledgers,
                    vec![(
                        &format!("Collateral of pool {} and asset {}", pool, asset),
                        line_data,
                    )],
                );
            };

            {
                let line_data: Vec<i64> = data
                    .borrowed
                    .iter()
                    .map(|(_, value)| *value as i64 / STROOP as i64)
                    .collect();
                let all_ledgers: Vec<String> = data
                    .borrowed
                    .iter()
                    .map(|(ledger, _)| ledger.to_string())
                    .collect();

                dashboard = dashboard.add_line_chart(
                    "Borrow supply evolution",
                    all_ledgers,
                    vec![(
                        &format!("Borrowed of pool {} and asset {}", pool, asset),
                        line_data,
                    )],
                );
            };

            {
                dashboard = dashboard.add_table(
                    &format!("{} pool {} volume", pool, asset),
                    vec!["Timeframe".into(), "Volume".into()],
                    vec![
                        vec![
                            "24hrs".into(),
                            format!("{} {}", data.volume_24hrs as u64 / STROOP as u64, denom),
                        ],
                        vec![
                            "week".into(),
                            format!("{} {}", data.volume_week as u64 / STROOP as u64, denom),
                        ],
                        vec![
                            "month".into(),
                            format!("{} {}", data.volume_month as u64 / STROOP as u64, denom),
                        ],
                    ],
                )
            };
        }
    }

    {
        let mut rows = Vec::new();
        for entry in borroweds.iter().rev() {
            let (kind, amount) = if entry.delta > 0 {
                ("borrow".into(), ((entry.delta as u128) as i64).to_string())
            } else {
                ("repay".into(), ((entry.delta as u128) as i64).to_string())
            };

            rows.push(vec![
                kind,
                entry.timestamp.to_string(),
                entry.ledger.to_string(),
                entry.pool.to_string(),
                entry.asset.to_string(),
                entry.source.to_string(),
                amount,
            ]);
        }

        dashboard = dashboard.add_table(
            "Borrow Actions",
            vec![
                "type".into(),
                "timestamp".into(),
                "ledger".into(),
                "pool".into(),
                "asset".into(),
                "source".into(),
                "amount".into(),
            ],
            rows,
        );
    };

    {
        let mut rows = Vec::new();
        for entry in collaterals.iter().rev() {
            let (kind, amount) = if entry.delta > 0 {
                ("supply".into(), ((entry.delta as u128) as i64).to_string())
            } else {
                (
                    "withdraw".into(),
                    ((entry.delta as u128) as i64).to_string(),
                )
            };

            rows.push(vec![
                kind,
                entry.timestamp.to_string(),
                entry.ledger.to_string(),
                entry.pool.to_string(),
                entry.asset.to_string(),
                entry.source.to_string(),
                amount,
            ]);
        }
        dashboard = dashboard.add_table(
            "Last Collateral Actions",
            vec![
                "type".into(),
                "timestamp".into(),
                "ledger".into(),
                "pool".into(),
                "asset".into(),
                "source".into(),
                "amount".into(),
            ],
            rows,
        );
    };

    env.log().debug("collateral table built", None);

    dashboard.build()
}

Last updated