Skip to main content

Dumbbell Chart

A custom visualization comparing two related metrics side-by-side, perfect for showing gaps between values like cart additions vs. purchases.

Best for: Funnel analysis, before/after comparisons, gap visualization

Key techniques: map(rows) loop, percentage calculations for positioning, CSS-based chart rendering

Dumbbell Chart Example

Template Code

<style>
.dumbbell-container {
padding: 1.5rem;
max-width: 800px;
}

.dumbbell-row {
display: grid;
grid-template-columns: 200px 1fr;
gap: 1.5rem;
align-items: center;
padding: 1rem 0;
border-bottom: 1px solid #F3F4F6;
}

.dumbbell-row:last-child {
border-bottom: none;
}

.dumbbell-label {
font-size: 0.875rem;
font-weight: 500;
color: #374151;
}

.dumbbell-plot-area {
position: relative;
height: 20px;
display: flex;
align-items: center;
}

.dumbbell-bar-container {
position: relative;
width: 100%;
height: 100%;
}

.dumbbell-line {
position: absolute;
height: 8px;
background: linear-gradient(to right, #0891F2, #F1434B);
border-radius: 4px;
opacity: 0.6;
top: 50%;
transform: translateY(-50%);
}

.dumbbell-dot {
position: absolute;
width: 16px;
height: 16px;
border-radius: 50%;
border: 3px solid white;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
top: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}

.dumbbell-dot.start {
background-color: #0891F2;
}

.dumbbell-dot.end {
background-color: #F1434B;
}

.dumbbell-value {
position: absolute;
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
top: 50%;
transform: translateY(-50%);
}

.dumbbell-value.start {
color: #0891F2;
transform: translate(calc(-100% - 12px), -50%);
}

.dumbbell-value.end {
color: #F1434B;
transform: translate(12px, -50%);
}

.dumbbell-legend {
display: flex;
gap: 2rem;
padding: 0 0 0.5rem 0;
font-size: 0.75rem;
color: #6B7280;
}

.legend-item {
display: flex;
align-items: center;
gap: 0.5rem;
}

.legend-dot {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}

.legend-dot.start {
background-color: #0891F2;
}

.legend-dot.end {
background-color: #F1434B;
}

@media (max-width: 768px) {
.dumbbell-row {
grid-template-columns: 1fr;
gap: 0.5rem;
}

.dumbbell-label {
font-weight: 600;
padding-bottom: 0.5rem;
}

.dumbbell-plot-area {
height: 60px;
}
}
</style>

<div class="dumbbell-legend">
<div class="legend-item">
<span class="legend-dot start"></span>
<span>Purchased</span>
</div>
<div class="legend-item">
<span class="legend-dot end"></span>
<span>Added to Cart</span>
</div>
</div>

<div class="dumbbell-container">
{% map(rows) %}
<div class="dumbbell-row">
<div class="dumbbell-label">{{ `Product name` }}</div>
<div class="dumbbell-plot-area">
<div class="dumbbell-bar-container">
<div class="dumbbell-line" style="left: {{ `Start Percent`.raw }}%; width: {{ `Bar Width`.raw }}%;"></div>
<div class="dumbbell-dot start" style="left: {{ `Start Percent`.raw }}%;"></div>
<div class="dumbbell-value start" style="left: {{ `Start Percent`.raw }}%;">{{ `Purchased` }}</div>
<div class="dumbbell-dot end" style="left: {{ `End Percent`.raw }}%;"></div>
<div class="dumbbell-value end" style="left: {{ `End Percent`.raw }}%;">{{ `Added to cart` }}</div>
</div>
</div>
</div>
{% end %}
</div>

Required Data Fields

FieldTypeDescription
Product nameDimensionRow label
PurchasedMeasureStart value (left dot)
Added to cartMeasureEnd value (right dot)
Start PercentCalculationPosition of start dot (0-100)
End PercentCalculationPosition of end dot (0-100)
Bar WidthCalculationWidth of connecting bar

Sample Data

Import this data into Holistics to use: sample-data-dumbbell.csv

Product name,Added to cart,Purchased,Start Percent,End Percent,Bar Width
Protein Powder,490,310,63.27,100,36.73
Dumbbells,520,500,96.15,100,3.85
Wireless Headphones,500,420,84.00,100,16.00
Water Bottle,520,350,67.31,100,32.69
Running Shoes,480,380,79.17,100,20.83
Resistance Bands,500,260,52.00,100,48.00
Yoga Mat,500,290,58.00,100,42.00
Jump Rope,480,420,87.50,100,12.50
Fitness Tracker,500,450,90.00,100,10.00
Exercise Bike,500,180,36.00,100,64.00

The percentage fields are calculated as: Start Percent = (Purchased / Added to cart) * 100, End Percent = 100, Bar Width = End Percent - Start Percent.

Customization Tips

  • Change the gradient colors in .dumbbell-line to match your brand
  • Adjust dot colors (.dumbbell-dot.start, .dumbbell-dot.end) for different comparisons
  • Modify the legend text to match your metric names
  • The percentage calculations should normalize your values to a 0-100 scale

Let us know what you think about this document :)