Powered by AppSignal & Oban Pro

Fundamentals: Configuration & Display

notebooks/03a_fundamentals_config.livemd

Fundamentals: Configuration & Display

Mix.install([
  {:plotly_ex, "~> 0.1"},
  {:kino, "~> 0.18"}
])

Fundamentals: Configuration & Display > Scroll and Zoom

config.scrollZoom: true enables zooming with the mouse wheel or two-finger trackpad scroll. Without it, scroll events pan the page rather than zoom the chart.

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3, 4, 5], y: [2, 4, 1, 5, 3], mode: "lines+markers")],
  layout: %{title: "Scroll to zoom — try the mouse wheel"},
  config: %{scrollZoom: true}
)
|> Plotly.show()

Fundamentals: Configuration & Display > Editable Mode

config.editable: true lets users double-click the chart title, axis labels, and legend entries to rename them directly in the browser — no Elixir round-trip needed.

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3], y: [4, 1, 6], mode: "markers", name: "Series A")],
  layout: %{title: "Double-click any label to edit it"},
  config: %{editable: true}
)
|> Plotly.show()

Fundamentals: Configuration & Display > Making a Static Chart

config.staticPlot: true disables all interactivity — no hover, no zoom, no modebar. Useful for embedding charts in reports where interaction is unwanted.

alias Plotly.{Figure, Bar}

Figure.new(
  data: [Bar.new(x: ["A", "B", "C", "D"], y: [3, 7, 2, 9])],
  layout: %{title: "Static chart — no hover, no zoom"},
  config: %{staticPlot: true}
)
|> Plotly.show()

Fundamentals: Configuration & Display > Customize Download Plot Options

config.toImageButtonOptions controls what the camera-icon modebar button downloads: format ("png", "svg", "jpeg", "webp"), filename, pixel dimensions, and scale factor.

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3, 4, 5], y: [2, 6, 3, 8, 5], mode: "lines+markers")],
  layout: %{title: "Click the camera icon to download"},
  config: %{
    toImageButtonOptions: %{
      format: "svg",
      filename: "my_plot",
      height: 500,
      width: 700,
      scale: 1
    }
  }
)
|> Plotly.show()

Fundamentals: Configuration & Display > Force The Modebar to Always Be Visible

By default the modebar fades in only on hover. config.displayModeBar: true pins it permanently — useful in dashboards where users need constant access to the tools.

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3], y: [4, 5, 6], mode: "lines+markers")],
  layout: %{title: "Modebar always visible"},
  config: %{displayModeBar: true}
)
|> Plotly.show()

Fundamentals: Configuration & Display > Never Display The Modebar

config.displayModeBar: false hides the toolbar entirely — ideal for embedded charts where the toolbar would distract or confuse end users.

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3], y: [4, 5, 6], mode: "lines+markers")],
  layout: %{title: "No modebar"},
  config: %{displayModeBar: false}
)
|> Plotly.show()

Fundamentals: Configuration & Display > Remove ModeBar Buttons

config.modeBarButtonsToRemove takes a list of button names to hide. Common names: "toImage", "zoom2d", "pan2d", "zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d", "hoverClosestCartesian", "hoverCompareCartesian", "toggleSpikelines".

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3, 4, 5], y: [5, 3, 7, 2, 8], mode: "lines+markers")],
  layout: %{title: "Modebar without the download button"},
  config: %{modeBarButtonsToRemove: ["toImage"]}
)
|> Plotly.show()

Remove multiple buttons:

Figure.new(
  data: [Scatter.new(x: [1, 2, 3, 4, 5], y: [5, 3, 7, 2, 8], mode: "lines+markers")],
  layout: %{title: "Zoom buttons removed"},
  config: %{
    modeBarButtonsToRemove: [
      "zoom2d",
      "pan2d",
      "zoomIn2d",
      "zoomOut2d",
      "autoScale2d",
      "resetScale2d"
    ]
  }
)
|> Plotly.show()

Fundamentals: Configuration & Display > Add Buttons to ModeBar

config.modeBarButtonsToAdd accepts a list of button definition maps. Each button has a name, an icon (SVG path or a Plotly.Icons.* reference), and a click handler function.

Limitation in Livebook: Plotly.js button definitions require JavaScript function objects for click and SVG data for icon. These are serialised as JSON strings via Plotly.RawJS — but JSON.parse() in the browser converts them back to plain strings, so the functions are not executable. This config option is best used via a custom Phoenix LiveView component where you can inject raw JavaScript, or via a <script> block in a standalone HTML page.

