DMC Range Slider Chart Zoom

I wanted to share a working example of how to implement range slider functionality similar to Plotly’s built-in range slider using Dash Mantine Components (DMC) LineChart, Sparkline, and RangeSlider components. This approach provides a clean, modern UI while maintaining the familiar zoom/pan functionality users expect.

:bullseye: The Challenge

While Plotly’s native graphs offer excellent range slider functionality out of the box, I wanted to explore creating similar functionality using Dash Mantine Components for projects that are already heavily invested in the Mantine design system. The goal was to create a responsive, interactive chart with:

  • A main detailed chart view
  • An overview sparkline showing the full dataset
  • A range slider for zooming into specific time periods
  • Visual feedback showing the selected range

:light_bulb: The Solution

I’ve created a working example that combines three DMC components to achieve this:

  1. DMC LineChart - For the main detailed view dmc.Linechart
  2. DMC Sparkline - For the overview/context view dmc.Sparkline
  3. DMC RangeSlider - For selecting the zoom range dmc.Slider

:bar_chart: Live Demo Screenshots

:laptop: Complete Working Code

import dash
from dash import dcc, html, Input, Output, State, callback
import dash_mantine_components as dmc
import pandas as pd
import numpy as np

# Load the Apple stock data
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')
df.columns = [col.replace("AAPL.", "") for col in df.columns]

# Convert date to datetime and sort
df['Date'] = pd.to_datetime(df['Date'])
df = df.sort_values('Date')

# Sample the data for better performance (take every 5th point)
df_sampled = df.iloc[::5].reset_index(drop=True)

# Prepare data for the charts
chart_data = []
for idx, row in df_sampled.iterrows():
    chart_data.append({
        'date': row['Date'].strftime('%Y-%m-%d'),
        'price': row['Close'],
        'index': idx
    })

# Extract just the prices for sparkline
sparkline_data = [d['price'] for d in chart_data]

# Initialize the Dash app
app = dash.Dash(__name__)

# Create JavaScript file in assets folder for custom functions
import os

assets_dir = 'assets'
if not os.path.exists(assets_dir):
    os.makedirs(assets_dir)

with open(os.path.join(assets_dir, 'dashMantineFunctions.js'), 'w') as f:
    f.write("""
window.dashMantineFunctions = {
    formatDollar: (value) => {
        return '$' + value.toFixed(2);
    }
};
""")

# Define the app layout
app.layout = dmc.MantineProvider(
    [
        dmc.Container(
            [
                dmc.Center(
                    dmc.Card(
                        [
                            dmc.CardSection(
                                [
                                    dmc.Text(
                                        "Apple Stock Price with Range Zoom",
                                        size="lg",
                                        fw=500,
                                        ta="center",
                                        mb="md"
                                    ),

                                    # Detailed View Label
                                    dmc.Text("Detailed View", size="sm", c="dimmed", mb="xs"),

                                    # Chart container with overlay controls
                                    dmc.Box(
                                        [
                                            # Main LineChart
                                            dmc.LineChart(
                                                id="main-linechart",
                                                h=400,
                                                data=chart_data,
                                                dataKey="date",
                                                series=[
                                                    {"name": "price", "color": "blue.6", "label": "Stock Price ($)"}
                                                ],
                                                curveType="linear",
                                                withDots=False,
                                                strokeWidth=2,
                                                gridAxis="xy",
                                                withLegend=False,
                                                tooltipAnimationDuration=200,
                                                valueFormatter={"function": "formatDollar"},
                                                style={"paddingBottom": "80px"}
                                            ),

                                            # Overlay container for sparkline and slider
                                            dmc.Box(
                                                [
                                                    # Sparkline container
                                                    dmc.Box(
                                                        id="sparkline-container",
                                                        children=[
                                                            dmc.Sparkline(
                                                                id="overview-sparkline",
                                                                data=sparkline_data,
                                                                h=50,
                                                                color="blue.6",
                                                                fillOpacity=0.2,
                                                                strokeWidth=1,
                                                                curveType="linear"
                                                            )
                                                        ],
                                                        style={
                                                            "position": "relative",
                                                            "width": "100%",
                                                            "backgroundColor": "rgba(248, 249, 250, 0.95)",
                                                            "borderRadius": "4px",
                                                            "padding": "5px 0"
                                                        }
                                                    ),

                                                    # Range slider directly below sparkline
                                                    dmc.RangeSlider(
                                                        id="zoom-slider",
                                                        value=[0, 100],
                                                        min=0,
                                                        max=100,
                                                        step=1,
                                                        minRange=5,
                                                        marks=[
                                                            {"value": 0, "label": "0%"},
                                                            {"value": 25, "label": "25%"},
                                                            {"value": 50, "label": "50%"},
                                                            {"value": 75, "label": "75%"},
                                                            {"value": 100, "label": "100%"}
                                                        ],
                                                        mb=30,
                                                        labelAlwaysOn=False,
                                                        updatemode="mouseup",
                                                        styles={
                                                            "track": {"backgroundColor": "#e9ecef"},
                                                            "bar": {"backgroundColor": "#339af0"},
                                                            "markLabel": {"fontSize": "11px"}
                                                        }
                                                    ),
                                                ],
                                                style={
                                                    "position": "absolute",
                                                    "bottom": "28%",
                                                    "left": "8%",
                                                    "right": "4%",
                                                    "width": "92%",
                                                    "backgroundColor": "rgba(255, 255, 255, 0.95)",
                                                    "borderRadius": "4px",
                                                    "padding": "5px 10px 0 10px",
                                                    "boxShadow": "0 -2px 4px rgba(0,0,0,0.05)"
                                                }
                                            )
                                        ],
                                        style={
                                            "position": "relative",
                                            "width": "100%",
                                        }
                                    ),

                                    # Display selected range info
                                    dmc.Center(
                                        dmc.Text(
                                            id="range-info",
                                            size="sm",
                                            c="dimmed",
                                            ta="center",
                                            mt="-20%"
                                        )
                                    )
                                ],
                                p="xl"
                            )
                        ],
                        shadow="sm",
                        radius="md",
                        withBorder=True,
                        style={
                            "width": "100%",
                            "maxWidth": "800px",
                            "margin": "auto"
                        }
                    ),
                    style={
                        "minHeight": "100vh",
                        "paddingTop": "50px",
                        "paddingBottom": "50px"
                    }
                )
            ],
            fluid=True,
            style={"backgroundColor": "#f8f9fa"}
        )
    ]
)


# Callback to update the LineChart based on slider range
@callback(
    [Output("main-linechart", "data"),
     Output("range-info", "children"),
     Output("sparkline-container", "children")],
    [Input("zoom-slider", "value")]
)
def update_chart_zoom(slider_value):
    # Calculate the data indices based on slider percentage
    total_points = len(chart_data)
    start_idx = int((slider_value[0] / 100) * total_points)
    end_idx = int((slider_value[1] / 100) * total_points)

    # Ensure we have at least some data points
    if end_idx <= start_idx:
        end_idx = start_idx + 1

    # Slice the data
    zoomed_data = chart_data[start_idx:end_idx]

    # Create range info text
    if zoomed_data:
        start_date = zoomed_data[0]['date']
        end_date = zoomed_data[-1]['date']
        range_text = f"Showing: {start_date} to {end_date} ({len(zoomed_data)} data points)"
    else:
        range_text = "No data in selected range"

    # Create sparkline with overlay to show selected range
    sparkline_with_overlay = [
        dmc.Sparkline(
            data=sparkline_data,
            h=50,
            color="gray.4",
            fillOpacity=0.1,
            strokeWidth=1,
            curveType="linear"
        ),
        # Overlay showing selected range
        html.Div(
            style={
                "position": "absolute",
                "top": "5px",
                "left": f"{slider_value[0]}%",
                "width": f"{slider_value[1] - slider_value[0]}%",
                "height": "50px",
                "backgroundColor": "rgba(33, 150, 243, 0.15)",
                "border": "1px solid rgba(33, 150, 243, 0.4)",
                "borderTop": "none",
                "borderBottom": "none",
                "pointerEvents": "none"
            }
        )
    ]

    return zoomed_data, range_text, sparkline_with_overlay


if __name__ == '__main__':
    app.run(debug=True, port=8050)

:package: Requirements

pip install dash dash-mantine-components pandas numpy

:man_running: Running the Example

  • Save the code to a file (e.g., dmc_range_slider.py)
  • Run: python dmc_range_slider.py
  • Open browser to http://localhost:8050

I hope this example helps others looking to implement range slider functionality with Dash Mantine Components! Feel free to adapt and improve upon this approach. Looking forward to your feedback and suggestions! :rocket:


Follow me on github: pip-install-python (Pip Install Python) · GitHub
check out some of my custom components: https://pip-install-python.com/

Hi @PipInstallPython

That’s a cool example - thanks for sharing!

You just made a great use-case for adding the Mantine Overlay component to DMC. It would really simplify the amount of CSS needed - plus it would automatically work with both light and dark modes. Feel free to open a Feature Request in our GitHub for that.

Also, that’s a nice way to add custom JavaScript functions from Python. However, it will wipe out any other functions that may already exist. To be consistent with what’s in our docs for adding JavaScrip functions as props , you could do it like this:

with open(os.path.join(assets_dir, 'dashMantineFunctions.js'), 'w') as f:
    f.write("""
var dmcfuncs = window.dashMantineFunctions = window.dashMantineFunctions || {};

dmcfuncs.formatDollar = function (value) {
  return '$' + value.toFixed(2);
};
""")


Thanks for jumping into the thread :grin: that mantine overlay component looks neat! Not sure how that would affect the tooltips of the wrapped chart, but im all excitee for a solution that would take less .css to get something like this working and looking nice!

Also appreciate the tip and associated code with the JavaScript functions as props and the best practice to implementing something like that. The way dmc can inject javascript into some of these components is trully an amazing feature :100: