Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Datetime64 fixes #816

Merged
merged 13 commits into from Aug 18, 2016
Merged
1 change: 1 addition & 0 deletions holoviews/core/__init__.py
Expand Up @@ -17,6 +17,7 @@
Dimension.type_formatters[float] = "%.5g"
Dimension.type_formatters[np.float32] = "%.5g"
Dimension.type_formatters[np.float64] = "%.5g"
Dimension.type_formatters[np.datetime64] = '%Y-%m-%d %H:%M:%S'


def public(obj):
Expand Down
2 changes: 1 addition & 1 deletion holoviews/core/data/__init__.py
Expand Up @@ -463,7 +463,7 @@ def get_dimension_type(self, dim):
dim_obj = self.get_dimension(dim)
if dim_obj and dim_obj.type is not None:
return dim_obj.type
return self.interface.dimension_type(self, dim)
return self.interface.dimension_type(self, dim_obj)


def dframe(self, dimensions=None):
Expand Down
6 changes: 2 additions & 4 deletions holoviews/core/data/grid.py
Expand Up @@ -84,10 +84,8 @@ def validate(cls, dataset):

@classmethod
def dimension_type(cls, dataset, dim):
if dim in dataset.kdims:
arr = dataset.data[dim.name]
elif dim in dataset.vdims:
arr = dataset.data[dim.name]
if dim in dataset.dimensions():
arr = cls.values(dataset, dim, False, False)
else:
return None
return arr.dtype.type
Expand Down
5 changes: 4 additions & 1 deletion holoviews/core/data/xarray.py
Expand Up @@ -78,7 +78,10 @@ def range(cls, dataset, dimension):
dim = dataset.get_dimension(dimension).name
if dim in dataset.data:
data = dataset.data[dim]
return data.min().item(), data.max().item()
dmin, dmax = data.min().data, data.max().data
dmin = dmin if np.isscalar(dmin) else dmin.item()
dmax = dmax if np.isscalar(dmax) else dmax.item()
return dmin, dmax
else:
return np.NaN, np.NaN

Expand Down
9 changes: 7 additions & 2 deletions holoviews/core/dimension.py
Expand Up @@ -5,6 +5,7 @@
"""
from __future__ import unicode_literals
import re
import datetime as dt
from operator import itemgetter

import numpy as np
Expand All @@ -13,7 +14,7 @@
from ..core.util import (basestring, sanitize_identifier,
group_sanitizer, label_sanitizer, max_range,
find_range, dimension_sanitizer, OrderedDict,
safe_unicode, unicode)
safe_unicode, unicode, dt64_to_dt)
from .options import Store, StoreOptions
from .pprint import PrettyPrinter

Expand Down Expand Up @@ -178,7 +179,11 @@ def pprint_value(self, value):
if callable(formatter):
return formatter(value)
elif isinstance(formatter, basestring):
if re.findall(r"\{(\w+)\}", formatter):
if isinstance(value, dt.datetime):
return value.strftime(formatter)
elif isinstance(value, np.datetime64):
return dt64_to_dt(value).strftime(formatter)
elif re.findall(r"\{(\w+)\}", formatter):
return formatter.format(value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the datetime64 objects will use datetime style formatting? That is fine, assuming datetime64 doesn't have its own formatting conventions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that's nice behavior, datetime64 can't be formatted directly so it converts to regular datetime first and then applies the formatter.

else:
return formatter % value
Expand Down
8 changes: 8 additions & 0 deletions holoviews/core/util.py
Expand Up @@ -3,6 +3,7 @@
import itertools
import string, fnmatch
import unicodedata
import datetime as dt
from collections import defaultdict

import numpy as np
Expand Down Expand Up @@ -917,3 +918,10 @@ def expand_grid_coords(dataset, dim):
idx = dataset.get_dimension_index(dim)
return cartesian_product(arrays)[idx]


def dt64_to_dt(dt64):
"""
Safely converts NumPy datetime64 to a datetime object.
"""
ts = (dt64 - np.datetime64('1970-01-01T00:00:00Z')) / np.timedelta64(1, 's')
return dt.datetime.utcfromtimestamp(ts)
17 changes: 15 additions & 2 deletions holoviews/plotting/mpl/chart.py
@@ -1,14 +1,16 @@
from __future__ import unicode_literals

from itertools import product

import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.dates import date2num, DateFormatter

import param

from ...core import OrderedDict
from ...core import OrderedDict, Dimension
from ...core.util import (match_spec, unique_iterator, safe_unicode,
basestring, max_range, unicode)
from ...element import Points, Raster, Polygons, HeatMap
Expand Down Expand Up @@ -59,8 +61,19 @@ class CurvePlot(ChartPlot):
def get_data(self, element, ranges, style):
xs = element.dimension_values(0)
ys = element.dimension_values(1)
return (xs, ys), style, {}
dims = element.dimensions()
if xs.dtype.kind == 'M':
dt_format = Dimension.type_formatters[np.datetime64]
dims[0] = dims[0](value_format=DateFormatter(dt_format))
return (xs, ys), style, {'dimensions': dims}

def init_artists(self, ax, plot_args, plot_kwargs):
xs, ys = plot_args
if xs.dtype.kind == 'M':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding a comment mentioning what the 'M' code means. I assume it is the numpy datetime64 type?

artist = ax.plot_date(xs, ys, '-', **plot_kwargs)[0]
else:
artist = ax.plot(xs, ys, **plot_kwargs)[0]
return {'artist': artist}

def update_handles(self, key, axis, element, ranges, style):
artist = self.handles['artist']
Expand Down
11 changes: 7 additions & 4 deletions holoviews/plotting/mpl/element.py
@@ -1,10 +1,11 @@
import math

import param
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import ticker
from matplotlib import colors
import matplotlib.pyplot as plt
import numpy as np
import param
from matplotlib.dates import date2num

from ...core import util
from ...core import (OrderedDict, NdOverlay, DynamicMap,
Expand Down Expand Up @@ -301,7 +302,9 @@ def _set_axis_limits(self, axis, view, subplots, ranges):
scalex, scaley = True, True
extents = self.get_extents(view, ranges)
if extents and not self.overlaid:
coords = [coord if np.isreal(coord) else np.NaN for coord in extents]
coords = [coord if np.isreal(coord) or isinstance(coord, np.datetime64) else np.NaN for coord in extents]
coords = [date2num(util.dt64_to_dt(c)) if isinstance(c, np.datetime64) else c
for c in coords]
valid_lim = lambda c: util.isnumeric(c) and not np.isnan(c)
if self.projection == '3d' or len(extents) == 6:
l, b, zmin, r, t, zmax = coords
Expand Down
22 changes: 4 additions & 18 deletions holoviews/plotting/mpl/plot.py
Expand Up @@ -262,9 +262,6 @@ class GridPlot(CompositePlot):
show_legend = param.Boolean(default=False, doc="""
Legends add to much clutter in a grid and are disabled by default.""")

tick_format = param.String(default="%.2f", doc="""
Formatting string for the GridPlot ticklabels.""")

xaxis = param.ObjectSelector(default='bottom',
Copy link
Contributor

@jlstevens jlstevens Aug 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this parameter is now deprecated?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yes, we have a system for tick formatting using Dimension.value_format and Dimension.type_formatters, so this is redundant. You think we need to go through a deprecation cycle for this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will at least need to be mentioned in the CHANGELOG. Other than that I think it is fine.

objects=['bottom', 'top', None], doc="""
Whether and where to display the xaxis, supported options are
Expand Down Expand Up @@ -496,13 +493,15 @@ def _layout_axis(self, layout, axis):
yticks = [(plot_height/2)+(r*(plot_height+border_height)) for r in range(self.rows)]

