Dot Chart in R Base Graphics

Draw a Cleveland dot plot with base R dotchart()

Data Visualization

Learn to make a Cleveland dot chart in R with base graphics — the dotchart() function for a single vector, grouped and coloured dots, and matrix data. A clearer alternative to the bar chart.

Published

July 8, 2026

Modified

July 9, 2026

TipKey takeaways
  • Draw a Cleveland dot plot with dotchart() — pass a numeric vector (or a matrix) and it plots one dot per value against a category. No package needed.
  • Order the data first (df[order(df$x), ]) — a dot chart earns its keep by making a ranking easy to read top to bottom.
  • Label the dots with labels =, and set the point style with pch and cex.
  • Group and colour the dots with groups = (a factor), color = (per-point colours) and gcolor = (the group-label colour).
  • Pass a matrix and dotchart() draws one panel per column — a compact way to compare a value across two crossed categories.

Introduction

You have a value measured for many labelled items — fuel economy for 32 cars, sales by region, a score per gene — and you want to compare them at a glance. A Cleveland dot plot puts each item on its own row and marks its value with a single dot. Order the rows and the ranking reads straight down the page: who is highest, who is lowest, where the gaps are.

It is a cleaner alternative to the bar chart. A bar draws a whole filled rectangle to encode one number, which adds ink and a baseline you rarely need; a dot encodes the same number with a single mark, so more items fit and the values are easier to compare (Cleveland, 1985, The Elements of Graphing Data).

In base R you draw one with dotchart(). Pass a numeric vector to chart a single variable, or a matrix to compare a value across crossed categories. This lesson covers both, plus the styling you actually reach for — ordering, labels, point symbols, and colouring the dots by group.

We use two built-in datasets: mtcars (road-test figures for 32 cars) and VADeaths (1940 Virginia death rates by age, sex and area).

head(mtcars[, c("mpg", "cyl")])
                   mpg cyl
Mazda RX4         21.0   6
Mazda RX4 Wag     21.0   6
Datsun 710        22.8   4
Hornet 4 Drive    21.4   6
Hornet Sportabout 18.7   8
Valiant           18.1   6

Prefer the grammar of graphics? ggplot2 draws the same chart with geom_point() plus coord_flip() — see the ggplot2 scatter plot lesson for that idiom.

A basic dot chart

Pass a numeric vector and a matching vector of labels. The one move that makes a dot chart worth drawing is ordering the data first — here we sort mtcars by mpg so the dots climb from the thirstiest car at the bottom to the most efficient at the top:

# Order the cars by mpg so the ranking reads bottom to top
cars <- mtcars[order(mtcars$mpg), ]

dotchart(cars$mpg, labels = row.names(cars),
         cex = 0.6, pch = 19,
         color = "#3a86d4",
         xlab = "Miles per gallon (mpg)")

A base R Cleveland dot chart of miles per gallon for 32 cars, one azure dot per car, ordered from lowest to highest mpg with the car names down the left.

row.names(cars) supplies the car names for the y-axis, pch = 19 uses solid dots, and cex = 0.6 shrinks them so 32 rows fit without crowding. Because we ordered by mpg, the chart is a ranking — the Toyota Corolla tops it, the big-block cars sit at the bottom.

Key dotchart() arguments

A handful of arguments cover almost everything you will do:

Argument What it controls
x The numeric vector or matrix to plot.
labels A vector of row labels, one per value.
groups A factor that splits the dots into labelled groups.
color The colour(s) of the dots (and their labels) — one value or a vector.
gcolor The colour of the group labels and heading values.
pch, cex Point symbol and size.
xlab, main Axis title and plot title.

Everything below is these arguments in combination.

Group and colour the dots

Set groups = to a factor and dotchart() splits the dots into labelled blocks — one per level — with a small heading for each. Colour the dots by that same factor to reinforce the grouping. Here we split the cars by cylinder count (cyl) and give each group a colourblind-safe colour:

cars <- mtcars[order(mtcars$mpg), ]
grps <- factor(cars$cyl)                        # 4, 6 or 8 cylinders
my_cols <- c("#3a86d4", "#f4a259", "#5fad56")   # colourblind-safe, one per group

dotchart(cars$mpg, labels = row.names(cars),
         groups = grps,
         gcolor = "black",                      # colour of the group headings
         color  = my_cols[grps],                # one colour per point, by group
         cex = 0.6, pch = 19,
         xlab = "Miles per gallon (mpg)")

A base R Cleveland dot chart of miles per gallon by car, the dots split into three cylinder-count groups and coloured azure, orange and green, mpg falling as cylinders rise.

Two things to get right. The groups factor, the labels and color are all parallel to x — the i-th entry of each describes the same car — so they must come from the same ordered data frame. my_cols[grps] is the key trick: indexing the palette by the factor gives every point the colour of its group, in order. The chart now shows both the ranking and the clear story that fewer cylinders means more miles per gallon.

Dot chart of a matrix

