# Interactive Visualizations

> This is the 3rd chapter of the Dash Fundamentals. The previous chapter covered basic callback usage. The next chapter describes how to share data between callbacks. Just getting started? Make sure to install the necessary dependencies.

The `dashCoreComponents` package includes a Graph component called `dccGraph`.

`dccGraph` renders interactive data visualizations using the open source plotly.js JavaScript graphing library. Plotly.js supports over 35 chart types and renders charts in both vector-quality SVG and high-performance WebGL.

The `figure` argument in the `dccGraph` component is the same `figure` argument that is used by `plotly`. Check out the plotly.py documentation and gallery to learn more.

As we already saw, Dash components are described by a set of attributes. Any of these attributes can be updated by callback functions, but only a subset of these attributes are updated through user interaction, such as typing inside a `dccInput` component or clicking an option in a `dccDropdown` component.

The `dccGraph` component has four attributes that can change through user-interaction: `hoverData`, `clickData`, `selectedData`, `relayoutData`. These properties update when you hover over points, click on points, or select regions of points in a graph.

Here's an example that prints these attributes to the screen.

Show code

    library(dash)

    app <- dash_app()
    app %>% add_stylesheet('https://codepen.io/chriddyp/pen/bWLwgP.css')

    pre_style <- list(
      border = 'thin lightgrey solid',
      overflowX = 'scroll'
    )

    figure_data <- list(
      list(
        x = c(1, 2),
        y = c(1, 2),
        name = 'apple',
        mode = 'markers',
        marker = list(size = 20)
      ),
      list(
        x = c(1, 2),
        y = c(3, 4),
        name = 'orange',
        mode = 'markers',
        marker = list(size = 20)
      )
    )

    app %>% set_layout(
      dccGraph(
        id = 'basic-interactions',
        figure = list(
          data = figure_data,
          layout = list(
            clickmode = 'event+select'
          )
        )
      ),

      div(
        className = 'row',

        div(
          dccMarkdown(
            "
              **Hover Data**
              Mouse over values in the graph.
              "
          ),
          html$pre(id = 'hover-data', style = pre_style),
          className = 'three columns'
        ),

        div(
          dccMarkdown(
            "
              **Click Data**
              Click on points in the graph.
              "
          ),
          html$pre(id = 'click-data', style = pre_style),
          className = 'three columns'
        ),

        div(
          dccMarkdown(
            "
              **Selection Data**
              Choose the lasso or rectangle tool in the graph's menu
              bar and then select points in the graph.
              Selection data also accumulates (or un-accumulates) selected
              data if you hold down the shift button while clicking.
              "
          ),
          html$pre(id = 'selected-data', style = pre_style),
          className = 'three columns'
        ),

        div(
          dccMarkdown(
            "
              **Zoom and Relayout Data**
              Click and drag on the graph to zoom or click on the zoom
              buttons in the graph's menu bar.
              Clicking on legend items will also fire
              this event.
              "
          ),
          html$pre(id = 'relayout-data', style = pre_style),
          className = 'three columns'
        )
      )
    )

    json_return <- function(data) {
      jsonlite::prettify(jsonlite::toJSON(data), indent = 2)
    }

    app %>% add_callback(
      output('hover-data', 'children'),
      input('basic-interactions', 'hoverData'),
      function(hoverData) {
        json_return(hoverData)
      }
    )

    app %>% add_callback(
      output('click-data', 'children'),
      input('basic-interactions', 'clickData'),
      function(clickData) {
        json_return(clickData)
      }
    )

    app %>% add_callback(
      output('selected-data', 'children'),
      input('basic-interactions', 'selectedData'),
      function(selectedData) {
        json_return(selectedData)
      }
    )

    app %>% add_callback(
      output('relayout-data', 'children'),
      input('basic-interactions', 'relayoutData'),
      function(relayoutData) {
        json_return(relayoutData)
      }
    )

    app %>% run_app()

Hover Data

Mouse over values in the graph.

    null

Click Data

Click on points in the graph.

    null

Selection Data

Choose the lasso or rectangle tool in the graph's menu bar and then select points in the graph.

Note that if `layout.clickmode = 'event+select'`, selection data also accumulates (or un-accumulates) selected data if you hold down the shift button while clicking.

    null

