sumolib.visualization.helpers

  1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
  2# Copyright (C) 2013-2026 German Aerospace Center (DLR) and others.
  3# This program and the accompanying materials are made available under the
  4# terms of the Eclipse Public License 2.0 which is available at
  5# https://www.eclipse.org/legal/epl-2.0/
  6# This Source Code may also be made available under the following Secondary
  7# Licenses when the conditions for such availability set forth in the Eclipse
  8# Public License 2.0 are satisfied: GNU General Public License, version 2
  9# or later which is available at
 10# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
 11# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
 12
 13# @file    helpers.py
 14# @author  Daniel Krajzewicz
 15# @author  Laura Bieker
 16# @author  Michael Behrisch
 17# @author  Mirko Barthauer
 18# @date    2013-11-11
 19
 20from __future__ import absolute_import
 21from __future__ import print_function
 22
 23import gc
 24import sys
 25
 26import matplotlib
 27from pylab import arange, close, cm, figure, legend, log, plt, savefig, show, title
 28from pylab import xlabel, xlim, xticks, ylabel, ylim, yticks
 29from matplotlib.ticker import FuncFormatter as ff
 30from matplotlib.collections import LineCollection
 31from packaging import version
 32
 33from ..options import ArgumentParser
 34mpl_version = version.parse(matplotlib.__version__)
 35
 36if sys.version_info[:2] >= (3, 14):
 37    # workaround for https://github.com/matplotlib/matplotlib/issues/29157
 38    if mpl_version < version.parse("3.10.5"):
 39        import copy  # noqa
 40
 41        def _safe_path_deepcopy(self, memo):
 42            # create new instance and deepcopy only attributes (avoid deepcopy(super()))
 43            cls = self.__class__
 44            new = cls.__new__(cls)
 45            memo[id(self)] = new
 46            for k, v in self.__dict__.items():
 47                new.__dict__[k] = copy.deepcopy(v, memo)
 48            return new
 49
 50        matplotlib.path.Path.__deepcopy__ = _safe_path_deepcopy
 51
 52# http://datadebrief.blogspot.de/2010/10/plotting-sunrise-sunset-times-in-python.html
 53
 54
 55def m2hm0(x, i):
 56    h = int(x / 3600)
 57    return '%(h)02d' % {'h': h}
 58
 59
 60def m2hm1(x, i):
 61    h = int(x / 3600)
 62    m = int((x % 3600) / 60)
 63    return '%(h)02d:%(m)02d' % {'h': h, 'm': m}
 64
 65
 66def m2hm2(x, i):
 67    h = int(x / 3600)
 68    m = int((x % 3600) / 60)
 69    s = int(x % 60)
 70    return '%(h)02d:%(m)02d:%(s)02d' % {'h': h, 'm': m, 's': s}
 71
 72
 73def addPlotOptions(ap):
 74    ap.add_argument("--colors", dest="colors", category="visualization",
 75                    default=None, help="Defines the colors to use")
 76    ap.add_argument("--colormap", dest="colormap", category="visualization",
 77                    default="nipy_spectral", help="Defines the colormap to use")
 78    ap.add_argument("--colormap.center", dest="colormapCenter", type=float, category="visualization",
 79                    help="centers colors at the given value")
 80    ap.add_argument("-l", "--labels", dest="labels", category="visualization",
 81                    default=None, help="Defines the labels to use")
 82    ap.add_argument("--xlim", dest="xlim", category="visualization",
 83                    default=None, help="Defines x-limits of the figure XMIN,XMAX")
 84    ap.add_argument("--ylim", dest="ylim", category="visualization",
 85                    default=None, help="Defines y-limits of the figure YMIN,YMAX")
 86    ap.add_argument("--xticks", dest="xticks", category="visualization",
 87                    default=None, help="Set x-axis ticks XMIN,XMAX,XSTEP,XSIZE or XSIZE")
 88    ap.add_argument("--yticks", dest="yticks", category="visualization",
 89                    default=None, help="Set y-axis ticks YMIN,YMAX,YSTEP,YSIZE or YSIZE")
 90    ap.add_argument("--xticks-file", dest="xticksFile", category="input", type=ap.file,
 91                    default=None, help="Load x-axis ticks from file (LABEL or FLOAT:LABEL per line)")
 92    ap.add_argument("--yticks-file", dest="yticksFile", category="input", type=ap.file,
 93                    default=None, help="Load y-axis ticks from file (LABEL or FLOAT:LABEL per line)")
 94    ap.add_argument("--xtime0", dest="xtime0", action="store_true", category="time",
 95                    default=False, help="Use a time formatter for x-ticks (hh)")
 96    ap.add_argument("--ytime0", dest="ytime0", action="store_true", category="time",
 97                    default=False, help="Use a time formatter for y-ticks (hh)")
 98    ap.add_argument("--xtime1", dest="xtime1", action="store_true", category="time",
 99                    default=False, help="Use a time formatter for x-ticks (hh:mm)")
