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

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

def toFloat(val):
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]))

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

def toColor(val, colormap):
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])

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

def parseColorMap(mapDef):
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
def parseTicks(tickfile, mapping=None):
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