Overview
muiCharts wraps the components shipped by MUI X Charts. Sometimes you
need something the library does not provide out of the box — for example
error bars (confidence intervals) drawn on top of a bar
chart.
The good news: you can add your own chart component without
rebuilding the R package. A custom component is just a small
JavaScript file that you load alongside the chart. This article walks
through the whole pattern using an ErrorBar component as a
worked example.
The three pieces are always the same:
- A JavaScript file that defines a React component and registers it.
- An HTML dependency that loads that file in the browser.
- A thin R wrapper function that emits the component and passes props from R.
You don’t need to know React — ask an AI
Custom components are written in JSX (React’s
JavaScript syntax). If you are an R programmer who has never touched
React, that is fine: modern AI assistants (Claude, ChatGPT, Copilot, …)
are very good at writing these small components for you. The trick is to
give the assistant the few facts it needs about how
muiCharts exposes React to the browser.
A prompt like this works well:
Write a plain-browser JavaScript file (no build step, no
import/require) that defines a React component calledErrorBarfor MUI X Charts. React is available aswindow.jsmodule['react']and the charts library aswindow.jsmodule['@mui/x-charts'](which exports theuseXScaleanduseYScalehooks). The component receives adataprop — an array of{ x, min, max }objects — and draws a vertical error bar with caps for each category, mapping values to pixels with the scales. Register it withwindow.jsmodule['muiCharts-custom'] = { ErrorBar: ErrorBar }. UseReact.createElement(not JSX) so the file runs directly in the browser.
The two things the AI must know are the globals
(window.jsmodule[...]) and the registration
line — everything below explains why.
How muiCharts finds a component
Every muiCharts function ultimately calls
shiny.react::reactElement(module, name, ...). In the
browser, shiny.react looks the component up as:
The bundled dependencies already populate
window.jsmodule with everything you need to build on top of
the library:
-
window.jsmodule['react']— React itself (createElement,useMemo, …) -
window.jsmodule['@mui/x-charts']— every chart component and hook, includinguseXScale()anduseYScale()for mapping data to pixels.
So a custom component just has to add one more entry
to that object under a module name of your choosing — here we use
"muiCharts-custom":
Then, on the R side, you point reactElement() at that
same module and name.
Step 1 — The JavaScript component
Below is the full component. It is plain browser
JavaScript — no import, no require,
no webpack. It reads React and the chart hooks from
window.jsmodule, so it runs as-is once the chart’s
dependencies have loaded.
It auto-detects the chart orientation (the band axis is the one that
has a .bandwidth() method) and draws a line with two caps
per category.
error_bar_js <- r"---(
(function () {
var jsmodule = window.jsmodule;
var React = jsmodule['react'];
var charts = jsmodule['@mui/x-charts'];
var useXScale = charts.useXScale;
var useYScale = charts.useYScale;
function ErrorBar(props) {
var data = props.data;
var color = props.color || '#555';
var lineWidth = props.lineWidth || 1.5;
var capSize = props.capSize || 6;
var xScale = useXScale();
var yScale = useYScale();
// The band (category) scale is the one exposing .bandwidth()
var horizontal = typeof yScale.bandwidth === 'function';
var bandScale = horizontal ? yScale : xScale;
var valueScale = horizontal ? xScale : yScale;
var bandwidth = typeof bandScale.bandwidth === 'function'
? bandScale.bandwidth() : 0;
if (!data || !Array.isArray(data)) return null;
return React.createElement(
'g',
{ className: 'error-bars' },
data.map(function (d, i) {
if (d.x == null || d.min == null || d.max == null) return null;
var band = bandScale(d.x);
if (band == null || isNaN(band)) return null;
band += bandwidth / 2;
var vMin = valueScale(d.min);
var vMax = valueScale(d.max);
if (isNaN(vMin) || isNaN(vMax)) return null;
var stroke = { stroke: color, strokeWidth: lineWidth };
if (horizontal) {
return React.createElement('g', { key: i },
React.createElement('line', Object.assign({ key: 'l',
x1: vMin, y1: band, x2: vMax, y2: band }, stroke)),
React.createElement('line', Object.assign({ key: 'a',
x1: vMin, y1: band - capSize, x2: vMin, y2: band + capSize }, stroke)),
React.createElement('line', Object.assign({ key: 'b',
x1: vMax, y1: band - capSize, x2: vMax, y2: band + capSize }, stroke))
);
}
// Vertical (default)
return React.createElement('g', { key: i },
React.createElement('line', Object.assign({ key: 'l',
x1: band, y1: vMin, x2: band, y2: vMax }, stroke)),
React.createElement('line', Object.assign({ key: 'a',
x1: band - capSize, y1: vMin, x2: band + capSize, y2: vMin }, stroke)),
React.createElement('line', Object.assign({ key: 'b',
x1: band - capSize, y1: vMax, x2: band + capSize, y2: vMax }, stroke))
);
})
);
}
// Register the component so shiny.react can find it
window.jsmodule = window.jsmodule || {};
window.jsmodule['muiCharts-custom'] =
Object.assign({}, window.jsmodule['muiCharts-custom'], { ErrorBar: ErrorBar });
})();
)---"In a real project you would keep this in its own
error-bar.jsfile. Here we hold it in an R string so the article stays self-contained and reproducible.
Step 2 — Load the JavaScript as an HTML dependency
Write the script to disk and wrap it in an
htmltools::htmlDependency(). This is how Shiny and R
Markdown know to include the <script> in the page. We
give it a name and version of our own; nothing here touches the
installed package.
library(htmltools)
# Write the component next to the rendered document
js_dir <- file.path(tempdir(), "muiCharts-custom")
dir.create(js_dir, showWarnings = FALSE)
writeLines(error_bar_js, file.path(js_dir, "error-bar.js"))
error_bar_dep <- htmlDependency(
name = "muiCharts-custom",
version = "1.0.0",
src = js_dir,
script = "error-bar.js"
)Step 3 — The R wrapper function
Finally, an R function that emits the component. It mirrors how the
package’s own components are defined: call
shiny.react::reactElement() with the module and name you
registered in JavaScript, and attach both the chart
dependencies and your custom script via deps.
library(muiCharts)
ErrorBar <- function(data, color = "#555", lineWidth = 1.5, capSize = 6) {
shiny.react::reactElement(
module = "muiCharts-custom",
name = "ErrorBar",
props = shiny.react::asProps(
data = data,
color = color,
lineWidth = lineWidth,
capSize = capSize
),
# Load MUI X Charts (react + the chart library) AND our custom script
deps = c(muiChartsDependency(), list(error_bar_dep))
)
}A small helper makes it easy to turn a data frame with
min/max columns into the list-of-lists shape
the component expects:
Putting it together
Now use ErrorBar() like any other component — as a child
of BarChart(). The x values must match the
x-axis categories so the bars line up.
library(dplyr)
# Average height by species, with 95% confidence intervals
starwars_ci <- dplyr::starwars |>
filter(!is.na(height), !is.na(species)) |>
group_by(species) |>
summarize(
avg_height = mean(height),
se = sd(height) / sqrt(n()),
n = n(),
.groups = "drop"
) |>
filter(n >= 2) |>
mutate(lower = avg_height - 1.96 * se, upper = avg_height + 1.96 * se) |>
arrange(desc(n)) |>
slice_head(n = 5)
BarChart(
dataset = starwars_ci,
xAxis = list(list(scaleType = "band", dataKey = "species", label = "Species")),
series = list(list(dataKey = "avg_height", label = "Average height (cm)")),
height = 320,
ErrorBar(
data = error_bar_data(starwars_ci$species, starwars_ci$lower, starwars_ci$upper),
color = "#d32f2f",
lineWidth = 2,
capSize = 8
)
)Because the component auto-detects orientation from the scales, the
same ErrorBar() works on a horizontal bar
chart — just set layout = "horizontal" on the chart:
BarChart(
dataset = starwars_ci,
layout = "horizontal",
yAxis = list(list(scaleType = "band", dataKey = "species", label = "Species")),
series = list(list(dataKey = "avg_height", label = "Average height (cm)")),
height = 320,
ErrorBar(
data = error_bar_data(starwars_ci$species, starwars_ci$lower, starwars_ci$upper),
color = "#d32f2f",
lineWidth = 2,
capSize = 8
)
)Recap
To add any custom chart component without rebuilding
muiCharts:
-
Write a JavaScript component that reads React and
the chart hooks from
window.jsmoduleand registers itself under a module name of your choice (window.jsmodule['muiCharts-custom'] = { MyComponent }). Don’t know React? Ask an AI assistant to write it — just tell it about thewindow.jsmoduleglobals and the registration line. -
Load it with
htmltools::htmlDependency(). -
Wrap it in R with
shiny.react::reactElement(), pointing at the same module and name, and attachc(muiChartsDependency(), list(your_dep))asdeps.
The same recipe works in Shiny apps and Quarto documents — see the Shiny and Quarto
articles. Once a component proves useful, you can graduate it into a
bundled component by adding its source to the package’s js/
directory and rebuilding, but that is never required to get started.