# points.py - functions to plot points

#    Copyright (C) 2003 Jeremy S. Sanders
#    Email: Jeremy Sanders <jeremy@jeremysanders.net>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################

import numpy as N

from .. import qtall as qt
from . import colormap
from ..helpers.qtloops import plotPathsToPainter, scalePath

"""Symbol plotting part of Veusz

There are actually several different ways symbols are plotted.
We choose the most appropriate one for the shape:

QPainterPath symbols plotted with _plotPathSymbols
line symbols are plotted with _plotLineSymbols
ploygon symbols are plotted wiht _plotPolygonSymbols

Many of these are implemented as paths internally, and drawn using
QPainterPaths
"""

#######################################################################
## draw symbols which are sets of line segments

linesymbols = {
    'asterisk': (
        ((-0.707, -0.707), (0.707,  0.707)),
        ((-0.707,  0.707), (0.707, -0.707)),
        ((-1, 0), (1, 0)), ((0, -1), (0, 1))
    ),
    'lineplus': ( ((-1, 0), (1, 0)), ((0, -1), (0, 1)) ),
    'linecross': (
        ((-0.707, -0.707), (0.707,  0.707)),
        ((-0.707,  0.707), (0.707, -0.707))
    ),
    'plushair': (
        ((-1, 0), (-0.5, 0)), ((1, 0), (0.5, 0)),
        ((0, -1), (0, -0.5)), ((0, 1), (0, 0.5))
    ),
    'crosshair': (
        ((-0.707, -0.707), (-0.354, -0.354)),
        (( 0.707,  0.707), ( 0.354,  0.354)),
        (( 0.707, -0.707), ( 0.354, -0.354)),
        ((-0.707,  0.707), (-0.354,  0.354))
    ),
    'asteriskhair': (
        ((-1, 0), (-0.5, 0)), ((1, 0), (0.5, 0)),
        ((0, -1), (0, -0.5)), ((0, 1), (0, 0.5)),
        ((-0.707, -0.707), (-0.354, -0.354)),
        (( 0.707,  0.707), ( 0.354,  0.354)),
        (( 0.707, -0.707), ( 0.354, -0.354)),
        ((-0.707,  0.707), (-0.354,  0.354))
    ),
    'linehorz': ( ((-1, 0), (1, 0)), ),
    'linevert': ( ((0, -1), (0, 1)), ),
    'linehorzgap': ( ((-1, 0), (-0.5, 0)), ((1, 0), (0.5, 0)) ),
    'linevertgap': ( ((0, -1), (0, -0.5)), ((0, 1), (0, 0.5)) ),

    # arrows
    'arrowleft': ( ((1, -0.8), (0, 0), (1, 0.8)), ((2, 0), (0, 0)) ),
    'arrowleftaway': ( ((-1, -0.8), (-2, 0), (-1, 0.8)), ((-2, 0), (0, 0)) ),
    'arrowright': ( ((-1, -0.8), (0, 0), (-1, 0.8)), ((-2, 0), (0, 0)) ),
    'arrowrightaway': ( ((1, -0.8), (2, 0), (1, 0.8)), ((2, 0), (0, 0)) ),
    'arrowup': ( ((-0.8, 1), (0, 0), (0.8, 1)), ((0, 2), (0, 0)) ),
    'arrowupaway': ( ((-0.8, -1), (0, -2), (0.8, -1)), ((0, 0), (0, -2)) ),
    'arrowdown': ( ((-0.8, -1), (0, 0), (0.8, -1)), ((0, -2), (0, 0)) ),
    'arrowdownaway': ( ((-0.8, 1), (0, 2), (0.8, 1)), ((0, 0), (0, 2)) ),

    # limits
    'limitlower': (
        ((-0.8, -1), (0, 0), (0.8, -1)), ((0, -2), (0, 0)),
        ((-1, 0), (1, 0))
    ),
    'limitupper': (
        ((-0.8, 1), (0, 0), (0.8, 1)), ((0, 2), (0, 0)),
        ((-1, 0), (1, 0))
    ),
    'limitleft': (
        ((1, -0.8), (0, 0), (1, 0.8)), ((2, 0), (0, 0)),
        ((0, -1), (0, 1))
    ),
    'limitright': (
        ((-1, -0.8), (0, 0), (-1, 0.8)), ((-2, 0), (0, 0)),
        ((0, -1), (0, 1))
    ),
    'limitupperaway': (
        ((-0.8, -1), (0, -2), (0.8, -1)), ((0, 0), (0, -2)),
        ((-1, 0), (1, 0))
    ),
    'limitloweraway': (
        ((-0.8, 1), (0, 2), (0.8, 1)), ((0, 0), (0, 2)),
        ((-1, 0), (1, 0))
    ),
    'limitleftaway': (
        ((-1, -0.8), (-2, 0), (-1, 0.8)), ((-2, 0), (0, 0)),
        ((0, -1), (0, 1))
    ),
    'limitrightaway': (
        ((1, -0.8), (2, 0), (1, 0.8)), ((2, 0), (0, 0)),
        ((0, -1), (0, 1))
    ),

    'arrowlowerleftaway': (
        ((-0.8, 1), (0, 2), (0.8, 1)),
        ((0, 2), (0, 0), (-2, 0)),
        ((-1, -0.8), (-2, 0), (-1, 0.8))
    ),
    'arrowlowerrightaway': (
        ((1, -0.8), (2, 0), (1, 0.8)),
        ((2, 0), (0, 0), (0, 2)),
        ((-0.8, 1), (0, 2), (0.8, 1))
    ),
    'arrowupperleftaway':(
        ((-0.8, -1), (0, -2), (0.8, -1)),
        ((0, -2), (0, 0), (-2, 0)),
        ((-1, -0.8), (-2, 0), (-1, 0.8))
    ),
    'arrowupperrightaway': (
        ((-0.8, -1), (0, -2), (0.8, -1)),
        ((2, 0), (0, 0), (0, -2)),
        ((1, -0.8), (2, 0), (1, 0.8))
    ),

    'lineup': ( ((0, 0), (0, -1)), ),
    'linedown': ( ((0, 0), (0, 1)), ),
    'lineleft': ( ((0, 0), (-1, 0)), ),
    'lineright': ( ((0, 0), (1, 0)), ),

    # for arrows
    '_linearrow': ( ((-1.8, -1), (0, 0), (-1.8, 1)), ),
    '_linearrowreverse': ( ((1.8, -1), (0, 0), (1.8, 1)), ),
}

