# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
# Copyright (C) 2016-2026 German Aerospace Center (DLR) and others.
# hybridPY module
# Copyright (C) 2012-2026 University of Bologna - DICAM, Technical University of Munich
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0/
# This Source Code may also be made available under the following Secondary
# Licenses when the conditions for such availability set forth in the Eclipse
# Public License 2.0 are satisfied: GNU General Public License, version 2
# or later which is available at
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later

# @file    matplottools.py
# @author  Joerg Schweizer
# @author  Fabian Schuhmann
# @author  Ngoc An Nguyen
# @author  Cristian Poliziani
# @date    2012

from agilepy.lib_wx.processdialog import ProcessDialog, ProcessDialogInteractive
from agilepy.lib_base.processes import Process
from agilepy.lib_base.geometry import *
import agilepy.lib_base.arrayman as am
import agilepy.lib_base.classman as cm
import os
import numpy as np
from collections import OrderedDict
import matplotlib as mpl


if 1:  # change backend...makes problems with plt
    gui_env = ['WXAgg', 'GTKAgg', 'Qt4Agg', 'TKAgg',]
    # for gui in gui_env:
    try:
        gui = 'WXAgg'
        print("Try Matplotlib backend:", gui)
        mpl.use(gui, warn=False, force=True)
        from mpl.patches import Arrow, Circle, Wedge, Polygon, FancyArrow
        from mpl.mpl import PatchCollection
        import matplotlib.colors as colors
        import mpl.cm as cmx
        import mpl.pyplot as plt
        import mpl.image as image
        from mpl.ticker import MaxNLocator
        import mpl.patches as mpatches

    except:
        from matplotlib.patches import Arrow, Circle, Wedge, Polygon, FancyArrow
        from matplotlib.collections import PatchCollection
        import matplotlib.colors as colors
        import matplotlib.cm as cmx
        import matplotlib.pyplot as plt
        import matplotlib.image as image
        from matplotlib.ticker import MaxNLocator
        import matplotlib.patches as mpatches

from matplotlib.patches import Arrow, Circle, Wedge, Polygon, FancyArrow
from matplotlib.collections import PatchCollection
import matplotlib.colors as colors
import matplotlib.cm as cmx
import matplotlib.pyplot as plt
import matplotlib.image as image
from matplotlib.ticker import MaxNLocator
import matplotlib.patches as mpatches
print("Using Matplotlib backend", mpl.get_backend())


try:
    import wx
except:
    print('WARNING: no wxwindows support')

mpl.rc('figure', max_open_warning=0)
COLORS = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c',
          '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5',
          '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f',
          '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5']

LINESTYLES = ['-', '--', '-.', ':', 'None']


MARKERSTYLES = ['None', 'o', 'v', 's', '*', '^', 'h', 'D', 'p', '<', '>']

global ind_fig
ind_fig = 0


def get_color(i):
    return COLORS[i % len(COLORS)]


def get_colors(inds):
    return np.array(COLORS, dtype=np.str)[np.mod(inds, len(COLORS))]


def get_marker(i):
    return MARKERSTYLES[i % len(MARKERSTYLES)]


def get_markers(inds):
    return np.array(MARKERSTYLES, dtype=np.str)[np.mod(inds, len(MARKERSTYLES))]


class ResultDialog(ProcessDialog):
    def _get_buttons(self):
        buttons = [('Plot and close',   self.on_run, 'Plot  selected quantity in matplotlib window and close this window thereafter.'),
                   ('Plot',   self.on_show, 'Plot selected quantity  in matplotlib window.'),
                   ('Save Options...', self.on_save_options, self.on_save_options.__doc__),
                   ('Load Options...', self.on_load_options, self.on_load_options.__doc__),
                   ]
        defaultbutton = 'Plot and close'
        standartbuttons = ['cancel',]

        return buttons, defaultbutton, standartbuttons

    def on_show(self, event):
        self.process.show()


def get_mplicon():
    icondir = os.path.join(os.path.dirname(__file__), 'images')
    return wx.Bitmap(os.path.join(icondir, 'icon_mpl.png'))


def init_plot(tight_layout=True):
    # if possible, use self.create_figure
    plt.close("all")
    fig = plt.figure()
    ax = fig.add_subplot(111)
    if tight_layout:
        fig.tight_layout()
    return ax


def init_plot_fig_ax():
    # if possible, use self.create_figure
    plt.close("all")
    fig = plt.figure()
    ax = fig.add_subplot(111)
    return fig, ax


def new_plot(tight_layout=True):
    # depricated
    global ind_fig
    fig = plt.figure(ind_fig)
    ind_fig += 1
    ax = fig.add_subplot(111)
    if tight_layout:
        fig.tight_layout()
    return ax


def next_plot(ind_fig=0, is_tight_layout=False, ):
    # if possible, use self.create_figure
    fig = plt.figure(ind_fig)
    ax = fig.add_subplot(111)
    if is_tight_layout:
        fig.tight_layout()
    ind_fig += 1
    return ax, ind_fig


def new_doubkeplot(tight_layout=True):
    # if possible, use self.create_figure
    global ind_fig
    fig = plt.figure(ind_fig)
    ind_fig += 1
    if tight_layout:
        fig.tight_layout()
    return plt.subplot(2, 1, 1), plt.subplot(2, 1, 2)


def plot_net(ax, net, color_edge="gray", width_edge=2, color_node=None,
             alpha=0.5, is_show_connections=False, mapscale=1.0,
             scale_width_edge=0.8, is_show_nodes=True, zorder=-100):

    n_edges = len(net.edges)
    n_nodes = len(net.nodes)
    print('plot_net with', n_edges, 'edges and', n_nodes, 'nodes')
    i = 0
    Polygon = mpl.patches.Polygon
    ids_edge = net.edges.get_ids()
    if scale_width_edge > 0:
        edgewidths = np.array(net.edges.widths[ids_edge] * scale_width_edge, dtype=np.int32)+1
    else:
        edgewidths = np.ones(len(ids_edge), dtype=np.int32)*int(width_edge)

    for shape, width in zip(net.edges.shapes[ids_edge], edgewidths):
        # x_vec = np.array(shape)[:,0]
        # y_vec = np.array(shape)[:,1]
        # ax.plot(x_vec*mapscale, y_vec*mapscale, color = color_edge, lw = width_edge,alpha=alpha ,zorder = -100)

        ax.add_patch(Polygon(np.array(shape)[:, :2]*mapscale,
                             linewidth=width,
                             edgecolor=color_edge,
                             facecolor=None,
                             fill=False,
                             alpha=alpha,
                             zorder=zorder))

        i += 1
        # print '  plot edge %d/%d %d pc'%(i,n_edges,int(100.0*i/n_edges))

    if is_show_connections:
        for id_connection in net.connections.get_ids():
            x_vec = np.array([net.edges.shapes[net.lanes.ids_edge[net.connections.ids_fromlane[id_connection]]]
                             [-1, 0], net.edges.shapes[net.lanes.ids_edge[net.connections.ids_tolane[id_connection]]][0, 0]])
            y_vec = np.array([net.edges.shapes[net.lanes.ids_edge[net.connections.ids_fromlane[id_connection]]]
                             [-1, 1], net.edges.shapes[net.lanes.ids_edge[net.connections.ids_tolane[id_connection]]][0, 1]])
            ax.plot(x_vec*mapscale, y_vec*mapscale, color=color_edge,
                    lw=width_edge*scale_width_edge, alpha=alpha, zorder=-100)

    if is_show_nodes:
        # do nodes
        if color_node is None:
            is_nodefill = False
            color_node = 'none'
        else:
            is_nodefill = True

        # ax.scatter(x_vec[0], y_vec[0], s=np.pi * (10.0)**2, c=colors, alpha=0.5)
        coords = net.nodes.coords.get_value()
        radii = net.nodes.radii.get_value()
        # ax.scatter(coords[:,0], coords[:,1], s=np.pi * (radii)**2, alpha=0.5)
        # patches = []
        i = 0
        for coord, radius in zip(coords, radii):
            i += 1
            # print '  plot node %d/%d %d pc'%(i,n_nodes,int(100.0*i/n_nodes))
            ax.add_patch(Circle(coord*mapscale, radius=radius*mapscale,
                                linewidth=width_edge,
                                edgecolor=color_edge,
                                facecolor=color_node,
                                fill=is_nodefill,
                                alpha=alpha,
                                zorder=-50))


def plot_maps(ax, maps, alpha=0.5, mapscale=1.0):
    # print 'plot_maps'
    net = maps.parent.get_net()
    rootdir = os.path.dirname(net.parent.get_rootfilepath())

    for filename, bbox in zip(maps.filenames.get_value(), maps.bboxes.get_value()):
        # print '  ',filename,
        if filename == os.path.basename(filename):
            # filename does not contain path info
            filepath = os.path.join(rootdir, filename)
        else:
            # filename contains path info (can happen if interactively inserted)
            filepath = filename
        # print '  filepath',filepath
        im = image.imread(filepath)
        myaximage = ax.imshow(im, aspect='auto', extent=(
            bbox[0, 0]*mapscale, bbox[1, 0]*mapscale, bbox[0, 1]*mapscale, bbox[1, 1]*mapscale), alpha=alpha, zorder=-1000)