The code below demonstrates the correct Elixir data structure. The button will appear in the modebar but the click handler will be a string rather than a function.

alias Plotly.{Figure, Scatter, RawJS}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3, 4, 5], y: [2, 4, 1, 5, 3], mode: "lines+markers")],
  layout: %{title: "Chart with custom modebar button structure"},
  config: %{
    displayModeBar: true,
    modeBarButtonsToAdd: [
      %{
        name: "alert_button",
        title: "Click me",
        icon: RawJS.new("Plotly.Icons.camera"),
        click: RawJS.new("function(gd) { alert('Chart id: ' + gd.id); }")
      }
    ]
  }
)
|> Plotly.show()

For a fully functional custom button in a standalone HTML page, use JavaScript directly:

# This is the equivalent JavaScript (for reference — not executable in Livebook):
# config = {
#   modeBarButtonsToAdd: [{
#     name: 'alert_button',
#     title: 'Click me',
#     icon: Plotly.Icons.camera,
#     click: function(gd) { alert('Chart id: ' + gd.id); }
#   }]
# }

Fundamentals: Configuration & Display > Disabling Buttons for Specific Axes

Certain modebar buttons can be disabled on a per-axis basis using the axis-level fixedrange layout property. Setting fixedrange: true on an axis prevents zooming and panning on that axis, disabling the corresponding modebar buttons for it.

This is a layout property, not a config property.

alias Plotly.{Figure, Scatter}

# Lock the y-axis range — users can only zoom/pan horizontally
Figure.new(
  data: [
    Scatter.new(
      x: Enum.to_list(1..20),
      y: Enum.map(1..20, fn x -> :math.sin(x / 3.0) end),
      mode: "lines"
    )
  ],
  layout: %{
    title: "Y-axis is locked (fixedrange: true)",
    yaxis: %{fixedrange: true}
  }
)
|> Plotly.show()
# Lock both axes — effectively disables all zoom/pan
Figure.new(
  data: [
    Scatter.new(
      x: Enum.to_list(1..20),
      y: Enum.map(1..20, fn x -> :math.sin(x / 3.0) end),
      mode: "lines"
    )
  ],
  layout: %{
    title: "Both axes locked",
    xaxis: %{fixedrange: true},
    yaxis: %{fixedrange: true}
  }
)
|> Plotly.show()

Fundamentals: Configuration & Display > Display the Edit Chart Link

Chart Studio was retired on October 31, 2025. The plotly.js “Edit chart” link is deprecated in plotly_ex, and the related config options should be removed:

  • plotlyServerURL
  • showLink
  • linkText
  • showEditInChartStudio
  • showSendToCloud
  • sendData

Fundamentals: Configuration & Display > Customize The Edit Chart Link Text

Chart Studio was retired on October 31, 2025. The edit link it provided is deprecated, so config.linkText is deprecated as well.

Fundamentals: Configuration & Display > Display Edit in Chart Studio Modebar Button

Chart Studio was retired on October 31, 2025. The edit button it provided is deprecated, so config.showEditInChartStudio is deprecated as well.

Fundamentals: Configuration & Display > Change the Default Locale

config.locale sets the interface language for the chart. PlotlyLive automatically loads the corresponding locale registration script from the plotly CDN when this is set — no extra setup needed in Livebook.

Available locales include "fr", "de", "es", "zh-CN", "pt-BR", "ja", and many others. See the full list at https://github.com/plotly/plotly.js/tree/master/dist (files named plotly-locale-*.js).

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3, 4, 5], y: [2, 4, 1, 5, 3], mode: "lines+markers")],
  layout: %{title: "French locale — hover and modebar buttons translated"},
  config: %{locale: "fr"}
)
|> Plotly.show()
# German locale
Figure.new(
  data: [Scatter.new(x: [1, 2, 3, 4, 5], y: [5, 3, 7, 2, 8], mode: "lines+markers")],
  layout: %{title: "German locale"},
  config: %{locale: "de"}
)
|> Plotly.show()

Fundamentals: Configuration & Display > Hide the Plotly Logo on the Modebar

config.displaylogo: false removes the Plotly logo from the modebar. Useful for white-label dashboards or when branding guidelines prohibit third-party logos.

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3, 4, 5], y: [2, 4, 1, 5, 3], mode: "lines+markers")],
  layout: %{title: "No Plotly logo in the modebar"},
  config: %{displaylogo: false}
)
|> Plotly.show()

Fundamentals: Configuration & Display > Making a Responsive Chart

config.responsive: true makes the chart fill its container and automatically resize when the window or panel changes size. Without it, the chart renders at a fixed pixel size.

In Livebook, notebook panels resize frequently — responsive: true is generally recommended for all charts.

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3, 4, 5], y: [2, 4, 1, 5, 3], mode: "lines+markers")],
  layout: %{title: "Resize the panel — chart follows"},
  config: %{responsive: true}
)
|> Plotly.show()

Fundamentals: Configuration & Display > Double Click Delay

config.doubleClickDelay sets the maximum milliseconds between two clicks for them to be recognised as a double-click (default: 300 ms). Plotly uses double-click to reset zoom. Increase this value if users on touch screens or slow devices struggle to trigger double-click resets.

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: [1, 2, 3, 4, 5], y: [2, 4, 1, 5, 3], mode: "lines+markers")],
  layout: %{title: "Double-click delay: 1000 ms — zoom then try double-click to reset"},
  config: %{doubleClickDelay: 1000}
)
|> Plotly.show()

Fundamentals: Configuration & Display > Responsive Plots

A responsive (fluid) plot fills its container and automatically resizes when the window or panel changes size. Set config.responsive: true together with layout.autosize: true.

In Livebook, Kino cells resize dynamically — responsive: true is recommended for all charts to avoid fixed-width overflow.

alias Plotly.{Figure, Scatter}

Figure.new(
  data: [Scatter.new(x: Enum.to_list(1..10), y: [2, 4, 1, 5, 3, 7, 2, 6, 4, 8], mode: "lines+markers")],
  layout: %{title: "Responsive plot — resize the panel to see it reflow", autosize: true},
  config: %{responsive: true}
)
|> Plotly.show()

Fundamentals: Configuration & Display > Persist User Changes

layout.uirevision controls whether plotly.js preserves the user’s interactive state (zoom, pan, legend toggles) when the figure is updated via PlotlyLive.push.

When uirevision stays the same value across updates, the user’s current view is preserved — only the data changes underneath. This is useful for live dashboards where users want to stay zoomed in while data refreshes.

Two-cell pattern: evaluate Cell A first, zoom in on the chart, then evaluate Cell B to push updates. Your zoom will be preserved.

Cell A — create the widget

alias Plotly.{Figure, Scatter}

kino =
  Figure.new(
    data: [Scatter.new(
      x: Enum.to_list(1..20),
      y: Enum.map(1..20, fn _ -> :rand.uniform() * 10 end),
      mode: "lines+markers",
      name: "live data"
    )],
    layout: %{
      title: "Step 0 — zoom in, then run Cell B",
      uirevision: "constant"
    }
  )
  |> Plotly.show()

Cell B — push updates (zoom is preserved)

for i <- 1..8 do
  new_y = Enum.map(1..20, fn _ -> :rand.uniform() * 10 end)

  Figure.new(
    data: [Scatter.new(x: Enum.to_list(1..20), y: new_y, mode: "lines+markers", name: "live data")],
    layout: %{title: "Update #{i} — zoom preserved (uirevision unchanged)", uirevision: "constant"}
  )
  |> then(&Plotly.Kino.PlotlyLive.push(kino, &1))

  Process.sleep(600)
end

:ok

Fundamentals: Configuration & Display > Reset User Changes

When layout.uirevision changes on each update, plotly.js resets the user’s interactive state back to the original view — as if the chart was freshly rendered. This is useful when the data changes so significantly that the old zoom window is no longer meaningful.

Two-cell pattern: evaluate Cell A first, zoom in on the chart, then evaluate Cell B. Your zoom will reset on every update.

Cell A — create the widget

alias Plotly.{Figure, Scatter}

kino =
  Figure.new(
    data: [Scatter.new(
      x: Enum.to_list(1..20),
      y: Enum.map(1..20, fn _ -> :rand.uniform() * 10 end),
      mode: "lines+markers",
      name: "live data"
    )],
    layout: %{
      title: "Step 0 — zoom in, then run Cell B",
      uirevision: 0
    }
  )
  |> Plotly.show()

Cell B — push updates (zoom resets each time)

for i <- 1..8 do
  new_y = Enum.map(1..20, fn _ -> :rand.uniform() * 10 end)

  Figure.new(
    data: [Scatter.new(x: Enum.to_list(1..20), y: new_y, mode: "lines+markers", name: "live data")],
    layout: %{title: "Update #{i} — zoom reset (uirevision: #{i})", uirevision: i}
  )
  |> then(&Plotly.Kino.PlotlyLive.push(kino, &1))

  Process.sleep(600)
end

:ok