def getLinePainterPath(name, size):
    """Get a painter path for line like objects."""
    path = qt.QPainterPath()
    for lines in linesymbols[name]:
        path.moveTo(lines[0][0]*size, lines[0][1]*size)
        for x, y in lines[1:]:
            path.lineTo(x*size, y*size)
    return path

#######################################################################
## draw symbols which are polygons

# X and Y pts for corners of polygons
polygons = {

    # make the diamond the same area as the square
    'diamond': ( (0., 1.414), (1.414, 0.), (0., -1.414), (-1.414, 0.) ),
    'barhorz': ( (-1, -0.5), (1, -0.5), (1, 0.5), (-1, 0.5) ),
    'barvert': ( (-0.5, -1), (0.5, -1), (0.5, 1), (-0.5, 1) ),
    'plus': (
        (0.4, 1), (0.4, 0.4), (1, 0.4), (1, -0.4),
        (0.4, -0.4), (0.4, -1), (-0.4, -1), (-0.4, -0.4),
        (-1, -0.4), (-1, 0.4), (-0.4, 0.4), (-0.4, 1)
    ),
    'octogon': (
        (0.414, 1), (1, 0.414), (1, -0.414), (0.414, -1),
        (-0.414, -1), (-1, -0.414), (-1, 0.414), (-0.414, 1)
    ),
    'triangle': ( (0, -1.2), (1.0392, 0.6), (-1.0392, 0.6) ),
    'triangledown': ( (0, 1.2), (1.0392, -0.6), (-1.0392, -0.6) ),
    'triangleleft': ( (-1.2, 0), (0.6, 1.0392), (0.6, -1.0392) ),
    'triangleright': ( (1.2, 0), (-0.6, 1.0392), (-0.6, -1.0392) ),
    'cross': (
        (-0.594, 1.1028), (0, 0.5088), (0.594, 1.1028),
        (1.1028, 0.594), (0.5088, -0), (1.1028, -0.594),
        (0.594, -1.1028), (-0, -0.5088), (-0.594, -1.1028),
        (-1.1028, -0.594), (-0.5088, 0), (-1.1028, 0.594)
    ),
    'star': (
        (0, -1.2), (-0.27, -0.3708), (-1.1412, -0.3708),
        (-0.4356, 0.1416), (-0.7056, 0.9708), (-0, 0.4584),
        (0.7056, 0.9708), (0.4356, 0.1416), (1.1412, -0.3708),
        (0.27, -0.3708)
    ),
    'pentagon': (
        (0, -1.2), (1.1412, -0.3708), (0.6936, 0.9708),
        (-0.6936, 0.9708), (-1.1412, -0.3708)
    ),
    'tievert': ( (-1, -1), (1, -1), (-1, 1), (1, 1) ),
    'tiehorz': ( (-1, -1), (-1, 1), (1, -1), (1, 1) ),
    'lozengehorz': ( (0, 0.707), (1.414, 0), (0, -0.707), (-1.414, 0) ),
    'lozengevert': ( (0, 1.414), (0.707, 0), (0, -1.414), (-0.707, 0) ),

    'star3': (
        (0., -1.), (0.173, -0.1), (0.866, 0.5), (0, 0.2),
        (-0.866, 0.5), (-0.173, -0.1)
    ),
    'star4': (
        (0.000, 1.000), (-0.354, 0.354), (-1.000, 0.000),
        (-0.354, -0.354), (0.000, -1.000), (0.354, -0.354),
        (1.000, -0.000), (0.354, 0.354),
    ),
    'star6': (
        (0.000, 1.000), (-0.250, 0.433), (-0.866, 0.500),
        (-0.500, 0.000), (-0.866, -0.500), (-0.250, -0.433),
        (-0.000, -1.000), (0.250, -0.433), (0.866, -0.500),
        (0.500, 0.000), (0.866, 0.500), (0.250, 0.433),
    ),
    'star8': (
        (0.000, 1.000), (-0.191, 0.462), (-0.707, 0.707),
        (-0.462, 0.191), (-1.000, 0.000), (-0.462, -0.191),
        (-0.707, -0.707), (-0.191, -0.462), (0.000, -1.000),
        (0.191, -0.462), (0.707, -0.707), (0.462, -0.191),
        (1.000, -0.000), (0.462, 0.191), (0.707, 0.707),
        (0.191, 0.462),
    ),
    'hexagon': (
        (0, 1), (0.866, 0.5), (0.866, -0.5),
        (0, -1), (-0.866, -0.5), (-0.866, 0.5),
    ),
    'starinvert': (
        (0, 1.2), (-0.27, 0.3708), (-1.1412, 0.3708),
        (-0.4356, -0.1416), (-0.7056, -0.9708), (0, -0.4584),
        (0.7056, -0.9708), (0.4356, -0.1416), (1.1412, 0.3708),
        (0.27, 0.3708)
    ),
    'squashbox': (
        (-1, 1), (0, 0.5), (1, 1), (0.5, 0),
        (1, -1), (0, -0.5), (-1, -1), (-0.5, 0)
    ),
    'plusnarrow': (
        (0.2, 1), (0.2, 0.2), (1, 0.2), (1, -0.2),
        (0.2, -0.2), (0.2, -1), (-0.2, -1), (-0.2, -0.2),
        (-1, -0.2), (-1, 0.2), (-0.2, 0.2), (-0.2, 1)
    ),
    'crossnarrow': (
        (-0.566, 0.849), (0, 0.283), (0.566, 0.849),
        (0.849, 0.566), (0.283, 0), (0.849, -0.566),
        (0.566, -0.849), (0, -0.283), (-0.566, -0.849),
        (-0.849, -0.566), (-0.283, 0), (-0.849, 0.566)
    ),

    'limitupperaway2': (
        (-1, 0), (0, 0), (0, -1), (-1, -1), (0, -2),
        (1, -1), (0, -1), (0, 0), (1, 0)
    ),
    'limitloweraway2': (
        (-1, 0), (0, 0), (0, 1), (-1, 1), (0, 2),
        (1, 1), (0, 1), (0, 0), (1, 0)
    ),
    'limitleftaway2': (
        (0, -1), (0, 0) , (-1, 0), (-1, -1), (-2, 0),
        (-1, 1), (-1, 0), (0, 0), (0, 1)
    ),
    'limitrightaway2': (
        (0, -1), (0, 0), (1, 0), (1, -1), (2, 0),
        (1, 1), (1, 0), (0, 0), (0, 1)
    ),

    # special arrow symbols
    '_arrow': ( (0, 0), (-1.8, 1), (-1.4, 0), (-1.8, -1) ),
    '_arrowtriangle': ( (0, 0), (-1.8, 1), (-1.8, -1) ),
    '_arrownarrow': ( (0, 0), (-1.8, 0.5), (-1.8, -0.5) ),
    '_arrowreverse': ( (0, 0), (1.8, 1), (1.4, 0), (1.8, -1) ),

}

