123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- """
- An experimental support for curvilinear grid.
- """
- from __future__ import (absolute_import, division, print_function,
- unicode_literals)
- import six
- from six.moves import zip
- from itertools import chain
- from .grid_finder import GridFinder
- from .axislines import AxisArtistHelper, GridHelperBase
- from .axis_artist import AxisArtist
- from matplotlib.transforms import Affine2D, IdentityTransform
- import numpy as np
- from matplotlib.path import Path
- class FixedAxisArtistHelper(AxisArtistHelper.Fixed):
- """
- Helper class for a fixed axis.
- """
- def __init__(self, grid_helper, side, nth_coord_ticks=None):
- """
- nth_coord = along which coordinate value varies.
- nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
- """
- super(FixedAxisArtistHelper, self).__init__(loc=side)
- self.grid_helper = grid_helper
- if nth_coord_ticks is None:
- nth_coord_ticks = self.nth_coord
- self.nth_coord_ticks = nth_coord_ticks
- self.side = side
- self._limits_inverted = False
- def update_lim(self, axes):
- self.grid_helper.update_lim(axes)
- if self.nth_coord == 0:
- xy1, xy2 = axes.get_ylim()
- else:
- xy1, xy2 = axes.get_xlim()
- if xy1 > xy2:
- self._limits_inverted = True
- else:
- self._limits_inverted = False
- def change_tick_coord(self, coord_number=None):
- if coord_number is None:
- self.nth_coord_ticks = 1 - self.nth_coord_ticks
- elif coord_number in [0, 1]:
- self.nth_coord_ticks = coord_number
- else:
- raise Exception("wrong coord number")
- def get_tick_transform(self, axes):
- return axes.transData
- def get_tick_iterators(self, axes):
- """tick_loc, tick_angle, tick_label"""
- g = self.grid_helper
- if self._limits_inverted:
- side = {"left":"right","right":"left",
- "top":"bottom", "bottom":"top"}[self.side]
- else:
- side = self.side
- ti1 = g.get_tick_iterator(self.nth_coord_ticks, side)
- ti2 = g.get_tick_iterator(1-self.nth_coord_ticks, side, minor=True)
- #ti2 = g.get_tick_iterator(1-self.nth_coord_ticks, self.side, minor=True)
- return chain(ti1, ti2), iter([])
- class FloatingAxisArtistHelper(AxisArtistHelper.Floating):
- def __init__(self, grid_helper, nth_coord, value, axis_direction=None):
- """
- nth_coord = along which coordinate value varies.
- nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
- """
- super(FloatingAxisArtistHelper, self).__init__(nth_coord,
- value,
- )
- self.value = value
- self.grid_helper = grid_helper
- self._extremes = None, None
- self._get_line_path = None # a method that returns a Path.
- self._line_num_points = 100 # number of points to create a line
- def set_extremes(self, e1, e2):
- self._extremes = e1, e2
- def update_lim(self, axes):
- self.grid_helper.update_lim(axes)
- x1, x2 = axes.get_xlim()
- y1, y2 = axes.get_ylim()
- grid_finder = self.grid_helper.grid_finder
- extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy,
- x1, y1, x2, y2)
- extremes = list(extremes)
- e1, e2 = self._extremes # ranges of other coordinates
- if self.nth_coord == 0:
- if e1 is not None:
- extremes[2] = max(e1, extremes[2])
- if e2 is not None:
- extremes[3] = min(e2, extremes[3])
- elif self.nth_coord == 1:
- if e1 is not None:
- extremes[0] = max(e1, extremes[0])
- if e2 is not None:
- extremes[1] = min(e2, extremes[1])
- grid_info = dict()
- lon_min, lon_max, lat_min, lat_max = extremes
- lon_levs, lon_n, lon_factor = \
- grid_finder.grid_locator1(lon_min, lon_max)
- lat_levs, lat_n, lat_factor = \
- grid_finder.grid_locator2(lat_min, lat_max)
- grid_info["extremes"] = extremes
- grid_info["lon_info"] = lon_levs, lon_n, lon_factor
- grid_info["lat_info"] = lat_levs, lat_n, lat_factor
- grid_info["lon_labels"] = grid_finder.tick_formatter1("bottom",
- lon_factor,
- lon_levs)
- grid_info["lat_labels"] = grid_finder.tick_formatter2("bottom",
- lat_factor,
- lat_levs)
- grid_finder = self.grid_helper.grid_finder
- #e1, e2 = self._extremes # ranges of other coordinates
- if self.nth_coord == 0:
- xx0 = np.linspace(self.value, self.value, self._line_num_points)
- yy0 = np.linspace(extremes[2], extremes[3], self._line_num_points)
- xx, yy = grid_finder.transform_xy(xx0, yy0)
- elif self.nth_coord == 1:
- xx0 = np.linspace(extremes[0], extremes[1], self._line_num_points)
- yy0 = np.linspace(self.value, self.value, self._line_num_points)
- xx, yy = grid_finder.transform_xy(xx0, yy0)
- grid_info["line_xy"] = xx, yy
- self.grid_info = grid_info
- def get_axislabel_transform(self, axes):
- return Affine2D() #axes.transData
- def get_axislabel_pos_angle(self, axes):
- extremes = self.grid_info["extremes"]
- if self.nth_coord == 0:
- xx0 = self.value
- yy0 = (extremes[2]+extremes[3])/2.
- dxx, dyy = 0., abs(extremes[2]-extremes[3])/1000.
- elif self.nth_coord == 1:
- xx0 = (extremes[0]+extremes[1])/2.
- yy0 = self.value
- dxx, dyy = abs(extremes[0]-extremes[1])/1000., 0.
- grid_finder = self.grid_helper.grid_finder
- xx1, yy1 = grid_finder.transform_xy([xx0], [yy0])
- trans_passingthrough_point = axes.transData + axes.transAxes.inverted()
- p = trans_passingthrough_point.transform_point([xx1[0], yy1[0]])
- if (0. <= p[0] <= 1.) and (0. <= p[1] <= 1.):
- xx1c, yy1c = axes.transData.transform_point([xx1[0], yy1[0]])
- xx2, yy2 = grid_finder.transform_xy([xx0+dxx], [yy0+dyy])
- xx2c, yy2c = axes.transData.transform_point([xx2[0], yy2[0]])
- return (xx1c, yy1c), np.arctan2(yy2c-yy1c, xx2c-xx1c)/np.pi*180.
- else:
- return None, None
- def get_tick_transform(self, axes):
- return IdentityTransform() #axes.transData
- def get_tick_iterators(self, axes):
- """tick_loc, tick_angle, tick_label, (optionally) tick_label"""
- grid_finder = self.grid_helper.grid_finder
- lat_levs, lat_n, lat_factor = self.grid_info["lat_info"]
- lat_levs = np.asarray(lat_levs)
- if lat_factor is not None:
- yy0 = lat_levs / lat_factor
- dy = 0.01 / lat_factor
- else:
- yy0 = lat_levs
- dy = 0.01
- lon_levs, lon_n, lon_factor = self.grid_info["lon_info"]
- lon_levs = np.asarray(lon_levs)
- if lon_factor is not None:
- xx0 = lon_levs / lon_factor
- dx = 0.01 / lon_factor
- else:
- xx0 = lon_levs
- dx = 0.01
- if None in self._extremes:
- e0, e1 = self._extremes
- else:
- e0, e1 = sorted(self._extremes)
- if e0 is None:
- e0 = -np.inf
- if e1 is None:
- e1 = np.inf
- if self.nth_coord == 0:
- mask = (e0 <= yy0) & (yy0 <= e1)
- #xx0, yy0 = xx0[mask], yy0[mask]
- yy0 = yy0[mask]
- elif self.nth_coord == 1:
- mask = (e0 <= xx0) & (xx0 <= e1)
- #xx0, yy0 = xx0[mask], yy0[mask]
- xx0 = xx0[mask]
- def transform_xy(x, y):
- x1, y1 = grid_finder.transform_xy(x, y)
- x2y2 = axes.transData.transform(np.array([x1, y1]).transpose())
- x2, y2 = x2y2.transpose()
- return x2, y2
- # find angles
- if self.nth_coord == 0:
- xx0 = np.empty_like(yy0)
- xx0.fill(self.value)
- xx1, yy1 = transform_xy(xx0, yy0)
- xx00 = xx0.copy()
- xx00[xx0+dx>e1] -= dx
- xx1a, yy1a = transform_xy(xx00, yy0)
- xx1b, yy1b = transform_xy(xx00+dx, yy0)
- xx2a, yy2a = transform_xy(xx0, yy0)
- xx2b, yy2b = transform_xy(xx0, yy0+dy)
- labels = self.grid_info["lat_labels"]
- labels = [l for l, m in zip(labels, mask) if m]
- elif self.nth_coord == 1:
- yy0 = np.empty_like(xx0)
- yy0.fill(self.value)
- xx1, yy1 = transform_xy(xx0, yy0)
- xx1a, yy1a = transform_xy(xx0, yy0)
- xx1b, yy1b = transform_xy(xx0, yy0+dy)
- xx00 = xx0.copy()
- xx00[xx0+dx>e1] -= dx
- xx2a, yy2a = transform_xy(xx00, yy0)
- xx2b, yy2b = transform_xy(xx00+dx, yy0)
- labels = self.grid_info["lon_labels"]
- labels = [l for l, m in zip(labels, mask) if m]
- def f1():
- dd = np.arctan2(yy1b-yy1a, xx1b-xx1a) # angle normal
- dd2 = np.arctan2(yy2b-yy2a, xx2b-xx2a) # angle tangent
- mm = ((yy1b-yy1a)==0.) & ((xx1b-xx1a)==0.) # mask where dd1 is not defined
- dd[mm] = dd2[mm] + np.pi / 2
- #dd = np.arctan2(yy2-yy1, xx2-xx1) # angle normal
- #dd2 = np.arctan2(yy3-yy1, xx3-xx1) # angle tangent
- #mm = ((yy2-yy1)==0.) & ((xx2-xx1)==0.) # mask where dd1 is not defined
- #dd[mm] = dd2[mm] + np.pi / 2
- #dd += np.pi
- #dd = np.arctan2(xx2-xx1, angle_tangent-yy1)
- trans_tick = self.get_tick_transform(axes)
- tr2ax = trans_tick + axes.transAxes.inverted()
- for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels):
- c2 = tr2ax.transform_point((x, y))
- delta=0.00001
- if (0. -delta<= c2[0] <= 1.+delta) and \
- (0. -delta<= c2[1] <= 1.+delta):
- d1 = d/3.14159*180.
- d2 = d2/3.14159*180.
- yield [x, y], d1, d2, lab
- return f1(), iter([])
- def get_line_transform(self, axes):
- return axes.transData
- def get_line(self, axes):
- self.update_lim(axes)
- x, y = self.grid_info["line_xy"]
- if self._get_line_path is None:
- return Path(np.column_stack([x, y]))
- else:
- return self._get_line_path(axes, x, y)
- class GridHelperCurveLinear(GridHelperBase):
- def __init__(self, aux_trans,
- extreme_finder=None,
- grid_locator1=None,
- grid_locator2=None,
- tick_formatter1=None,
- tick_formatter2=None):
- """
- aux_trans : a transform from the source (curved) coordinate to
- target (rectilinear) coordinate. An instance of MPL's Transform
- (inverse transform should be defined) or a tuple of two callable
- objects which defines the transform and its inverse. The callables
- need take two arguments of array of source coordinates and
- should return two target coordinates.
- e.g., ``x2, y2 = trans(x1, y1)``
- """
- super(GridHelperCurveLinear, self).__init__()
- self.grid_info = None
- self._old_values = None
- #self._grid_params = dict()
- self._aux_trans = aux_trans
- self.grid_finder = GridFinder(aux_trans,
- extreme_finder,
- grid_locator1,
- grid_locator2,
- tick_formatter1,
- tick_formatter2)
- def update_grid_finder(self, aux_trans=None, **kw):
- if aux_trans is not None:
- self.grid_finder.update_transform(aux_trans)
- self.grid_finder.update(**kw)
- self.invalidate()
- def _update(self, x1, x2, y1, y2):
- "bbox in 0-based image coordinates"
- # update wcsgrid
- if self.valid() and self._old_values == (x1, x2, y1, y2):
- return
- self._update_grid(x1, y1, x2, y2)
- self._old_values = (x1, x2, y1, y2)
- self._force_update = False
- def new_fixed_axis(self, loc,
- nth_coord=None,
- axis_direction=None,
- offset=None,
- axes=None):
- if axes is None:
- axes = self.axes
- if axis_direction is None:
- axis_direction = loc
- _helper = FixedAxisArtistHelper(self, loc,
- #nth_coord,
- nth_coord_ticks=nth_coord,
- )
- axisline = AxisArtist(axes, _helper, axis_direction=axis_direction)
- return axisline
- def new_floating_axis(self, nth_coord,
- value,
- axes=None,
- axis_direction="bottom"
- ):
- if axes is None:
- axes = self.axes
- _helper = FloatingAxisArtistHelper(
- self, nth_coord, value, axis_direction)
- axisline = AxisArtist(axes, _helper)
- #_helper = FloatingAxisArtistHelper(self, nth_coord,
- # value,
- # label_direction=label_direction,
- # )
- #axisline = AxisArtistFloating(axes, _helper,
- # axis_direction=axis_direction)
- axisline.line.set_clip_on(True)
- axisline.line.set_clip_box(axisline.axes.bbox)
- #axisline.major_ticklabels.set_visible(True)
- #axisline.minor_ticklabels.set_visible(False)
- #axisline.major_ticklabels.set_rotate_along_line(True)
- #axisline.set_rotate_label_along_line(True)
- return axisline
- def _update_grid(self, x1, y1, x2, y2):
- self.grid_info = self.grid_finder.get_grid_info(x1, y1, x2, y2)
- def get_gridlines(self, which="major", axis="both"):
- grid_lines = []
- if axis in ["both", "x"]:
- for gl in self.grid_info["lon"]["lines"]:
- grid_lines.extend(gl)
- if axis in ["both", "y"]:
- for gl in self.grid_info["lat"]["lines"]:
- grid_lines.extend(gl)
- return grid_lines
- def get_tick_iterator(self, nth_coord, axis_side, minor=False):
- #axisnr = dict(left=0, bottom=1, right=2, top=3)[axis_side]
- angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side]
- #angle = [0, 90, 180, 270][axisnr]
- lon_or_lat = ["lon", "lat"][nth_coord]
- if not minor: # major ticks
- def f():
- for (xy, a), l in zip(self.grid_info[lon_or_lat]["tick_locs"][axis_side],
- self.grid_info[lon_or_lat]["tick_labels"][axis_side]):
- angle_normal = a
- yield xy, angle_normal, angle_tangent, l
- else:
- def f():
- for (xy, a), l in zip(self.grid_info[lon_or_lat]["tick_locs"][axis_side],
- self.grid_info[lon_or_lat]["tick_labels"][axis_side]):
- angle_normal = a
- yield xy, angle_normal, angle_tangent, ""
- #for xy, a, l in self.grid_info[lon_or_lat]["ticks"][axis_side]:
- # yield xy, a, ""
- return f()
|