100    ap.add_argument("--ytime1", dest="ytime1", action="store_true", category="time",
101                    default=False, help="Use a time formatter for y-ticks (hh:mm)")
102    ap.add_argument("--xtime2", dest="xtime2", action="store_true", category="time",
103                    default=False, help="Use a time formatter for x-ticks (hh:mm:ss)")
104    ap.add_argument("--ytime2", dest="ytime2", action="store_true", category="time",
105                    default=False, help="Use a time formatter for y-ticks (hh:mm:ss)")
106    ap.add_argument("--xgrid", dest="xgrid", action="store_true", category="visualization",
107                    default=False, help="Enable grid on x-axis")
108    ap.add_argument("--ygrid", dest="ygrid", action="store_true", category="visualization",
109                    default=False, help="Enable grid on y-axis")
110    ap.add_argument("--xticksorientation", dest="xticksorientation", category="visualization",
111                    type=float, default=None, help="Set the orientation of the x-axis ticks")
112    ap.add_argument("--yticksorientation", dest="yticksorientation", category="visualization",
113                    type=float, default=None, help="Set the orientation of the x-axis ticks")
114    ap.add_argument("--xlabel", dest="xlabel", category="visualization",
115                    default=None, help="Set the x-axis label")
116    ap.add_argument("--ylabel", dest="ylabel", category="visualization",
117                    default=None, help="Set the y-axis label")
118    ap.add_argument("--xlabelsize", dest="xlabelsize", category="visualization",
119                    type=int, default=16, help="Set the size of the x-axis label")
120    ap.add_argument("--ylabelsize", dest="ylabelsize", category="visualization",
121                    type=int, default=16, help="Set the size of the x-axis label")
122    ap.add_argument("--marker", dest="marker", default=None, category="visualization",
123                    help="marker for single points (default o for scatter, None otherwise)")
124    ap.add_argument("--linestyle", dest="linestyle", default="-", category="visualization",
125                    help="plot line style (default -)")
126    ap.add_argument("--title", dest="title", category="visualization",
127                    default=None, help="Set the title")
128    ap.add_argument("--titlesize", dest="titlesize", category="visualization",
129                    type=int, default=16, help="Set the title size")
130    ap.add_argument("--adjust", dest="adjust", category="visualization",
131                    default=None, help="Adjust the subplots LEFT,BOTTOM or LEFT,BOTTOM,RIGHT,TOP")
132    ap.add_argument("-s", "--size", dest="size", category="visualization",
133                    default=False, help="Defines the figure size X,Y")
134    ap.add_argument("--no-legend", dest="nolegend", action="store_true", category="visualization",
135                    default=False, help="Disables the legend")
136    ap.add_argument("--legend-position", dest="legendposition", category="visualization",
137                    default=None, help="Sets the legend position")
138    ap.add_argument("--dpi", dest="dpi", type=float, category="visualization",
139                    default=None, help="Define dpi resolution for figures")
140    ap.add_argument("--alpha", type=float,
141                    default=1., help="Define background transparency of the figure in the range 0..1")
142
143
144def addInteractionOptions(optParser):
145    optParser.add_option("-o", "--output", category="output", dest="output", metavar="FILE",
146                         type=ArgumentParser.file_list,
147                         default=None, help="Comma separated list of filename(s) the figure shall be written to")
148    optParser.add_option("-b", "--blind", dest="blind", action="store_true",
149                         default=False, help="If set, the figure will not be shown")
150
151
152def addNetOptions(optParser):
153    optParser.add_option("-w", "--default-width", dest="defaultWidth",
154                         type=float, default=.1, help="Defines the default edge width")
155    optParser.add_option("--default-color", dest="defaultColor",
156                         default='k', help="Defines the default edge color")
157
158
159def applyPlotOptions(fig, ax, options):
160    if options.xlim:
161        xlim(float(options.xlim.split(",")[0]), float(
162            options.xlim.split(",")[1]))
163    if options.yticksorientation:
164        ax.tick_params(
165            axis='y', which='major', tickdir=options.xticksorientation)
166    if options.xticks:
167        vals = options.xticks.split(",")
168        if len(vals) == 1:
169            ax.tick_params(axis='x', which='major', labelsize=float(vals[0]))
170        elif len(vals) == 4:
171            xticks(arange(float(vals[0]), float(vals[1]), float(vals[2])), size=float(vals[3]))
172        else:
173            raise ValueError(
174                "Error: ticks must be given as one float (<SIZE>) or four floats (<MIN>,<MAX>,<STEP>,<SIZE>)")
175    if options.xticksFile:
176        xticks(*parseTicks(options.xticksFile))
177    if options.xtime0:
178        if max(ax.get_xticks()) < 3600:
179            print("Warning: x ticks not suited for hh format.")
180        ax.xaxis.set_major_formatter(ff(m2hm0))
181    if options.xtime1:
182        if max(ax.get_yticks()) < 60:
183            print("Warning: x ticks not suited for hh:mm format.")
184        ax.xaxis.set_major_formatter(ff(m2hm1))
185    if options.xtime2:
186        ax.xaxis.set_major_formatter(ff(m2hm2))
187    if options.xgrid:
188        ax.xaxis.grid(True)
189    if options.xlabel:
190        xlabel(options.xlabel, size=options.xlabelsize)
191    if options.xticksorientation:
192        labels = ax.get_xticklabels()
193        for label in labels:
194            label.set_rotation(options.xticksorientation)
195
196    if options.ylim:
197        ylim(float(options.ylim.split(",")[0]), float(
198            options.ylim.split(",")[1]))
199    if options.yticks:
200        vals = options.yticks.split(",")
201        if len(vals) == 1:
202            ax.tick_params(axis='y', which='major', labelsize=float(vals[0]))
203        elif len(vals) == 4:
204            yticks(
205                arange(float(vals[0]), float(vals[1]), float(vals[2])), size=float(vals[3]))
206        else:
207            raise ValueError(
208                "Error: ticks must be given as one float (<SIZE>) or four floats (<MIN>,<MAX>,<STEP>,<SIZE>)")
209    if options.yticksFile:
210        yticks(*parseTicks(options.yticksFile))
211    if options.ytime0:
212        if max(ax.get_yticks()) < 3600:
213            print("Warning: y ticks not suited for hh format.")
214        ax.yaxis.set_major_formatter(ff(m2hm0))
215    if options.ytime1:
216        if max(ax.get_yticks()) < 60:
217            print("Warning: y ticks not suited for hh:mm format.")
218        ax.yaxis.set_major_formatter(ff(m2hm1))
219    if options.ytime2:
220        ax.yaxis.set_major_formatter(ff(m2hm2))
221    if options.ygrid:
222        ax.yaxis.grid(True)
223    if options.ylabel:
224        ylabel(options.ylabel, size=options.ylabelsize)
225    if options.yticksorientation:
226        labels = ax.get_yticklabels()
227        for label in labels:
228            label.set_rotation(options.yticksorientation)
229
230    if options.title:
231        title(options.title, size=options.titlesize)
232    if options.adjust:
233        vals = options.adjust.split(",")
234        if len(vals) == 2:
235            fig.subplots_adjust(left=float(vals[0]), bottom=float(vals[1]))
236        elif len(vals) == 4:
237            fig.subplots_adjust(left=float(vals[0]), bottom=float(
238                vals[1]), right=float(vals[2]), top=float(vals[3]))
239        else:
240            raise ValueError(
241                "Error: adjust must be given as two floats (<LEFT>,<BOTTOM>) or four floats " +
242                "(<LEFT>,<BOTTOM>,<RIGHT>,<TOP>)")
243    if options.alpha is not None:
244        alpha = max(0., min(1., options.alpha))
245        fig.patch.set_alpha(alpha)
246        ax.patch.set_alpha(alpha)
247
248
249def plotNet(net, colors, widths, options):
250    shapes = []
251    c = []
252    w = []
253    for e in net._edges:
254        shapes.append(e.getShape())
255        if e._id in colors:
256            c.append(colors[str(e._id)])
257        else:
258            c.append(options.defaultColor)
259        if e._id in widths:
260            w.append(widths[str(e._id)])
261        else:
262            w.append(options.defaultWidth)
263
264    line_segments = LineCollection(shapes, linewidths=w, colors=c, linestyles=options.linestyle)
265    ax = plt.gca()
266    ax.add_collection(line_segments)
267    ax.set_xmargin(0.1)
268    ax.set_ymargin(0.1)
269    ax.autoscale_view(True, True, True)
270
271
272def getColorMap(options):
273    if mpl_version < version.parse("3.6.0"):
274        return matplotlib.cm.get_cmap(options.colormap)
275    return matplotlib.colormaps[options.colormap]
276
277
278def getColor(options, i, a):
279    if options.colors:
280        v = options.colors.split(",")
281        if i >= len(v):
282            raise ValueError("Error: not enough colors given")
283        return v[i]
284    if options.colormap[0] == '#':
285        colormap = parseColorMap(options.colormap[1:])
286        if mpl_version < version.parse("3.6.0"):
287            cm.register_cmap(name="CUSTOM", cmap=colormap)
288        else:
289            matplotlib.colormaps.register(name="CUSTOM", cmap=colormap)
290        options.colormap = "CUSTOM"
291    if options.colormapCenter:
292        cNorm = matplotlib.colors.TwoSlopeNorm(vmin=0, vcenter=options.colormapCenter, vmax=a)
293    else:
294        cNorm = matplotlib.colors.Normalize(vmin=0, vmax=a)
295    scalarMap = matplotlib.cm.ScalarMappable(norm=cNorm, cmap=getColorMap(options))
296    return scalarMap.to_rgba(i)
297
298
299def getLabel(f, i, options):
300    label = f
301    if options.labels:
302        label = options.labels.split(",")[i]
303    return label
304
305
306def openFigure(options):
307    if options.size:
308        x = float(options.size.split(",")[0])
309        y = float(options.size.split(",")[1])
310        fig = figure(figsize=(x, y))
311    else:
312        fig = figure()
313    ax = fig.add_subplot(111)
314    return fig, ax
315
316
317def closeFigure(fig, ax, options, haveLabels=True, optOut=None):
318    if haveLabels and not options.nolegend:
319        if options.legendposition:
320            legend(loc=options.legendposition)
321        else:
322            legend()
323    applyPlotOptions(fig, ax, options)
324    if options.output or optOut is not None:
325        n = options.output
326        myDpi = options.dpi
327        if myDpi is not None:
328            myDpi = float(myDpi)
329        if optOut is not None:
330            n = optOut
331        for o in n.split(","):
332            savefig(o, dpi=myDpi)
333    if not options.blind:
334        show()
335    try:
336        fig.clf()
337    except:  # noqa
338        pass
339    close()
340    gc.collect()
341
342
343def logNormalise(values, maxValue):
344    if not maxValue:
345        for e in values:
346            if not maxValue or maxValue < values[e]:
347                maxValue = values[e]
348    emin = None
349    emax = None
350    for e in values:
351        if values[e] != 0:
352            values[e] = log(values[e]) / log(maxValue)
353        if not emin or emin > values[e]:
354            emin = values[e]
355        if not emax or emax < values[e]:
356            emax = values[e]
357    if emax is not None and emin is not None:
358        valRange = emax - emin
359        if valRange == 0:
360            valRange = 1
361        for e in values:
362            values[e] = (values[e] - emin) / valRange
363
364
365def linNormalise(values, minColorValue, maxColorValue):
366    if minColorValue is not None and maxColorValue is not None:
367        valRange = maxColorValue - minColorValue
368        if valRange == 0:
369            valRange = 1
370        for e in values:
371            values[e] = (values[e] - minColorValue) / valRange
372
373
374def toHex(val):
375    """Converts the given value (0-255) into its hexadecimal representation"""
376    hex = "0123456789abcdef"
377    return hex[int(val / 16)] + hex[int(val - int(val / 16) * 16)]
378
379
380def toFloat(val):
381    """Converts the given value (0-255) into its hexadecimal representation"""
382    hex = "0123456789abcdef"
383    return float(hex.find(val[0]) * 16 + hex.find(val[1]))
384
385
386def toColor(val, colormap):
387    """Converts the given value (0-1) into a color definition parseable by matplotlib"""
388    for i in range(0, len(colormap) - 1):
389        if colormap[i + 1][0] > val:
390            scale = (val - colormap[i][0]) / \
391                (colormap[i + 1][0] - colormap[i][0])
392            r = colormap[i][1][0] + \
393                (colormap[i + 1][1][0] - colormap[i][1][0]) * scale
394            g = colormap[i][1][1] + \
395                (colormap[i + 1][1][1] - colormap[i][1][1]) * scale
396            b = colormap[i][1][2] + \
397                (colormap[i + 1][1][2] - colormap[i][1][2]) * scale
398            return "#" + toHex(r) + toHex(g) + toHex(b)
399    return "#" + toHex(colormap[-1][1][0]) + toHex(colormap[-1][1][1]) + toHex(colormap[-1][1][2])
400
401
402def parseColorMap(mapDef):
403    ret = {"red": [], "green": [], "blue": []}
404    defs = mapDef.split(",")
405    for d in defs:
406        (value, color) = d.split(":")
407        value = float(value)
408        r = color[1:3]
409        g = color[3:5]
410        b = color[5:7]
411        # ret.append( (float(value), ( toFloat(r), toFloat(g), toFloat(b) ) ) )
412        ret["red"].append((value, toFloat(r) / 255., toFloat(r) / 255.))
413        ret["green"].append((value, toFloat(g) / 255., toFloat(g) / 255.))
414        ret["blue"].append((value, toFloat(b) / 255., toFloat(b) / 255.))
415
416        # ret.append( (value, color) )
417    colormap = matplotlib.colors.LinearSegmentedColormap("CUSTOM", ret, 1024)
418    return colormap
419
420
421def parseTicks(tickfile, mapping=None):
422    # there are multiple possible formats:
423    # 1. for defining the order (label is a data value or a wildcard):
424    #   <LABEL>
425    # 2. for defining the tick positions for the data values
426    #   <FLOAT>:<LABEL>
427    # 3. for defining the tick positions and a mapping from data values to displayed labels
428    #   <FLOAT>:<DATA>:<LABEL>
429
430    # whether explicit tick positions  are available
431    haveOffsets = True
432    # whether a data->label mapping is available
433
434    offsets = []
435    labels = []
436    with open(tickfile) as tf:
437        for line in tf:
438            line = line.strip()
439            if not line:
440                continue
441            of_label = line.split(':')
442            try:
443                of = float(of_label[0])
444                offsets.append(of)
445                if len(of_label) > 1:
446                    if len(of_label) == 3:
447                        labels.append(of_label[2])
448                        if mapping is not None:
449                            mapping[of_label[1]] = of_label[2]
450                    else:
451                        labels.append(' '.join(of_label[1:]))
452                else:
453                    # also accept <FLOAT> format
454                    labels.append(str(of))
455            except ValueError:
456                haveOffsets = False
457                labels.append(line)
458
459    if not haveOffsets:
460        offsets = range(len(labels))
461    return offsets, labels
mpl_version = <Version('3.6.3')>
def m2hm0(x, i):
56def m2hm0(x, i):
57    h = int(x / 3600)
58    return '%(h)02d' % {'h': h}
def m2hm1(x, i):
61def m2hm1(x, i):
62    h = int(x / 3600)
63    m = int((x % 3600) / 60)
64    return '%(h)02d:%(m)02d' % {'h': h, 'm': m}
def m2hm2(x, i):
67def m2hm2(x, i):
68    h = int(x / 3600)
69    m = int((x % 3600) / 60)
70    s = int(x % 60)
71    return '%(h)02d:%(m)02d:%(s)02d' % {'h': h, 'm': m, 's': s}
def addPlotOptions(ap):
 74def addPlotOptions(ap):
 75    ap.add_argument("--colors", dest="colors", category="visualization",
 76                    default=None, help="Defines the colors to use")
 77    ap.add_argument("--colormap", dest="colormap", category="visualization",
 78                    default="nipy_spectral", help="Defines the colormap to use")
 79    ap.add_argument("--colormap.center", dest="colormapCenter", type=float, category="visualization",
 80                    help="centers colors at the given value")
 81    ap.add_argument("-l", "--labels", dest="labels", category="visualization",
 82                    default=None, help="Defines the labels to use")
 83    ap.add_argument("--xlim", dest="xlim", category="visualization",
 84                    default=None, help="Defines x-limits of the figure XMIN,XMAX")
 85    ap.add_argument("--ylim", dest="ylim", category="visualization",
 86                    default=None, help="Defines y-limits of the figure YMIN,YMAX")
 87    ap.add_argument("--xticks", dest="xticks", category="visualization",
 88                    default=None, help="Set x-axis ticks XMIN,XMAX,XSTEP,XSIZE or XSIZE")
 89    ap.add_argument("--yticks", dest="yticks", category="visualization",
 90                    default=None, help="Set y-axis ticks YMIN,YMAX,YSTEP,YSIZE or YSIZE")
 91    ap.add_argument("--xticks-file", dest="xticksFile", category="input", type=ap.file,
 92                    default=None, help="Load x-axis ticks from file (LABEL or FLOAT:LABEL per line)")
 93    ap.add_argument("--yticks-file", dest="yticksFile", category="input", type=ap.file,
 94                    default=None, help="Load y-axis ticks from file (LABEL or FLOAT:LABEL per line)")
 95    ap.add_argument("--xtime0", dest="xtime0", action="store_true", category="time",
 96                    default=False, help="Use a time formatter for x-ticks (hh)")
 97    ap.add_argument("--ytime0", dest="ytime0", action="store_true", category="time",
 98                    default=False, help="Use a time formatter for y-ticks (hh)")
 99    ap.add_argument("--xtime1", dest="xtime1", action="store_true", category="time",
100                    default=False, help="Use a time formatter for x-ticks (hh:mm)")
101    ap.add_argument("--ytime1", dest="ytime1", action="store_true", category="time",
102                    default=False, help="Use a time formatter for y-ticks (hh:mm)")
103    ap.add_argument("--xtime2", dest="xtime2", action="store_true", category="time",
104                    default=False, help="Use a time formatter for x-ticks (hh:mm:ss)")
105    ap.add_argument("--ytime2", dest="ytime2", action="store_true", category="time",
106                    default=False, help="Use a time formatter for y-ticks (hh:mm:ss)")
107    ap.add_argument("--xgrid", dest="xgrid", action="store_true", category="visualization",
108                    default=False, help="Enable grid on x-axis")
109    ap.add_argument("--ygrid", dest="ygrid", action="store_true", category="visualization",
110                    default=False, help="Enable grid on y-axis")
111    ap.add_argument("--xticksorientation", dest="xticksorientation", category="visualization",
112                    type=float, default=None, help="Set the orientation of the x-axis ticks")
113    ap.add_argument("--yticksorientation", dest="yticksorientation", category="visualization",
114                    type=float, default=None, help="Set the orientation of the x-axis ticks")
115    ap.add_argument("--xlabel", dest="xlabel", category="visualization",
116                    default=None, help="Set the x-axis label")
117    ap.add_argument("--ylabel", dest="ylabel", category="visualization",
118                    default=None, help="Set the y-axis label")
119    ap.add_argument("--xlabelsize", dest="xlabelsize", category="visualization",
120                    type=int, default=16, help="Set the size of the x-axis label")
121    ap.add_argument("--ylabelsize", dest="ylabelsize", category="visualization",
122                    type=int, default=16, help="Set the size of the x-axis label")
123    ap.add_argument("--marker", dest="marker", default=None, category="visualization",
124                    help="marker for single points (default o for scatter, None otherwise)")
125    ap.add_argument("--linestyle", dest="linestyle", default="-", category="visualization",
126                    help="plot line style (default -)")
127    ap.add_argument("--title", dest="title", category="visualization",
128                    default=None, help="Set the title")
129    ap.add_argument("--titlesize", dest="titlesize", category="visualization",
130                    type=int, default=16, help="Set the title size")
131    ap.add_argument("--adjust", dest="adjust", category="visualization",
132                    default=None, help="Adjust the subplots LEFT,BOTTOM or LEFT,BOTTOM,RIGHT,TOP")
133    ap.add_argument("-s", "--size", dest="size", category="visualization",
134                    default=False, help="Defines the figure size X,Y")
135    ap.add_argument("--no-legend", dest="nolegend", action="store_true", category="visualization",
136                    default=False, help="Disables the legend")
137    ap.add_argument("--legend-position", dest="legendposition", category="visualization",
138                    default=None, help="Sets the legend position")
139    ap.add_argument("--dpi", dest="dpi", type=float, category="visualization",
140                    default=None, help="Define dpi resolution for figures")
141    ap.add_argument("--alpha", type=float,
142                    default=1., help="Define background transparency of the figure in the range 0..1")
def addInteractionOptions(optParser):
145def addInteractionOptions(optParser):
146    optParser.add_option("-o", "--output", category="output", dest="output", metavar="FILE",
147                         type=ArgumentParser.file_list,
148                         default=None, help="Comma separated list of filename(s) the figure shall be written to")
149    optParser.add_option("-b", "--blind", dest="blind", action="store_true",
150                         default=False, help="If set, the figure will not be shown")
def addNetOptions(optParser):
153def addNetOptions(optParser):
154    optParser.add_option("-w", "--default-width", dest="defaultWidth",
155                         type=float, default=.1, help="Defines the default edge width")
156    optParser.add_option("--default-color", dest="defaultColor",
157                         default='k', help="Defines the default edge color")
def applyPlotOptions(fig, ax, options):
160def applyPlotOptions(fig, ax, options):
161    if options.xlim:
162        xlim(float(options.xlim.split(",")[0]), float(
163            options.xlim.split(",")[1]))
164    if options.yticksorientation:
165        ax.tick_params(
166            axis='y', which='major', tickdir=options.xticksorientation)
167    if options.xticks:
168        vals = options.xticks.split(",")
169        if len(vals) == 1:
170            ax.tick_params(axis='x', which='major', labelsize=float(vals[0]))
171        elif len(vals) == 4:
172            xticks(arange(float(vals[0]), float(vals[1]), float(vals[2])), size=float(vals[3]))
173        else:
174            raise ValueError(
175                "Error: ticks must be given as one float (<SIZE>) or four floats (<MIN>,<MAX>,<STEP>,<SIZE>)")
176    if options.xticksFile:
177        xticks(*parseTicks(options.xticksFile))
178    if options.xtime0:
179        if max(ax.get_xticks()) < 3600:
180            print("Warning: x ticks not suited for hh format.")
181        ax.xaxis.set_major_formatter(ff(m2hm0))
182    if options.xtime1:
183        if max(ax.get_yticks()) < 60:
184            print("Warning: x ticks not suited for hh:mm format.")
185        ax.xaxis.set_major_formatter(ff(m2hm1))
186    if options.xtime2:
187        ax.xaxis.set_major_formatter(ff(m2hm2))
188    if options.xgrid:
189        ax.xaxis.grid(True)
190    if options.xlabel:
191        xlabel(options.xlabel, size=options.xlabelsize)
192    if options.xticksorientation:
193        labels = ax.get_xticklabels()
194        for label in labels:
195            label.set_rotation(options.xticksorientation)
196
197    if options.ylim:
198        ylim(float(options.ylim.split(",")[0]), float(
199            options.ylim.split(",")[1]))
200    if options.yticks:
201        vals = options.yticks.split(",")
202        if len(vals) == 1:
203            ax.tick_params(axis='y', which='major', labelsize=float(vals[0]))
204        elif len(vals) == 4:
205            yticks(
206                arange(float(vals[0]), float(vals[1]), float(vals[2])), size=float(vals[3]))
207        else:
208            raise ValueError(
209                "Error: ticks must be given as one float (<SIZE>) or four floats (<MIN>,<MAX>,<STEP>,<SIZE>)")
210    if options.yticksFile:
211        yticks(*parseTicks(options.yticksFile))
212    if options.ytime0:
213        if max(ax.get_yticks()) < 3600:
214            print("Warning: y ticks not suited for hh format.")
215        ax.yaxis.set_major_formatter(ff(m2hm0))
216    if options.ytime1:
217        if max(ax.get_yticks()) < 60:
218            print("Warning: y ticks not suited for hh:mm format.")
219        ax.yaxis.set_major_formatter(ff(m2hm1))
220    if options.ytime2:
221        ax.yaxis.set_major_formatter(ff(m2hm2))
222    if options.ygrid:
223        ax.yaxis.grid(True)
224    if options.ylabel:
225        ylabel(options.ylabel, size=options.ylabelsize)
226    if options.yticksorientation:
227        labels = ax.get_yticklabels()
228        for label in labels:
229            label.set_rotation(options.yticksorientation)
230
231    if options.title:
232        title(options.title, size=options.titlesize)
233    if options.adjust:
234        vals = options.adjust.split(",")
235        if len(vals) == 2:
236            fig.subplots_adjust(left=float(vals[0]), bottom=float(vals[1]))
237        elif len(vals) == 4:
238            fig.subplots_adjust(left=float(vals[0]), bottom=float(
239                vals[1]), right=float(vals[2]), top=float(vals[3]))
240        else:
241            raise ValueError(
242                "Error: adjust must be given as two floats (<LEFT>,<BOTTOM>) or four floats " +
243                "(<LEFT>,<BOTTOM>,<RIGHT>,<TOP>)")
244    if options.alpha is not None:
245        alpha = max(0., min(1., options.alpha))
246        fig.patch.set_alpha(alpha)
247        ax.patch.set_alpha(alpha)
def plotNet(net, colors, widths, options):
250def plotNet(net, colors, widths, options):
251    shapes = []
252    c = []
253    w = []
254    for e in net._edges:
255        shapes.append(e.getShape())
256        if e._id in colors:
257            c.append(colors[str(e._id)])
258        else:
259            c.append(options.defaultColor)
260        if e._id in widths:
261            w.append(widths[str(e._id)])
262        else:
263            w.append(options.defaultWidth)
264
265    line_segments = LineCollection(shapes, linewidths=w, colors=c, linestyles=options.linestyle)
266    ax = plt.gca()
267    ax.add_collection(line_segments)
268    ax.set_xmargin(0.1)
269    ax.set_ymargin(0.1)
270    ax.autoscale_view(True, True, True)
def getColorMap(options):
273def getColorMap(options):
274    if mpl_version < version.parse("3.6.0"):
275        return matplotlib.cm.get_cmap(options.colormap)
276    return matplotlib.colormaps[options.colormap]
def getColor(options, i, a):
279def getColor(options, i, a):
280    if options.colors:
281        v = options.colors.split(",")
282        if i >= len(v):
283            raise ValueError("Error: not enough colors given")
284        return v[i]
285    if options.colormap[0] == '#':
286        colormap = parseColorMap(options.colormap[1:])
287        if mpl_version < version.parse("3.6.0"):
288            cm.register_cmap(name="CUSTOM", cmap=colormap)
289        else:
290            matplotlib.colormaps.register(name="CUSTOM", cmap=colormap)
291        options.colormap = "CUSTOM"
292    if options.colormapCenter:
293        cNorm = matplotlib.colors.TwoSlopeNorm(vmin=0, vcenter=options.colormapCenter, vmax=a)
294    else:
295        cNorm = matplotlib.colors.Normalize(vmin=0, vmax=a)
296    scalarMap = matplotlib.cm.ScalarMappable(norm=cNorm, cmap=getColorMap(options))
297    return scalarMap.to_rgba(i)
def getLabel(f, i, options):
300def getLabel(f, i, options):
301    label = f
302    if options.labels:
303        label = options.labels.split(",")[i]
304    return label
def openFigure(options):
307def openFigure(options):
308    if options.size:
309        x = float(options.size.split(",")[0])
310        y = float(options.size.split(",")[1])
311        fig = figure(figsize=(x, y))
312    else:
313        fig = figure()
314    ax = fig.add_subplot(111)
315    return fig, ax
def closeFigure(fig, ax, options, haveLabels=True, optOut=None):
318def closeFigure(fig, ax, options, haveLabels=True, optOut=None):
319    if haveLabels and not options.nolegend:
320        if options.legendposition:
321            legend(loc=options.legendposition)
322        else:
323            legend()
324    applyPlotOptions(fig, ax, options)
325    if options.output or optOut is not None:
326        n = options.output
327        myDpi = options.dpi
328        if myDpi is not None:
329            myDpi = float(myDpi)
330        if optOut is not None:
331            n = optOut
332        for o in n.split(","):
333            savefig(o, dpi=myDpi)
334    if not options.blind:
335        show()
336    try:
337        fig.clf()
338    except:  # noqa
339        pass
340    close()
341    gc.collect()
def logNormalise(values, maxValue):
344def logNormalise(values, maxValue):
345    if not maxValue:
346        for e in values:
347            if not maxValue or maxValue < values[e]:
348                maxValue = values[e]
349    emin = None
350    emax = None
351    for e in values:
352        if values[e] != 0:
353            values[e] = log(values[e]) / log(maxValue)
354        if not emin or emin > values[e]:
355            emin = values[e]
356        if not emax or emax < values[e]:
357            emax = values[e]
358    if emax is not None and emin is not None:
359        valRange = emax - emin
360        if valRange == 0:
361            valRange = 1
362        for e in values:
363            values[e] = (values[e] - emin) / valRange
def linNormalise(values, minColorValue, maxColorValue):
366def linNormalise(values, minColorValue, maxColorValue):
367    if minColorValue is not None and maxColorValue is not None:
368        valRange = maxColorValue - minColorValue
369        if valRange == 0:
370            valRange = 1
371        for e in values:
372            values[e] = (values[e] - minColorValue) / valRange
def toHex(val):
375def toHex(val):
376    """Converts the given value (0-255) into its hexadecimal representation"""
377    hex = "0123456789abcdef"
378    return hex[int(val / 16)] + hex[int(val - int(val / 16) * 16)]

