[Solved] Has Anyone Made a Date-Range-Slider?

Hi Everyone,

So I know there is a Date-Picker component, I know there is a Range-Slider as well, but I was wondering if someone had figured out how to make a “Date-Range-Slider” which could be used to filter a dataset between two datetime objects.

If anyone has something on this please let me know - thank you!

1 Like
from dateutil.relativedelta import relativedelta

# Usual DASH stuff


        dcc.RangeSlider(
            id = 'datetime_RangeSlider',
            updatemode = 'mouseup', #don't let it update till mouse released
            min = unix_time_millis(d.datetime.min()),
            max = unix_time_millis(d.datetime.max()),
            value = [unix_time_millis(d.datetime.min()),
                         unix_time_millis(d.datetime.max())],
            #TODO add markers for key dates
            marks=get_marks_from_start_end(d.datetime.min(),
                                           d.datetime.max()),
        ),

def get_marks_from_start_end(start, end):
    ''' Returns dict with one item per month
    {1440080188.1900003: '2015-08',
    '''
    result = []
    current = start
    while current <= end:
        result.append(current)
        current += relativedelta(months=1)
    return {unix_time_millis(m):(str(m.strftime('%Y-%m'))) for m in result}

worked for me.

4 Likes

Thanks for the reply. It looks like you call a function “unix_time_millis” which I assume you defined yourself, could you show that function please?

Appreciate the help!

You might want to use a pandas time series here.

Why do you think a pandas time series would be superior to the regular datetime objects? Thanks

1 Like

epoch = datetime.datetime.utcfromtimestamp(0)

def unix_time_millis(dt):
return (dt - epoch).total_seconds() #* 1000.0

1 Like

Thanks a lot this has worked perfectly!

Here’s an approach I used to create a date RangeSlider. Just sharing as an alternate approach…

   html.Div([
    dcc.RangeSlider(
        id='month-slider',
        updatemode='mouseup',
        count=1,
        min=1,
        max=maxmarks,
        step=1,
        value=[maxmarks-5,maxmarks],
        marks=tags,
        pushable=1
    )
    ],

The following code runs before the RangeSlider is called…

maxmarks=13
tday=pd.Timestamp.today() #gets timestamp of today
m1date=tday+DateOffset(months=-maxmarks+1) #first month on slider
datelist=pd.date_range(m1date, periods=maxmarks, freq='M') # list of months as dates
dlist=pd.DatetimeIndex(datelist).normalize()
tags={} #dictionary relating marks on slider to tags. tags are shown as "Apr', "May', etc
datevalues={} #dictionary relating mark to date value
x=1
for i in dlist:
    tags[x]=(i+DateOffset(months=1)).strftime('%b') #gets the string representation of next month ex:'Apr'
    datevalues[x]=i
    x=x+1

include these libraries…

import pandas as pd
from pandas.tseries.offsets import *

This is what it looks like…
image

3 Likes

This is how the range Slider looks in my case.

The code I used:
dcc.RangeSlider(
id=‘my-range-slider’,
updatemode = ‘mouseup’,
min = unix_time_millis(downloads()[‘AUDITTIME’].min()),
max = unix_time_millis(downloads()[‘AUDITTIME’].max()),
step=None,
value = [unix_time_millis(downloads()[‘AUDITTIME’].min()),
unix_time_millis(downloads()[‘AUDITTIME’].max())],
marks=get_marks_from_start_end(downloads()[‘AUDITTIME’].min(),downloads()[‘AUDITTIME’].max())
)

and it doesnt not seperate the months exactly from 1-30/31 days.

Thanks for sharing the code. It works, except from that the label marks are not shown. So the dots are , but not the date itself. No errors shown and get_marks_from_start_end seems to have the expected output. Any ideas? thanks

I did something like this (with a df that has datetimeindex):

Function

def get__marks(f):

dates = {}
for z in f.index:
    dates[f.index.get_loc(z)] = {}
    dates[f.index.get_loc(z)] = str(z.month) + "-" + str(z.day)

return j

App Layout

dcc.RangeSlider(
    id='range-slider',
    updatemode='mouseup',
    min=0,
    max=len(df.index) - 1,
    count=1,
    step=1,
    value=[0, len(df.index) - 1],
    marks=get_marks(df),
)
1 Like

How about something like

marks={int(i):str(j) for i,j in
zip(range(len(df.years)),df[“years”] )}

And then you can mimic the same in the functional callbacks to update the values by dictionary keys

1 Like

What’s the variable j you are returning ?

In case it helps anyone - this approach seemed to work well with a Pandas DateTimeIndex:

import numpy as np
import pandas as pd

## --- Dash stuff ---

dcc.RangeSlider(
    allowCross=False,
    id="date-slider",
    min=pd.Timestamp(df.index.min()).timestamp(),
    max=pd.Timestamp(df.index.max()).timestamp(),
    marks = get_marks(df),
)

def get_marks(df):
    """Convert DateTimeIndex to a dict that maps epoch to str

    Parameters:
        df (Pandas DataFrame): df with index of type DateTimeIndex 

    Returns:
        dict: format is {
            1270080000: '04-2010',
            1235865600: '03-2009',
             ...etc.
        }
    """
    # extract unique month/year combinations as a PeriodIndex
    months = df.index.to_period("M").unique()

    # convert PeriodIndex to epoch series and MM-YYYY string series
    epochs = months.to_timestamp().astype(np.int64) // 10**9
    strings = months.strftime("%m-%Y")

    return dict(zip(epochs, strings))

Output looks like this:

1 Like