Skip to content

Commit

Permalink
DateTime support. Fixes #462
Browse files Browse the repository at this point in the history
  • Loading branch information
dcjones committed Dec 30, 2014
1 parent e47af14 commit 1dfebcc
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 56 deletions.
2 changes: 1 addition & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ Iterators 0.1.5
JSON
KernelDensity
Loess
Showoff 0.0.2
Showoff 0.0.3
StatsBase
120 changes: 83 additions & 37 deletions src/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -305,53 +305,85 @@ end
if VERSION < v"0.4-dev"
using Dates

function Showoff.showoff{T <: Date}(ds::AbstractArray{T}, style=:none)
const month_names = [
"January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"
]

const month_abbrevs = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
]

day_all_1 = all(map(d -> Dates.day(d) == 1, ds))
month_all_1 = all(map(d -> Dates.month(d) == 1, ds))
function Showoff.showoff{T <: Union(Date, DateTime)}(ds::AbstractArray{T}, style=:none)
years = Set()
months = Set()
days = Set()
hours = Set()
minutes = Set()
seconds = Set()
for d in ds
push!(years, Dates.year(d))
push!(months, Dates.month(d))
push!(days, Dates.day(d))
push!(hours, Dates.hour(d))
push!(minutes, Dates.minute(d))
push!(seconds, Dates.second(d))
end
all_same_year = length(years) == 1
all_one_month = length(months) == 1 && 1 in months
all_one_day = length(days) == 1 && 1 in days
all_zero_hour = length(hours) == 1 && 0 in hours
all_zero_minute = length(minutes) == 1 && 0 in minutes
all_zero_seconds = length(minutes) == 1 && 0 in minutes
all_zero_milliseconds = length(minutes) == 1 && 0 in minutes

# first label format
label_months = false
label_days = false
f1 = "u d, yyyy"
f2 = ""
if !all_zero_seconds
f2 = "HH:MM:SS.sss"
elseif !all_zero_seconds
f2 = "HH:MM:SS"
elseif !all_zero_hour || !all_zero_minute
f2 = "HH:MM"
else
if !all_one_day
first_label_format = "u d yyyy"
elseif !all_one_month
first_label_format = "u yyyy"
elseif !all_one_day
first_label_format = "yyyy"
end
end
if f2 != ""
first_label_format = string(f1, " ", f2)
else
first_label_format = f1
end

buf = IOBuffer()
labels = Array(String, length(ds))
if day_all_1 && month_all_1
# only label years
for (i, d) in enumerate(ds)
print(buf, Dates.year(d))
labels[i] = takebuf_string(buf)
end
elseif day_all_1
# label months and years
for (i, d) in enumerate(ds)
if d == ds[1] || Dates.month(d) == 1
print(buf, month_abbrevs[Dates.month(d)], " ", Dates.year(d))
labels[1] = Dates.format(ds[1], first_label_format)
d_last = ds[1]
for (i, d) in enumerate(ds[2:end])
if Dates.year(d) != Dates.year(d_last)
if all_one_day && all_one_month
f1 = "yyyy"
elseif all_one_day && !all_one_month
f1 = "u yyyy"
else
print(buf, month_abbrevs[Dates.month(d)])
f1 = "u d, yyyy"
end
labels[i] = takebuf_string(buf)
elseif Dates.month(d) != Dates.month(d_last)
f1 = all_one_day ? "u" : "u d"
elseif Dates.day(d) != Dates.day(d_last)
f1 = "d"
else
f1 = ""
end
else
for (i, d) in enumerate(ds)
if d == ds[1] || (Dates.month(d) == 1 && Dates.day(d) == 1)
print(buf, month_abbrevs[Dates.month(d)], " ", Dates.day(d), " ", Dates.year(d))
elseif Dates.day(d) == 1
print(buf, month_abbrevs[Dates.month(d)], " ", Dates.day(d))
else
print(buf, Dates.day(d))
end
labels[i] = takebuf_string(buf)

if f2 != ""
f = string(f1, " ", f2)
elseif f1 != ""
f = f1
else
f = first_label_format
end

labels[i+1] = Dates.format(d, f)
d_last = d
end

return labels
Expand All @@ -368,9 +400,23 @@ if !method_exists(/, (Dates.Day, Real))
/(a::Dates.Day, b::Real) = Dates.Day(round(Integer, (a.value / b)))
end

if !method_exists(/, (Dates.Millisecond, Dates.Millisecond))
/(a::Dates.Millisecond, b::Dates.Millisecond) = a.value / b.value
end

if !method_exists(/, (Dates.Millisecond, Real))
/(a::Dates.Millisecond, b::Real) = Dates.Millisecond(round(Integer, (a.value / b)))
end

if !method_exists(-, (Dates.Date, Dates.DateTime))
-(a::Dates.Date, b::Dates.DateTime) = convert(Dates.DateTime, a) - b
end

#if !method_exists(*, (FloatingPoint, Dates.Day))
*(a::FloatingPoint, b::Dates.Day) = Dates.Day(round(Integer, (a * b.value)))
*(a::Dates.Day, b::FloatingPoint) = b * a
*(a::FloatingPoint, b::Dates.Millisecond) = Dates.Millisecond(round(Integer, (a * b.value)))
*(a::Dates.Millisecond, b::FloatingPoint) = b * a
#end


102 changes: 86 additions & 16 deletions src/ticks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,29 @@ end
function optimize_ticks(x_min::Date, x_max::Date; extend_ticks::Bool=false,
k_min=nothing, k_max=nothing,
scale=:auto)
# This can be pretty simple. We are choosing ticks on one of three
# scales: years, months, days.
return optimize_ticks(convert(DateTime, x_min), convert(DateTime, x_max),
extend_ticks=extend_ticks, scale=scale)
end


