Skip to content

FIX: Restore hatch color tracking edge color #7976

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

Merged
merged 3 commits into from
Feb 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions doc/api/api_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,30 @@ out what caused the breakage and how to fix it by updating your code.
For new features that were added to Matplotlib, please see
:ref:`whats-new`.

API Changes in 2.0.1
====================

Extensions to `matplotlib.backend_bases.GraphicsContextBase`
------------------------------------------------------------

To better support controlling the color of hatches, the method
`matplotlib.backend_bases.GraphicsContextBase.set_hatch_color` was
added to the expected API of ``GraphicsContext`` classes. Calls to
this method are currently wrapped with a ``try:...except Attribute:``
block to preserve back-compatibility with any third-party backends
which do not extend `~matplotlib.backend_bases.GraphicsContextBase`.

This value can be accessed in the backends via
`matplotlib.backend_bases.GraphicsContextBase.get_hatch_color` (which
was added in 2.0 see :ref:`gc_get_hatch_color_wn`) and should be used
to color the hatches.

In the future there may also be ``hatch_linewidth`` and
``hatch_density`` related methods added. It is encouraged, but not
Copy link
Member Author

Choose a reason for hiding this comment

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

@NelleV Added the 'please derive from us' verbiage here.

Copy link
Member

Choose a reason for hiding this comment

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

Are you canceling by hand the old travis-ci tests or have we activated the new beta feature to cancel tests on PRs if a newer version exists?

Copy link
Member Author

Choose a reason for hiding this comment

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

I am canceling them by hand.

required that third-party backends extend
`~matplotlib.backend_bases.GraphicsContextBase` to make adapting to
these changes easier.


API Changes in 2.0.0
====================
Expand Down
22 changes: 13 additions & 9 deletions doc/users/dflt_style_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -617,20 +617,24 @@ To restore the previous behavior explicitly pass the keyword argument
Hatching
========

The color and width of the lines in a hatch pattern are now configurable by the
rcParams `hatch.color` and `hatch.linewidth`, with defaults of black and 1
point, respectively. The old behaviour for the color was to apply the edge
color or use black, depending on the artist; the old behavior for the line
width was different depending on backend:

The color of the lines in the hatch is now determined by

- If an edge color is explicitly set, use that for the hatch color
- If the edge color is not explicitly set, use ``rcParam['hatch.color']`` which
is looked up at artist creation time.

The width of the lines in a hatch pattern is now configurable by the
rcParams `hatch.linewidth`, which defaults to 1 point. The old
behavior for the line width was different depending on backend:

- PDF: 0.1 pt
- SVG: 1.0 pt
- PS: 1 px
- Agg: 1 px

The old color behavior can not be restored. The old line width behavior can not
be restored across all backends simultaneously, but can be restored for a
single backend by setting::
The old line width behavior can not be restored across all backends
simultaneously, but can be restored for a single backend by setting::

mpl.rcParams['hatch.linewidth'] = 0.1 # previous pdf hatch linewidth
mpl.rcParams['hatch.linewidth'] = 1.0 # previous svg hatch linewidth
Expand All @@ -643,7 +647,7 @@ The behavior of the PS and Agg backends was DPI dependent, thus::
mpl.rcParams['hatch.linewidth'] = 1.0 / dpi # previous ps and Agg hatch linewidth


There is no API level control of the hatch color or linewidth.
There is no direct API level control of the hatch color or linewidth.

Hatching patterns are now rendered at a consistent density, regardless of DPI.
Formerly, high DPI figures would be more dense than the default, and low DPI
Expand Down
14 changes: 14 additions & 0 deletions doc/users/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,20 @@ value of ``None`` instead of ``2``. If ``None`` is given as ``zorder``,
:func:`streamplot` has a default ``zorder`` of
``matplotlib.lines.Line2D.zorder``.

.. _gc_get_hatch_color_wn:

Extension to `matplotlib.backend_bases.GraphicsContextBase`
-----------------------------------------------------------

To support standardizing hatch behavior across the backends we ship
the `matplotlib.backend_bases.GraphicsContextBase.get_hatch_color`
method as added to `matplotlib.backend_bases.GraphicsContextBase`.
This is only used during the render process in the backends we ship so
will not break any third-party backends.

