From 1dfebccdeb531ee29c5baf635f882956d5fe7282 Mon Sep 17 00:00:00 2001 From: Daniel Jones Date: Tue, 30 Dec 2014 12:21:29 -0800 Subject: [PATCH] DateTime support. Fixes #462 --- REQUIRE | 2 +- src/misc.jl | 120 ++++++++++++++++++++++++++------------ src/ticks.jl | 102 +++++++++++++++++++++++++++----- test/runtests.jl | 3 +- test/single_datetime.jl | 16 +++++ test/timeseries_year_2.jl | 4 +- 6 files changed, 191 insertions(+), 56 deletions(-) create mode 100644 test/single_datetime.jl diff --git a/REQUIRE b/REQUIRE index ef3f81f23..37c66fb36 100644 --- a/REQUIRE +++ b/REQUIRE @@ -13,5 +13,5 @@ Iterators 0.1.5 JSON KernelDensity Loess -Showoff 0.0.2 +Showoff 0.0.3 StatsBase diff --git a/src/misc.jl b/src/misc.jl index aad31d151..cffbc3abd 100644 --- a/src/misc.jl +++ b/src/misc.jl @@ -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 @@ -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 diff --git a/src/ticks.jl b/src/ticks.jl index 7939054ae..f638139a8 100644 --- a/src/ticks.jl +++ b/src/ticks.jl @@ -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) @@ -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 @@ -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] diff --git a/test/runtests.jl b/test/runtests.jl index ae5b0b760..aacd6dc83 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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) ] diff --git a/test/single_datetime.jl b/test/single_datetime.jl new file mode 100644 index 000000000..e9b7991a8 --- /dev/null +++ b/test/single_datetime.jl @@ -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) + diff --git a/test/timeseries_year_2.jl b/test/timeseries_year_2.jl index 126cf6ebd..2399934c3 100644 --- a/test/timeseries_year_2.jl +++ b/test/timeseries_year_2.jl @@ -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)