Hand dotchart() a matrix and it draws one labelled panel per column, with the row names down the side of each. It is a compact way to compare a value across two crossed categories. VADeaths holds death rates per 1000 by age group (rows) crossed with sex and area (columns):

dotchart(VADeaths, cex = 0.7, pch = 19,
         color = "#3a86d4",
         main = "Death rates in Virginia (1940)",
         xlab = "Deaths per 1000")

A base R dot chart of 1940 Virginia death rates, one panel for each of Rural Male, Rural Female, Urban Male and Urban Female, the age groups down each panel with azure dots.

Each column of the matrix becomes a group, and within it the age groups are the rows. Reading across the panels, the death rate climbs steeply with age in every group and runs higher for men than women — the kind of pattern a dot chart surfaces at a glance.

Try it live

Edit the data, the grouping factor or the colours and press Run — it executes in your browser via webR, no install needed.

🟢 With an AI agent

Ask Prova “how do I sort a dot chart from largest to smallest in base R?” — it answers with base-R code you can run, so the fix is reproducible. The runtime is the judge. Ask Prova →

Common issues

The dots come out in a jumbled order. dotchart() plots the values in the order they sit in the vector — it does not sort for you. Order the data frame first with df[order(df$x), ] (append -df$x inside order() for descending), then pass the ordered vector and its labels.

The colours don’t line up with the groups. color colours the dots in position order. To colour by group you must index a palette by the factor — color = my_cols[grps] — not pass the factor itself. Define one colour per level, then index it, and make sure grps comes from the same ordered data as x.

Only the first few labels show, or they overlap. With many rows the labels crowd. Shrink them with a smaller cex (e.g. cex = 0.6), or make the figure taller (#| fig-height) so every row has room.

Frequently asked questions

Call dotchart() with a numeric vector and a matching labels vector — for example dotchart(mtcars$mpg, labels = row.names(mtcars)). It ships with base R, so no package is required. Order the data first (mtcars[order(mtcars$mpg), ]) to turn the chart into a readable ranking.

A Cleveland dot plot places each labelled item on its own row and marks its value with a single dot. It was proposed by William Cleveland as a clearer, lower-ink alternative to the bar chart for comparing values across categories. dotchart() draws one in base R.

Pass a factor to groups = to split the dots into labelled blocks, and colour them by indexing a palette with that factor: color = my_cols[grps]. Use gcolor = to set the colour of the group headings. Keep the factor, labels and colours parallel to the data vector.

Use a dot chart when you are comparing one value across many labelled items, especially when there are a lot of them. A dot encodes the value with a single mark instead of a whole bar, so more items fit and the comparison is easier to read. Bars suit parts-of-a-whole and counts you want to see stacked from zero.

Pass the matrix straight to dotchart()dotchart(VADeaths). It draws one labelled panel per column, with the row names down the side of each, which is a compact way to compare a value across two crossed categories.

Test your understanding

Starting from mtcars, draw a Cleveland dot chart of horsepower (hp), ordered from lowest to highest, with the car names as labels. Group and colour the dots by the number of gears (gear), using the three colourblind-safe colours below.

Order with mtcars[order(mtcars$hp), ], make the factor with factor(cars$gear), then call dotchart() with groups =, gcolor = "black" and color = my_cols[grps].

my_cols <- c("#3a86d4", "#f4a259", "#5fad56")

cars <- mtcars[order(mtcars$hp), ]
grps <- factor(cars$gear)

dotchart(cars$hp, labels = row.names(cars),
         groups = grps, gcolor = "black",
         color = my_cols[grps],
         cex = 0.6, pch = 19,
         xlab = "Horsepower (hp)")

Ordering mtcars by hp makes the chart a ranking; my_cols[grps] colours each dot by its gear count.

Quick check. You call dotchart(mtcars$mpg) without ordering mtcars first. Why does the chart look harder to read than a sorted one?

dotchart() plots values in the order they appear in the vector — it does not sort. Unordered, the dots scatter up and down with no visual ranking; sorting the data first (mtcars[order(mtcars$mpg), ]) lines the values into a clean top-to-bottom order.

Conclusion

dotchart() is the one base-R function for a Cleveland dot plot. Order your data, pass the vector and its labels, and you get a clean ranking. From there, groups = splits the dots into labelled blocks, color = my_cols[factor] colours them by group, gcolor sets the heading colour, and handing the function a matrix compares a value across two crossed categories. When you are comparing one number across many labelled items, it reads better than a bar chart — a single dot, no wasted ink.

Reuse

Citation

BibTeX citation:
@online{2026,
  author = {},
  title = {Dot {Chart} in {R} {Base} {Graphics}},
  date = {2026-07-08},
  url = {https://www.datanovia.com/learn/data-visualization/r-base/dot-chart},
  langid = {en}
}
For attribution, please cite this work as:
“Dot Chart in R Base Graphics.” 2026. July 8. https://www.datanovia.com/learn/data-visualization/r-base/dot-chart.