If you maintain a third-party backend which extends
`~matplotlib.backend_bases.GraphicsContextBase` this method is now
available to you and should be used to color hatch patterns.

Previous Whats New
==================
Expand Down
8 changes: 8 additions & 0 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,8 @@ def copy_properties(self, gc):
self._linewidth = gc._linewidth
self._rgb = gc._rgb
self._hatch = gc._hatch
self._hatch_color = gc._hatch_color
self._hatch_linewidth = gc._hatch_linewidth
self._url = gc._url
self._gid = gc._gid
self._snap = gc._snap
Expand Down Expand Up @@ -1111,6 +1113,12 @@ def get_hatch_color(self):
"""
return self._hatch_color

def set_hatch_color(self, hatch_color):
"""
sets the color to use for hatching.
"""
self._hatch_color = hatch_color

def get_hatch_linewidth(self):
"""
Gets the linewidth to use for hatching.
Expand Down
9 changes: 5 additions & 4 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2210,14 +2210,14 @@ def alpha_cmd(self, alpha, forced, effective_alphas):
name = self.file.alphaState(effective_alphas)
return [name, Op.setgstate]

def hatch_cmd(self, hatch):
def hatch_cmd(self, hatch, hatch_color):
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain this extra argument instead of using self._hatch_color?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because of the way the pdf backend deals with the fact that internally pdf's have a stack-based graphics context so when we change the gc in the draw methods, the delta method is used to determine what is different between the 'current' gc and the 'gc' that was just passed in. The way that method works is by checking a bunch of internal state and if it needs to be updated emit some commands to the pdf file. hatch_cmd is what generates the hatch commands. To tell if we need to change the hatch we need to check both _hatch and _hatch_color and then all of the parameters you check are passed into the command function (see L2323 ish). And the instance of the gc that the command methods are bound to is the 'old' gc, not the 'new' gc so we have to inject it this way.

It is possible that the pdf backend could be cleaned up, but that is a much bigger project.

sorry this is rambley, but it took me a while to sort this out last night.

Copy link
Member

Choose a reason for hiding this comment

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

I think I understand…

Copy link
Member Author

Choose a reason for hiding this comment

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

I feel the same way 😅

Copy link
Member

Choose a reason for hiding this comment

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

Also, this is made more complicated by how we implement hatching in pdf: it replaces the fill color with a named pattern. I think the backend_pdf changes are correct, and you added tests for the new functionality so we should be fine.

if not hatch:
if self._fillcolor is not None:
return self.fillcolor_cmd(self._fillcolor)
else:
return [Name('DeviceRGB'), Op.setcolorspace_nonstroke]
else:
hatch_style = (self._hatch_color, self._fillcolor, hatch)
hatch_style = (hatch_color, self._fillcolor, hatch)
name = self.file.hatchPattern(hatch_style)
return [Name('Pattern'), Op.setcolorspace_nonstroke,
name, Op.setcolor_nonstroke]
Expand Down Expand Up @@ -2281,7 +2281,8 @@ def clip_cmd(self, cliprect, clippath):
(('_linewidth',), linewidth_cmd),
(('_dashes',), dash_cmd),
(('_rgb',), rgb_cmd),
(('_hatch',), hatch_cmd), # must come after fillcolor and rgb
# must come after fillcolor and rgb
(('_hatch', '_hatch_color'), hatch_cmd),
)

# TODO: _linestyle
Expand Down Expand Up @@ -2312,7 +2313,7 @@ def delta(self, other):
break

# Need to update hatching if we also updated fillcolor
if params == ('_hatch',) and fill_performed:
if params == ('_hatch', '_hatch_color') and fill_performed:
different = True

