1# points.py - functions to plot points
2
3#    Copyright (C) 2003 Jeremy S. Sanders
4#    Email: Jeremy Sanders <jeremy@jeremysanders.net>
5#
6#    This program is free software; you can redistribute it and/or modify
7#    it under the terms of the GNU General Public License as published by
8#    the Free Software Foundation; either version 2 of the License, or
9#    (at your option) any later version.
10#
11#    This program is distributed in the hope that it will be useful,
12#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14#    GNU General Public License for more details.
15#
16#    You should have received a copy of the GNU General Public License along
17#    with this program; if not, write to the Free Software Foundation, Inc.,
18#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19###############################################################################
20
21from __future__ import division
22from .. import qtall as qt
23import numpy as N
24
25from ..helpers.qtloops import plotPathsToPainter
26
27from . import colormap
28
29"""This is the symbol plotting part of Veusz
30
31There are actually several different ways symbols are plotted.
32We choose the most appropriate one for the shape:
33
34QPainterPath symbols plotted with _plotPathSymbols
35line symbols are plotted with _plotLineSymbols
36ploygon symbols are plotted wiht _plotPolygonSymbols
37
38Many of these are implemented as paths internally, and drawn using
39QPainterPaths
40"""
41
42#######################################################################
43## draw symbols which are sets of line segments
44
45linesymbols = {
46    'asterisk': ( ((-0.707, -0.707), (0.707,  0.707)),
47                  ((-0.707,  0.707), (0.707, -0.707)),
48                  ((-1, 0), (1, 0)), ((0, -1), (0, 1)) ),
49    'lineplus': ( ((-1, 0), (1, 0)), ((0, -1), (0, 1)) ),
50    'linecross': ( ((-0.707, -0.707), (0.707,  0.707)),
51                   ((-0.707,  0.707), (0.707, -0.707)) ),
52    'plushair': ( ((-1, 0), (-0.5, 0)), ((1, 0), (0.5, 0)),
53                  ((0, -1), (0, -0.5)), ((0, 1), (0, 0.5)) ),
54    'crosshair': ( ((-0.707, -0.707), (-0.354, -0.354)),
55                   (( 0.707,  0.707), ( 0.354,  0.354)),
56                   (( 0.707, -0.707), ( 0.354, -0.354)),
57                   ((-0.707,  0.707), (-0.354,  0.354)) ),
58    'asteriskhair': ( ((-1, 0), (-0.5, 0)), ((1, 0), (0.5, 0)),
59                      ((0, -1), (0, -0.5)), ((0, 1), (0, 0.5)),
60                      ((-0.707, -0.707), (-0.354, -0.354)),
61                      (( 0.707,  0.707), ( 0.354,  0.354)),
62                      (( 0.707, -0.707), ( 0.354, -0.354)),
63                      ((-0.707,  0.707), (-0.354,  0.354)) ),
64    'linehorz': ( ((-1, 0), (1, 0)), ),
65    'linevert': ( ((0, -1), (0, 1)), ),
66    'linehorzgap': ( ((-1, 0), (-0.5, 0)), ((1, 0), (0.5, 0)) ),
67    'linevertgap': ( ((0, -1), (0, -0.5)), ((0, 1), (0, 0.5)) ),
68
69    # arrows
70    'arrowleft': ( ((1, -0.8), (0, 0), (1, 0.8)), ((2, 0), (0, 0)) ),
71    'arrowleftaway': ( ((-1, -0.8), (-2, 0), (-1, 0.8)), ((-2, 0), (0, 0)) ),
72    'arrowright': ( ((-1, -0.8), (0, 0), (-1, 0.8)), ((-2, 0), (0, 0)) ),
73    'arrowrightaway': ( ((1, -0.8), (2, 0), (1, 0.8)), ((2, 0), (0, 0)) ),
74    'arrowup': ( ((-0.8, 1), (0, 0), (0.8, 1)), ((0, 2), (0, 0)) ),
75    'arrowupaway': ( ((-0.8, -1), (0, -2), (0.8, -1)), ((0, 0), (0, -2)) ),
76    'arrowdown': ( ((-0.8, -1), (0, 0), (0.8, -1)), ((0, -2), (0, 0)) ),
77    'arrowdownaway': ( ((-0.8, 1), (0, 2), (0.8, 1)), ((0, 0), (0, 2)) ),
78
79    # limits
80    'limitlower': ( ((-0.8, -1), (0, 0), (0.8, -1)), ((0, -2), (0, 0)),
81                    ((-1, 0), (1, 0)) ),
82    'limitupper': ( ((-0.8, 1), (0, 0), (0.8, 1)), ((0, 2), (0, 0)),
83                    ((-1, 0), (1, 0)) ),
84    'limitleft': ( ((1, -0.8), (0, 0), (1, 0.8)), ((2, 0), (0, 0)),
85                   ((0, -1), (0, 1)) ),
86    'limitright': ( ((-1, -0.8), (0, 0), (-1, 0.8)), ((-2, 0), (0, 0)),
87                    ((0, -1), (0, 1)) ),
88    'limitupperaway': ( ((-0.8, -1), (0, -2), (0.8, -1)), ((0, 0), (0, -2)),
89                        ((-1, 0), (1, 0)) ),
90    'limitloweraway': ( ((-0.8, 1), (0, 2), (0.8, 1)), ((0, 0), (0, 2)),
91                        ((-1, 0), (1, 0)) ),
92    'limitleftaway': ( ((-1, -0.8), (-2, 0), (-1, 0.8)), ((-2, 0), (0, 0)),
93                       ((0, -1), (0, 1)) ),
94    'limitrightaway': ( ((1, -0.8), (2, 0), (1, 0.8)), ((2, 0), (0, 0)),
95                        ((0, -1), (0, 1)) ),
96
97    'arrowlowerleftaway':( ((-0.8, 1), (0, 2), (0.8, 1)),
98                           ((0, 2), (0, 0), (-2, 0)),
99                           ((-1, -0.8), (-2, 0), (-1, 0.8)) ),
100    'arrowlowerrightaway': ( ((1, -0.8), (2, 0), (1, 0.8)),
101                             ((2, 0), (0, 0), (0, 2)),
102                             ((-0.8, 1), (0, 2), (0.8, 1)) ),
103    'arrowupperleftaway':( ((-0.8, -1), (0, -2), (0.8, -1)),
104                           ((0, -2), (0, 0), (-2, 0)),
105                           ((-1, -0.8), (-2, 0), (-1, 0.8)) ),
106    'arrowupperrightaway': ( ((-0.8, -1), (0, -2), (0.8, -1)),
107                             ((2, 0), (0, 0), (0, -2)),
108                             ((1, -0.8), (2, 0), (1, 0.8)) ),
109
110    'lineup': ( ((0, 0), (0, -1)), ),
111    'linedown': ( ((0, 0), (0, 1)), ),
112    'lineleft': ( ((0, 0), (-1, 0)), ),
113    'lineright': ( ((0, 0), (1, 0)), ),
114
115    # for arrows
116    '_linearrow': ( ((-1.8, -1), (0, 0), (-1.8, 1)), ),
117    '_linearrowreverse': ( ((1.8, -1), (0, 0), (1.8, 1)), ),
118    }
119
120def getLinePainterPath(name, size):
121    """Get a painter path for line like objects."""
122    path = qt.QPainterPath()
123    for lines in linesymbols[name]:
124        path.moveTo(lines[0][0]*size, lines[0][1]*size)
125        for x, y in lines[1:]:
126            path.lineTo(x*size, y*size)
127    return path
128
129#######################################################################
130## draw symbols which are polygons
131
132# X and Y pts for corners of polygons
133polygons = {
134    # make the diamond the same area as the square
135    'diamond': ( (0., 1.414), (1.414, 0.), (0., -1.414), (-1.414, 0.) ),
136    'barhorz': ( (-1, -0.5), (1, -0.5), (1, 0.5), (-1, 0.5) ),
137    'barvert': ( (-0.5, -1), (0.5, -1), (0.5, 1), (-0.5, 1) ),
138    'plus': ( (0.4, 1), (0.4, 0.4), (1, 0.4), (1, -0.4),
139              (0.4, -0.4), (0.4, -1), (-0.4, -1), (-0.4, -0.4),
140              (-1, -0.4), (-1, 0.4), (-0.4, 0.4), (-0.4, 1) ),
141    'octogon': ( (0.414, 1), (1, 0.414), (1, -0.414), (0.414, -1),
142                 (-0.414, -1), (-1, -0.414), (-1, 0.414), (-0.414, 1) ),
143    'triangle': ( (0, -1.2), (1.0392, 0.6), (-1.0392, 0.6) ),
144    'triangledown': ( (0, 1.2), (1.0392, -0.6), (-1.0392, -0.6) ),
145    'triangleleft': ( (-1.2, 0), (0.6, 1.0392), (0.6, -1.0392) ),
146    'triangleright': ( (1.2, 0), (-0.6, 1.0392), (-0.6, -1.0392) ),
147    'cross': ( (-0.594, 1.1028), (0, 0.5088), (0.594, 1.1028),
148               (1.1028, 0.594), (0.5088, -0), (1.1028, -0.594),
149               (0.594, -1.1028), (-0, -0.5088), (-0.594, -1.1028),
150               (-1.1028, -0.594), (-0.5088, 0), (-1.1028, 0.594) ),
151    'star': ( (0, -1.2), (-0.27, -0.3708), (-1.1412, -0.3708),
152              (-0.4356, 0.1416), (-0.7056, 0.9708), (-0, 0.4584),
153              (0.7056, 0.9708), (0.4356, 0.1416), (1.1412, -0.3708),
154              (0.27, -0.3708) ),
155    'pentagon': ((0, -1.2), (1.1412, -0.3708), (0.6936, 0.9708),
156                 (-0.6936, 0.9708), (-1.1412, -0.3708)),
157    'tievert': ( (-1, -1), (1, -1), (-1, 1), (1, 1) ),
158    'tiehorz': ( (-1, -1), (-1, 1), (1, -1), (1, 1) ),
159    'lozengehorz': ( (0, 0.707), (1.414, 0), (0, -0.707), (-1.414, 0) ),
160    'lozengevert': ( (0, 1.414), (0.707, 0), (0, -1.414), (-0.707, 0) ),
161
162    'star3': ( (0., -1.), (0.173, -0.1), (0.866, 0.5), (0, 0.2),
163               (-0.866, 0.5), (-0.173, -0.1) ),
164    'star4': ( (0.000, 1.000), (-0.354, 0.354), (-1.000, 0.000),
165               (-0.354, -0.354), (0.000, -1.000), (0.354, -0.354),
166               (1.000, -0.000), (0.354, 0.354), ),
167    'star6': ( (0.000, 1.000), (-0.250, 0.433), (-0.866, 0.500),
168               (-0.500, 0.000), (-0.866, -0.500), (-0.250, -0.433),
169               (-0.000, -1.000), (0.250, -0.433), (0.866, -0.500),
170               (0.500, 0.000), (0.866, 0.500), (0.250, 0.433), ),
171    'star8': ( (0.000, 1.000), (-0.191, 0.462), (-0.707, 0.707),
172               (-0.462, 0.191), (-1.000, 0.000), (-0.462, -0.191),
173               (-0.707, -0.707), (-0.191, -0.462), (0.000, -1.000),
174               (0.191, -0.462), (0.707, -0.707), (0.462, -0.191),
175               (1.000, -0.000), (0.462, 0.191), (0.707, 0.707),
176               (0.191, 0.462), ),
177    'hexagon': ( (0, 1), (0.866, 0.5), (0.866, -0.5),
178                 (0, -1), (-0.866, -0.5), (-0.866, 0.5), ),
179    'starinvert': ( (0, 1.2), (-0.27, 0.3708), (-1.1412, 0.3708),
180                    (-0.4356, -0.1416), (-0.7056, -0.9708), (0, -0.4584),
181                    (0.7056, -0.9708), (0.4356, -0.1416), (1.1412, 0.3708),
182                    (0.27, 0.3708) ),
183    'squashbox': ( (-1, 1), (0, 0.5), (1, 1), (0.5, 0),
184                   (1, -1), (0, -0.5), (-1, -1), (-0.5, 0) ),
185    'plusnarrow': ( (0.2, 1), (0.2, 0.2), (1, 0.2), (1, -0.2),
186                    (0.2, -0.2), (0.2, -1), (-0.2, -1), (-0.2, -0.2),
187                    (-1, -0.2), (-1, 0.2), (-0.2, 0.2), (-0.2, 1) ),
188    'crossnarrow': ( (-0.566, 0.849), (0, 0.283), (0.566, 0.849),
189                     (0.849, 0.566), (0.283, 0), (0.849, -0.566),
190                     (0.566, -0.849), (0, -0.283), (-0.566, -0.849),
191                     (-0.849, -0.566), (-0.283, 0), (-0.849, 0.566) ),
192
193    'limitupperaway2': ( (-1, 0), (0, 0), (0, -1), (-1, -1), (0, -2),
194                         (1, -1), (0, -1), (0, 0), (1, 0) ),
195    'limitloweraway2': ( (-1, 0), (0, 0), (0, 1), (-1, 1), (0, 2),
196                         (1, 1), (0, 1), (0, 0), (1, 0) ),
197    'limitleftaway2':  ( (0, -1), (0, 0) , (-1, 0), (-1, -1), (-2, 0),
198                         (-1, 1), (-1, 0), (0, 0), (0, 1) ),
199    'limitrightaway2': ( (0, -1), (0, 0), (1, 0), (1, -1), (2, 0),
200                         (1, 1), (1, 0), (0, 0), (0, 1) ),
201
202    # special arrow symbols
203    '_arrow': ( (0, 0), (-1.8, 1), (-1.4, 0), (-1.8, -1) ),
204    '_arrowtriangle': ( (0, 0), (-1.8, 1), (-1.8, -1) ),
205    '_arrownarrow': ( (0, 0), (-1.8, 0.5), (-1.8, -0.5) ),
206    '_arrowreverse': ( (0, 0), (1.8, 1), (1.4, 0), (1.8, -1) ),
207    }
208
209def addPolyPath(path, vals):
210    """Add a polygon with the list of x,y pts as tuples in vals."""
211    poly = qt.QPolygonF()
212    for x, y in vals:
213        poly.append( qt.QPointF(x, y) )
214    path.addPolygon(poly)
215    path.closeSubpath()
216
217def getPolygonPainterPath(name, size):
218    """Create a poly path for a polygon."""
219    path = qt.QPainterPath()
220    addPolyPath(path, N.array(polygons[name])*size)
221    return path
222
223#######################################################################
224## draw symbols using a QPainterPath
225
226def squarePath(path, size, linewidth):
227    """Square path of size given."""
228    path.addRect( qt.QRectF(-size, -size, size*2, size*2) )
229
230def circlePath(path, size, linewidth):
231    """Circle path of size given."""
232    path.addEllipse( qt.QRectF(-size, -size, size*2, size*2) )
233
234def circlePlusPath(path, size, linewidth):
235    """Circle path with plus."""
236    path.addEllipse( qt.QRectF(-size, -size, size*2, size*2) )
237    path.moveTo(0, -size)
238    path.lineTo(0, size)
239    path.moveTo(-size, 0)
240    path.lineTo(size, 0)
241
242def circleCrossPath(path, size, linewidth):
243    """Circle path with cross."""
244    path.addEllipse( qt.QRectF(-size, -size, size*2, size*2) )
245    m = N.sqrt(2.)*size*0.5
246    path.moveTo(-m, -m)
247    path.lineTo(m, m)
248    path.moveTo(-m, m)
249    path.lineTo(m, -m)
250
251def circlePairPathHorz(path, size, linewidth):
252    """2 circles next to each other (horizontal)."""
253    path.addEllipse( qt.QRectF(-size, -size*0.5, size, size) )
254    path.addEllipse( qt.QRectF(0,  -size*0.5, size, size) )
255
256def circlePairPathVert(path, size, linewidth):
257    """2 circles next to each other (vertical)."""
258    path.addEllipse( qt.QRectF(-size*0.5, -size, size, size) )
259    path.addEllipse( qt.QRectF(-size*0.5, 0, size, size) )
260
261def ellipseHorzPath(path, size, linewidth):
262    """Horizontal ellipse path."""
263    path.addEllipse( qt.QRectF(-size, -size*0.5, size*2, size) )
264
265def ellipseVertPath(path, size, linewidth):
266    """Vertical ellipse path."""
267    path.addEllipse( qt.QRectF(-size*0.5, -size, size, size*2) )
268
269def circleHolePath(path, size, linewidth):
270    """Circle with centre missing."""
271    circlePath(path, size, linewidth)
272    circlePath(path, size*0.5, linewidth)
273
274def squarePlusPath(path, size, linewidth):
275    """Square with plus sign."""
276    path.addRect( qt.QRectF(-size, -size, size*2, size*2) )
277    path.moveTo(0, -size)
278    path.lineTo(0, size)
279    path.moveTo(-size, 0)
280    path.lineTo(size, 0)
281
282def squareCrossPath(path, size, linewidth):
283    """Square with cross sign."""
284    path.addRect( qt.QRectF(-size, -size, size*2, size*2) )
285    path.moveTo(-size, -size)
286    path.lineTo(size, size)
287    path.moveTo(-size, size)
288    path.lineTo(size, -size)
289
290def squareHolePath(path, size, linewidth):
291    """Square with centre missing."""
292    path.addRect( qt.QRectF(-size, -size, size*2, size*2) )
293    path.addRect( qt.QRectF(-size*0.5, -size*0.5, size, size) )
294
295def diamondHolePath(path, size, linewidth):
296    """Diamond with centre missing."""
297    pts = N.array(polygons['diamond'])*size
298    addPolyPath(path, pts)
299    addPolyPath(path, pts*0.5)
300
301def pentagonHolePath(path, size, linewidth):
302    """Pentagon with centre missing."""
303    pts = N.array(polygons['pentagon'])*size
304    addPolyPath(path, pts)
305    addPolyPath(path, pts*0.5)
306
307def squareRoundedPath(path, size, linewidth):
308    """A square with rounded corners."""
309    path.addRoundedRect(
310        qt.QRectF(-size, -size, size*2, size*2),
311        50, 50, qt.Qt.RelativeSize)
312
313def dotPath(path, size, linewidth):
314    """Draw a dot."""
315    path.addEllipse(qt.QRectF(
316        -linewidth*0.5, -linewidth*0.5, linewidth, linewidth))
317
318def bullseyePath(path, size, linewidth):
319    """A filled circle inside a filled circle."""
320    path.setFillRule(qt.Qt.WindingFill)
321    circlePath(path, size, linewidth)
322    circlePath(path, size*0.5, linewidth)
323
324def circleDotPath(path, size, linewidth):
325    """A dot inside a circle."""
326    path.setFillRule(qt.Qt.WindingFill)
327    circlePath(path, size, linewidth)
328    dotPath(path, size, linewidth)
329
330pathsymbols = {
331    'square': squarePath,
332    'circle': circlePath,
333    'circleplus': circlePlusPath,
334    'circlecross': circleCrossPath,
335    'circlepairhorz': circlePairPathHorz,
336    'circlepairvert': circlePairPathVert,
337    'ellipsehorz': ellipseHorzPath,
338    'ellipsevert': ellipseVertPath,
339    'circlehole': circleHolePath,
340    'squareplus': squarePlusPath,
341    'squarecross': squareCrossPath,
342    'squarehole': squareHolePath,
343    'diamondhole': diamondHolePath,
344    'pentagonhole': pentagonHolePath,
345    'squarerounded': squareRoundedPath,
346    'dot': dotPath,
347    'bullseye': bullseyePath,
348    'circledot': circleDotPath,
349    }
350
351def getSymbolPainterPath(name, size, linewidth):
352    """Get a painter path for a symbol shape."""
353    path = qt.QPainterPath()
354    pathsymbols[name](path, size, linewidth)
355    return path
356
357# translate arrow shapes to point types (we reuse them)
358arrow_translate = {
359    'none': 'none',
360    'arrow': '_arrow',
361    'arrownarrow': '_arrownarrow',
362    'arrowtriangle': '_arrowtriangle',
363    'arrowreverse': '_arrowreverse',
364    'linearrow': '_linearrow',
365    'linearrowreverse': '_linearrowreverse',
366    'bar': 'linevert',
367    'linecross': 'linecross',
368    'asterisk': 'asterisk',
369    'circle': 'circle',
370    'square': 'square',
371    'diamond': 'diamond',
372}
373
374#######################################################################
375## external interfaces
376
377def getPointPainterPath(name, size, linewidth):
378    """Return a painter path for the name and size.
379
380    Returns (painterpath, enablefill)."""
381    if name in linesymbols:
382        return getLinePainterPath(name, size), False
383    elif name in polygons:
384        return getPolygonPainterPath(name, size), True
385    elif name in pathsymbols:
386        return getSymbolPainterPath(name, size, linewidth), True
387    elif name == 'none':
388        return qt.QPainterPath(), True
389    else:
390        raise ValueError("Invalid marker name %s" % name)
391
392# list of codes supported
393MarkerCodes = (
394    'none',
395    'circle', 'diamond', 'square',
396    'cross', 'plus', 'star',
397    'barhorz', 'barvert',
398    'pentagon', 'hexagon', 'octogon', 'tievert', 'tiehorz',
399    'triangle', 'triangledown', 'triangleleft', 'triangleright',
400    'dot', 'circledot', 'bullseye',
401    'circlehole', 'squarehole', 'diamondhole', 'pentagonhole',
402    'squarerounded', 'squashbox',
403    'ellipsehorz', 'ellipsevert',
404    'lozengehorz', 'lozengevert',
405    'plusnarrow', 'crossnarrow',
406    'circleplus', 'circlecross', 'squareplus', 'squarecross',
407    'star3', 'star4', 'star6', 'star8', 'starinvert',
408    'circlepairhorz', 'circlepairvert',
409    'asterisk', 'lineplus', 'linecross',
410    'plushair', 'crosshair', 'asteriskhair',
411    'linevert', 'linehorz', 'linevertgap', 'linehorzgap',
412    'arrowleft', 'arrowright', 'arrowup', 'arrowdown',
413    'arrowleftaway', 'arrowrightaway',
414    'arrowupaway', 'arrowdownaway',
415    'limitupper', 'limitlower', 'limitleft', 'limitright',
416    'limitupperaway', 'limitloweraway',
417    'limitleftaway', 'limitrightaway',
418    'limitupperaway2', 'limitloweraway2',
419    'limitleftaway2', 'limitrightaway2',
420    'arrowupperleftaway', 'arrowupperrightaway',
421    'arrowlowerrightaway', 'arrowlowerleftaway',
422    'lineup', 'linedown', 'lineleft', 'lineright',
423    )
424
425def plotMarkers(painter, xpos, ypos, markername, markersize, scaling=None,
426                clip=None, cmap=None, colorvals=None, scaleline=False):
427    """Funtion to plot an array of markers on a painter.
428
429    painter: QPainter
430    xpos, ypos: iterable item of positions
431    markername: name of marker from MarkerCodes
432    markersize: size of marker to plot
433    scaling: scale size of markers by array, or don't in None
434    clip: rectangle if clipping wanted
435    cmap: colormap to use if colorvals is set
436    colorvals: color values 0-1 of each point if used
437    scaleline: if scaling, scale border line width with scaling
438    """
439
440    # minor optimization
441    if markername == 'none':
442        return
443
444    painter.save()
445
446    # get sharper angles and more exact positions using these settings
447    pen = painter.pen()
448    pen.setJoinStyle( qt.Qt.MiterJoin )
449    painter.setPen(pen)
450
451    # get path to draw and whether to fill
452    path, fill = getPointPainterPath(
453        markername, markersize, painter.pen().widthF())
454    if not fill:
455        # turn off brush
456        painter.setBrush( qt.QBrush() )
457
458    # if using colored points
459    colorimg = None
460    if colorvals is not None:
461        # convert colors to rgb values via a 2D image and pass to function
462        trans = (1-painter.brush().color().alphaF())*100
463        color2d = colorvals.reshape( 1, len(colorvals) )
464        colorimg = colormap.applyColorMap(
465            cmap, 'linear', color2d, 0., 1., trans)
466
467    plotPathsToPainter(painter, path, xpos, ypos, scaling, clip, colorimg,
468                       scaleline)
469
470    painter.restore()
471
472def plotMarker(painter, xpos, ypos, markername, markersize):
473    """Function to plot a marker on a painter, posn xpos, ypos, type and size
474    """
475    plotMarkers(painter, (xpos,), (ypos,), markername, markersize)
476
477# translate arrow shapes to point types (we reuse them)
478arrow_translate = {
479    'none': 'none',
480    'arrow': '_arrow',
481    'arrownarrow': '_arrownarrow',
482    'arrowtriangle': '_arrowtriangle',
483    'arrowreverse': '_arrowreverse',
484    'linearrow': '_linearrow',
485    'linearrowreverse': '_linearrowreverse',
486    'bar': 'linevert',
487    'linecross': 'linecross',
488    'asterisk': 'asterisk',
489    'circle': 'circle',
490    'square': 'square',
491    'diamond': 'diamond',
492    'lineup': 'lineup',
493    'linedown': 'linedown',
494    'lineextend': 'lineright',
495}
496
497# codes of allowable arrows
498ArrowCodes = ( 'none', 'arrow', 'arrownarrow',
499               'arrowtriangle',
500               'arrowreverse',
501               'linearrow', 'linearrowreverse',
502               'bar', 'linecross',
503               'asterisk',
504               'circle', 'square', 'diamond',
505               'lineup', 'linedown',
506               'lineextend',
507               )
508
509def plotLineArrow(painter, xpos, ypos, length, angle,
510                  arrowsize=0,
511                  arrowleft='none', arrowright='none'):
512    """Plot a line or arrow.
513
514    xpos, ypos is the starting point of the line
515    angle is the angle to the horizontal (degrees)
516    arrowleft and arrowright are arrow codes."""
517
518    painter.save()
519    painter.translate(xpos, ypos)
520    painter.rotate(angle)
521
522    linepen = qt.QPen(painter.pen())
523    arrowpen = painter.pen()
524    arrowpen.setStyle(qt.Qt.SolidLine)
525    painter.setPen(arrowpen)
526
527    # plot marker at one end of line
528    plotMarker(painter, length, 0., arrow_translate[arrowright], arrowsize)
529
530    # plot reversed marker at other end
531    painter.scale(-1, 1)
532    plotMarker(painter, 0, 0, arrow_translate[arrowleft], arrowsize)
533
534    linepen.setCapStyle(qt.Qt.FlatCap)
535    painter.setPen(linepen)
536    painter.scale(-1, 1)
537    painter.drawLine(qt.QPointF(0, 0), qt.QPointF(length, 0))
538
539    painter.restore()
540