def _calcAreaScales():
    """Calculate polygon areas for normalization.

    This is run separately to get the values to put in the below scaling
    array
    """

    import sys
    app = qt.QApplication(sys.argv)
    scale = 600
    c = scale*4
    w = c*2
    pix = qt.QPixmap(w,w)

    areas = {}
    white = qt.QColor(255,255,255)
    for poly in MarkerCodes:
        pix.fill(white)
        path, tofill = getPointPainterPath(poly, scale, 1)
        if not tofill or poly in ('none', 'dot'):
            #areas[poly] = 1
            continue

        # draw marker with large size
        painter = qt.QPainter(pix)
        painter.setRenderHint(qt.QPainter.RenderHint.Antialiasing, False)
        painter.translate(c,c)
        painter.fillPath(path, qt.QBrush(qt.Qt.BrushStyle.SolidPattern))
        painter.end()

        # count pixels
        img = pix.toImage()
        assert img.depth() == 32
        ptr = img.constBits()
        ptr.setsize(w*w*4)
        arr = N.ndarray(shape=(w,w), buffer=ptr, dtype=N.uint32)
        tot = N.sum(arr != 0xffffffff)
        # scale area
        factor = N.sqrt( (N.pi*scale**2) / tot )
        if abs(factor-1) > 1e-3:
            areas[poly] = round(factor, 3)
            print(poly, areas[poly])
    return areas

