Incorporating TikZ into Matplotlib

I would advocate for calling yours something different. path.Path is really a container for a Bezier curve and is probably best left as simple as possible. There is probably an interesting discussion about right is-a and has-a relations between Path, FancyPath (don’t use that name!), and FancyArrow (which I do not have a clear view of yet).

Tom

···

On Fri, May 15, 2015 at 10:53 AM, Thomas Caswell <tcaswell@…149…> wrote:

A few very quick comments (just skimmed the docstrings)

We already have a mpl.path.Path class, please don’t shadow that.

I read the Path class and based mine on that. The problem is that I want to be able to place nodes along the path (like labels) and so I need to ask it questions. Maybe we should just extend the existing Path class? Or else we should call my Path something different?

Is your Path going to be an Artist that is responsible for drawing it’s self or does in serve a role like the existing Path in that it is used by other artists as part of their draw?

This feels very similar to the FancyArrow (with classes being passed in to control how the arrow is styled), would this make sense as an extension to that code? This does seem more general, maybe it makes sense to start from scratch and implement FancyArrow in terms of this code.

Yes! Didn’t know about that. I think modifying and extending that code might be a good way forward.

Tom

On Fri, May 15, 2015 at 10:40 AM Neil Girdhar <mistersheik@…149…> wrote:

I have a draft proposal of the long term goal for what an interface could look like for drawing arrows between coordinates or nodes. I based the design on the tikz manual (http://pgf.sourceforge.net/pgf_CVS.pdf), so it might help to flip through that to get an idea for the basis of this design. I tried to separate the creating of Path objects with the drawing of paths since it’s often really useful when compositing layouts to be able to do math with with the positions of things before drawing anything. For example, when automatically positioning nodes.

I’m not committed to this design; it’s just an outline to get feedback.

Best,

Neil

class Axes_(_AxesBase):

def path(self, path, draw=True, fill=False):

“”"

If draw is not falsy, draws along the path using the draw

specification.

If fill is not falsy, fills the closed path using the fill

specification.

Parameters


path is a Path object or path commands with which to create one.

draw is a draw specification:

either the value True, which indicates some defaults, or else

False, or else a dictionary with the following keys:

color

opacity

line_width

line_join

begin_tip is a Tip object

tip or end_tip is a Tip object

dashed is a dash specification

a dash specification

either dictionary containing:

dash_pattern

an iterable of numbers specifying the length of the dashes

and gaps in points. E.g., [2, 3, 4, 3] means on for 2

points, off for 3, on for 4, off for 3, i.e., dash-dotted.

dash_phase

Shifts the start of the dash pattern by dash_phase points.

or a string, one of:

‘solid’

‘dotted’, ‘densely dotted’, ‘loosely dotted’

‘dashed’, ‘densely dashed’, ‘loosely dashed’

‘dash dot’, ‘densely dash dot’, ‘loosely dash dot’

‘dash dot dot’, ‘densely dash dot dot’, ‘loosely dash dot dot’

fill is a fill specification:

TODO

“”"

class Path:

def init(self, path_commands):

“”"

path_commands is either

a coordinate (representing a move to in the first position, or a

line to in any other position)

MoveTo(coordinate)

LineTo(coordinate_or_node, draw=None)

CurveTo(coordinate_or_node, control_points, draw=None)

ClosePolygon()

optional draw commands override the draw specification of the whole

path within that edge.

a coordinate is either an (x, y) pair, or a Coordinate object.

a node is a Node object.

“”"

def at_position(self, fraction=0.5):

“”"

Returns a coordinate fraction of the way along the line.

fraction can be one of ‘at end’, ‘very near end’, ‘near end’,

‘midway’, ‘near start’, ‘very near start’, ‘at start’

“”"

def node_at(node, fraction=0.5, location, …)

“”"

Sets the node’s position so that it sits flush to the path.

Parameters


location :

Could be ‘above’, ‘below’, ‘on’, or a number, which is the number

of points away from the path to place the node.

“”"

def pin_node(node, pin_distance, draw=draw_specification):

pass

class Coordinate:

@property

def coordinate(self):

return (self.x, self.y)

def node_at(self, node, angle):

“”"

Places the node so that it is in the direction angle from the

coordinate. E.g.,

angle=pi/2, or angle=‘above’ places the node so that the coordinate is

touching the center-bottom of the node.

angle could be ‘above’, ‘below’, ‘left’, ‘right’, ‘above left’, etc.

“”"

class Node:

“”"

Available Node objects:

Rectangle, Circle

“”"

@property

def center(self):

return (self.x, self.y)

def node_at(self, node, angle):

“”"

Places the node so that it is in the direction angle from the

coordinate. The node could be an arrowhead for example.

“”"

def convex_hulls(self):

“”"

Returns a list of convex hulls. The convex hulls are used when

position one node or arrowhead flush with another using the

separating axis algorithm.

“”"

class Tip:

“”"

Available Tip objects:

ButtCap (no tip, the default)

RectangleCap, TriangleCap, RoundCap

ArcBarb, Bar, Bracket, Hooks, Parenthesis,

StraightBarb, TeeBarb

Circle, Diamond, Ellipse, Kite, Arrow,

Rectangle, Square, Stealth, Triangle,

TurnedSquare

TipCombination (accepts multiple tips and merges them)

“”"

def init(self, draw=None, fill=True, reversed_=False):

pass

def convex_hulls(self, line_width):

“”"

Returns a list of convex hulls for use with placement

whereby the arrow faces right starting at the origin.

“”"

def transmute(self, line_width):

“”"

Returns a pair of lists (draw_path, fill_path).

“”"

@property

def draw_specification(self):

“”"

is a draw specification, or None to use the parent line’s

“”"

def fill_specification(self):

“”"

Is a fill specification, or True to use defaults based

on the parent line’s draw color, or False to use an open fill.

“”"


Usage:

draw an arrow from point to point.

ax.path([(x, y), (x2, y2)], draw={‘tip’: Arrow()})

Create a path.

p = Path([(x, y), (x2, y2)])

Create a node along the path.

n = p.node_at(Label(“some label”))

Draw the path using an arrow, and the node.

ax.path(p, draw={‘tip’: Arrow()})

ax.node(n)

On Wed, May 13, 2015 at 11:27 PM, Thomas Caswell <tcaswell@…149…> wrote:

Sorry, I may have been being a bit dramatic

In mpl.patches: Arrow, FancyArrow, YAArrow, FancyArrowPatch, ConnectionPatch + annotation related artists + some classes in axisartist which now that I look at them are not really general purpose arrow tools. I had not been counting quiver (or barbs) or sankey.

Neil: Those are all great questions! Much of the arrow related code was written by Joe-Joon Lee who (by having read a good deal of his code) has a habit of writing very power but very opaque python.

I believe that the line join style is controlled by joinstyle on the graphics context and it is up to the backends to implement that correctly.

Tom

On Wed, May 13, 2015 at 10:58 PM Neil Girdhar <mistersheik@…149…> wrote:

Okay, I’m looking at this in more detail and there may be some design concerns:

The arrow placement is decided without asking the arrow any questions, such as its bounding box. Instead, the arrow should return a bounding box and then the line should retreat until the bounding box no longer intersects the target node. Then the arrow should be placed. This doesn’t matter so much when you have a simple arrow like this: ---->, but it’s a big deal when you have an arrow like ----| . In this case, the sides of the arrow risk intersecting with the target node.

I’m not keen on implementing every arrow three times: <-, ->, <->. This really should be handled by the code placing the arrows for many reasons:

  1. It should also be possible to have a different arrowhead at either end of the line.
  1. It should be possible to stack the arrows, for example having two heads one after another (to represent two kinds of relationships). This is another reason to be able to ask the arrowhead its length and so on.

I don’t understand the “monolithic” keyword. How can the arrow draw the line as well when it doesn’t know the line style, color and so on?

I think I like the design of the transmute function. I’m curious: ultimately, where does the mutation_size come from? Is it a global scale applied to the figure, or is it based on the linewidth, or?

When you emit a set of lines, how are they joined? If I draw a line having linewidth 0.1 from the origin to (1, 0), and back to (0, 0.5), what happens at the tip? Are two rectangles drawn (each having width 0.1, but oriented differently)? Is a bevel created? A miter? Or is the tip rounded? Can this be controlled? See page 166 of the manual I sent earlier (search for tikz/line join).

Best,

Neil

On Wed, May 13, 2015 at 10:14 PM, Neil Girdhar <mistersheik@…149…> wrote:

Thanks, it works!

I needed to add:

import matplotlib.patches

to one file and

plt.show()

to the other.

Any word on the locations in the code of the seven arrow drawing methods?

I’ve located the arrow drawing code in tikz, and so I can start porting it over. I’m curious, do we know the linewidth of the edge being decorated by the arrow? To make arrows scale nicely, most of the arrow dimensions are given in two pieces: an absolute value (in points for example) and a line width factor. The dimension is the absolute value plus the line width factor times the line width. The TikZ manual explains: “This makes it easy to vary the size of an arrow tip in accordance with the line width – usually a very good idea since thicker lines will need thicker arrow tips.”

Best,

Neil

On Wed, May 13, 2015 at 10:07 PM, Benjamin Reedlunn <breedlun@…149…> wrote:

Neil,

I have attached code to draw the arrowhead.

-Ben

On May 13, 2015, at 7:44 PM, Neil Girdhar <mistersheik@…149…> wrote:

Do you have the code that you used to draw the arrowhead? I’m up to date now on the development workflow (http://matplotlib.org/devel/gitwash/development_workflow.html), so I’m ready to start working.

Thanks,

Neil

On Wed, May 13, 2015 at 9:10 PM, Benjamin Reedlunn <breedlun@…149…> wrote:

Yes, I fully agree that we need to unify the many different ways to draw arrows.

Neil, in case an example would be helpful for you, I have attached a module that includes a custom arrowhead class. The arrowhead class works with the with the ax.annotate() method. (I like the annotate method because it allows me to easily mix and match coordinate systems for arrow placement.) As you can see in the attached pdf, the custom arrowhead doesn’t include fancy Bezier curves, but that could be added.

-Ben

On May 13, 2015, at 2:54 PM, Thomas Caswell <tcaswell@…149…> wrote:

The other thing that should be done is to unify the (I think 7?!?) unique ways to draw arrows in mpl.

On Wed, May 13, 2015 at 4:52 PM Neil Girdhar <mistersheik@…149…> wrote:

Yes, I just noticed that as well. That’s how the tikz pgf code looks (a sequence of line_to and curve_to commands and so on) so it should be easy to port over the various shapes.


One dashboard for servers and applications across Physical-Virtual-Cloud
Widest out-of-the-box monitoring support with 50+ applications
Performance metrics, stats and reports that give you Actionable Insights
Deep dive visibility with transaction tracing using APM Insight.

On Wed, May 13, 2015 at 4:49 PM, Eric Firing <efiring@…229…> wrote:

On 2015/05/13 10:12 AM, Neil Girdhar wrote:

If you want to make arrowheads look at all decent, they really need to
be enclosed in Bezier curves. See the diagram here:

Mpl paths support Bezier curves.
http://matplotlib.org/api/path_api.html?highlight=bezier

http://tex.stackexchange.com/questions/150289/how-do-you-accomplish-stealth-with-the-new-arrows-meta/230965#230965

The first two look like garbage. The last one is the only one that
looks good imho.

That depends on the application, and the observer.

Sure, but I may as well port them all of the tikz arrowheads over since most of the work would be figuring out how to do it.

Eric

Best,

Neil

On Wed, May 13, 2015 at 4:09 PM, Eric Firing <efiring@…229…
mailto:efiring@...229...> wrote:

On 2015/05/13 9:36 AM, Neil Girdhar wrote:

    I don't know matplotlib well enough (yet) to know what the
    change would
    consist of.

    I suggest you take a look at the beautiful tikz manual:
    [http://pgf.sourceforge.net/pgf_CVS.pdf](http://pgf.sourceforge.net/pgf_CVS.pdf)


Very helpful, thank you.


    The arrows.meta on page 201–212 are really well-designed and
    beautiful.

    Compare this with matplotlib's custom arrows:
    [http://stackoverflow.com/questions/16968007/custom-arrow-style-for-matplotlib-pyplot-annotate](http://stackoverflow.com/questions/16968007/custom-arrow-style-for-matplotlib-pyplot-annotate)

    How do I make tikz's arrowheads available for all backends?


My guess offhand is that this is a matter of using the mpl API.  I
don't think we would want to add all of these types and options to
the mpl core; but a toolkit might be ideal for this.  The mpl API,
which generates the same results for all backends, is quite complete
and flexible.  Things like arrowheads are Patch objects, and you can
specify any path you want.  The main trick is figuring out how to
handle transforms--what kind of coordinates should the path be
specifying?  How should things scale as a figure is reshaped and
resized?

For many of these types you could also use mpl Line2D objects, for
which several properties including cap style can be specified.  Not
all of the TikZ options would be available, but perhaps enough.

Eric

Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel


One dashboard for servers and applications across Physical-Virtual-Cloud
Widest out-of-the-box monitoring support with 50+ applications
Performance metrics, stats and reports that give you Actionable Insights
Deep dive visibility with transaction tracing using APM Insight.

Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

Okay, I’m going to wait for more feedback. An hour of design can be worth ten hours of implementation :slight_smile:

···

On Fri, May 15, 2015 at 11:11 AM, Thomas Caswell <tcaswell@…149…> wrote:

I would advocate for calling yours something different. path.Path is really a container for a Bezier curve and is probably best left as simple as possible. There is probably an interesting discussion about right is-a and has-a relations between Path, FancyPath (don’t use that name!), and FancyArrow (which I do not have a clear view of yet).

Tom

On Fri, May 15, 2015 at 11:04 AM Neil Girdhar <mistersheik@…149…> wrote:

On Fri, May 15, 2015 at 10:53 AM, Thomas Caswell <tcaswell@…761…> wrote:

A few very quick comments (just skimmed the docstrings)

We already have a mpl.path.Path class, please don’t shadow that.

I read the Path class and based mine on that. The problem is that I want to be able to place nodes along the path (like labels) and so I need to ask it questions. Maybe we should just extend the existing Path class? Or else we should call my Path something different?

Is your Path going to be an Artist that is responsible for drawing it’s self or does in serve a role like the existing Path in that it is used by other artists as part of their draw?

This feels very similar to the FancyArrow (with classes being passed in to control how the arrow is styled), would this make sense as an extension to that code? This does seem more general, maybe it makes sense to start from scratch and implement FancyArrow in terms of this code.

Yes! Didn’t know about that. I think modifying and extending that code might be a good way forward.

Tom

On Fri, May 15, 2015 at 10:40 AM Neil Girdhar <mistersheik@…149…> wrote:

I have a draft proposal of the long term goal for what an interface could look like for drawing arrows between coordinates or nodes. I based the design on the tikz manual (http://pgf.sourceforge.net/pgf_CVS.pdf), so it might help to flip through that to get an idea for the basis of this design. I tried to separate the creating of Path objects with the drawing of paths since it’s often really useful when compositing layouts to be able to do math with with the positions of things before drawing anything. For example, when automatically positioning nodes.

I’m not committed to this design; it’s just an outline to get feedback.

Best,

Neil

class Axes_(_AxesBase):

def path(self, path, draw=True, fill=False):

“”"

If draw is not falsy, draws along the path using the draw

specification.

If fill is not falsy, fills the closed path using the fill

specification.

Parameters


path is a Path object or path commands with which to create one.

draw is a draw specification:

either the value True, which indicates some defaults, or else

False, or else a dictionary with the following keys:

color

opacity

line_width

line_join

begin_tip is a Tip object

tip or end_tip is a Tip object

dashed is a dash specification

a dash specification

either dictionary containing:

dash_pattern

an iterable of numbers specifying the length of the dashes

and gaps in points. E.g., [2, 3, 4, 3] means on for 2

points, off for 3, on for 4, off for 3, i.e., dash-dotted.

dash_phase

Shifts the start of the dash pattern by dash_phase points.

or a string, one of:

‘solid’

‘dotted’, ‘densely dotted’, ‘loosely dotted’

‘dashed’, ‘densely dashed’, ‘loosely dashed’

‘dash dot’, ‘densely dash dot’, ‘loosely dash dot’

‘dash dot dot’, ‘densely dash dot dot’, ‘loosely dash dot dot’

fill is a fill specification:

TODO

“”"

class Path:

def init(self, path_commands):

“”"

path_commands is either

a coordinate (representing a move to in the first position, or a

line to in any other position)

MoveTo(coordinate)

LineTo(coordinate_or_node, draw=None)

CurveTo(coordinate_or_node, control_points, draw=None)

ClosePolygon()

optional draw commands override the draw specification of the whole

path within that edge.

a coordinate is either an (x, y) pair, or a Coordinate object.

a node is a Node object.

“”"

def at_position(self, fraction=0.5):

“”"

Returns a coordinate fraction of the way along the line.

fraction can be one of ‘at end’, ‘very near end’, ‘near end’,

‘midway’, ‘near start’, ‘very near start’, ‘at start’

“”"

def node_at(node, fraction=0.5, location, …)

“”"

Sets the node’s position so that it sits flush to the path.

Parameters


location :

Could be ‘above’, ‘below’, ‘on’, or a number, which is the number

of points away from the path to place the node.

“”"

def pin_node(node, pin_distance, draw=draw_specification):

pass

class Coordinate:

@property

def coordinate(self):

return (self.x, self.y)

def node_at(self, node, angle):

“”"

Places the node so that it is in the direction angle from the

coordinate. E.g.,

angle=pi/2, or angle=‘above’ places the node so that the coordinate is

touching the center-bottom of the node.

angle could be ‘above’, ‘below’, ‘left’, ‘right’, ‘above left’, etc.

“”"

class Node:

“”"

Available Node objects:

Rectangle, Circle

“”"

@property

def center(self):

return (self.x, self.y)

def node_at(self, node, angle):

“”"

Places the node so that it is in the direction angle from the

coordinate. The node could be an arrowhead for example.

“”"

def convex_hulls(self):

“”"

Returns a list of convex hulls. The convex hulls are used when

position one node or arrowhead flush with another using the

separating axis algorithm.

“”"

class Tip:

“”"

Available Tip objects:

ButtCap (no tip, the default)

RectangleCap, TriangleCap, RoundCap

ArcBarb, Bar, Bracket, Hooks, Parenthesis,

StraightBarb, TeeBarb

Circle, Diamond, Ellipse, Kite, Arrow,

Rectangle, Square, Stealth, Triangle,

TurnedSquare

TipCombination (accepts multiple tips and merges them)

“”"

def init(self, draw=None, fill=True, reversed_=False):

pass

def convex_hulls(self, line_width):

“”"

Returns a list of convex hulls for use with placement

whereby the arrow faces right starting at the origin.

“”"

def transmute(self, line_width):

“”"

Returns a pair of lists (draw_path, fill_path).

“”"

@property

def draw_specification(self):

“”"

is a draw specification, or None to use the parent line’s

“”"

def fill_specification(self):

“”"

Is a fill specification, or True to use defaults based

on the parent line’s draw color, or False to use an open fill.

“”"


Usage:

draw an arrow from point to point.

ax.path([(x, y), (x2, y2)], draw={‘tip’: Arrow()})

Create a path.

p = Path([(x, y), (x2, y2)])

Create a node along the path.

n = p.node_at(Label(“some label”))

Draw the path using an arrow, and the node.

ax.path(p, draw={‘tip’: Arrow()})

ax.node(n)

On Wed, May 13, 2015 at 11:27 PM, Thomas Caswell <tcaswell@…149…> wrote:

Sorry, I may have been being a bit dramatic

In mpl.patches: Arrow, FancyArrow, YAArrow, FancyArrowPatch, ConnectionPatch + annotation related artists + some classes in axisartist which now that I look at them are not really general purpose arrow tools. I had not been counting quiver (or barbs) or sankey.

Neil: Those are all great questions! Much of the arrow related code was written by Joe-Joon Lee who (by having read a good deal of his code) has a habit of writing very power but very opaque python.

I believe that the line join style is controlled by joinstyle on the graphics context and it is up to the backends to implement that correctly.

Tom

On Wed, May 13, 2015 at 10:58 PM Neil Girdhar <mistersheik@…149…> wrote:

Okay, I’m looking at this in more detail and there may be some design concerns:

The arrow placement is decided without asking the arrow any questions, such as its bounding box. Instead, the arrow should return a bounding box and then the line should retreat until the bounding box no longer intersects the target node. Then the arrow should be placed. This doesn’t matter so much when you have a simple arrow like this: ---->, but it’s a big deal when you have an arrow like ----| . In this case, the sides of the arrow risk intersecting with the target node.

I’m not keen on implementing every arrow three times: <-, ->, <->. This really should be handled by the code placing the arrows for many reasons:

  1. It should also be possible to have a different arrowhead at either end of the line.
  1. It should be possible to stack the arrows, for example having two heads one after another (to represent two kinds of relationships). This is another reason to be able to ask the arrowhead its length and so on.

I don’t understand the “monolithic” keyword. How can the arrow draw the line as well when it doesn’t know the line style, color and so on?

I think I like the design of the transmute function. I’m curious: ultimately, where does the mutation_size come from? Is it a global scale applied to the figure, or is it based on the linewidth, or?

When you emit a set of lines, how are they joined? If I draw a line having linewidth 0.1 from the origin to (1, 0), and back to (0, 0.5), what happens at the tip? Are two rectangles drawn (each having width 0.1, but oriented differently)? Is a bevel created? A miter? Or is the tip rounded? Can this be controlled? See page 166 of the manual I sent earlier (search for tikz/line join).

Best,

Neil

On Wed, May 13, 2015 at 10:14 PM, Neil Girdhar <mistersheik@…716…> wrote:

Thanks, it works!

I needed to add:

import matplotlib.patches

to one file and

plt.show()

to the other.

Any word on the locations in the code of the seven arrow drawing methods?

I’ve located the arrow drawing code in tikz, and so I can start porting it over. I’m curious, do we know the linewidth of the edge being decorated by the arrow? To make arrows scale nicely, most of the arrow dimensions are given in two pieces: an absolute value (in points for example) and a line width factor. The dimension is the absolute value plus the line width factor times the line width. The TikZ manual explains: “This makes it easy to vary the size of an arrow tip in accordance with the line width – usually a very good idea since thicker lines will need thicker arrow tips.”

Best,

Neil

On Wed, May 13, 2015 at 10:07 PM, Benjamin Reedlunn <breedlun@…149…> wrote:

Neil,

I have attached code to draw the arrowhead.

-Ben

On May 13, 2015, at 7:44 PM, Neil Girdhar <mistersheik@…149…> wrote:

Do you have the code that you used to draw the arrowhead? I’m up to date now on the development workflow (http://matplotlib.org/devel/gitwash/development_workflow.html), so I’m ready to start working.

Thanks,

Neil

On Wed, May 13, 2015 at 9:10 PM, Benjamin Reedlunn <breedlun@…149…> wrote:

Yes, I fully agree that we need to unify the many different ways to draw arrows.

Neil, in case an example would be helpful for you, I have attached a module that includes a custom arrowhead class. The arrowhead class works with the with the ax.annotate() method. (I like the annotate method because it allows me to easily mix and match coordinate systems for arrow placement.) As you can see in the attached pdf, the custom arrowhead doesn’t include fancy Bezier curves, but that could be added.

-Ben

On May 13, 2015, at 2:54 PM, Thomas Caswell <tcaswell@…149…> wrote:

The other thing that should be done is to unify the (I think 7?!?) unique ways to draw arrows in mpl.

On Wed, May 13, 2015 at 4:52 PM Neil Girdhar <mistersheik@…149…> wrote:

Yes, I just noticed that as well. That’s how the tikz pgf code looks (a sequence of line_to and curve_to commands and so on) so it should be easy to port over the various shapes.


One dashboard for servers and applications across Physical-Virtual-Cloud
Widest out-of-the-box monitoring support with 50+ applications
Performance metrics, stats and reports that give you Actionable Insights
Deep dive visibility with transaction tracing using APM Insight.

On Wed, May 13, 2015 at 4:49 PM, Eric Firing <efiring@…229…> wrote:

On 2015/05/13 10:12 AM, Neil Girdhar wrote:

If you want to make arrowheads look at all decent, they really need to
be enclosed in Bezier curves. See the diagram here:

Mpl paths support Bezier curves.
http://matplotlib.org/api/path_api.html?highlight=bezier

http://tex.stackexchange.com/questions/150289/how-do-you-accomplish-stealth-with-the-new-arrows-meta/230965#230965

The first two look like garbage. The last one is the only one that
looks good imho.

That depends on the application, and the observer.

Sure, but I may as well port them all of the tikz arrowheads over since most of the work would be figuring out how to do it.

Eric

Best,

Neil

On Wed, May 13, 2015 at 4:09 PM, Eric Firing <efiring@…229…
mailto:efiring@...229...> wrote:

On 2015/05/13 9:36 AM, Neil Girdhar wrote:

    I don't know matplotlib well enough (yet) to know what the
    change would
    consist of.

    I suggest you take a look at the beautiful tikz manual:
    [http://pgf.sourceforge.net/pgf_CVS.pdf](http://pgf.sourceforge.net/pgf_CVS.pdf)


Very helpful, thank you.


    The arrows.meta on page 201–212 are really well-designed and
    beautiful.

    Compare this with matplotlib's custom arrows:
    [http://stackoverflow.com/questions/16968007/custom-arrow-style-for-matplotlib-pyplot-annotate](http://stackoverflow.com/questions/16968007/custom-arrow-style-for-matplotlib-pyplot-annotate)

    How do I make tikz's arrowheads available for all backends?


My guess offhand is that this is a matter of using the mpl API.  I
don't think we would want to add all of these types and options to
the mpl core; but a toolkit might be ideal for this.  The mpl API,
which generates the same results for all backends, is quite complete
and flexible.  Things like arrowheads are Patch objects, and you can
specify any path you want.  The main trick is figuring out how to
handle transforms--what kind of coordinates should the path be
specifying?  How should things scale as a figure is reshaped and
resized?

For many of these types you could also use mpl Line2D objects, for
which several properties including cap style can be specified.  Not
all of the TikZ options would be available, but perhaps enough.

Eric

Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel


One dashboard for servers and applications across Physical-Virtual-Cloud
Widest out-of-the-box monitoring support with 50+ applications
Performance metrics, stats and reports that give you Actionable Insights
Deep dive visibility with transaction tracing using APM Insight.

Matplotlib-devel mailing list
Matplotlib-devel@…953…ge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

Come to think of it, I think the inheritance of draw and fill attributes that happen along the path would take advantage of traitlets if you guys decide to go that route.

···

On Fri, May 15, 2015 at 11:40 AM, Neil Girdhar <mistersheik@…149…> wrote:

Okay, I’m going to wait for more feedback. An hour of design can be worth ten hours of implementation :slight_smile:

On Fri, May 15, 2015 at 11:11 AM, Thomas Caswell <tcaswell@…149…> wrote:

I would advocate for calling yours something different. path.Path is really a container for a Bezier curve and is probably best left as simple as possible. There is probably an interesting discussion about right is-a and has-a relations between Path, FancyPath (don’t use that name!), and FancyArrow (which I do not have a clear view of yet).

Tom

On Fri, May 15, 2015 at 11:04 AM Neil Girdhar <mistersheik@…149…> wrote:

On Fri, May 15, 2015 at 10:53 AM, Thomas Caswell <tcaswell@…149…> wrote:

A few very quick comments (just skimmed the docstrings)

We already have a mpl.path.Path class, please don’t shadow that.

I read the Path class and based mine on that. The problem is that I want to be able to place nodes along the path (like labels) and so I need to ask it questions. Maybe we should just extend the existing Path class? Or else we should call my Path something different?

Is your Path going to be an Artist that is responsible for drawing it’s self or does in serve a role like the existing Path in that it is used by other artists as part of their draw?

This feels very similar to the FancyArrow (with classes being passed in to control how the arrow is styled), would this make sense as an extension to that code? This does seem more general, maybe it makes sense to start from scratch and implement FancyArrow in terms of this code.

Yes! Didn’t know about that. I think modifying and extending that code might be a good way forward.

Tom

On Fri, May 15, 2015 at 10:40 AM Neil Girdhar <mistersheik@…149…> wrote:

I have a draft proposal of the long term goal for what an interface could look like for drawing arrows between coordinates or nodes. I based the design on the tikz manual (http://pgf.sourceforge.net/pgf_CVS.pdf), so it might help to flip through that to get an idea for the basis of this design. I tried to separate the creating of Path objects with the drawing of paths since it’s often really useful when compositing layouts to be able to do math with with the positions of things before drawing anything. For example, when automatically positioning nodes.

I’m not committed to this design; it’s just an outline to get feedback.

Best,

Neil

class Axes_(_AxesBase):

def path(self, path, draw=True, fill=False):

“”"

If draw is not falsy, draws along the path using the draw

specification.

If fill is not falsy, fills the closed path using the fill

specification.

Parameters


path is a Path object or path commands with which to create one.

draw is a draw specification:

either the value True, which indicates some defaults, or else

False, or else a dictionary with the following keys:

color

opacity

line_width

line_join

begin_tip is a Tip object

tip or end_tip is a Tip object

dashed is a dash specification

a dash specification

either dictionary containing:

dash_pattern

an iterable of numbers specifying the length of the dashes

and gaps in points. E.g., [2, 3, 4, 3] means on for 2

points, off for 3, on for 4, off for 3, i.e., dash-dotted.

dash_phase

Shifts the start of the dash pattern by dash_phase points.

or a string, one of:

‘solid’

‘dotted’, ‘densely dotted’, ‘loosely dotted’

‘dashed’, ‘densely dashed’, ‘loosely dashed’

‘dash dot’, ‘densely dash dot’, ‘loosely dash dot’

‘dash dot dot’, ‘densely dash dot dot’, ‘loosely dash dot dot’

fill is a fill specification:

TODO

“”"

class Path:

def init(self, path_commands):

“”"

path_commands is either

a coordinate (representing a move to in the first position, or a

line to in any other position)

MoveTo(coordinate)

LineTo(coordinate_or_node, draw=None)

CurveTo(coordinate_or_node, control_points, draw=None)

ClosePolygon()

optional draw commands override the draw specification of the whole

path within that edge.

a coordinate is either an (x, y) pair, or a Coordinate object.

a node is a Node object.

“”"

def at_position(self, fraction=0.5):

“”"

Returns a coordinate fraction of the way along the line.

fraction can be one of ‘at end’, ‘very near end’, ‘near end’,

‘midway’, ‘near start’, ‘very near start’, ‘at start’

“”"

def node_at(node, fraction=0.5, location, …)

“”"

Sets the node’s position so that it sits flush to the path.

Parameters


location :

Could be ‘above’, ‘below’, ‘on’, or a number, which is the number

of points away from the path to place the node.

“”"

def pin_node(node, pin_distance, draw=draw_specification):

pass

class Coordinate:

@property

def coordinate(self):

return (self.x, self.y)

def node_at(self, node, angle):

“”"

Places the node so that it is in the direction angle from the

coordinate. E.g.,

angle=pi/2, or angle=‘above’ places the node so that the coordinate is

touching the center-bottom of the node.

angle could be ‘above’, ‘below’, ‘left’, ‘right’, ‘above left’, etc.

“”"

class Node:

“”"

Available Node objects:

Rectangle, Circle

“”"

@property

def center(self):

return (self.x, self.y)

def node_at(self, node, angle):

“”"

Places the node so that it is in the direction angle from the

coordinate. The node could be an arrowhead for example.

“”"

def convex_hulls(self):

“”"

Returns a list of convex hulls. The convex hulls are used when

position one node or arrowhead flush with another using the

separating axis algorithm.

“”"

class Tip:

“”"

Available Tip objects:

ButtCap (no tip, the default)

RectangleCap, TriangleCap, RoundCap

ArcBarb, Bar, Bracket, Hooks, Parenthesis,

StraightBarb, TeeBarb

Circle, Diamond, Ellipse, Kite, Arrow,

Rectangle, Square, Stealth, Triangle,

TurnedSquare

TipCombination (accepts multiple tips and merges them)

“”"

def init(self, draw=None, fill=True, reversed_=False):

pass

def convex_hulls(self, line_width):

“”"

Returns a list of convex hulls for use with placement

whereby the arrow faces right starting at the origin.

“”"

def transmute(self, line_width):

“”"

Returns a pair of lists (draw_path, fill_path).

“”"

@property

def draw_specification(self):

“”"

is a draw specification, or None to use the parent line’s

“”"

def fill_specification(self):

“”"

Is a fill specification, or True to use defaults based

on the parent line’s draw color, or False to use an open fill.

“”"


Usage:

draw an arrow from point to point.

ax.path([(x, y), (x2, y2)], draw={‘tip’: Arrow()})

Create a path.

p = Path([(x, y), (x2, y2)])

Create a node along the path.

n = p.node_at(Label(“some label”))

Draw the path using an arrow, and the node.

ax.path(p, draw={‘tip’: Arrow()})

ax.node(n)

On Wed, May 13, 2015 at 11:27 PM, Thomas Caswell <tcaswell@…149…> wrote:

Sorry, I may have been being a bit dramatic

In mpl.patches: Arrow, FancyArrow, YAArrow, FancyArrowPatch, ConnectionPatch + annotation related artists + some classes in axisartist which now that I look at them are not really general purpose arrow tools. I had not been counting quiver (or barbs) or sankey.

Neil: Those are all great questions! Much of the arrow related code was written by Joe-Joon Lee who (by having read a good deal of his code) has a habit of writing very power but very opaque python.

I believe that the line join style is controlled by joinstyle on the graphics context and it is up to the backends to implement that correctly.

Tom

On Wed, May 13, 2015 at 10:58 PM Neil Girdhar <mistersheik@…149…> wrote:

Okay, I’m looking at this in more detail and there may be some design concerns:

The arrow placement is decided without asking the arrow any questions, such as its bounding box. Instead, the arrow should return a bounding box and then the line should retreat until the bounding box no longer intersects the target node. Then the arrow should be placed. This doesn’t matter so much when you have a simple arrow like this: ---->, but it’s a big deal when you have an arrow like ----| . In this case, the sides of the arrow risk intersecting with the target node.

I’m not keen on implementing every arrow three times: <-, ->, <->. This really should be handled by the code placing the arrows for many reasons:

  1. It should also be possible to have a different arrowhead at either end of the line.
  1. It should be possible to stack the arrows, for example having two heads one after another (to represent two kinds of relationships). This is another reason to be able to ask the arrowhead its length and so on.

I don’t understand the “monolithic” keyword. How can the arrow draw the line as well when it doesn’t know the line style, color and so on?

I think I like the design of the transmute function. I’m curious: ultimately, where does the mutation_size come from? Is it a global scale applied to the figure, or is it based on the linewidth, or?

When you emit a set of lines, how are they joined? If I draw a line having linewidth 0.1 from the origin to (1, 0), and back to (0, 0.5), what happens at the tip? Are two rectangles drawn (each having width 0.1, but oriented differently)? Is a bevel created? A miter? Or is the tip rounded? Can this be controlled? See page 166 of the manual I sent earlier (search for tikz/line join).

Best,

Neil

On Wed, May 13, 2015 at 10:14 PM, Neil Girdhar <mistersheik@…149…> wrote:

Thanks, it works!

I needed to add:

import matplotlib.patches

to one file and

plt.show()

to the other.

Any word on the locations in the code of the seven arrow drawing methods?

I’ve located the arrow drawing code in tikz, and so I can start porting it over. I’m curious, do we know the linewidth of the edge being decorated by the arrow? To make arrows scale nicely, most of the arrow dimensions are given in two pieces: an absolute value (in points for example) and a line width factor. The dimension is the absolute value plus the line width factor times the line width. The TikZ manual explains: “This makes it easy to vary the size of an arrow tip in accordance with the line width – usually a very good idea since thicker lines will need thicker arrow tips.”

Best,

Neil

On Wed, May 13, 2015 at 10:07 PM, Benjamin Reedlunn <breedlun@…149…> wrote:

Neil,

I have attached code to draw the arrowhead.

-Ben

On May 13, 2015, at 7:44 PM, Neil Girdhar <mistersheik@…149…> wrote:

Do you have the code that you used to draw the arrowhead? I’m up to date now on the development workflow (http://matplotlib.org/devel/gitwash/development_workflow.html), so I’m ready to start working.

Thanks,

Neil

On Wed, May 13, 2015 at 9:10 PM, Benjamin Reedlunn <breedlun@…149…> wrote:

Yes, I fully agree that we need to unify the many different ways to draw arrows.

Neil, in case an example would be helpful for you, I have attached a module that includes a custom arrowhead class. The arrowhead class works with the with the ax.annotate() method. (I like the annotate method because it allows me to easily mix and match coordinate systems for arrow placement.) As you can see in the attached pdf, the custom arrowhead doesn’t include fancy Bezier curves, but that could be added.

-Ben

On May 13, 2015, at 2:54 PM, Thomas Caswell <tcaswell@…149…> wrote:

The other thing that should be done is to unify the (I think 7?!?) unique ways to draw arrows in mpl.

On Wed, May 13, 2015 at 4:52 PM Neil Girdhar <mistersheik@…149…> wrote:

Yes, I just noticed that as well. That’s how the tikz pgf code looks (a sequence of line_to and curve_to commands and so on) so it should be easy to port over the various shapes.


One dashboard for servers and applications across Physical-Virtual-Cloud
Widest out-of-the-box monitoring support with 50+ applications
Performance metrics, stats and reports that give you Actionable Insights
Deep dive visibility with transaction tracing using APM Insight.

On Wed, May 13, 2015 at 4:49 PM, Eric Firing <efiring@…229…> wrote:

On 2015/05/13 10:12 AM, Neil Girdhar wrote:

If you want to make arrowheads look at all decent, they really need to
be enclosed in Bezier curves. See the diagram here:

Mpl paths support Bezier curves.
http://matplotlib.org/api/path_api.html?highlight=bezier

http://tex.stackexchange.com/questions/150289/how-do-you-accomplish-stealth-with-the-new-arrows-meta/230965#230965

The first two look like garbage. The last one is the only one that
looks good imho.

That depends on the application, and the observer.

Sure, but I may as well port them all of the tikz arrowheads over since most of the work would be figuring out how to do it.

Eric

Best,

Neil

On Wed, May 13, 2015 at 4:09 PM, Eric Firing <efiring@…229…
mailto:efiring@...229...> wrote:

On 2015/05/13 9:36 AM, Neil Girdhar wrote:

    I don't know matplotlib well enough (yet) to know what the
    change would
    consist of.

    I suggest you take a look at the beautiful tikz manual:
    [http://pgf.sourceforge.net/pgf_CVS.pdf](http://pgf.sourceforge.net/pgf_CVS.pdf)


Very helpful, thank you.


    The arrows.meta on page 201–212 are really well-designed and
    beautiful.

    Compare this with matplotlib's custom arrows:
    [http://stackoverflow.com/questions/16968007/custom-arrow-style-for-matplotlib-pyplot-annotate](http://stackoverflow.com/questions/16968007/custom-arrow-style-for-matplotlib-pyplot-annotate)

    How do I make tikz's arrowheads available for all backends?


My guess offhand is that this is a matter of using the mpl API.  I
don't think we would want to add all of these types and options to
the mpl core; but a toolkit might be ideal for this.  The mpl API,
which generates the same results for all backends, is quite complete
and flexible.  Things like arrowheads are Patch objects, and you can
specify any path you want.  The main trick is figuring out how to
handle transforms--what kind of coordinates should the path be
specifying?  How should things scale as a figure is reshaped and
resized?

For many of these types you could also use mpl Line2D objects, for
which several properties including cap style can be specified.  Not
all of the TikZ options would be available, but perhaps enough.

Eric

Matplotlib-devel mailing list
Matplotlib-devel@…743…forge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel


One dashboard for servers and applications across Physical-Virtual-Cloud
Widest out-of-the-box monitoring support with 50+ applications
Performance metrics, stats and reports that give you Actionable Insights
Deep dive visibility with transaction tracing using APM Insight.

Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel