bar charts can't handle datetimes with less than a day between datapoints?

Hi,

I'm worried that I'm doing something stupid, but can't quite spot it.

testBarCharts() :- X axis in integers. Works fine.
testBarChartsDTMonths() :- X axis in datetimes, 1 month between data
points. Works fine
testBarChartsDTHours() :- X axis in datetimes, 1 hour between data
points. Bars seem to extend as far to the right as possible. When
printing the list of patches returned from plotting, the widths are
reported as 0.8, though.

Am I screwing up or is this a matplotlib bug? Using
matplotlib-0.98.5.3.win32-py2.6.exe and
numpy-1.3.0-win32-superpack-python2.6.exe

Many thanks,

Nick

import matplotlib.pyplot as plt
import matplotlib.dates
import datetime

# This works fine
def testBarCharts():
    xs = [10,11,12,13]
    ys = [100,300,200,600]

    fig = plt.figure(figsize=(20,12)) # dims in inches
    ax1 = fig.add_subplot(111)

    ax1.set_ylabel("foo")

    rects = ax1.bar(xs,ys,color=(1,0,0))
    # debugging
    print xs
    print ys
    for r in rects:
        print r.get_xy(),r.get_width(),"x",r.get_height()

    fig.savefig("foo.png")
    plt.close(fig)

# FIXME: This is all wonky
def testBarChartsDTHours():
    xs = [ \
                        datetime.datetime(2009,6,29,10),
                        datetime.datetime(2009,6,29,11),
                        datetime.datetime(2009,6,29,12),
                        datetime.datetime(2009,6,29,13) ]
    ys = [100,300,200,600]

    fig = plt.figure(figsize=(20,12)) # dims in inches
    ax1 = fig.add_subplot(111)

    ax1.set_ylabel("foo")

    ax1.xaxis_date()

    min_x = min(xs)
    max_x = max(xs)+datetime.timedelta(0,2*60*60)

    rects = ax1.bar(xs,ys,color=(1,0,0))
    # XXX note that all the rectangle dimensions look sane
    print xs
    print ys
    for r in rects:
        print r.get_xy(),r.get_width(),"x",r.get_height()

    ax1.set_xbound(min_x,max_x)

    fig.savefig("foo1.png")
    plt.close(fig)

# Works
def testBarChartsDTMonths():
    xs = [ \
                        datetime.datetime(2009,6,1),
                        datetime.datetime(2009,7,1),
                        datetime.datetime(2009,8,1),
                        datetime.datetime(2009,9,1) ]
    ys = [100,300,200,600]

    fig = plt.figure(figsize=(20,12)) # dims in inches
    ax1 = fig.add_subplot(111)

    ax1.set_ylabel("foo")

    ax1.xaxis_date()

    min_x = min(xs)
    max_x = max(xs)+datetime.timedelta(1)

    rects = ax1.bar(xs,ys,color=(1,0,0))
    # debugging
    print xs
    print ys
    for r in rects:
        print r.get_xy(),r.get_width(),"x",r.get_height()

    ax1.set_xbound(min_x,max_x)

    fig.savefig("foo2.png")
    plt.close(fig)

if __name__=='__main__':
    testBarCharts()
    testBarChartsDTMonths()
    testBarChartsDTHours()

under the hood, mpl converts datetime objects to days since 1/1/0000,
using a floating point representation for fractions of a day. So a
bar width or 1.0 corresponds to 1 day. The default width to the bar
command is 0.8, which is too thin for months, just right for days, and
too wide for hours. Eg, for hours do

  bar(x, y, width=0.8*1/24.) # width is 0.8 hours

for months, do something like

  bar(x, y, width=0.8*30/24.) # width approx 80% of a month

Perhaps bar should take a width=None arg and try to infer the ideal
with, eg assuming even spacing, but for now you need to specify the
width in fractional days when making bar plots with datetimes.

JDH

···

On Tue, Jul 21, 2009 at 8:00 PM, Nick Seow<nickseow@...287...> wrote:

Hi,

I'm worried that I'm doing something stupid, but can't quite spot it.

testBarCharts() :- X axis in integers. Works fine.
testBarChartsDTMonths() :- X axis in datetimes, 1 month between data
points. Works fine
testBarChartsDTHours() :- X axis in datetimes, 1 hour between data

Ah, thanks!

···

On Thu, Jul 23, 2009 at 12:18 PM, John Hunter<jdh2358@...287...> wrote:

bar width or 1.0 corresponds to 1 day. The default width to the bar
command is 0.8, which is too thin for months, just right for days, and
too wide for hours. Eg, for hours do

bar(x, y, width=0.8*1/24.) # width is 0.8 hours

for months, do something like

bar(x, y, width=0.8*30/24.) # width approx 80% of a month

It would be nice for the documentation to say something like "width:
  the widths of the bars as a proportion of the width of 1 day" instead
of "width: the widths of the bars"

Nick