# calculated using above function
area_scales = {
    'diamond': 0.886,
    'square': 0.886,
    'cross': 1.034,
    'plus': 1.108,
    'star': 1.395,
    'barhorz': 1.253,
    'barvert': 1.253,
    'pentagon': 0.96,
    'hexagon': 1.099,
    'octogon': 0.974,
    'tievert': 1.253,
    'tiehorz': 1.253,
    'triangle': 1.296,
    'triangledown': 1.296,
    'triangleleft': 1.296,
    'triangleright': 1.296,
    'circlehole': 1.155,
    'squarehole': 1.023,
    'diamondhole': 1.024,
    'pentagonhole': 1.109,
    'squarerounded': 0.911,
    'squashbox': 1.253,
    'ellipsehorz': 1.414,
    'ellipsevert': 1.414,
    'lozengehorz': 1.254,
    'lozengevert': 1.254,
    'plusnarrow': 1.477,
    'crossnarrow': 1.477,
    'squareplus': 0.886,
    'squarecross': 0.886,
    'star3': 2.46,
    'star4': 1.489,
    'star6': 1.447,
    'star8': 1.433,
    'starinvert': 1.395,
    'circlepairhorz': 1.414,
    'circlepairvert': 1.414,
    'limitupperaway2': 1.772,
    'limitloweraway2': 1.772,
    'limitleftaway2': 1.774,
    'limitrightaway2': 1.771,
}

def addPolyPath(path, vals):
    """Add a polygon with the list of x,y pts as tuples in vals."""
    poly = qt.QPolygonF()
    for x, y in vals:
        poly.append( qt.QPointF(x, y) )
    path.addPolygon(poly)
    path.closeSubpath()

