Skip to main content

Dumbbell Plot

The code for this example can be used in the Markdown visualization to create a table of dummbbell charts. Sometimes called barbell plots or connected dot plots, these charts are good for comparing related data points across a category. This example was inspired by charts in the results of the 2025 Stack Overflow Survey.

Setup

These charts use the Mustache iterator syntax to draw a chart for each row in the results. Your query should have a column with the category label for each dumbbell that you want to display along with the start and end values for each category. Additionally, you'll need a calculation to get the length of the bar.

Note: this example only covers a positive change between start and end values. You'll need to make some adjustments if the change you are measuring can also be negative.

The following table explains each field used in the example, including the calculation formulas.

 

ColNameDescription or formulaPurpose
ADatabasequery fieldLabel of the category
BDesired Percentagequery fieldstart (left) value of dumbbell
CAdmired Percentagequery fieldend (right) value of dumbbell
Dbar width=C1 - B1Length of the bar

Example code

<style>
h3 { margin-top: 0; }
.dumbbell-plot-table {
--start-color: #0891F2;
--end-color: #F1434B;
--dot-radius: 5px;
--dot-diameter: calc(var(--dot-radius) * 2);
--label-width: 7ch;
--line-opacity: 0.25;
--border-style: 1px dotted color-mix(in srgb, var(--color-border1), transparent 0%);

list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: min-content 1fr;
width: 100%;
}
.dumbbell-plot-table h4 {
white-space: nowrap;
margin: 0;
font-size: var(--font-xs);
border-bottom: var(--border-style);
}
.dumbbell-plot-table section.dumbbell-plot-wrapper {
padding-inline: var(--label-width);
border-bottom: var(--border-style);
}
ul.dumbbell-plot {
position: relative;
margin: 0;
padding: 0;
list-style: none;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
gap: 0;
align-items: center;

& li.dot-label {
position: absolute;
width: var(--label-width);
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
font-size: var(--font-xxs);
font-weight: 500;
}
& li.dot-start {
justify-content: flex-end;
color: var(--start-color);

& span.dot { background-color: var(--start-color); }
}
& li.dot-end {
justify-content: flex-start;
color: var(--end-color);

& span.dot { background-color: var(--end-color); }
}
& li .dot {
display: inline-block;
width: var(--dot-diameter);
height: var(--dot-diameter);
border-radius: 50%;
flex: 0 0 var(--dot-diameter);
}
& li.dot-span {
position: absolute;
background: magenta;
display: flex;
flex-direction: column;
justify-content: space-around;
opacity: var(--line-opacity);

& span.line {
display: block;
height: var(--dot-radius);
background-image: linear-gradient(to right, var(--start-color), var(--end-color));
width: 100%;
}
}
}
ul.dumbbell-plot-legend {
list-style: none;
margin: 0;
padding: 12px 0;
display: flex;
flex-direction: row;
gap: 16px;
font-size: var(--font-xs);
font-weight: 500;
grid-column: 1 / 2;

& li {
display: flex;
flex-direction: row;
gap: 6px;
align-items: center;
}

& .dumbbell-plot-legend-dot {
width: 10px;
height: 10px;
background-color: pink;
display: block;
border-radius: 50%;
}
& .dumbbell-plot-legend-dot.start {
background-color: var(--start-color);
}
& .dumbbell-plot-legend-dot.end {
background-color: var(--end-color);
}
}
</style>
<h3>Databases Dumbbell Plot from Stack Overflow</h3>
<p>As seen in the <a href="https://survey.stackoverflow.co/2025/technology#admired-and-desired">2025 Stack Overflow Admired/Desired charts</a>. From their site: To better gauge hype versus reality, we created a visualization that shows the distance between the proportion of respondents who want to use a technology (“desired”) and the proportion of users that have used the same technology in the past year and want to continue using it (“admired”).</p>
<article class="dumbbell-plot-table">
{{#result}}
<h4>{{stackoverflow_databases.database.value}}</h4>
<section class="dumbbell-plot-wrapper">
<ul class="dumbbell-plot">
<li class="dot-span" style="left:{{stackoverflow_databases.desired_percentage.value_static}}%; width: {{calc_1.value_static}}%;"><span class="line"></span></li>
<li class="dot-label dot-start" style="left: calc({{stackoverflow_databases.desired_percentage.value_static}}% - var(--label-width) + var(--dot-radius))"><span class="label">{{stackoverflow_databases.desired_percentage.value}}%</span><span class="dot"></span></li>
<li class="dot-label dot-end" style="left:calc({{stackoverflow_databases.admired_percentage.value_static}}% - var(--dot-radius));"><span class="dot"></span><span clas="label">{{stackoverflow_databases.admired_percentage.value}}%</span></li>
</ul>
</section>
{{/result}}
<ul class="dumbbell-plot-legend">
<li><span class="dumbbell-plot-legend-dot start"></span>Desired</li>
<li><span class="dumbbell-plot-legend-dot end"></span>Admired</li>
</ul>
</article>