def plot_facilities(ax, facilities, color_building="gray",
                    color_outline='white', width_line=2,
                    alpha=0.5,  zorder=-1000, mapscale=1.0,
                    colors_building=None):
    n = len(facilities)
    if colors_building is not None:
        ids_fac = facilities.get_ids()
        i = 0
        for shape, color in zip(facilities.shapes[ids_fac], colors_building):
            i += 1
            # print '  plot fac color %d/%d %d pc'%(i,n,int(100.0*i/n))
            ax.add_patch(mpl.patches.Polygon(np.array(shape)[:, :2]*mapscale,
                                             linewidth=width_line,
                                             edgecolor=color_outline,
                                             facecolor=color,
                                             fill=True,
                                             alpha=alpha,
                                             zorder=zorder))

        return True

    elif color_building is None:
        is_fill = False
        color_building = 'none'

    else:
        is_fill = True

    i = 0
    for shape in facilities.shapes.get_value():
        i += 1
        # print '  plot fac poly %d/%d %d pc'%(i,n,int(100.0*i/n))
        ax.add_patch(mpl.patches.Polygon(np.array(shape)[:, :2]*mapscale,
                                         linewidth=width_line,
                                         edgecolor=color_outline,
                                         facecolor=color_building,
                                         fill=is_fill,
                                         alpha=alpha,
                                         zorder=zorder))
    return True


def plot_zones(ax, zones, color_zone=None,
               color_outline='green', width_line=2,
               alpha=0.5, zorder=-200, mapscale=1.0):

    if color_zone is None:
        is_fill = False
        color_zone = 'none'
    else:
        is_fill = True
    for shape in zones.shapes.get_value():
        # x_vec = np.array(shape)[:,0]
        # y_vec = np.array(shape)[:,1]
        # ax.plot(x_vec, y_vec, color = color_building, lw = width_line,
        #        alpha=alpha )
        ax.add_patch(mpl.patches.Polygon(np.array(shape)[:, :2]*mapscale,
                                         linewidth=width_line,
                                         edgecolor=color_outline,
                                         facecolor=color_zone,
                                         fill=is_fill,
                                         alpha=alpha,
                                         zorder=zorder))


def plot_zone(ax, zone_shape, color_zone="gray",
              color_outline='white', width_line=2,
              alpha=0.5, zorder=-200, mapscale=1.0):

    if color_zone is None:
        is_fill = False
        color_building = 'none'
    else:
        is_fill = True

    shape = (np.array(zone_shape)[:, :2]*mapscale).tolist()
    print(shape)
    ax.add_patch(Polygon(shape,
                         linewidth=width_line,
                         edgecolor=color_outline,
                         facecolor=color_zone,
                         fill=is_fill,
                         alpha=alpha,
                         zorder=zorder))


def plot_edgevalues_lines(ax, ids_result=None, config_ids_edge=None, values=[],
                          ids_edge=None, edges=None,
                          width_max=10.0, alpha=0.8, printformat='%.2f',
                          color_outline=None, color_fill=None,
                          color_label='black', is_antialiased=True,
                          is_fill=True, is_widthvalue=True,
                          arrowshape='left', length_arrowhead=10.0,
                          headwidthstretch=1.3,
                          fontsize=32,
                          valuelabel='',
                          value_range=None,
                          is_colorbar=True,
                          mapscale=1.0,
                          valuescale=None,
                          cax=None):

    print('plot_edgevalues_lines valuescale', valuescale, 'cax', cax, 'is_colorbar', is_colorbar)
    # ATTENTION: do not change order of args for backwards compatibility

    head_width = headwidthstretch*width_max
    fontsize_ticks = int(0.8*fontsize)

    if ids_edge is None:
        edges = config_ids_edge.get_linktab()
        ids_edge = config_ids_edge[ids_result]
    if valuescale is None:
        values_norm = np.array(values, dtype=np.float32)/np.max(values)
    else:

        values_norm = np.array(values, dtype=np.float32)*valuescale
        print('    use valuescale', valuescale, np.min(values_norm), np.max(values_norm))

    patches = []
    displacement = float(width_max)/2.0
    if is_widthvalue:
        linewidths = width_max * values_norm
    else:
        linewidths = width_max * np.ones(len(values_norm), np.float32)

    deltaangle_text = -np.pi/2.0
    displacement_text = displacement+width_max

    jet = cm_jet = plt.get_cmap('jet')
    if value_range is None:
        c_norm = colors.Normalize(vmin=0, vmax=np.max(values))
    else:
        c_norm = colors.Normalize(vmin=value_range[0], vmax=value_range[1])

    # http://stackoverflow.com/questions/8342549/matplotlib-add-colorbar-to-a-sequence-of-line-plots/11558629#11558629
    sm = cmx.ScalarMappable(norm=c_norm, cmap=jet)
    sm.set_array(values)
    for id_edge, value, value_norm, linewidth in zip(ids_edge, values, values_norm, linewidths):
        if not np.isnan(value):
            shape, angles_perb = get_resultshape(edges, id_edge, displacement)
            x_vec = np.array(shape)[:, 0]
            y_vec = np.array(shape)[:, 1]
            deltax = x_vec[-1]-x_vec[0]
            deltay = y_vec[-1]-y_vec[0]
            if is_fill:  # means that fill with value proportional color
                color = sm.to_rgba(value)
            else:
                color = color_fill

            # http://matplotlib.org/users/pyplot_tutorial.html
            line = mpl.lines.Line2D(x_vec*mapscale, y_vec*mapscale, color=color,
                                    linewidth=linewidth,
                                    antialiased=is_antialiased,
                                    alpha=alpha,
                                    solid_capstyle='round',
                                    zorder=0,
                                    )
            ax.add_line(line)

            if printformat != '':
                angles_text = np.arctan2(deltay, deltax)+deltaangle_text
                # print '  angles_text',printformat%value,angles_text/(np.pi)*180,(angles_text>np.pi/2),(angles_text<3*np.pi/2)
                if (angles_text > np.pi/2) | (angles_text < -np.pi/2):
                    angles_text += np.pi
                x_label = x_vec[0]+0.66*deltax+displacement_text*np.cos(angles_text)
                y_label = y_vec[0]+0.66*deltay+displacement_text*np.sin(angles_text)

                ax.text(x_label*mapscale, y_label*mapscale, printformat % value,
                        rotation=angles_text/(np.pi)*180,
                        color=color_label,
                        fontsize=fontsize_ticks,
                        zorder=10,
                        )

    if is_fill & is_colorbar:
        cbar = ax.figure.colorbar(sm, ax=ax)
        # mpl.setp(cbar.ax.yaxis.get_ticklabels(),  fontsize=fontsize)#weight='bold',
        # cb.ax.tick_params(labelsize=font_size)
        if valuelabel != '':
            cbar.ax.set_ylabel(valuelabel, fontsize=fontsize)  # , weight="bold")
        for l in cbar.ax.yaxis.get_ticklabels():
            # l.set_weight("bold")
            l.set_fontsize(fontsize_ticks)