if different:
Expand Down
12 changes: 12 additions & 0 deletions lib/matplotlib/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def __init__(self,
self._linewidths = [0]
self._is_filled = True # May be modified by set_facecolor().

self._hatch_color = mcolors.to_rgba(mpl.rcParams['hatch.color'])
self.set_facecolor(facecolors)
self.set_edgecolor(edgecolors)
self.set_linewidth(linewidths)
Expand Down Expand Up @@ -293,6 +294,12 @@ def draw(self, renderer):

if self._hatch:
gc.set_hatch(self._hatch)
try:
gc.set_hatch_color(self._hatch_color)
except AttributeError:
# if we end up with a GC that does not have this method
warnings.warn("Your backend does not support setting the "
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you define set_hatch_color in the base graphicscontext class as a noop that prints the warning? (would avoid the repetition below).

Copy link
Member Author

Choose a reason for hiding this comment

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

It is defined as a functional function it the base class, this is in case there are backends in the wild that do not derive from our base class.

Copy link
Member

Choose a reason for hiding this comment

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

Could we suggest them to derive from our base class if they don't have set_hatch_color?

Copy link
Member Author

Choose a reason for hiding this comment

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

Why? On the draw method side we are ok duck-typing and the gc objects should belong to the backends so they should free to implement how ever they want.

Copy link
Member

Choose a reason for hiding this comment

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

Just that it'd be an easy fix to get this working.

Copy link
Member

Choose a reason for hiding this comment

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

In this particular case, it seems better practice than duck typing :)

"hatch color.")

if self.get_sketch_params() is not None:
gc.set_sketch_params(*self.get_sketch_params())
Expand Down Expand Up @@ -690,12 +697,15 @@ def get_edgecolor(self):
get_edgecolors = get_edgecolor

def _set_edgecolor(self, c):
set_hatch_color = True
if c is None:
if (mpl.rcParams['patch.force_edgecolor'] or
not self._is_filled or self._edge_default):
c = mpl.rcParams['patch.edgecolor']
else:
c = 'none'
set_hatch_color = False

self._is_stroked = True
try:
if c.lower() == 'none':
Expand All @@ -710,6 +720,8 @@ def _set_edgecolor(self, c):
except AttributeError:
pass
self._edgecolors = mcolors.to_rgba_array(c, self._alpha)
if set_hatch_color and len(self._edgecolors):
self._hatch_color = tuple(self._edgecolors[0])
self.stale = True

def set_edgecolor(self, c):
Expand Down
21 changes: 20 additions & 1 deletion lib/matplotlib/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import six
from six.moves import map, zip
import warnings

import math

Expand Down Expand Up @@ -113,10 +114,10 @@ def __init__(self,
if antialiased is None:
antialiased = mpl.rcParams['patch.antialiased']

self._hatch_color = colors.to_rgba(mpl.rcParams['hatch.color'])
self._fill = True # needed for set_facecolor call
if color is not None:
if (edgecolor is not None or facecolor is not None):
import warnings
warnings.warn("Setting the 'color' property will override"
"the edgecolor or facecolor properties. ")
self.set_color(color)
Expand Down Expand Up @@ -288,13 +289,18 @@ def set_aa(self, aa):
return self.set_antialiased(aa)

def _set_edgecolor(self, color):
set_hatch_color = True
if color is None:
if (mpl.rcParams['patch.force_edgecolor'] or
not self._fill or self._edge_default):
color = mpl.rcParams['patch.edgecolor']
else:
color = 'none'
set_hatch_color = False

self._edgecolor = colors.to_rgba(color, self._alpha)
if set_hatch_color:
self._hatch_color = self._edgecolor
self.stale = True

def set_edgecolor(self, color):
Expand Down Expand Up @@ -545,6 +551,12 @@ def draw(self, renderer):

if self._hatch:
gc.set_hatch(self._hatch)
try:
gc.set_hatch_color(self._hatch_color)
except AttributeError:
# if we end up with a GC that does not have this method
warnings.warn("Your backend does not have support for "
"setting the hatch color.")

if self.get_sketch_params() is not None:
gc.set_sketch_params(*self.get_sketch_params())
Expand Down Expand Up @@ -4286,6 +4298,13 @@ def draw(self, renderer):

if self._hatch:
gc.set_hatch(self._hatch)
if self._hatch_color is not None:
try:
gc.set_hatch_color(self._hatch_color)
except AttributeError:
# if we end up with a GC that does not have this method
warnings.warn("Your backend does not support setting the "
"hatch color.")

if self.get_sketch_params() is not None:
gc.set_sketch_params(*self.get_sketch_params())
Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading