This fourth tutorial will explain how charts synchronization works.

This tutorial will help you understand how-to:

  1. Synchronize charts using events
  2. Synchronize multiple charts' layouts using a SizeManager

What do you need?

Synchronizing charts events

When you have, in your application, distinct charts with related data, it may be relevant to synchronize them (axes, selection, highlighted item...).

The standard case is when you want to visualize different dataset values through different charts.

Let's do this and display 3 different cities' weather charts. Start by loading the weather statistics for Chicago, Mumbai, and Auckland, then create a chart for each city.

//run highlight-only=true from=35 to=47
import io.data2viz.charts.chart.Chart
import io.data2viz.charts.chart.chart
import io.data2viz.charts.chart.discrete
import io.data2viz.charts.chart.mark.bar
import io.data2viz.charts.chart.quantitative
import io.data2viz.charts.core.formatToInteger
import io.data2viz.charts.viz.VizContainer
import io.data2viz.charts.viz.newVizContainer
import io.data2viz.format.Locales
import io.data2viz.geom.Size
import kotlinx.browser.window
import kotlinx.html.div
import kotlinx.html.dom.append
import org.w3c.dom.Node
import org.w3c.fetch.Response
import kotlin.js.Promise

private val width = 500.0
private val height = 160.0

private val months =
    listOf("Jan.", "Feb.", "Mar.", "Apr.", "May", "Jun.", "Jul.", "Aug.", "Sep.", "Oct.", "Nov.", "Dec.")

private val year = 2017
private val citiesNames = listOf("Chicago", "Mumbai", "Auckland")

fun Node.createMultiChart() {

    val request: Promise<Response> =
        window.fetch("<https://docs.google.com/spreadsheets/d/e/2PACX-1vTX4QuCNyDvUoAwk6Jl6UJ4r336A87VIKQ5BVyEgowXG_raXdFBMvmUhmz1LLc07GavyC9J6pZ4YHqJ/pub?gid=650761999&single=true&output=csv>")

    request.then { response ->
        response.text().then { csvContent ->
            
            val weatherData = parseWeatherCSV(csvContent)
            val citiesWeather = citiesNames.map { filterData(weatherData, it) }

            append {
                val charts = citiesNames.mapIndexed { index, cityName ->
                    div {
                        newVizContainer().run {
                            size = Size(width, height)
                            buildChart(citiesWeather[index], cityName)
                        }
                    }
                }
            }
        }
    }
}

private fun filterData(weatherData: List<Weather>, cityName: String): List<Weather> =
    weatherData.filter { weather: Weather ->
        weather.city == cityName && weather.year == year
    }

private fun VizContainer.buildChart(chicago2017: List<Weather>, cityName: String): Chart<Weather> =
    chart(chicago2017) {

        val monthDimension = discrete({ domain.month }) {
            formatter = { months[this - 1] }
        }
        val temperatureDimension = quantitative({ domain.avgTemp }) {
            name = "Avg temp for $cityName"
            formatter = { "${this.formatToInteger(Locales.en_US)} °F" }
        }

        bar(monthDimension, temperatureDimension)
    }

Copy this code (get the whole code by hitting "+") in a MultiChart.kt file, call the createMultiChart() function from your client.kt file and you should see this:

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/1e39c072-745b-4396-811b-4c47355311ad/Untitled.png

Now, synchronizing events for these 3 charts is just as easy as it seems: listen for an event on a chart and propagate it on the 2 others.

Note that you can't just "pass" the event, you have to decompose it, but all useful information is already present:

//run highlight-only=true from=50 to=56
import io.data2viz.charts.chart.Chart
import io.data2viz.charts.chart.chart
import io.data2viz.charts.chart.discrete
import io.data2viz.charts.chart.mark.bar
import io.data2viz.charts.chart.quantitative
import io.data2viz.charts.core.formatToInteger
import io.data2viz.charts.event.HighlightEvent
import io.data2viz.charts.viz.VizContainer
import io.data2viz.charts.viz.newVizContainer
import io.data2viz.format.Locales
import io.data2viz.geom.Size
import kotlinx.browser.window
import kotlinx.html.div
import kotlinx.html.dom.append
import org.w3c.dom.Node
import org.w3c.fetch.Response
import kotlin.js.Promise

private val width = 500.0
private val height = 160.0

private val months =
    listOf("Jan.", "Feb.", "Mar.", "Apr.", "May", "Jun.", "Jul.", "Aug.", "Sep.", "Oct.", "Nov.", "Dec.")

private val year = 2017
private val citiesNames = listOf("Chicago", "Mumbai", "Auckland")

fun Node.createMultiChart() {

    val request: Promise<Response> =
        window.fetch("<https://docs.google.com/spreadsheets/d/e/2PACX-1vTX4QuCNyDvUoAwk6Jl6UJ4r336A87VIKQ5BVyEgowXG_raXdFBMvmUhmz1LLc07GavyC9J6pZ4YHqJ/pub?gid=650761999&single=true&output=csv>")

    request.then { response ->
        response.text().then { csvContent ->

            val weatherData = parseWeatherCSV(csvContent)
            val citiesWeather = citiesNames.map { filterData(weatherData, it) }

            append {
                val charts: MutableList<Chart<Weather>> = mutableListOf()
                citiesNames.mapIndexed { index, cityName ->
                    div {
                        newVizContainer().run {
                            size = Size(width, height)
                            charts += buildChart(citiesWeather[index], cityName)
                        }
                    }
                }

                charts.forEachIndexed { index, chart ->
                    chart.onHighlight { event: HighlightEvent<Weather> ->
                        if (index != 0) charts[0].highlight(event.data)
                        if (index != 1) charts[1].highlight(event.data)
                        if (index != 2) charts[2].highlight(event.data)
                    }
                }
            }
        }
    }
}

private fun filterData(weatherData: List<Weather>, cityName: String): List<Weather> =
    weatherData.filter { weather: Weather ->
        weather.city == cityName && weather.year == year
    }

private fun VizContainer.buildChart(chicago2017: List<Weather>, cityName: String): Chart<Weather> =
    chart(chicago2017) {

        val monthDimension = discrete({ domain.month }) {
            formatter = { months[this - 1] }
        }
        val temperatureDimension = quantitative({ domain.avgTemp }) {
            name = "Avg temp for $cityName"
            formatter = { "${this.formatToInteger(Locales.en_US)} °F" }
        }

        bar(monthDimension, temperatureDimension)
    }

Get the whole code and paste it in your file, refresh, you should see now that the highlight event is synchronized between the 3 charts:

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/1e949889-81b6-4311-a695-abfa8c9b51b6/Untitled.png