layout_axis.set_xticks(xticks)
layout_axis.set_xticklabels(self._process_ticklabels(sorted(set(dim1_keys)), dims[0]))
layout_axis.set_xticklabels([dims[0].pprint_value(l)
for l in sorted(set(dim1_keys))])
for tick in layout_axis.get_xticklabels():
tick.set_rotation(self.xrotation)

ydim = dims[1] if layout.ndims > 1 else None
layout_axis.set_yticks(yticks)
layout_axis.set_yticklabels(self._process_ticklabels(sorted(set(dim2_keys)), ydim))
layout_axis.set_yticklabels([ydim.pprint_value(l) if ydim else ''
for l in sorted(set(dim2_keys))])
for tick in layout_axis.get_yticklabels():
tick.set_rotation(self.yrotation)

Expand All @@ -529,19 +528,6 @@ def _layout_axis(self, layout, axis):
return layout_axis


def _process_ticklabels(self, labels, dim):
formatted_labels = []
for k in labels:
if dim and dim.value_format:
k = dim.value_format(k)
elif not isinstance(k, (str, type(None))):
k = self.tick_format % k
elif k is None:
k = ''
formatted_labels.append(k)
return formatted_labels


def _adjust_subplots(self, axis, subaxes):
bbox = axis.get_position()
l, b, w, h = bbox.x0, bbox.y0, bbox.width, bbox.height
Expand Down
5 changes: 3 additions & 2 deletions holoviews/plotting/mpl/raster.py
Expand Up @@ -379,8 +379,9 @@ def update_frame(self, key, ranges=None):
def _get_axis_kwargs(self):
xdim = self.layout.kdims[0]
ydim = self.layout.kdims[1] if self.layout.ndims > 1 else None
xticks = (self._xticks, self._process_ticklabels(self._xkeys, xdim))
yticks = (self._yticks, self._process_ticklabels(self._ykeys, ydim))
xticks = (self._xticks, [xdim.pprint_value(l) for l in self._xkeys])
yticks = (self._yticks, [ydim.pprint_value(l) if ydim else ''
for l in self._ykeys])
return dict(xlabel=xdim.pprint_label, ylabel=ydim.pprint_label if ydim else '',
xticks=xticks, yticks=yticks)

Expand Down