def getPolygonPainterPath(name, size):
    """Create a poly path for a polygon."""
    path = qt.QPainterPath()
    addPolyPath(path, N.array(polygons[name])*size)
    return path

#######################################################################
## draw symbols using a QPainterPath

def squarePath(path, size, linewidth):
    """Square path of size given."""
    path.addRect( qt.QRectF(-size, -size, size*2, size*2) )

def circlePath(path, size, linewidth):
    """Circle path of size given."""
    path.addEllipse( qt.QRectF(-size, -size, size*2, size*2) )

def circlePlusPath(path, size, linewidth):
    """Circle path with plus."""
    path.addEllipse( qt.QRectF(-size, -size, size*2, size*2) )
    path.moveTo(0, -size)
    path.lineTo(0, size)
    path.moveTo(-size, 0)
    path.lineTo(size, 0)

def circleCrossPath(path, size, linewidth):
    """Circle path with cross."""
    path.addEllipse( qt.QRectF(-size, -size, size*2, size*2) )
    m = N.sqrt(2.)*size*0.5
    path.moveTo(-m, -m)
    path.lineTo(m, m)
    path.moveTo(-m, m)
    path.lineTo(m, -m)

def circlePairPathHorz(path, size, linewidth):
    """2 circles next to each other (horizontal)."""
    path.addEllipse( qt.QRectF(-size, -size*0.5, size, size) )
    path.addEllipse( qt.QRectF(0,  -size*0.5, size, size) )

def circlePairPathVert(path, size, linewidth):
    """2 circles next to each other (vertical)."""
    path.addEllipse( qt.QRectF(-size*0.5, -size, size, size) )
    path.addEllipse( qt.QRectF(-size*0.5, 0, size, size) )

def ellipseHorzPath(path, size, linewidth):
    """Horizontal ellipse path."""
    path.addEllipse( qt.QRectF(-size, -size*0.5, size*2, size) )

def ellipseVertPath(path, size, linewidth):
    """Vertical ellipse path."""
    path.addEllipse( qt.QRectF(-size*0.5, -size, size, size*2) )

def circleHolePath(path, size, linewidth):
    """Circle with centre missing."""
    circlePath(path, size, linewidth)
    circlePath(path, size*0.5, linewidth)

def squarePlusPath(path, size, linewidth):
    """Square with plus sign."""
    path.addRect( qt.QRectF(-size, -size, size*2, size*2) )
    path.moveTo(0, -size)
    path.lineTo(0, size)
    path.moveTo(-size, 0)
    path.lineTo(size, 0)

def squareCrossPath(path, size, linewidth):
    """Square with cross sign."""
    path.addRect( qt.QRectF(-size, -size, size*2, size*2) )
    path.moveTo(-size, -size)
    path.lineTo(size, size)
    path.moveTo(-size, size)
    path.lineTo(size, -size)

def squareHolePath(path, size, linewidth):
    """Square with centre missing."""
    path.addRect( qt.QRectF(-size, -size, size*2, size*2) )
    path.addRect( qt.QRectF(-size*0.5, -size*0.5, size, size) )

def diamondHolePath(path, size, linewidth):
    """Diamond with centre missing."""
    pts = N.array(polygons['diamond'])*size
    addPolyPath(path, pts)
    addPolyPath(path, pts*0.5)

def pentagonHolePath(path, size, linewidth):
    """Pentagon with centre missing."""
    pts = N.array(polygons['pentagon'])*size
    addPolyPath(path, pts)
    addPolyPath(path, pts*0.5)

def squareRoundedPath(path, size, linewidth):
    """A square with rounded corners."""
    path.addRoundedRect(
        qt.QRectF(-size, -size, size*2, size*2),
        50, 50, qt.Qt.SizeMode.RelativeSize)

def dotPath(path, size, linewidth):
    """Draw a dot."""
    path.addEllipse(qt.QRectF(
        -linewidth*0.5, -linewidth*0.5, linewidth, linewidth))

