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 ` 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. ```elixir 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: ```elixir # 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-levelfixedrangelayout property. Settingfixedrange: trueon an axis prevents zooming and panning on that axis, disabling the corresponding modebar buttons for it. This is a **layout** property, not aconfigproperty. ```elixir 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() ``` ```elixir # 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 inplotlyex, 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, soconfig.linkTextis 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, soconfig.showEditInChartStudiois deprecated as well. ## Fundamentals: Configuration & Display > Change the Default Localeconfig.localesets the interface language for the chart.PlotlyLiveautomatically 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 namedplotly-locale-.js). ```elixir 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() ``` ```elixir # 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 Modebarconfig.displaylogo: falseremoves the Plotly logo from the modebar. Useful for white-label dashboards or when branding guidelines prohibit third-party logos. ```elixir 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 Chartconfig.responsive: truemakes 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: trueis generally recommended for all charts. ```elixir 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 Delayconfig.doubleClickDelaysets 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. ```elixir 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. Setconfig.responsive: truetogether withlayout.autosize: true. In Livebook, Kino cells resize dynamically —responsive: trueis recommended for all charts to avoid fixed-width overflow. ```elixir 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 Changeslayout.uirevisioncontrols whether plotly.js preserves the user's interactive state (zoom, pan, legend toggles) when the figure is updated viaPlotlyLive.push. Whenuirevision` 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 ```elixir 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)**elixir for i <- 1..8 do newy = Enum.map(1..20, fn -> :rand.uniform() 10 end) Figure.new( data: [Scatter.new(x: Enum.tolist(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**elixir 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)**elixir for i <- 1..8 do newy = 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 ```