Converts the given value (0-255) into its hexadecimal representation

def toFloat(val):
381def toFloat(val):
382    """Converts the given value (0-255) into its hexadecimal representation"""
383    hex = "0123456789abcdef"
384    return float(hex.find(val[0]) * 16 + hex.find(val[1]))

Converts the given value (0-255) into its hexadecimal representation

def toColor(val, colormap):
387def toColor(val, colormap):
388    """Converts the given value (0-1) into a color definition parseable by matplotlib"""
389    for i in range(0, len(colormap) - 1):
390        if colormap[i + 1][0] > val:
391            scale = (val - colormap[i][0]) / \
392                (colormap[i + 1][0] - colormap[i][0])
393            r = colormap[i][1][0] + \
394                (colormap[i + 1][1][0] - colormap[i][1][0]) * scale
395            g = colormap[i][1][1] + \
396                (colormap[i + 1][1][1] - colormap[i][1][1]) * scale
397            b = colormap[i][1][2] + \
398                (colormap[i + 1][1][2] - colormap[i][1][2]) * scale
399            return "#" + toHex(r) + toHex(g) + toHex(b)
400    return "#" + toHex(colormap[-1][1][0]) + toHex(colormap[-1][1][1]) + toHex(colormap[-1][1][2])

Converts the given value (0-1) into a color definition parseable by matplotlib