def bullseyePath(path, size, linewidth):
    """A filled circle inside a filled circle."""
    path.setFillRule(qt.Qt.FillRule.WindingFill)
    circlePath(path, size, linewidth)
    circlePath(path, size*0.5, linewidth)

def circleDotPath(path, size, linewidth):
    """A dot inside a circle."""
    path.setFillRule(qt.Qt.FillRule.WindingFill)
    circlePath(path, size, linewidth)
    dotPath(path, size, linewidth)

pathsymbols = {
    'square': squarePath,
    'circle': circlePath,
    'circleplus': circlePlusPath,
    'circlecross': circleCrossPath,
    'circlepairhorz': circlePairPathHorz,
    'circlepairvert': circlePairPathVert,
    'ellipsehorz': ellipseHorzPath,
    'ellipsevert': ellipseVertPath,
    'circlehole': circleHolePath,
    'squareplus': squarePlusPath,
    'squarecross': squareCrossPath,
    'squarehole': squareHolePath,
    'diamondhole': diamondHolePath,
    'pentagonhole': pentagonHolePath,
    'squarerounded': squareRoundedPath,
    'dot': dotPath,
    'bullseye': bullseyePath,
    'circledot': circleDotPath,
}

def getSymbolPainterPath(name, size, linewidth):
    """Get a painter path for a symbol shape."""
    path = qt.QPainterPath()
    pathsymbols[name](path, size, linewidth)
    return path

# translate arrow shapes to point types (we reuse them)
arrow_translate = {
    'none': 'none',
    'arrow': '_arrow',
    'arrownarrow': '_arrownarrow',
    'arrowtriangle': '_arrowtriangle',
    'arrowreverse': '_arrowreverse',
    'linearrow': '_linearrow',
    'linearrowreverse': '_linearrowreverse',
    'bar': 'linevert',
    'linecross': 'linecross',
    'asterisk': 'asterisk',
    'circle': 'circle',
    'square': 'square',
    'diamond': 'diamond',
}

#######################################################################
## external interfaces

def getPointPainterPath(name, size, linewidth):
    """Return a painter path for the name and size.

    Returns (painterpath, enablefill)."""
    if name in linesymbols:
        return getLinePainterPath(name, size), False
    elif name in polygons:
        return getPolygonPainterPath(name, size), True
    elif name in pathsymbols:
        return getSymbolPainterPath(name, size, linewidth), True
    elif name == 'none':
        return qt.QPainterPath(), True
    else:
        raise ValueError("Invalid marker name %s" % name)

# list of codes supported
MarkerCodes = (
    'none',
    'circle', 'diamond', 'square',
    'cross', 'plus', 'star',
    'barhorz', 'barvert',
    'pentagon', 'hexagon', 'octogon', 'tievert', 'tiehorz',
    'triangle', 'triangledown', 'triangleleft', 'triangleright',
    'dot', 'circledot', 'bullseye',
    'circlehole', 'squarehole', 'diamondhole', 'pentagonhole',
    'squarerounded', 'squashbox',
    'ellipsehorz', 'ellipsevert',
    'lozengehorz', 'lozengevert',
    'plusnarrow', 'crossnarrow',
    'circleplus', 'circlecross', 'squareplus', 'squarecross',
    'star3', 'star4', 'star6', 'star8', 'starinvert',
    'circlepairhorz', 'circlepairvert',
    'asterisk', 'lineplus', 'linecross',
    'plushair', 'crosshair', 'asteriskhair',
    'linevert', 'linehorz', 'linevertgap', 'linehorzgap',
    'arrowleft', 'arrowright', 'arrowup', 'arrowdown',
    'arrowleftaway', 'arrowrightaway',
    'arrowupaway', 'arrowdownaway',
    'limitupper', 'limitlower', 'limitleft', 'limitright',
    'limitupperaway', 'limitloweraway',
    'limitleftaway', 'limitrightaway',
    'limitupperaway2', 'limitloweraway2',
    'limitleftaway2', 'limitrightaway2',
    'arrowupperleftaway', 'arrowupperrightaway',
    'arrowlowerrightaway', 'arrowlowerleftaway',
    'lineup', 'linedown', 'lineleft', 'lineright',
)