def plot_connectionvalues_lines(ax, ids_result=None, config_ids_connection=None, values=[],
                                ids_connection=None, connections=None, net=None,
                                width_max=10.0, alpha=0.8, printformat='%.2f',
                                color_outline=None, color_fill=None,
                                color_label='black', is_antialiased=True,
                                is_fill=True, is_widthvalue=True,
                                arrowshape='left', length_arrowhead=10.0,
                                headwidthstretch=1.3,
                                fontsize=32,
                                valuelabel='',
                                value_range=None,
                                is_colorbar=True,
                                mapscale=1.0,
                                valuescale=None):
    # ATTENTION: do not change order of args for backwards compatibility

    head_width = headwidthstretch*width_max
    fontsize_ticks = int(0.8*fontsize)

    if valuescale is None:
        values_norm = np.array(values, dtype=np.float32)/np.max(values)
    else:
        values_norm = np.array(values, dtype=np.float32)*valuescale

    patches = []
    displacement = float(width_max)/2.0
    if is_widthvalue:
        linewidths = width_max*values_norm
    else:
        linewidths = width_max * np.ones(len(values_norm), np.float32)

    deltaangle_text = -np.pi/2.0
    displacement_text = displacement+width_max

    jet = cm_jet = plt.get_cmap('jet')
    if value_range is None:
        c_norm = colors.Normalize(vmin=0, vmax=np.max(values))
    else:
        c_norm = colors.Normalize(vmin=value_range[0], vmax=value_range[1])

    # http://stackoverflow.com/questions/8342549/matplotlib-add-colorbar-to-a-sequence-of-line-plots/11558629#11558629
    sm = cmx.ScalarMappable(norm=c_norm, cmap=jet)
    sm.set_array(values)
    for id_connection, value, value_norm, linewidth in zip(ids_connection, values, values_norm, linewidths):
        if not np.isnan(value):
            shape, angles_perb = get_resultshape_connections(net, id_connection, displacement)
            x_vec = np.array(shape)[:, 0]
            y_vec = np.array(shape)[:, 1]
            deltax = x_vec[-1]-x_vec[0]
            deltay = y_vec[-1]-y_vec[0]
            if is_fill:  # means that fill with value proportional color
                color = sm.to_rgba(value)
            else:
                color = color_fill

            # http://matplotlib.org/users/pyplot_tutorial.html
            line = mpl.lines.Line2D(x_vec*mapscale, y_vec*mapscale, color=color,
                                    linewidth=linewidth,
                                    antialiased=is_antialiased,
                                    alpha=alpha,
                                    solid_capstyle='round',
                                    zorder=0,
                                    )
            ax.add_line(line)

            if printformat != '':
                angles_text = np.arctan2(deltay, deltax)+deltaangle_text
                # print '  angles_text',printformat%value,angles_text/(np.pi)*180,(angles_text>np.pi/2),(angles_text<3*np.pi/2)
                if (angles_text > np.pi/2) | (angles_text < -np.pi/2):
                    angles_text += np.pi
                x_label = x_vec[0]+0.66*deltax+displacement_text*np.cos(angles_text)
                y_label = y_vec[0]+0.66*deltay+displacement_text*np.sin(angles_text)

                ax.text(x_label*mapscale, y_label*mapscale, printformat % value,
                        rotation=angles_text/(np.pi)*180,
                        color=color_label,
                        fontsize=fontsize_ticks,
                        zorder=10,
                        )

    if is_fill & is_colorbar:
        cbar = axis.figure.colorbar(sm, ax=axis)
        # mpl.setp(cbar.ax.yaxis.get_ticklabels(),  fontsize=fontsize)#weight='bold',
        # cb.ax.tick_params(labelsize=font_size)
        if valuelabel != '':
            cbar.ax.set_ylabel(valuelabel, fontsize=fontsize)  # , weight="bold")
        for l in cbar.ax.yaxis.get_ticklabels():
            # l.set_weight("bold")
            l.set_fontsize(fontsize_ticks)


def plot_edgevalues_arrows(ax, ids_result=None, config_ids_edge=None, values=[],
                           ids_edge=None, edges=None,
                           width_max=10.0, alpha=0.8, printformat='%.2f',
                           color_outline=None, color_fill=None,
                           color_label='black', is_antialiased=True,
                           is_fill=True, is_widthvalue=True,
                           arrowshape='left', length_arrowhead=10.0,
                           headwidthstretch=1.3,
                           fontsize=32,
                           valuelabel='',
                           value_range=None,
                           mapscale=1.0,
                           valuescale=None,
                           is_cbar=True):

    # ATTENTION: do not change order of args for backwards compatibility
    print('plot_edgevalues_arrows ids_result width_max', width_max, 'is_cbar', is_cbar)
    # print '  ids_result',ids_result
    # print '  ids_edge',ids_edge
    if ids_edge is None:
        edges = config_ids_edge.get_linktab()
        ids_edge = config_ids_edge[ids_result]

    head_width = headwidthstretch*width_max
    fontsize_ticks = int(0.8*fontsize)

    if valuescale is None:
        values_norm = np.array(values, dtype=np.float32)/np.max(values)
    else:
        values_norm = np.array(values, dtype=np.float32)*valuescale

    patches = []
    displacement = float(width_max)/4.0
    if is_widthvalue:
        linewidths = width_max*values_norm
    else:
        linewidths = width_max * np.ones(len(values_norm), np.float32)

    deltaangle_text = -np.pi/2.0
    displacement_text = displacement+width_max
    for id_edge, value, value_norm, linewidth in zip(ids_edge, values, values_norm, linewidths):
        if not np.isnan(value):
            shape, angles_perb = get_resultshape(edges, id_edge, displacement)
            x_vec = np.array(shape)[:, 0]
            y_vec = np.array(shape)[:, 1]
            deltax = x_vec[-1]-x_vec[0]
            deltay = y_vec[-1]-y_vec[0]

            if printformat != '':
                angles_text = np.arctan2(deltay, deltax)+deltaangle_text
                if (angles_text > np.pi/2) | (angles_text < -np.pi/2):
                    angles_text += np.pi
                x_label = x_vec[0]+0.66*deltax+displacement_text*np.cos(angles_text)
                y_label = y_vec[0]+0.66*deltay+displacement_text*np.sin(angles_text)

                ax.text(x_label*mapscale, y_label*mapscale, printformat % value,
                        rotation=angles_text/(np.pi)*180,
                        color=color_label,
                        fontsize=fontsize_ticks,
                        zorder=10,
                        )

            if is_widthvalue:
                head_width = headwidthstretch*linewidth
            arrow = FancyArrow(x_vec[0]*mapscale, y_vec[0]*mapscale, deltax*mapscale, deltay*mapscale, width=linewidth*mapscale,
                               antialiased=is_antialiased,
                               edgecolor=color_outline, facecolor=color_fill,
                               head_width=head_width, head_length=length_arrowhead,
                               length_includes_head=True,
                               fill=True, shape=arrowshape, zorder=0)
            patches.append(arrow)

    if is_fill:
        alpha_patch = alpha
        patchcollection = PatchCollection(patches, cmap=mpl.cm.jet, alpha=alpha_patch)
        patchcollection.set_array(values)

        # custom value range
        if value_range is not None:
            patchcollection.set_clim(value_range)

        ax.add_collection(patchcollection)
        if is_cbar:
            cbar = plt.colorbar(patchcollection, cax=cax, orientation='vertical')
            if valuelabel != '':
                cbar.ax.set_ylabel(valuelabel, fontsize=fontsize)  # , weight="bold")
            for l in cbar.ax.yaxis.get_ticklabels():
                # l.set_weight("bold")
                l.set_fontsize(fontsize_ticks)
    else:
        for patch in patches:
            ax.add_patch(patch)


def plot_connectionvalues_arrows(ax, ids_result=None, config_ids_edge=None, values=[],
                                 ids_connection=None, connections=None, net=None,
                                 width_max=10.0, alpha=0.8, printformat='%.2f',
                                 color_outline=None, color_fill=None,
                                 color_label='black', is_antialiased=True,
                                 is_fill=True, is_widthvalue=True,
                                 arrowshape='left', length_arrowhead=10.0,
                                 headwidthstretch=1.3,
                                 fontsize=32,
                                 valuelabel='',
                                 value_range=None,
                                 mapscale=1.0,
                                 valuescale=None):

    # ATTENTION: do not change order of args for backwards compatibility
    print('plot_connectionvalues_arrows ids_result')

    head_width = headwidthstretch*width_max
    fontsize_ticks = int(0.8*fontsize)

    if valuescale is None:
        values_norm = np.array(values, dtype=np.float32)/np.max(values)
    else:
        values_norm = np.array(values, dtype=np.float32)*valuescale

    patches = []
    displacement = float(width_max)/4.0
    if is_widthvalue:
        linewidths = width_max*values_norm
    else:
        linewidths = width_max * np.ones(len(values_norm), np.float32)

    deltaangle_text = -np.pi/2.0
    displacement_text = displacement+width_max
    for id_connection, value, value_norm, linewidth in zip(ids_connection, values, values_norm, linewidths):
        if not np.isnan(value):
            shape, angles_perb = get_resultshape_connections(net, id_connection, displacement)
            x_vec = np.array(shape)[:, 0]
            y_vec = np.array(shape)[:, 1]
            deltax = x_vec[-1]-x_vec[0]
            deltay = y_vec[-1]-y_vec[0]

            if printformat != '':
                angles_text = np.arctan2(deltay, deltax)+deltaangle_text
                if (angles_text > np.pi/2) | (angles_text < -np.pi/2):
                    angles_text += np.pi
                x_label = x_vec[0]+0.66*deltax+displacement_text*np.cos(angles_text)
                y_label = y_vec[0]+0.66*deltay+displacement_text*np.sin(angles_text)

                ax.text(x_label*mapscale, y_label*mapscale, printformat % value,
                        rotation=angles_text/(np.pi)*180,
                        color=color_label,
                        fontsize=fontsize_ticks,
                        zorder=10,
                        )

            if is_widthvalue:
                head_width = headwidthstretch*linewidth
            arrow = FancyArrow(x_vec[0]*mapscale, y_vec[0]*mapscale, deltax*mapscale, deltay*mapscale, width=linewidth*mapscale,
                               antialiased=is_antialiased,
                               edgecolor=color_outline, facecolor=color_fill,
                               head_width=head_width, head_length=length_arrowhead,
                               length_includes_head=True,
                               fill=True, shape=arrowshape, zorder=0)
            patches.append(arrow)

    if is_fill:
        alpha_patch = alpha
        patchcollection = PatchCollection(patches, cmap=mpl.cm.jet, alpha=alpha_patch)
        patchcollection.set_array(values)

        # custom value range
        if value_range is not None:
            patchcollection.set_clim(value_range)

        ax.add_collection(patchcollection)
        cbar = plt.colorbar(patchcollection)
        if valuelabel != '':
            cbar.ax.set_ylabel(valuelabel, fontsize=fontsize)  # , weight="bold")
        for l in cbar.ax.yaxis.get_ticklabels():
            # l.set_weight("bold")
            l.set_fontsize(fontsize_ticks)
    else:
        for patch in patches:
            ax.add_patch(patch)