Zoom and Relayout Data

Click and drag on the graph to zoom or click on the zoom buttons in the graph's menu bar.

    {
      "autosize": true
    }

> For optimal user interaction and chart loading performance, Dash apps in production should consider the Job Queue, HPC, Datashader, and horizontal scaling capabilities of Dash Enterprise.

## Update Graphs on Hover

Let's update our world indicators example from the previous chapter by updating the time series when we hover over points in our scatter plot.

Show code

    library(dash)

    df <- read.csv('https://plotly.github.io/datasets/country_indicators.csv', header = TRUE, sep = ",")
    available_indicators <- unique(df$Indicator.Name)
    option_indicator <- lapply(available_indicators, function(x) list(label = x, value = x))

    app <- dash_app()
    app %>% add_stylesheet('https://codepen.io/chriddyp/pen/bWLwgP.css')

    app %>% set_layout(
      div(
        style = list(
          borderBottom = 'thin lightgrey solid',
          backgroundColor = 'rgb(250, 250, 250)',
          padding = '10px 5px'
        ),

        div(
          dccDropdown(
            id = 'crossfilter-xaxis-column',
            options = option_indicator,
            value = 'Fertility rate, total (births per woman)'
          ),
          dccRadioItems(
            id = 'crossfilter-xaxis-type',
            options = list(list(label = 'Linear', value = 'linear'),
                           list(label = 'Log', value = 'log')),
            value = 'linear',
            labelStyle = list(display = 'inline-block')
          ),
          style = list(width = '49%', display = 'inline-block')
        ),

        div(
          dccDropdown(
            id = 'crossfilter-yaxis-column',
            options = option_indicator,
            value = 'Life expectancy at birth, total (years)'
          ),
          dccRadioItems(
            id = 'crossfilter-yaxis-type',
            options = list(list(label = 'Linear', value = 'linear'),
                           list(label = 'Log', value = 'log')),
            value = 'linear',
            labelStyle = list(display = 'inline-block')
          ),
          style = list(width = '49%', flaot = 'display', display = 'inline-block')
        ),

        div(
          dccGraph(
            id = 'crossfilter-indicator-scatter',
            hoverData = list(points = list(list(customdata = 'Japan')))
          ),
          style = list(
            width ='49%',
            display = 'inline-block',
            padding = '0 20'
          )
        ),

        div(
          dccGraph(id='x-time-series'),
          dccGraph(id='y-time-series'),
          style = list(display = 'inline-block', width = '49%')
        ),

        div(
          dccSlider(
            id = 'crossfilter-year--slider',
            min = 0,
            max = length(unique(df$Year))-1,
            marks = unique(df$Year),
            value = length(unique(df$Year))-1
          ),
          style = list(width = '49%', padding = '0px 20px 20px 20px')
        )
      )
    )

    app %>% add_callback(
      output('crossfilter-indicator-scatter', 'figure'),
      list(
        input('crossfilter-xaxis-column', 'value'),
        input('crossfilter-yaxis-column', 'value'),
        input('crossfilter-xaxis-type', 'value'),
        input('crossfilter-yaxis-type', 'value'),
        input('crossfilter-year--slider', 'value')
      ),
      function(xaxis_column_name, yaxis_column_name, xaxis_type, yaxis_type, year_value) {
        selected_year <- unique(df$Year)[year_value]
        traces <- list()

        if (selected_year %in% unique(df$Year)) {
          filtered_df <- df[df[["Year"]] %in% selected_year, ]
          traces[[1]] <- list(
            x = filtered_df[filtered_df$Indicator.Name %in% xaxis_column_name, "Value"],
            y = filtered_df[filtered_df$Indicator.Name %in% yaxis_column_name, "Value"],
            opacity=0.7,
            text = filtered_df[filtered_df$Indicator.Name %in% yaxis_column_name, "Country.Name"],
            customdata = filtered_df[filtered_df$Indicator.Name %in% yaxis_column_name, "Country.Name"],
            mode = 'markers',
            marker = list(
              'size'= 15,
              'opacity' = 0.5,
              'line' = list('width' = 0.5, 'color' = 'white')
            )
          )

          list(
            'data' = traces,
            'layout'= list(
              xaxis = list('title' = xaxis_column_name, 'type' = xaxis_type),
              yaxis = list('title' = yaxis_column_name, 'type' = yaxis_type),
              margin = list('l' = 40, 'b' = 30, 't' = 10, 'r' = 0),
              height = 450,
              hovermode = 'closest'
            )
          )
        }
      }
    )

    create_time_series <- function(dff, axis_type, title) {
      list(
        'data' = list(list(
          x = dff[['Year']],
          y = dff[['Value']],
          mode = 'lines+markers'
        )),
        'layout' = list(
          height = 225,
          margin = list('l' = 20, 'b' = 30, 'r' = 10, 't' = 10),
          'annotations' = list(list(
            x = 0, 'y' = 0.85, xanchor = 'left', yanchor = 'bottom',
            xref = 'paper', yref = 'paper', showarrow = FALSE,
            align = 'left', bgcolor = 'rgba(255, 255, 255, 0.5)',
            text = title[1]
          )),
          yaxis = list(type = axis_type),
          xaxis = list(showgrid = FALSE)
        )
      )
    }

    app %>% add_callback(
      output('x-time-series', 'figure'),
      list(
        input('crossfilter-indicator-scatter', 'hoverData'),
        input('crossfilter-xaxis-column', 'value'),
        input('crossfilter-xaxis-type', 'value')
      ),
      function(hoverData, xaxis_column_name, axis_type) {
        Country.Name <- hoverData$points[[1]]$customdata
        dff <- df[df[["Country.Name"]] %in% Country.Name, ]
        dff <- dff[dff[["Indicator.Name"]] %in% xaxis_column_name, ]
        title <- paste(c(Country.Name, xaxis_column_name), sep = '<br>')
        create_time_series(dff, axis_type, title)
      }
    )

    app %>% add_callback(
      output('y-time-series', 'figure'),
      list(
        input('crossfilter-indicator-scatter', 'hoverData'),
        input('crossfilter-yaxis-column', 'value'),
        input('crossfilter-yaxis-type', 'value')
      ),
      function(hoverData, yaxis_column_name, axis_type) {
        dff <- df[df[["Country.Name"]] %in% hoverData$points[[1]]$customdata, ]
        dff <- dff[dff[["Indicator.Name"]] %in% yaxis_column_name, ]
        create_time_series(dff, axis_type, yaxis_column_name)
      }
    )

    app %>% run_app()

Fertility rate, total (births per woman)
×
Linear Log
Life expectancy at birth, total (years)
×
Linear Log
1962 1967 1972 1977 1982 1987 1992 1997 2002 2007

Try moving the mouse over the points in the scatter plot on the left. Notice how the line graphs on the right update based on the point that you are hovering over.

Sign up for Dash Club → Two free cheat sheets plus updates from Chris Parmer and Adam Schroeder delivered to your inbox every two months. Includes tips and tricks, community apps, and deep dives into the Dash architecture. Join now.

## Generic Crossfilter Recipe

Here's an example of crossfiltering across a six-column data set. Each scatter plot's selection filters the underlying dataset.

Show code

    library(dash)

    app <- dash_app()
    app %>% add_stylesheet('https://codepen.io/chriddyp/pen/bWLwgP.css')

    df <- as.data.frame(matrix(rnorm(180, seq(0, 50, 10), 0.1), ncol=6, byrow = TRUE))
    colnames(df) <- paste("Column", 1:6)

    app %>% set_layout(
      div(
        div(
          dccGraph(
            id = 'graph1',
            config = list(displayModeBar = FALSE)
          ),
          className = 'four columns'
        ),
        div(
          dccGraph(
            id = 'graph2',
            config = list(displayModeBar = FALSE)
          ),
          className='four columns'
        ),
        div(
          dccGraph(
            id = 'graph3',
            config = list(displayModeBar = FALSE)
          ),
          className = 'four columns'
        ),
        className = 'row'
      )
    )

    callback <- function(g1, g2, g3, x, y){
      selectedDatas = na.omit(c(g1, g2, g3))
      selectedpoints = as.numeric(rownames(df))
      selected_index =  lapply(1:length(selectedDatas[['points']]),
                               function(i) {selectedDatas[['points']][[i]][['customdata']]
                               })
      if(length(selected_index) > 0){
        selectedpoints = intersect(selectedpoints, selected_index)
      }

      data = list(
        x = df[[x]],
        y = df[[y]],
        text = as.numeric(rownames(df)),
        textposition = 'top',
        selectedpoints = unlist(lapply(selectedpoints, function(x){x-1})),
        customdata = as.numeric(rownames(df)),
        type = 'scatter',
        mode = 'markers+text',
        marker = list(
          color = 'rgb(0, 116, 217, 0.7)',
          size = 12,
          line = list(
            color = 'rgb(0, 116, 217)',
            width = 0.5
          )
        ),
        textfont = list(
          color = 'rgb(30, 30, 30, 1)'
        ),
        unselected = list(
          marker =list(
            opacity = 0.3
          ),
          textfont = list(
            # make text transparent when not selected
            color = 'rgb(0, 0, 0, 0)'
          )
        )
      )
      layout = list(
        clickmode = 'event+select',
        margin = list('l' = 15, 'r' = 0, 'b' = 15, 't' = 5),
        dragmode = 'select',
        hovermode = 'closest',
        xaxis = list(range = list(min(df[x]),max(df[x]))),
        yaxis = list(range = list(min(df[y]),max(df[y]))),
        showlegend = FALSE
      )
      figure = list(
        data = list(data
        ),
        layout = layout
      )
      shape = list(
        type = 'rect',
        line = list(
          width = 1,
          dash = 'dot',
          color = 'darkgrey'
        )
      )
      if(is.null(selectedDatas[['range']]) == FALSE){
        figure[['layout']]['shapes'] = list(list(list(
          'x0' = unlist(selectedDatas[['range']][['x']][1]),
          'x1' = unlist(selectedDatas[['range']][['x']][2]),
          'y0' = unlist(selectedDatas[['range']][['y']][1]),
          'y1' = unlist(selectedDatas[['range']][['y']][2]),shape)))
      }
      else{
        figure[['layout']]['shapes'] = list(list(list(
          'type' = 'rect',
          'x0' = min(df[x], na.rm = TRUE),
          'x1' = max(df[x], na.rm = TRUE),
          'y0' = min(df[y], na.rm = TRUE),
          'y1' = max(df[y], na.rm = TRUE),shape)))
      }
      return(figure)
    }

    app %>% add_callback(
      output('graph1', 'figure'),
      list(
        input('graph1', 'selectedData'),
        input('graph2', 'selectedData'),
        input('graph3', 'selectedData')
      ),
      function(g1, g2, g3){
        callback(g1, g2, g3, 'Column 1', 'Column 2')
      }
    )

    app %>% add_callback(
      output('graph2', 'figure'),
      list(
        input('graph2', 'selectedData'),
        input('graph1', 'selectedData'),
        input('graph3', 'selectedData')
      ),
      function(g2, g1, g3){
        callback(g2, g1, g3, 'Column 3', 'Column 4')
      }
    )

    app %>% add_callback(
      output('graph3', 'figure'),
      list(
        input('graph3', 'selectedData'),
        input('graph1', 'selectedData'),
        input('graph2', 'selectedData')
      ),
      function(g3, g1, g2){
        callback(g3, g1, g2, 'Column 5', 'Column 6')
      }
    )

    app %>% run_app()

On every selection, the three graph callbacks are fired with the latest selected regions of each plot. A dataframe is filtered based on the selected points and the graphs are replotted with the selected points highlighted and the selected region drawn as a dashed rectangle.

> As an aside, if you find yourself filtering and visualizing highly-dimensional datasets, you should consider checking out the parallel coordinates chart type.

## Current Limitations

There are a few limitations in graph interactions right now.

- It is not currently possible to customize the style of the hover interactions or the select box. This issue is being worked on in https://github.com/plotly/plotly.js/issues/1847.

There's a lot that you can do with these interactive plotting features. If you need help exploring your use case, open up a thread in the Dash Community Forum.

The next chapter of the Dash Fundamentals explains how to share data between callbacks. Dash Fundamentals Part 4. Sharing Data Between Callbacks