def plotMarkers(painter, xpos, ypos, markername, markersize, scaling=None,
                clip=None, cmap=None, colorvals=None, scaleline=False,
                equalarea=False):
    """Funtion to plot an array of markers on a painter.

    painter: QPainter
    xpos, ypos: iterable item of positions
    markername: name of marker from MarkerCodes
    markersize: size of marker to plot
    scaling: scale size of markers by array, or don't in None
    clip: rectangle if clipping wanted
    cmap: colormap to use if colorvals is set
    colorvals: color values 0-1 of each point if used
    scaleline: if scaling, scale border line width with scaling
    equalarea: apply scaling factor for marker area (for filled markers)
    """

    # minor optimization
    if markername == 'none':
        return

    painter.save()

    # get sharper angles and more exact positions using these settings
    pen = painter.pen()
    pen.setJoinStyle( qt.Qt.PenJoinStyle.MiterJoin )
    painter.setPen(pen)

    # get path to draw and whether to fill
    path, fill = getPointPainterPath(
        markername, markersize, painter.pen().widthF())

    if not fill:
        # turn off brush
        painter.setBrush( qt.QBrush() )

    if equalarea and markername in area_scales:
        path = scalePath(path, area_scales[markername])

    # if using colored points
    colorimg = None
    if colorvals is not None:
        # convert colors to rgb values via a 2D image and pass to function
        trans = (1-painter.brush().color().alphaF())*100
        color2d = colorvals.reshape( 1, len(colorvals) )
        colorimg = colormap.applyColorMap(
            cmap, 'linear', color2d, 0., 1., trans)

    plotPathsToPainter(
        painter, path, xpos, ypos, scaling, clip, colorimg, scaleline)

    painter.restore()

def plotMarker(painter, xpos, ypos, markername, markersize):
    """Function to plot a marker on a painter, posn xpos, ypos, type and size
    """
    plotMarkers(painter, (xpos,), (ypos,), markername, markersize)

# translate arrow shapes to point types (we reuse them)
arrow_translate = {
    'none': 'none',
    'arrow': '_arrow',
    'arrownarrow': '_arrownarrow',
    'arrowtriangle': '_arrowtriangle',
    'arrowreverse': '_arrowreverse',
    'linearrow': '_linearrow',
    'linearrowreverse': '_linearrowreverse',
    'bar': 'linevert',
    'linecross': 'linecross',
    'asterisk': 'asterisk',
    'circle': 'circle',
    'square': 'square',
    'diamond': 'diamond',
    'lineup': 'lineup',
    'linedown': 'linedown',
    'lineextend': 'lineright',
}

# codes of allowable arrows
ArrowCodes = (
    'none', 'arrow', 'arrownarrow',
    'arrowtriangle',
    'arrowreverse',
    'linearrow', 'linearrowreverse',
    'bar', 'linecross',
    'asterisk',
    'circle', 'square', 'diamond',
    'lineup', 'linedown',
    'lineextend',
)

def plotLineArrow(painter, xpos, ypos, length, angle,
                  arrowsize=0,
                  arrowleft='none', arrowright='none'):
    """Plot a line or arrow.

    xpos, ypos is the starting point of the line
    angle is the angle to the horizontal (degrees)
    arrowleft and arrowright are arrow codes."""

    painter.save()
    painter.translate(xpos, ypos)
    painter.rotate(angle)

    linepen = qt.QPen(painter.pen())
    arrowpen = painter.pen()
    arrowpen.setStyle(qt.Qt.PenStyle.SolidLine)
    painter.setPen(arrowpen)

    # plot marker at one end of line
    plotMarker(painter, length, 0., arrow_translate[arrowright], arrowsize)

    # plot reversed marker at other end
    painter.scale(-1, 1)
    plotMarker(painter, 0, 0, arrow_translate[arrowleft], arrowsize)

    linepen.setCapStyle(qt.Qt.PenCapStyle.FlatCap)
    painter.setPen(linepen)
    painter.scale(-1, 1)
    painter.drawLine(qt.QPointF(0, 0), qt.QPointF(length, 0))

    painter.restore()