def plot_nodevalues(ax, ids_result=None, config_ids_edge=None, values=[],
                    ids_node=None, nodes=None, net=None,
                    # width_max = 10.0,
                    alpha=0.8,
                    printformat='%.2f',
                    # color_outline = None,
                    # color_fill = None,
                    # color_label = 'black',
                    # is_antialiased = True,
                    is_fill=True,
                    # is_widthvalue = True,
                    # arrowshape = 'left',
                    # length_arrowhead = 10.0,
                    # headwidthstretch = 1.3,
                    fontsize=32,
                    valuelabel='',
                    value_range=None,
                    mapscale=1.0):

    # ATTENTION: do not change order of args for backwards compatibility
    print('plot_nodevalues ids_result')
    fontsize_ticks = int(0.8*fontsize)

    patches = []
    for id_node, value, in zip(ids_node, values):

        if not np.isnan(value):
            poly = mpl.patches.Polygon(np.transpose(np.stack((np.array(net.nodes.shapes[id_node])[
                                       :][:, 0]*mapscale, np.array(net.nodes.shapes[id_node])[:][:, 1]*mapscale))))
            patches.append(poly)

    if is_fill:
        alpha_patch = alpha
        patchcollection = PatchCollection(patches, cmap=mpl.cm.jet, alpha=alpha_patch)
        patchcollection.set_array(values)

        # custom value range
        if value_range is not None:
            patchcollection.set_clim(value_range)

        ax.add_collection(patchcollection)
        cbar = plt.colorbar(patchcollection)
        if valuelabel != '':
            cbar.ax.set_ylabel(valuelabel, fontsize=fontsize)  # , weight="bold")
        for l in cbar.ax.yaxis.get_ticklabels():
            # l.set_weight("bold")
            l.set_fontsize(fontsize_ticks)
    else:
        for patch in patches:
            ax.add_patch(patch)


def plot_pointvalues(ax, x_coords, y_coords,  values,
                     net=None,
                     width_max=10.0,
                     alpha=0.8,
                     printformat='%.2f',
                     # color_outline = None,
                     # color_fill = None,
                     # color_label = 'black',
                     # is_antialiased = True,
                     is_fill=True,
                     is_widthvalue=True,
                     # arrowshape = 'left',
                     # length_arrowhead = 10.0,
                     # headwidthstretch = 1.3,
                     fontsize=32,
                     valuelabel='',
                     value_range=None,
                     mapscale=1.0
                     ):

    # ATTENTION: do not change order of args for backwards compatibility
    print('plot_pointvalues ids_result')
    fontsize_ticks = int(0.8*fontsize)
# i = 0
    patches = []
    for x, y, value in zip(x_coords*mapscale, y_coords*mapscale, values):

        if not np.isnan(value):
            if is_widthvalue:
                poly = mpl.patches.Circle([x, y], radius=value*width_max/np.max(values)*mapscale, )
            else:
                poly = mpl.patches.Circle([x, y], radius=width_max*mapscale)
            patches.append(poly)
# print i
# i+=1

    if is_fill:
        alpha_patch = alpha
        patchcollection = PatchCollection(patches, cmap=mpl.cm.jet, alpha=alpha_patch)
        patchcollection.set_array(values)

        # custom value range
        if value_range is not None:
            patchcollection.set_clim(value_range)

        ax.add_collection(patchcollection)
        cbar = plt.colorbar(patchcollection)
        if valuelabel != '':
            cbar.ax.set_ylabel(valuelabel, fontsize=fontsize)  # , weight="bold")
        for l in cbar.ax.yaxis.get_ticklabels():
            # l.set_weight("bold")
            l.set_fontsize(fontsize_ticks)
    else:
        for patch in patches:
            ax.add_patch(patch)


def get_resultshape(edges, id_edge, dispacement):
    """
    Return resultshape coords for this edge.
    """
    # print 'get_resultshape',edges,'id_edge',id_edge,'dispacement', dispacement
    shape = np.array(edges.shapes[id_edge], np.float32)
    n_vert = len(shape)
    resultshape = np.zeros(shape.shape, np.float32)
    # laneshapes = np.zeros((n_lanes,n_vert,3), np.float32)
    angles_perb = get_angles_perpendicular(shape)
    if edges.types_spread[id_edge] == 0:  # right spread

        resultshape[:, 0] = shape[:, 0] + np.cos(angles_perb) * dispacement
        resultshape[:, 1] = shape[:, 1] + np.sin(angles_perb) * dispacement
        return resultshape, angles_perb
    else:
        return shape.copy(), angles_perb


def get_resultshape_connections(net, id_connection, dispacement):
    """
    Return resultshape coords for this connection.
    """
    print('get_resultshape', 'connections', 'id_connection', id_connection, 'dispacement', dispacement)
    x0 = net.edges.shapes[net.lanes.ids_edge[net.connections.ids_fromlane[id_connection]]][-1, 0]
    y0 = net.edges.shapes[net.lanes.ids_edge[net.connections.ids_fromlane[id_connection]]][-1, 1]
    x1 = net.edges.shapes[net.lanes.ids_edge[net.connections.ids_tolane[id_connection]]][0, 0]
    y1 = net.edges.shapes[net.lanes.ids_edge[net.connections.ids_tolane[id_connection]]][0, 1]
    if x0 != x1 or y0 != y1:
        shape = np.array([[x0, y0, 0.], [x1, y1, 0.]])
    else:
        shape = np.array([[x0, y0, 0.], [x1 + 1., y1 + 1., 0.]])
        print('connection', id_connection, 'has no shape')
    n_vert = len(shape)
    resultshape = np.zeros(shape.shape, np.float32)
    # laneshapes = np.zeros((n_lanes,n_vert,3), np.float32)
    angles_perb = get_angles_perpendicular(shape)

    return shape.copy(), angles_perb


def show_plot():
    plt.show()