function optimize_ticks(x_min::DateTime, x_max::DateTime; extend_ticks::Bool=false,
k_min=nothing, k_max=nothing,
scale=:auto)
if x_min == x_max
x_max += Second(1)
end

if year(x_max) - year(x_min) <= 1 && scale != :year
if year(x_max) == year(x_min) && month(x_max) - month(x_min) <= 1 && scale != :month
ticks = Date[]
if x_max - x_min > Day(7) && scale != :day
# This will probably need to be smarter
ticks = DateTime[]

const scales = [
Day(1), Hour(1), Minute(1), Second(1), Millisecond(100),
Millisecond(10), Millisecond(1)
]

# ticks on week boundries
if x_min + Day(7) < x_max || scale == :week
push!(ticks, x_min)
while true
next_month = Date(year(ticks[end]), month(ticks[end])) + Month(1)
Expand All @@ -164,29 +180,74 @@ function optimize_ticks(x_min::Date, x_max::Date; extend_ticks::Bool=false,
end
end
else
push!(ticks, x_min)
scale = nothing
if scale != :auto
# TODO: manually setting scale with :day, :minute, etc
end

if scale === nothing
for proposed_scale in [Day(1), Hour(1), Minute(1),
Second(1), Millisecond(100),
Millisecond(10), Millisecond(1)]
if x_min + proposed_scale < x_max
scale = proposed_scale
break
end
end
end

if scale === nothing
scale = Millisecond(1)
end

# round x_min down
if scale === Day(1)
first_tick = DateTime(year(x_min), month(x_min), day(x_min))
elseif scale === Hour(1)
first_tick = DateTime(year(x_min), month(x_min), day(x_min),
hour(x_min))
elseif scale === Minute(1)
first_tick = DateTime(year(x_min), month(x_min), day(x_min),
hour(x_min), minute(x_min))
elseif scale === Second(1)
first_tick = DateTime(year(x_min), month(x_min), day(x_min),
hour(x_min), minute(x_min), second(x_min))
elseif scale === Millisecond(100)
first_tick = DateTime(year(x_min), month(x_min), day(x_min),
hour(x_min), minute(x_min),
second(x_min), millisecond(x_min) % 100)
elseif scale === Millisecond(10)
first_tick = DateTime(year(x_min), month(x_min), day(x_min),
hour(x_min), minute(x_min),
second(x_min), millisecond(x_min) % 10)
else
first_tick = x_min
end
push!(ticks, first_tick)

while ticks[end] < x_max
push!(ticks, ticks[end] + day(1))
push!(ticks, ticks[end] + scale)
end
end

viewmin, viewmax = ticks[1], ticks[end]
ticks, viewmin, viewmax
return ticks, viewmin, viewmax
else
ticks = Date[]
ticks = DateTime[]
push!(ticks, Date(year(x_min), month(x_min)))
while ticks[end] < x_max
push!(ticks, ticks[end] + Month(1))
end
viewmin, viewmax = ticks[1], ticks[end]

ticks, x_min, x_max
return ticks, x_min, x_max
end
else
ticks, viewmin, viewmax =
optimize_ticks(year(x_min), year(x_max + Year(1) - Day(1)), extend_ticks=extend_ticks)
return Date[Date(round(y)) for y in ticks],
Date(round(viewmin)), Date(round(viewmax))

return DateTime[DateTime(round(y)) for y in ticks],
DateTime(round(viewmin)), DateTime(round(viewmax))
end
end

Expand All @@ -210,15 +271,24 @@ end

function multilevel_ticks(viewmin::Date, viewmax::Date;
scales=[:year, :month, :day])
span = viewmax - viewmin
return multilevel_ticks(convert(DateTime, viewmin),
convert(DateTime, viewmax),
scales=scales)
end


function multilevel_ticks(viewmin::DateTime, viewmax::DateTime;
scales=[:year, :month, :day])
# TODO: This needs to be improved for DateTime
span = convert(Float64, Dates.toms(viewmax - viewmin))
ticks = Dict()
for scale in scales
if scale == :year
s = span / Day(360)
s = span / Dates.toms(Day(360))
elseif scale == :month
s = span / Day(90)
s = span / Dates.toms(Day(90))
else
s = span / Day(1)
s = span / Dates.toms(Day(1))
end

ticks[s/20] = optimize_ticks(viewmin, viewmax, scale=scale)[1]
Expand Down
3 changes: 2 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ tests = [
("single_boxplot", 6inch, 3inch),
("subplot_scales", 6inch, 3inch),
("issue509", 6inch, 3inch),
("layer_order", 6inch, 3inch)
("layer_order", 6inch, 3inch),
("single_datetime", 6inch, 3inch)
]


Expand Down
16 changes: 16 additions & 0 deletions test/single_datetime.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

# issue 462

using Gadfly

if VERSION < v"0.4-dev"
using Dates
else
using Base.Dates
end

a = [unix2datetime(100)]
b = [10]

plot(x=a, y=b, Geom.point)

4 changes: 3 additions & 1 deletion test/timeseries_year_2.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ else
end

economics = dataset("HistData", "Prostitutes")
dates = Date[Date(d) for d in economics[:Date]]
# NOTE: I know these aren't unix times, but I'm not sure what they are, and this
# is just a test so it doesn't matter.
dates = DateTime[unix2datetime(d) for d in economics[:Date]]
economics[:Date] = dates

p = plot(economics, x=:Date, y=:Count, Geom.line)
Expand Down

0 comments on commit 1dfebcc

Please sign in to comment.