def parseColorMap(mapDef):
403def parseColorMap(mapDef):
404    ret = {"red": [], "green": [], "blue": []}
405    defs = mapDef.split(",")
406    for d in defs:
407        (value, color) = d.split(":")
408        value = float(value)
409        r = color[1:3]
410        g = color[3:5]
411        b = color[5:7]
412        # ret.append( (float(value), ( toFloat(r), toFloat(g), toFloat(b) ) ) )
413        ret["red"].append((value, toFloat(r) / 255., toFloat(r) / 255.))
414        ret["green"].append((value, toFloat(g) / 255., toFloat(g) / 255.))
415        ret["blue"].append((value, toFloat(b) / 255., toFloat(b) / 255.))
416
417        # ret.append( (value, color) )
418    colormap = matplotlib.colors.LinearSegmentedColormap("CUSTOM", ret, 1024)
419    return colormap
def parseTicks(tickfile, mapping=None):
422def parseTicks(tickfile, mapping=None):
423    # there are multiple possible formats:
424    # 1. for defining the order (label is a data value or a wildcard):
425    #   <LABEL>
426    # 2. for defining the tick positions for the data values
427    #   <FLOAT>:<LABEL>
428    # 3. for defining the tick positions and a mapping from data values to displayed labels
429    #   <FLOAT>:<DATA>:<LABEL>
430
431    # whether explicit tick positions  are available
432    haveOffsets = True
433    # whether a data->label mapping is available
434
435    offsets = []
436    labels = []
437    with open(tickfile) as tf:
438        for line in tf:
439            line = line.strip()
440            if not line:
441                continue
442            of_label = line.split(':')
443            try:
444                of = float(of_label[0])
445                offsets.append(of)
446                if len(of_label) > 1:
447                    if len(of_label) == 3:
448                        labels.append(of_label[2])
449                        if mapping is not None:
450                            mapping[of_label[1]] = of_label[2]
451                    else:
452                        labels.append(' '.join(of_label[1:]))
453                else:
454                    # also accept <FLOAT> format
455                    labels.append(str(of))
456            except ValueError:
457                haveOffsets = False
458                labels.append(line)
459
460    if not haveOffsets:
461        offsets = range(len(labels))
462    return offsets, labels