class PlotoptionsMixin:

    def add_plotoptions(self, **kwargs):
        """
        [depricated]
        These are plotoptions for edge results shown on a map. 
        """
        attrsman = self.get_attrsman()
        self.add_edgeresultoptions(**kwargs)
        self.add_networkoptions(**kwargs)
        self.add_facilityoptions(**kwargs)
        self.add_zoneoptions(**kwargs)
        self.add_plotoptions_mapbase(**kwargs)
        self.add_plotoptions_base(**kwargs)

    def add_edgeresultoptions(self, **kwargs):
        attrsman = self.get_attrsman()
        plottypes = ['arrows', 'polygons']
        self.plottype = attrsman.add(cm.AttrConf('plottype', kwargs.get('plottype', 'polygons'),
                                                 choices=plottypes,
                                                 groupnames=['options'],
                                                 name='Plot types',
                                                 info='Plot type representing the results.',
                                                 ))

        self.resultwidth = attrsman.add(cm.AttrConf('resultwidth', kwargs.get('resultwidth', 4.0),
                                        groupnames=['options'],
                                        name='Result width',
                                        unit='m',
                                        info='Maximum width of graphical resuls on map.',
                                                    ))

        self.length_arrowhead = attrsman.add(cm.AttrConf('length_arrowhead', kwargs.get('length_arrowhead', 10.0),
                                                         groupnames=['options'],
                                                         name='Arrow length',
                                                         unit='m',
                                                         info='Length of arrowhead on result map.',
                                                         ))

        self.is_widthvalue = attrsman.add(cm.AttrConf('is_widthvalue', kwargs.get('is_widthvalue', True),
                                                      groupnames=['options'],
                                                      name='Value width?',
                                                      info='If True, the arrow width of the graphical representation is proportional to the result value.',
                                                      ))

        self.is_colorvalue = attrsman.add(cm.AttrConf('is_colorvalue', kwargs.get('is_colorvalue', True),
                                                      groupnames=['options'],
                                                      name='Value color?',
                                                      info='If True, the arrows of the graphical representation are filled with a colour representing the result value.',
                                                      ))

        self.is_value_range = attrsman.add(cm.AttrConf('is_value_range', kwargs.get('is_value_range', False),
                                                       groupnames=['options'],
                                                       name='Manual value range?',
                                                       info='If True, value range is set manually, otherwise value range is adjusted according to values.',
                                                       ))

        self.value_range_min = attrsman.add(cm.AttrConf('value_range_min', kwargs.get('value_range_min', 1.0),
                                                        groupnames=['options'],
                                                        name='Min. value range',
                                                        info='Minimum value range, if manual value range is selected.',
                                                        ))

        self.value_range_max = attrsman.add(cm.AttrConf('value_range_max', kwargs.get('value_range_max', 40.0),
                                                        groupnames=['options'],
                                                        name='Max. value range',
                                                        info='Maximum value range, if manual value range is selected.',
                                                        ))
        self.color_outline = attrsman.add(cm.AttrConf('color_outline', kwargs.get('color_outline', np.array([0.0, 0.0, 0.0, 0.95], dtype=np.float32)),
                                                      groupnames=['options'],
                                                      perm='wr',
                                                      metatype='color',
                                                      name='Outline color',
                                                      info='Outline color of result arrows in graphical representation. Only valid if no color-fill is chosen.',
                                                      ))

        self.color_fill = attrsman.add(cm.AttrConf('color_fill', kwargs.get('color_fill', np.array([0.3, 0.3, 1.0, 0.95], dtype=np.float32)),
                                                   groupnames=['options'],
                                                   perm='wr',
                                                   metatype='color',
                                                   name='Fill color',
                                                   info='Fill color of result arrows in graphical representation.',
                                                   ))

        self.valuelabel = attrsman.add(cm.AttrConf('valuelabel', kwargs.get('valuelabel',  ''),
                                                   groupnames=['options'],
                                                   name='Value label',
                                                   info='Label of results, as shown in the plot. Leave blank for default values.',
                                                   ))

        self.alpha_results = attrsman.add(cm.AttrConf('alpha_results', kwargs.get('alpha_results',  0.5),
                                                      groupnames=['options'],
                                                      name='Result transparency',
                                                      info='Transparency of result objects in graphical representation.',
                                                      ))

    def add_networkoptions(self, **kwargs):
        """
        Network plot options
        """
        attrsman = self.get_attrsman()
        self.is_show_network = attrsman.add(cm.AttrConf('is_show_network', kwargs.get('is_show_network', True),
                                                        groupnames=['options', 'network'],
                                                        name='Show network',
                                                        info='Shows a schematic network in the background.',
                                                        ))

        self.is_show_connections = attrsman.add(cm.AttrConf('is_show_connections', kwargs.get('is_show_connections', False),
                                                            groupnames=['options', 'network'],
                                                            name='Show network-connections',
                                                            info='Shows connections between edges in the network.',
                                                            ))

        self.color_network = attrsman.add(cm.AttrConf('color_network', kwargs.get('color_network', np.array([0.8, 0.8, 0.8, 0.8], dtype=np.float32)),
                                                      groupnames=['options', 'network'],
                                                      perm='wr',
                                                      metatype='color',
                                                      name='Network color',
                                                      info='Outline color of schematic network in the background.',
                                                      ))

        self.width_edge = attrsman.add(cm.AttrConf('width_edge', kwargs.get('width_edge', 2),
                                                   groupnames=['options', 'network'],
                                                   name='Edge width',
                                                   info='The actual line width for edges equal to this fixed width.',
                                                   ))

        self.scale_width_edge = attrsman.add(cm.AttrConf('scale_width_edge', kwargs.get('scale_width_edge', -0.5),
                                                         groupnames=['options', 'network'],
                                                         name='Edge width scale',
                                                         info='If positive, the actual line width for edges is the edge width scale x edge width. Otherwise see fixed line width.',
                                                         ))

        self.color_nodes = attrsman.add(cm.AttrConf('color_nodes', kwargs.get('color_nodes', np.array([1, 1, 1, 1], dtype=np.float32)),
                                        groupnames=['options', 'network'],
                                        perm='wr',
                                        metatype='color',
                                        name='Nodes color',
                                        info='Color of simplified nodes (or juctions in the background.',
                                                    ))
        self.alpha_net = attrsman.add(cm.AttrConf('alpha_net', kwargs.get('alpha_net', 0.5),
                                                  groupnames=['options', 'network'],
                                                  name='Net transparency',
                                                  info='Transparency of network (edges and nodes) in graphical representation.',
                                                  ))
        self.is_show_maps = attrsman.add(cm.AttrConf('is_show_maps', kwargs.get('is_show_maps', False),
                                                     groupnames=['options', 'network'],
                                                     name='Show map?',
                                                     info='If True, shows map as background in graphical representation. This feature requires that maps have been previously downloaded.',
                                                     ))

        self.alpha_maps = attrsman.add(cm.AttrConf('alpha_maps', kwargs.get('alpha_maps', 0.5),
                                                   groupnames=['options', 'network'],
                                                   name='Map transparency',
                                                   info='Transparency of background maps in graphical representation.',
                                                   ))

    def add_facilityoptions(self, **kwargs):
        attrsman = self.get_attrsman()
        self.is_show_facilities = attrsman.add(cm.AttrConf('is_show_facilities', kwargs.get('is_show_facilities', False),
                                                           groupnames=['options', 'facilities'],
                                                           name='Show facilities',
                                                           info='Shows a schematic facilities (buildings, parks, etc.) in the background.',
                                                           ))

        self.is_color_facilities_landuse = attrsman.add(cm.AttrConf('is_color_facilities_landuse', kwargs.get('is_color_facilities_landuse', True),
                                                                    groupnames=['options', 'facilities'],
                                                                    name='Use landuse colors',
                                                                    info='Fill facilities with colors of landuse.',
                                                                    ))

        self.color_facilities = attrsman.add(cm.AttrConf('color_facilities',  kwargs.get('color_facilities', np.array([1, 1, 1, 1], dtype=np.float32)),
                                                         groupnames=['options', 'facilities'],
                                                         perm='wr',
                                                         metatype='color',
                                                         name='Facilities color',
                                                         info='Fill color of facilities, in case no landuse colors are selected.',
                                                         ))

        self.alpha_facilities = attrsman.add(cm.AttrConf('alpha_facilities', kwargs.get('alpha_facilities', 0.5),
                                                         groupnames=['options', 'facilities'],
                                                         name='Facility transparency',
                                                         info='Transparency of facilities in graphical representation.',
                                                         ))

        self.color_borders = attrsman.add(cm.AttrConf('color_borders', kwargs.get('color_borders', np.array([0.7, 0.7, 0.7, 0.8], dtype=np.float32)),
                                                      groupnames=['options', 'facilities'],
                                                      perm='wr',
                                                      metatype='color',
                                                      name='Facility border color',
                                                      info='Facility border (or building walls) color of schematic facilities in the background.',
                                                      ))

    def add_zoneoptions(self, **kwargs):
        attrsman = self.get_attrsman()
        self.is_show_zones = attrsman.add(cm.AttrConf('is_show_zones', kwargs.get('is_show_zones', False),
                                                      groupnames=['options', 'zones'],
                                                      name='Show Zones',
                                                      info='Show zone polygons.',
                                                      ))

        self.color_zones = attrsman.add(cm.AttrConf('color_zones',  kwargs.get('color_zones', np.array([0.2, 0.8, 0.2, 1.0], dtype=np.float32)),
                                        groupnames=['options', 'zones'],
                                        perm='wr',
                                        metatype='color',
                                        name='Zone color',
                                        info='Fill color of Zone.',
                                                    ))

        self.alpha_zones = attrsman.add(cm.AttrConf('alpha_zones', kwargs.get('alpha_zones', 0.1),
                                        groupnames=['options', 'zones'],
                                        name='Zone transparency',
                                        info='Transparency of color of zones.',
                                                    ))

        self.color_zoneborders = attrsman.add(cm.AttrConf('color_zoneborders', kwargs.get('color_zoneborders', np.array([0.0, 0.7, 0.0, 1.0], dtype=np.float32)),
                                                          groupnames=['options', 'zones'],
                                                          perm='wr',
                                                          metatype='color',
                                                          name='Zone border color',
                                                          info='Color of zone border.',
                                                          ))

    def add_plotoptions_lineplot(self, is_marker_only=False, is_no_markers=False, **kwargs):
        """
        These are plotoptions for y over x oriented graphs. 
        """
        print('add_plotoptions_lineplot is_marker_only', is_marker_only, 'is_no_markers', is_no_markers)
        attrsman = self.get_attrsman()

        if not is_marker_only:
            self.color_line = attrsman.add(cm.AttrConf('color_line', kwargs.get('color_line', np.array([1, 0, 0, 1], dtype=np.float32)),
                                                       groupnames=['options'],
                                                       perm='wr',
                                                       metatype='color',
                                                       name='Line color',
                                                       info='Color of line in various diagrams.',
                                                       ))

            self.width_line = attrsman.add(cm.AttrConf('width_line', kwargs.get('width_line', 2),
                                                       groupnames=['options'],
                                                       perm='wr',
                                                       name='Line width',
                                                       info='Width of plotted lines.',
                                                       ))

            self.style_line = attrsman.add(cm.AttrConf('style_line', kwargs.get('style_line', '-'),
                                                       choices=LINESTYLES,
                                                       groupnames=['options'],
                                                       name='Line Style',
                                                       info='Line styles used for plots.',
                                                       ))

            self.alpha_line = attrsman.add(cm.AttrConf('alpha_line', kwargs.get('alpha_line', 0.9),
                                                       groupnames=['options'],
                                                       name='Line transparency',
                                                       info='Line transparency used for plots.',
                                                       ))
            self.alpha_results = attrsman.add(cm.AttrConf('alpha_results', kwargs.get('alpha_results',  0.5),
                                                          groupnames=['options'],
                                                          name='Result transparency',
                                                          info='Transparency of result objects in graphical representation.',
                                                          ))

        if is_marker_only:
            style_marker_default = 'o'
        else:
            style_marker_default = 'o'

        if not is_no_markers:
            print('  add marker options')
            self.style_marker = attrsman.add(cm.AttrConf('style_marker', kwargs.get('style_marker', style_marker_default),
                                                         choices=MARKERSTYLES,
                                                         groupnames=['options'],
                                                         name='Marker Style',
                                                         info='Marker style used for plots.',
                                                         ))

            self.color_marker = attrsman.add(cm.AttrConf('color_marker', kwargs.get('color_marker', np.array([0, 0, 1, 1], dtype=np.float32)),
                                                         groupnames=['options'],
                                                         perm='wr',
                                                         metatype='color',
                                                         name='Marker color',
                                                         info='Color of marker in plot.',
                                                         ))

            self.size_marker = attrsman.add(cm.AttrConf('size_marker', kwargs.get('size_marker', 16),
                                            groupnames=['options'],
                                            perm='wr',
                                            name='Marker size',
                                            info='Size of marker in plot.',
                                                        ))

        self.xlabel = attrsman.add(cm.AttrConf('xlabel', kwargs.get('xlabel', ''),
                                               groupnames=['options'],
                                               name='X label',
                                               info='X-axis label text, if blank then default values are used.',
                                               ))

        self.ylabel = attrsman.add(cm.AttrConf('ylabel', kwargs.get('ylabel', ''),
                                               groupnames=['options'],
                                               name='Y label',
                                               info='Y-axis label text, if blank then default values are used.',
                                               ))
        self.add_plotoptions_base(**kwargs)

    def add_plotoptions_mapbase(self, **kwargs):
        """
        These are all necessary option to plot whatever on a map.
        """
        attrsman = self.get_attrsman()
        self.unit_mapscale = attrsman.add(cm.AttrConf('unit_mapscale', kwargs.get('unit_mapscale', 'm'),
                                                      groupnames=['options', 'network'],
                                                      choices=['m', 'km'],
                                                      mapscales={'m': 1.0, 'km': 0.001},
                                                      name='Unit',
                                                      info='Unit used for map.',
                                                      ))
        self.is_bbox_explicit = attrsman.add(cm.AttrConf('is_bbox_explicit', kwargs.get('is_bbox_explicit', False),
                                                         groupnames=['options'],
                                                         perm='rw',
                                                         name='Zoom to bounding box',
                                                         info='Zoom to bounding box. If False, network boundaries are applied.',
                                                         ))

        self.bbox_explicit = attrsman.add(cm.ListConf('bbox_explicit', kwargs.get('bbox_explicit', [0.0, 0.0, 0.0, 0.0]),
                                                      groupnames=['options'],
                                                      name='Explicit bounding box',
                                                      info="""Expicit bounding box of the format [x_min, y_min,x_max, y_max]. Empty list means no explicit bounding borders are applied.""",
                                                      ))

    def add_plotoptions_base(self, **kwargs):
        print('add_plotoptions_base')
        attrsman = self.get_attrsman()
        self.axis = None
        self._i_fig = 0
        self.is_create_anim = False  # this attribute is required

        self.color_background = attrsman.add(cm.AttrConf('color_background', kwargs.get('color_background', np.array([1, 1, 1, 1], dtype=np.float32)),
                                                         groupnames=['options'],
                                                         perm='wr',
                                                         metatype='color',
                                                         name='Background color',
                                                         info='Background color of schematic network in the background.',
                                                         ))

        self.is_grid = attrsman.add(cm.AttrConf('is_grid', kwargs.get('is_grid', False),
                                                groupnames=['options'],
                                                name='Show grid?',
                                                info='If True, shows a grid on the graphical representation.',
                                                ))

        self.is_show_title = attrsman.add(cm.AttrConf('is_show_title', kwargs.get('is_show_title', True),
                                                      groupnames=['options'],
                                                      name='Show title',
                                                      info='Shows title on top of figure.',
                                                      ))

        self.title = attrsman.add(cm.AttrConf('title', kwargs.get('title', ''),
                                              groupnames=['options'],
                                              name='Title',
                                              info='Title text, if blank then default values are used.',
                                              ))

        self.size_titlefont = attrsman.add(cm.AttrConf('size_titlefont', kwargs.get('size_titlefont', 16),
                                                       groupnames=['options'],
                                                       name='Title fontsize',
                                                       info='Title fontsize.',
                                                       ))

        self.size_labelfont = attrsman.add(cm.AttrConf('size_labelfont', kwargs.get('size_labelfont', 14),
                                                       groupnames=['options'],
                                                       name='Label fontsize',
                                                       info='Label fontsize.',
                                                       ))

        self.printformat = attrsman.add(cm.AttrConf('printformat', kwargs.get('printformat', ''),
                                        choices=OrderedDict([
                                            ('Show no values', ''),
                                            ('x', '%.d'),
                                            ('x.x', '%.1f'),
                                            ('x.xx', '%.2f'),
                                            ('x.xxx', '%.3f'),
                                            ('x.xxxx', '%.4f'),
                                        ]),
            groupnames=['options'],
            name='Label formatting',
            info='Print formatting of value label in graphical representation.',
        ))

        self.color_label = attrsman.add(cm.AttrConf('color_label', kwargs.get('color_label', np.array([0, 0, 0, 1], dtype=np.float32)),
                                        groupnames=['options'],
                                        perm='wr',
                                        metatype='color',
                                        name='Label color',
                                        info='Color of value label in graphical representation.',
                                                    ))

        self.is_show = attrsman.add(cm.AttrConf('is_show', kwargs.get('is_show', True),
                                                groupnames=['options'],
                                                name='Show plots',
                                                info='If true, plots are shown in windows on the screen.',
                                                ))

    def add_save_options(self, **kwargs):

        attrsman = self.get_attrsman()
        self.is_save = attrsman.add(cm.AttrConf('is_save', kwargs.get('is_save', False),
                                                groupnames=['options'],
                                                name='Save plots',
                                                info='If true, plots are saved to disk.',
                                                ))

        self.figformat = attrsman.add(cm.AttrConf('figformat', kwargs.get('figformat', 'png'),
                                                  groupnames=['options'],
                                                  choices=['png', 'pdf', 'jpg', 'svg', 'gif'],
                                                  name='Graphics format',
                                                  info='Format of saved graphic.',
                                                  ))
        self.resolution = attrsman.add(cm.AttrConf('resolution', kwargs.get('resolution', 100),
                                                   groupnames=['options'],
                                                   name='Resolution',
                                                   unit='dpi',
                                                   info='Resolution of saved graphic in dots per inch (dpi).',
                                                   ))
        self.figwidth = attrsman.add(cm.AttrConf('figwidth', kwargs.get('figwidth', 30.0),
                                                 groupnames=['options'],
                                                 name='Figure width',
                                                 unit='cm',
                                                 info='Figure width.',
                                                 ))
        self.figheight = attrsman.add(cm.AttrConf('figheight', kwargs.get('figheight', 20.0),
                                                  groupnames=['options'],
                                                  name='Figure height',
                                                  unit='cm',
                                                  info='Figure height.',
                                                  ))
        self.is_transparent = attrsman.add(cm.AttrConf('is_transparent', kwargs.get('is_transparent', False),
                                                       groupnames=['options'],
                                                       name='Transparent image',
                                                       info='If true, save plots instead of showing on screen.',
                                                       ))

        self.figdirpath = attrsman.add(cm.AttrConf('figdirpath', kwargs.get('figdirpath', self.get_scenario().get_workdirpath()),
                                                   groupnames=['options'],
                                                   name='Figure directory',
                                                   info='Directory where figures are saved.',
                                                   metatype='dirpath',
                                                   ))

    def get_scenario(self):
        return self.parent.get_scenario()

    def init_figures(self):
        self._i_fig = 0
        plt.close("all")

    def create_figure(self,  is_return_axes=False, n_rows=None, n_cols=None, is_tight_layout=True):
        self._i_fig += 1

        if n_rows is None:
            fig = plt.figure(self._i_fig,
                             # dpi=self.resolution,#this may cause huge windows in interactive mode
                             tight_layout=is_tight_layout,
                             figsize=(self.figwidth*0.393701, self.figheight*0.393701)
                             )
            if is_return_axes:
                ax = fig.add_subplot(111)
                return fig, ax
            else:
                return fig

        else:
            if n_cols is None:
                n_cols = 1

            fig, axs = plt.subplots(n_rows, n_cols,
                                    tight_layout=is_tight_layout,
                                    figsize=(self.figwidth*0.393701, self.figheight*0.393701)
                                    )
            return fig, axs

    def _autolabel_bars(self, ax, bars, labels):
        """
        Helperfunction: Attach a text label above each bar displaying its height
        """
        for rect, label in zip(bars, labels):
            height = rect.get_height()
            if not np.isnan(height):
                ax.text(rect.get_x() + rect.get_width()/2., 1.05*height,
                        '%s' % label,
                        ha='center', va='bottom',
                        fontsize=int(0.8*self.size_labelfont),
                        )

    def do(self):
        """"This method calls the show method which should be overriden
        by to execute class  specific plot actions."""
        # print 'do',self.edgeattrname
        self.show()
        return True

    def set_figmargins(self):
        plt.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.1)

    def save_fig(self, figname):

        # rootfilepath = self.get_scenario().get_rootfilepath()
        rootfilepath = os.path.join(self.figdirpath, self.parent.get_scenario().get_rootfilename())
        filepath = "%s_%s.%s" % (rootfilepath, figname, self.figformat)
        print('save_fig', filepath)
        plt.savefig(filepath, format=self.figformat,
                    dpi=self.resolution,
                    # orientation = 'landscape',
                    orientation='portrait',
                    transparent=self.is_transparent
                    )

    def show_or_save(self, figname, left=0.12, bottom=0.1, right=0.86, top=0.9, wspace=0.2, hspace=0.2, is_show=True):
        print('show_or_save', left, right)
        # plt.subplots_adjust(left=left, bottom=bottom, right=right, top=top, wspace=wspace, hspace=hspace)

        if self.is_save | self.is_create_anim:

            self.save_fig(figname)

        if self.is_show & is_show:
            show_plot()

    def get_edgeresults(self):
        """
        Returns table with results which must have the attribute 'ids_edge'
        """
        return self.parent.edgeresults

    def set_axisborder(self, ax):
        ax.spines['top'].set_visible(False)
        ax.spines['right'].set_visible(False)
        ax.spines['bottom'].set_linewidth(0.5)
        ax.spines['left'].set_linewidth(0.5)

    def plot_hist(self, ax, x, bins=None, range=None,
                  color='blue', facecolor=None,
                  alpha=0.75, label=None,
                  histtype='step',  # {'bar', 'barstacked', 'step', 'stepfilled'}
                  is_normed=True,
                  is_cumulative=True,
                  is_show_mean=True,
                  is_show_std=False,
                  is_rel_frequ=False,
                  is_percent=False,
                  ):

        # the histogram of the data
        # print 'plot_hist',bins,range
        if bins is None:
            bins = self.n_bins

        if color is None:
            color = self.color_line

        if is_rel_frequ:
            is_cumulative = False
            is_normed = False
            weights = np.zeros_like(x) + 1. / x.size
        else:
            weights = np.zeros_like(x) + 1.

        if is_percent:
            weights *= 100

        n, bins0, patches = ax.hist(x, bins, range=range,
                                    normed=is_normed,
                                    cumulative=is_cumulative,
                                    histtype=histtype,
                                    linewidth=self.width_line,
                                    color=color, facecolor=facecolor,
                                    alpha=alpha, label=label,
                                    weights=weights,
                                    )

        mean = np.mean(x)
        std = np.std(x)
        y_min, y_max = ax.get_ylim()
        x_min = np.min(x)
        x_max = np.max(x)
        dy = 0.05*(y_max-y_min)
        dx = 0.05*(x_max-x_min)
        if is_show_mean:
            ax.plot([mean, mean], [y_min, y_max], color=color, linestyle='-.', linewidth=2*self.width_line)
            # very good: https://matplotlib.org/users/text_intro.html
            # ax.text(3, 8, 'boxed italics text in data coords', style='italic',
            #        bbox={'facecolor':'red', 'alpha':0.5, 'pad':10})
            # ax.annotate(r'$\mu=%.2f$'%mean, xy=(mean, y_max-dy),
            #                xytext=(mean-dx, y_max-2*dy),
            #                fontsize=int(0.8*self.size_labelfont),
            #                arrowprops=dict(facecolor='black', shrink=0.05)
            #                )

        if is_show_std:
            ax.plot([mean-std, mean-std], [y_min, y_max], color=color, linestyle='--', linewidth=self.width_line)
            ax.plot([mean+std, mean+std], [y_min, y_max], color=color, linestyle='--', linewidth=self.width_line)

        # hist uses np.histogram under the hood to create 'n' and 'bins'.
        # np.histogram returns the bin edges, so there will be 50 probability
        # density values in n, 51 bin edges in bins and 50 patches.  To get
        # everything lined up, we'll compute the bin centers
        bincenters = 0.5*(bins0[1:]+bins0[:-1])
        return bincenters

    def plot_results_on_map(self, axis, ids_edgeres=None, values=[],
                            ids_edge=None,
                            title='', valuelabel='',
                            is_connection_results=False,
                            ids_connectionres=None, ids_connection=None,
                            valuescale=None,
                            is_colorbar=True,
                            cax=None,
                            ):
        print('plot_results_on_map valuescale', valuescale)
        # axis, ids, resultattrconf[ids] , title, valuelabel = resultattrconf.format_symbol()

        if self.valuelabel != "":
            valuelabel = self.valuelabel

        if is_connection_results:
            if ids_connection is None:
                ids_connection = self.get_connectionresults().ids_connection[ids_connectionres]
        else:
            if ids_edge is None:
                ids_edge = self.get_edgeresults().ids_edge[ids_edgeres]

        net = self.get_scenario().net

        unit = self.unit_mapscale
        mapscale = self.get_attrsman().get_config('unit_mapscale').mapscales[unit]
        # print '  unit',unit
        # print '  mapscale',mapscale
        # xticks =axis.get_xticks()
        # yticks =axis.get_yticks()
        # print 'xticks',xticks
        # print 'yticks',yticks
        self.plot_net(axis, mapscale=mapscale,  unit=unit, is_configure=False)

        if self.is_value_range:
            value_range = (self.value_range_min, self.value_range_max)
        else:
            value_range = None

        if is_connection_results:
            if self.plottype == 'arrows':  # = ['Arrows','Polygons']
                plot_connectionvalues_arrows(axis,
                                             # ids,
                                             # edgesresults.ids_edge,
                                             ids_connection=ids_connection,
                                             values=values,
                                             connections=net.connections,
                                             net=net,
                                             width_max=self.resultwidth,
                                             alpha=self.alpha_results,
                                             printformat=self.printformat,
                                             color_outline=self.color_outline,
                                             color_fill=self.color_fill,
                                             color_label=self.color_label,
                                             is_antialiased=True,
                                             is_fill=self.is_colorvalue,
                                             is_widthvalue=self.is_widthvalue,
                                             length_arrowhead=self.length_arrowhead,
                                             fontsize=self.size_labelfont,
                                             valuelabel=valuelabel,
                                             value_range=value_range,
                                             mapscale=mapscale,
                                             valuescale=valuescale)
            elif self.plottype == 'polygons':
                plot_connectionvalues_lines(axis,
                                            # ids,
                                            # edgesresults.ids_edge,
                                            ids_connection=ids_connection,
                                            values=values,
                                            connections=net.connections,
                                            net=net,
                                            width_max=self.resultwidth,
                                            alpha=self.alpha_results,
                                            printformat=self.printformat,
                                            color_outline=self.color_outline,
                                            color_fill=self.color_fill,
                                            color_label=self.color_label,
                                            is_antialiased=True,
                                            is_fill=self.is_colorvalue,
                                            is_widthvalue=self.is_widthvalue,
                                            length_arrowhead=self.length_arrowhead,
                                            fontsize=self.size_labelfont,
                                            valuelabel=valuelabel,
                                            value_range=value_range,
                                            mapscale=mapscale,
                                            valuescale=valuescale)
        else:
            if len(ids_edge) > 0:
                if self.plottype == 'arrows':  # = ['Arrows','Polygons']
                    plot_edgevalues_arrows(axis,
                                           # ids,
                                           # edgesresults.ids_edge,
                                           ids_edge=ids_edge,
                                           values=values,
                                           edges=net.edges,
                                           width_max=self.resultwidth,
                                           alpha=self.alpha_results,
                                           printformat=self.printformat,
                                           color_outline=self.color_outline,
                                           color_fill=self.color_fill,
                                           color_label=self.color_label,
                                           is_antialiased=True,
                                           is_fill=self.is_colorvalue,
                                           is_widthvalue=self.is_widthvalue,
                                           length_arrowhead=self.length_arrowhead,
                                           fontsize=self.size_labelfont,
                                           valuelabel=valuelabel,
                                           value_range=value_range,
                                           mapscale=mapscale,
                                           valuescale=valuescale,
                                           is_cbar=is_colorbar)
                elif self.plottype == 'polygons':
                    plot_edgevalues_lines(axis,
                                          # ids,
                                          # edgesresults.ids_edge,
                                          ids_edge=ids_edge,
                                          values=values,
                                          edges=net.edges,
                                          width_max=self.resultwidth,
                                          alpha=self.alpha_results,
                                          printformat=self.printformat,
                                          color_outline=self.color_outline,
                                          color_fill=self.color_fill,
                                          color_label=self.color_label,
                                          is_colorbar=is_colorbar,
                                          is_antialiased=True,
                                          is_fill=self.is_colorvalue,
                                          is_widthvalue=self.is_widthvalue,
                                          length_arrowhead=self.length_arrowhead,
                                          fontsize=self.size_labelfont,
                                          valuelabel=valuelabel,
                                          value_range=value_range,
                                          mapscale=mapscale,
                                          valuescale=valuescale,
                                          cax=cax,
                                          )

        self.configure_map(axis, title, unit=unit,)

    def configure_map(self, axis, title=None, unit='m',):

        if self.is_show_title:
            if self.title != "":
                title = self.title

            axis.set_title(title, fontsize=self.size_titlefont)

        axis.axis('equal')
        # ax.legend(loc='best',shadow=True)

        axis.grid(self.is_grid)
        axis.set_xlabel('West-East ['+unit+']', fontsize=self.size_labelfont)
        axis.set_ylabel('South-North ['+unit+']', fontsize=self.size_labelfont)
        axis.tick_params(axis='x', labelsize=int(0.8*self.size_labelfont))
        axis.tick_params(axis='y', labelsize=int(0.8*self.size_labelfont))

        # get borders right, this seems to work
        plt.subplots_adjust(left=0.12, bottom=0.1, right=0.86, top=0.9, wspace=0.2, hspace=0.2)

        # show_plot()

        if self.is_bbox_explicit:
            # x_min,y_min,x_max,y_max = net.get_boundaries(is_netboundaries = True)
            [x_min, y_min, x_max, y_max] = self.bbox_explicit
            axis.set_autoscaley_on(False)
            axis.set_xlim([x_min, x_max])
            axis.set_ylim([y_min, y_max])

    def plot_net(self, axis=None, title="", unit='', mapscale=None, is_configure=True):
        print('plot_net mapscale', mapscale)
        if mapscale is None:
            unit = self.unit_mapscale
            mapscale = self.get_attrsman().get_config('unit_mapscale').mapscales[unit]

        if axis is None:
            axis = init_plot()

        if hasattr(self, 'is_show_network'):
            if self.is_show_network:
                net = self.get_scenario().net
                plot_net(axis, net, color_edge=self.color_network, width_edge=self.width_edge,
                         color_node=self.color_nodes, alpha=self.alpha_net,
                         is_show_connections=self.is_show_connections, mapscale=mapscale,
                         scale_width_edge=self.scale_width_edge)

        if hasattr(self, 'is_show_facilities'):
            if self.is_show_facilities:
                facilities = self.get_scenario().landuse.facilities
                color_building = self.color_facilities
                if self.is_color_facilities_landuse:
                    ids_facility = facilities.get_ids()
                    colors_building = self.get_scenario(
                    ).landuse.landusetypes.colors[facilities.ids_landusetype[ids_facility]]
                else:
                    colors_building = None

                plot_facilities(axis, facilities, color_building=color_building,
                                color_outline=self.color_borders,
                                width_line=2, alpha=self.alpha_facilities,
                                mapscale=mapscale, colors_building=colors_building)
        if hasattr(self, 'is_show_maps'):
            if self.is_show_maps:
                plot_maps(axis, self.get_scenario().landuse.maps, alpha=self.alpha_maps, mapscale=mapscale)

        if hasattr(self, 'is_show_zones'):
            if self.is_show_zones:
                zones = self.get_scenario().landuse.zones
                plot_zones(axis, zones, color_zone=self.color_zones,
                           color_outline=self.color_zoneborders,
                           width_line=2, alpha=self.alpha_zones,
                           mapscale=mapscale)
        if is_configure:
            self.configure_map(axis, title=title, unit=unit)

        return axis

    def plot_node_results_on_map(self, axis, ids_noderes=None, values=[],
                                 ids_node=None,
                                 title='', valuelabel='',
                                 is_node_results=False, ):

        if ids_node is None:
            ids_node = self.get_noderesults().ids_node[ids_noderes]

        net = self.get_scenario().net

        unit = self.unit_mapscale
        mapscale = self.get_attrsman().get_config('unit_mapscale').mapscales[unit]
        self.plot_net(axis, mapscale=mapscale,  unit=unit, is_configure=False)

        if self.is_value_range:
            value_range = (self.value_range_min, self.value_range_max)
        else:
            value_range = None

        if is_node_results:
            plot_nodevalues(axis,
                            # ids,
                            # edgesresults.ids_edge,
                            ids_node=ids_node,
                            values=values,
                            nodes=net.nodes,
                            # width_max = self.resultwidth,
                            alpha=self.alpha_results,
                            # printformat = self.printformat,
                            # color_outline = self.color_outline,
                            # color_fill = self.color_fill,
                            # color_label = self.color_label,
                            # is_antialiased = True,
                            is_fill=self.is_colorvalue,
                            # is_widthvalue = self.is_widthvalue,
                            # length_arrowhead = self.length_arrowhead,
                            fontsize=self.size_labelfont,
                            valuelabel=valuelabel,
                            value_range=value_range,
                            net=net
                            )

        self.configure_map(axis, title, unit=unit)

    def plot_point_results_on_map(self, net, axis, x_coords, y_coords, values=[],
                                  title='', valuelabel='',
                                  ):

        unit = self.unit_mapscale
        mapscale = self.get_attrsman().get_config('unit_mapscale').mapscales[unit]
        self.plot_net(axis, mapscale=mapscale,  unit=unit, is_configure=False)

        if self.is_value_range:
            value_range = (self.value_range_min, self.value_range_max)
        else:
            value_range = None

        plot_pointvalues(axis,
                         # ids,
                         # edgesresults.ids_edge,
                         x_coords=x_coords,
                         y_coords=y_coords,
                         values=values,
                         width_max=self.resultwidth,
                         alpha=self.alpha_results,
                         # printformat = self.printformat,
                         # color_outline = self.color_outline,
                         # color_fill = self.color_fill,
                         # color_label = self.color_label,
                         # is_antialiased = True,
                         is_fill=self.is_colorvalue,
                         is_widthvalue=self.is_widthvalue,
                         # length_arrowhead = self.length_arrowhead,
                         fontsize=self.size_labelfont,
                         valuelabel=valuelabel,
                         value_range=value_range,
                         net=net
                         )

        self.configure_map(axis, title, unit=unit)


class Netplotter(PlotoptionsMixin, Process):
    def __init__(self, net, name='Plot network',
                 info="High quality plot using matplotlib",
                 logger=None,
                 **kwargs):

        self._init_common('netplotter', parent=net, name=name,
                          info=info, logger=logger)

        # print 'Resultplotter.__init__',results,self.parent
        attrsman = self.get_attrsman()

        # change some defaults
        kwargs.update({'color_network': np.array([0.1, 0.1, 1.0, 1.0], dtype=np.float32),
                       'color_nodes': np.array([0.1, 1.0, 1.0, 1.0], dtype=np.float32),
                       'alpha_net': 0.9,
                       'alpha_maps': 0.9,
                       'color_facilities': np.array([0.9, 0.7, 0.4, 0.6], dtype=np.float32),
                       'color_borders': np.array([0.3, 0.2, 0.0, 0.8], dtype=np.float32),
                       'color_zones': np.array([0.2, 0.8, 0.2, 0.1], dtype=np.float32),
                       'alpha_zones': 0.2,
                       'color_zoneborders': np.array([0.0, 0.5, 0.0, 0.9], dtype=np.float32),
                       'color_background': np.array([1, 1, 1, 1], dtype=np.float32),
                       # 'title':
                       })

        self.add_networkoptions(**kwargs)
        self.add_facilityoptions(**kwargs)
        self.add_zoneoptions(**kwargs)
        self.add_plotoptions_mapbase(**kwargs)
        self.add_plotoptions_base(**kwargs)
        self.add_save_options(**kwargs)

    def show(self):
        ax = init_plot()
        self.plot_net(axis=ax)

        if self.is_save:
            plt.subplots_adjust(left=0.12, bottom=0.1, right=0.86, top=0.9, wspace=0.2, hspace=0.2)
            self.save_fig('net')
        else:
            show_plot()

    def do(self):
        # print 'do',self.edgeattrname
        self.show()

    def get_scenario(self):
        return self.parent.parent
