I'm trying to set the domain on my x-axis for a time based line/area chart. I've tried a few different things based on other issues raised here- but this is my closest result:
y = list(range(0,100))
x = [datetime.datetime(2017, 1, 1) + datetime.timedelta(days=i) for i in y]
# What I wanted to do
# domain = [datetime.datetime(2017,1,5), datetime.datetime(2017,1,30)]
# Per https://github.com/altair-viz/altair/issues/558
# domain_seconds = [(x - datetime.datetime(1970,1,1)).total_seconds() for x in domain]
# Per comment: https://github.com/altair-viz/altair/issues/187#issuecomment-244126070
domain_pd = pd.to_datetime(['2016-12-15', '2017-01-15']).astype(int) / 10 ** 6
time_data = pd.DataFrame({
'x': x,
'y': y
})
alt.Chart(time_data).mark_line().encode(
alt.X('x:T', timeUnit='yearmonthdate', scale=alt.Scale(domain=list(domain_pd))),
alt.Y('y:Q')
).properties(
title='Example chart'
)

As you can see, this didn't exactly work out. What I really want to do is pass in dates in the domain field and have the chart start and end on those dates. This should happen regardless of the presence of data. For my application there may or may not be data in the dates given. Am I passing in these dates in the right place?
FYI I'm running altair 2.1.0 in a jupyter notebook.
I'm tracking the following issues to try and resolve this:
It looks like the domain adjustment worked fine; you just need to also specify clip=True. See https://altair-viz.github.io/user_guide/customization.html#adjusting-axis-limits
Thanks! Although this isn't my favorite solution for setting time axis domain's it works. Do you have any plans to support [datetime.datetime(2017,1,5), datetime.datetime(2017,1,30)] as the passed in domain?
For the sake of those who search this issue, here is the completed graph:
y = list(range(0,100))
x = [datetime.datetime(2017, 1, 1) + datetime.timedelta(days=i) for i in y]
domain_pd = pd.to_datetime(['2016-12-15', '2017-01-15']).astype(int) / 10 ** 6
time_data = pd.DataFrame({
'x': x,
'y': y
})
alt.Chart(time_data).mark_line(clip=True).encode(
alt.X('x:T', timeUnit='yearmonthdate', scale=alt.Scale(domain=list(domain_pd))),
alt.Y('y:Q')
).properties(
title='Example chart',
width=800
)

Do you have any plans to support [datetime.datetime(2017,1,5), datetime.datetime(2017,1,30)]
No definite plans... but it's probably a good idea. It would involve overwriting the auto-generated alt.Scale class with a derived subclass of the same name that has special handling for the domain argument. We do that already with a couple other schema definitions.
Here's a slightly cleaner way to pass datetime data to Altair domains:
import altair as alt
import pandas as pd
def to_altair_datetime(dt):
dt = pd.to_datetime(dt)
return alt.DateTime(year=dt.year, month=dt.month, date=dt.day,
hours=dt.hour, minutes=dt.minute, seconds=dt.second,
milliseconds=0.001 * dt.microsecond)
data = pd.DataFrame({
'x': pd.date_range('2017-01-01', freq='D', periods=100),
'y': range(100)
})
domain = [to_altair_datetime('2016-12-15'),
to_altair_datetime('2017-01-15')]
alt.Chart(data).mark_line(clip=True).encode(
alt.X('x:T', timeUnit='yearmonthdate', scale=alt.Scale(domain=domain)),
alt.Y('y:Q')
).properties(
title='Example chart',
width=800
)
The tricky thing about doing this automatically is trying to infer when the scale domain should be converted to dates (when scale is converted to dict, it doesn't have any information about the type of the variable it's referring to). Making it context-aware would require some fundamental changes in how Altair generates plot specifications.
Just thinking out loud... sorry for the multiple posts.
Maybe the best way to do this would be to make alt.DateTime accept a flexible range of inputs, so that the user could do
alt.Scale(domain=[alt.DateTime('2016-12-15'), alt.DateTime('2017-01-15')])
That seems concise and convenient, and also nice and explicit.
Edit: This would involve adding the following class to altair/vegalite/v2/api/py:
import altair as alt
import pandas as pd
class DateTime(alt.DateTime):
@staticmethod
def _shorthand_to_dict(shorthand):
dt = pd.to_datetime(shorthand)
return dict(year=dt.year, month=dt.month, date=dt.day,
hours=dt.hour, minutes=dt.minute, seconds=dt.second,
milliseconds=0.001 * dt.microsecond)
def __init__(self, shorthand=alt.Undefined, **kwargs):
if shorthand is not alt.Undefined:
# TODO: warn if this involves overwriting
kwargs.update(self._shorthand_to_dict(shorthand))
super(alt.DateTime, self).__init__(**kwargs)
Oh, cool, I didn't realize that (despite having commented on that PR :smile:)
Since that will soon be supported in Altair, I think we should avoid adding this extra DateTime logic on the Python side.
Nice to see vega-lite will be making this a little easier. If I understand right after vega-lite incorporates this into a release (is this in 2.6.0?) altair can handle:
domain=['2016-12-15', '2017-01-15']
# or
domain=[datetime.date(2016, 12, 15).isoformat(), datetime.date(2017, 1, 15).isoformat()]
I'm looking forward to trying this out.
is this in 2.6.0?
Yep, this is already in 2.6.0. :)
Confirmed, this is working in altair mast branch now. I can successfully use str isodates as domain args.
@jakevdp I'm not sure what your process for handling issues is. Would you like to close this now or wait until documentation is made or a release is performed?
Resolved in master with 087e490ba9f17d17d8a801c72c480a8a6b982c63
Most helpful comment
Just thinking out loud... sorry for the multiple posts.
Maybe the best way to do this would be to make
alt.DateTimeaccept a flexible range of inputs, so that the user could doThat seems concise and convenient, and also nice and explicit.
Edit: This would involve adding the following class to
altair/vegalite/v2/api/py: