Example #1
0
def speed_on_the_ground(graph: nx.DiGraph, filename: str):
    mpl.use("Agg")

    with commons.Section("Getting the background OSM map"):
        nodes = pd.DataFrame(data=nx.get_node_attributes(graph, name="pos"),
                             index=["lon", "lat"]).T
        extent = maps.ax4(nodes.lat, nodes.lon)
        osmap = maps.get_map_by_bbox(maps.ax2mb(*extent))

    with mpl.rc_context(PARAM['mpl_style']), commons.Axes() as ax1:
        velocity = pd.Series(nx.get_edge_attributes(
            graph, name="len")) / pd.Series(
                nx.get_edge_attributes(graph, name=PARAM['edge_time_attr']))
        cmap = LinearSegmentedColormap.from_list(
            name="noname", colors=["brown", "r", "orange", "g"])

        ax1.imshow(osmap, extent=extent, interpolation='quadric', zorder=-100)

        nx.draw(graph,
                ax=ax1,
                pos=nx.get_node_attributes(graph, name="pos"),
                edgelist=list(velocity.index),
                edge_color=list(velocity),
                edge_cmap=cmap,
                edge_vmin=0,
                edge_vmax=7,
                with_labels=False,
                arrows=False,
                node_size=0,
                alpha=0.8,
                width=0.3)

        fn = PARAM['out_images_path'] / commons.myname() / F"{filename}.png"
        ax1.figure.savefig(commons.makedirs(fn))
Example #2
0
def nx_draw_met_by_len(graph, edges_met=None, mpl_backend="Agg", printer=None):
    mpl.use(mpl_backend)

    with Section("Preparing to draw graph", out=printer):

        nodes = pd.DataFrame(data=nx.get_node_attributes(graph, name="loc"),
                             index=["lat", "lon"]).T
        edges_len = pd.Series(data=nx.get_edge_attributes(graph, name="len"),
                              name="len")

        if edges_met is not None:
            edges_met = pd.Series(name="met", data=edges_met)
        else:
            edges_met = pd.Series(name="met",
                                  data=nx.get_edge_attributes(graph,
                                                              name="met"))

        cmap = LinearSegmentedColormap.from_list(
            name="noname", colors=["g", "y", "r", "brown"])

    with Section("Getting the background OSM map", out=printer):
        extent = maps.ax4(nodes.lat, nodes.lon)
        osmap = maps.get_map_by_bbox(maps.ax2mb(*extent))

    with Section("Drawing image", out=printer):

        fig: plt.Figure
        ax1: plt.Axes
        (fig, ax1) = plt.subplots()

        # The background map
        ax1.imshow(osmap, extent=extent, interpolation='quadric', zorder=-100)

        ax1.axis("off")

        edge_colors = (edges_met / edges_len).clip(lower=0.9, upper=1.5)

        nx.draw(graph,
                ax=ax1,
                pos=nx.get_node_attributes(graph, name="pos"),
                edge_list=list(edge_colors.index),
                edge_color=list(edge_colors),
                edge_cmap=cmap,
                with_labels=False,
                arrows=False,
                node_size=0,
                alpha=1,
                width=0.4)

        ax1.set_xlim(extent[0:2])
        ax1.set_ylim(extent[2:4])

    try:
        # Note:
        # "yield (fig, ax1)" does not work with the "retry" context manager
        return iter([(fig, ax1)])
    finally:
        plt.close(fig)
Example #3
0
def compare_multiple_trajectories(table_name):
	mpl.use("Agg")

	# Number of trips to plot
	N = 10
	# Number of trajectories per trip
	M = 12

	graph = get_road_graph()
	nodes = pd.DataFrame(data=nx.get_node_attributes(graph, "loc"), index=["lat", "lon"]).T
	edges_len = nx.get_edge_attributes(graph, name="len")

	where = "('2016-05-02 08:00' <= pickup_datetime) and (pickup_datetime <= '2016-05-02 09:00')"
	trips = get_trip_data(table_name, graph, order="", where=where)

	trips = trips.sample(min(N, len(trips)), random_state=1)
	logger.debug(F"{len(trips)} trips")

	with Section("Getting the background OSM map", out=logger.debug):
		extent = maps.ax4(nodes.lat, nodes.lon)
		osmap = maps.get_map_by_bbox(maps.ax2mb(*extent))

	with plt.style.context({**PARAM['mpl_style'], 'font.size': 5}), Axes() as ax1:
		# The background map
		ax1.imshow(osmap, extent=extent, interpolation='quadric', zorder=-100)

		ax1.axis("off")

		ax1.set_xlim(extent[0:2])
		ax1.set_ylim(extent[2:4])

		for (__, trip) in trips.iterrows():
			with Section("Computing candidate trajectories", out=logger.debug):
				trajectories = pd.DataFrame(data={'path': [
					path
					for (__, path) in
					zip(range(M), nx.shortest_simple_paths(graph, source=trip.u, target=trip.v))
				]})
				trajectories['dist'] = [sum(edges_len[e] for e in pairwise(path)) for path in trajectories.path]
				trajectories = trajectories.sort_values(by='dist', ascending=False)

			marker = dict(markersize=2, markeredgewidth=0.2, markerfacecolor="None")
			ax1.plot(trip['pickup_longitude'], trip['pickup_latitude'], 'og', **marker)
			ax1.plot(trip['dropoff_longitude'], trip['dropoff_latitude'], 'xr', **marker)

			cmap = LinearSegmentedColormap.from_list(name="noname", colors=["g", "orange", "r", "brown"])
			colors = cmap(pd.Series(trajectories['dist'] / trip['distance']).rank(pct=True))

			for (c, path) in zip(colors, trajectories.path):
				(y, x) = nodes.loc[list(path)].values.T
				ax1.plot(x, y, c=c, alpha=0.5, lw=0.3)

			# Save to file
			fn = os.path.join(PARAM['out_images_path'], F"{myname()}/{table_name}.png")
			ax1.figure.savefig(makedirs(fn))
Example #4
0
def trip_trajectories_ingraph(table_name):
	mpl.use("Agg")

	# Max number of trajectories to plot
	N = 1000

	graph = get_road_graph()
	nodes = pd.DataFrame(data=nx.get_node_attributes(graph, "loc"), index=["lat", "lon"]).T

	trips = get_trip_data(table_name, graph)

	trips = trips.sample(min(N, len(trips)))
	logger.debug(F"{len(trips)} trips")

	logger.debug("Computing trajectories")
	trajectories = parallel_map(GraphPathDist(graph).path_only, zip(trips.u, trips.v))

	with Section("Getting the background OSM map", out=logger.debug):
		extent = maps.ax4(nodes.lat, nodes.lon)
		osmap = maps.get_map_by_bbox(maps.ax2mb(*extent))

	with plt.style.context({**PARAM['mpl_style'], 'font.size': 5}):
		with Axes() as ax1:
			# The background map
			ax1.imshow(osmap, extent=extent, interpolation='quadric', zorder=-100)

			ax1.axis("off")

			ax1.set_xlim(extent[0:2])
			ax1.set_ylim(extent[2:4])

			c = 'b'
			if ("green" in table_name): c = "green"
			if ("yello" in table_name): c = "orange"

			logger.debug("Plotting trajectories")
			for traj in trajectories:
				(y, x) = nodes.loc[list(traj)].values.T
				ax1.plot(x, y, c=c, alpha=0.1, lw=0.3)

			# Save to file
			fn = os.path.join(PARAM['out_images_path'], F"{myname()}/{table_name}.png")
			ax1.figure.savefig(makedirs(fn))

			# Meta info
			json.dump({'number_of_trajectories': len(trips)}, open((fn + ".txt"), 'w'))
Example #5
0
def trip_trajectories_initial(table_name):
    mpl.use("Agg")

    N = 10000

    cols = [
        "pickup_latitude", "pickup_longitude", "dropoff_latitude",
        "dropoff_longitude"
    ]

    sql = F"SELECT {(', '.join(cols))} FROM [{table_name}] ORDER BY RANDOM() LIMIT {N}"
    df = query(sql)
    df = df.rename(columns=dict(zip(cols, ["lat0", "lon0", "lat1", "lon1"])))

    fig: plt.Figure
    ax1: plt.Axes
    (fig, ax1) = plt.subplots()
    ax1.tick_params(axis='both', which='both', labelsize='3')

    c = "b"
    if ("green" in table_name): c = "green"
    if ("yellow" in table_name): c = "orange"

    for (yy, xx) in zip(df[['lat0', 'lat1']].values, df[['lon0',
                                                         'lon1']].values):
        ax1.plot(xx, yy, c=c, ls='-', alpha=0.1, lw=0.1)

    ax1.axis("off")

    # Get the background map
    axis = ax1.axis()
    img_map = maps.get_map_by_bbox(maps.ax2mb(*axis))

    ax1.imshow(img_map, extent=axis, interpolation='quadric', zorder=-100)

    fn = os.path.join(PARAM['out_images_path'], F"{myname()}/{table_name}.png")
    fig.savefig(makedirs(fn), **PARAM['savefig_args'])
Example #6
0
def trip_trajectories_velocity(table_name):
	mpl.use("Agg")

	# Max number of trajectories to use
	N = 10000

	graph = get_road_graph()
	nodes = pd.DataFrame(data=nx.get_node_attributes(graph, "loc"), index=["lat", "lon"]).T

	edge_name = pd.Series(nx.get_edge_attributes(graph, name="name"))

	where = "('2016-05-02 08:00' <= pickup_datetime) and (pickup_datetime <= '2016-05-02 09:00')"
	trips = get_trip_data(table_name, graph, order="", limit=N, where=where)

	trips['velocity'] = trips['distance'] / trips['duration/s']
	trips = trips.sort_values(by='velocity', ascending=True)

	logger.debug(F"{len(trips)} trips")

	with Section("Computing estimated trajectories", out=logger.debug):
		trips['traj'] = parallel_map(GraphPathDist(graph).path_only, zip(trips.u, trips.v))

	with Section("Getting the background OSM map", out=logger.debug):
		extent = maps.ax4(nodes.lat, nodes.lon)
		osmap = maps.get_map_by_bbox(maps.ax2mb(*extent))

	with Section("Computing edge velocities", out=logger.debug):
		edge_vel = defaultdict(list)
		for (traj, v) in zip(trips.traj, trips.velocity):
			for e in pairwise(traj):
				edge_vel[e].append(v)
		edge_vel = pd.Series({e: np.mean(v or np.nan) for (e, v) in edge_vel.items()}, index=graph.edges)
		edge_vel = edge_vel.dropna()

	with plt.style.context({**PARAM['mpl_style'], 'font.size': 5}), Axes() as ax1:
		# The background map
		ax1.imshow(osmap, extent=extent, interpolation='quadric', zorder=-100)

		ax1.axis("off")

		ax1.set_xlim(extent[0:2])
		ax1.set_ylim(extent[2:4])

		cmap_velocity = LinearSegmentedColormap.from_list(name="noname", colors=["brown", "r", "orange", "g"])

		# marker = dict(markersize=0.5, markeredgewidth=0.1, markerfacecolor="None")
		# ax1.plot(trips['pickup_longitude'], trips['pickup_latitude'], 'og', **marker)
		# ax1.plot(trips['dropoff_longitude'], trips['dropoff_latitude'], 'xr', **marker)

		# for e in edge_name[edge_name == "65th Street Transverse"].index:
		# 	print(e, edge_vel[e])

		edge_vel: pd.Series
		# edge_vel = edge_vel.rank(pct=True)
		edge_vel = edge_vel.clip(lower=2, upper=6).round()
		edge_vel = (edge_vel - edge_vel.min()) / (edge_vel.max() - edge_vel.min())
		edge_vel = edge_vel.apply(cmap_velocity)

		nx.draw_networkx_edges(
			graph.edge_subgraph(edge_vel.index),
			ax=ax1,
			pos=nx.get_node_attributes(graph, name="pos"),
			edge_list=list(edge_vel.index),
			edge_color=list(edge_vel),
			# edge_cmap=cmap_velocity,
			# vmin=0, vmax=1,
			with_labels=False, arrows=False, node_size=0, alpha=0.8, width=0.3,
		)

		# Save to file
		fn = os.path.join(PARAM['out_images_path'], F"{myname()}/{table_name}.png")
		ax1.figure.savefig(makedirs(fn))

		# Meta info
		json.dump({'number_of_trajectories': len(trips)}, open((fn + ".txt"), 'w'))
Example #7
0
    def mm_callback(result):

        if (result['status'] == "zero"):
            print("(Preparing)")

        if (result['status'] == "init"):
            print("(Optimizing)")

        if (result['status'] == "opti"):
            if (dt.datetime.now() < result.get('nfu', dt.datetime.min)):
                return

        if (result['status'] == "done"):
            print("(Done)")

        if (result['status'] == "zero"):
            fig: plt.Figure
            ax: plt.Axes
            (fig, ax) = plt.subplots()

            for (n, (y, x)) in enumerate(result['waypoints']):
                ax.plot(x, y, 'bo')

            ax.axis(commons.niceaxis(ax.axis(), expand=1.1))
            ax.autoscale(enable=False)

            # Download the background map
            try:
                mapi = maps.get_map_by_bbox(maps.ax2mb(*ax.axis()),
                                            token=mapbox_token)
            except maps.URLError:
                commons.logger.warning("No map (no connection?)")
                mapi = None

            result['plt'] = {
                'fig': fig,
                'ax': ax,
                'map': mapi,
                'bbox': ax.axis()
            }

        if (result['status'] in ["zero", "opti", "done"]):

            fig: plt.Figure
            ax: plt.Axes
            (fig, ax, mapi,
             bbox) = commons.inspect({'plt':
                                      ('fig', 'ax', 'map', 'bbox')})(result)

            # Clear the axes
            ax.cla()

            # Apply the background map
            if mapi:
                img = ax.imshow(mapi,
                                extent=bbox,
                                interpolation='none',
                                zorder=-100)

            for (n, (y, x)) in enumerate(result['waypoints']):
                ax.plot(x, y, 'o', c='m', markersize=4)

            if ('geo_path' in result):
                (y, x) = zip(*result['geo_path'])
                ax.plot(x, y, 'b--', linewidth=2, zorder=-50)

            if ('(edge_clouds)' in result):
                for (nc, pc) in enumerate(result['(edge_clouds)']):
                    for (e, p) in pc.items():
                        (y, x) = zip(*[g.nodes[i]['pos'] for i in e])
                        c = ('g' if
                             (result['(active_edges)'][nc] == e) else 'r')
                        ax.plot(x,
                                y,
                                '-',
                                c=c,
                                linewidth=2,
                                alpha=(p / max(pc.values())),
                                zorder=150)

            ax.axis(bbox)

            plt.pause(0.1)

        if (result['status'] == "opti"):
            # Next figure update
            result['nfu'] = dt.datetime.now() + dt.timedelta(seconds=4)

        if (result['status'] == "done"):
            open("graph_mm_callback.gpx", 'w').write(
                simple_gpx(result['waypoints'], [result['geo_path']]).to_xml())
Example #8
0
def vis1() :

	# OSM = pickle.load(open(IFILE['OSM'], 'rb'))
	# for (route_id, route) in OSM['rels']['route'].items():
	# 	# Skip non-bus routes
	# 	if not (route['t'].get('route') == 'bus'): continue
	#
	# 	route_name = route['t'].get('name')
	#
	# 	route_ref = route['t']['ref']
	# 	#if (route_ref == '88') :
	# 	print(route_name, route_id, route['t'])
	# exit(39)

	routeid_of = (lambda r: r['SubRouteUID'])

	# List of filenames, one file per physical bus, identified by plate number
	bus_files = commons.ls(IFILE['busses'].format(busid="*"))

	# Refile bus runs by their route ID
	runs_by_route = defaultdict(list)
	for fn in bus_files :
		runs = commons.zipjson_load(fn)
		for run in runs :
			runs_by_route[routeid_of(run)].append(run)

	#
	route_stops = commons.index_dicts_by_key(commons.zipjson_load(IFILE['route-stops']), routeid_of)

	# Are those valid route ID that can be found among the routes?
	unknown_route_ids = sorted(set(runs_by_route.keys()) - set(route_stops.keys()))

	if unknown_route_ids :
		print("The following route IDs from bus records are unknown:")
		print(", ".join(unknown_route_ids))
		raise KeyError("Unkown route IDs in bus records")

	#

	route_uid = 'KHH24'

	runs = runs_by_route[route_uid]
	route = route_stops[route_uid]

	# Kaohsiung (left, bottom, right, top)
	bbox = (120.2593, 22.5828, 120.3935, 22.6886)
	(left, bottom, right, top) = bbox

	# Download the background map
	i = maps.get_map_by_bbox(bbox, token=PARAM['mapbox_api_token'])

	# Show the background map
	(fig, ax) = plt.subplots()
	plt.ion()
	ax.axis([left, right, bottom, top])
	ax.imshow(i, extent=(left, right, bottom, top), interpolation='quadric')

	#fig.canvas.draw_idle()

	plt.pause(0.1)


	stops_by_direction = dict(zip(route['Direction'], route['Stops']))

	# Draw stops for both route directions
	for (dir, stops) in stops_by_direction.items() :

		# Stop locations
		(y, x) = zip(*[
			commons.inspect({'StopPosition': ('PositionLat', 'PositionLon')})(stop)
			for stop in stops
		])

		# Plot as dots
		ax.scatter(x, y, c=('b' if dir else 'g'), marker='o', s=4)


	# Show bus location

	for run in runs :

		# Trace bus
		(y, x) = (run['PositionLat'], run['PositionLon'])
		h1 = ax.plot(x, y, '--+', c='r', linewidth=1)
		h2 = ax.plot(x[0], y[0], 'o', c='r')
		h3 = ax.plot(x[-1], y[-1], 's', c='r')

		plt.title(run['PlateNumb'])

		#plt.savefig("{}.png".format(route_uid), dpi=180)
		plt.pause(0.1)

		bus_at_stops(run, stops_by_direction[run['Direction']])

		plt.pause(0.1)
		[h[0].remove() for h in [h1, h2, h3]]

	return
Example #9
0
def nx_draw(graph, nodes=None, edges=None):
    # mpl.use("Agg")

    logger.debug("Preparing to draw graph")

    if nodes is None:
        nodes = pd.DataFrame(data=nx.get_node_attributes(graph, name="loc"),
                             index=["lat", "lon"]).T

    if edges is None:
        edges = pd.DataFrame([
            pd.Series(data=nx.get_edge_attributes(graph, name="met"),
                      name="met"),
            pd.Series(data=nx.get_edge_attributes(graph, name="len"),
                      name="len"),
        ]).T
        edges['color'] = (edges.met / edges.len)

    cmap = LinearSegmentedColormap.from_list(name="noname",
                                             colors=["g", "y", "r", "brown"])

    # Axes window
    # extent = np.dot(
    # 	[[min(nodes.lon), max(nodes.lon)], [min(nodes.lat), max(nodes.lat)]],
    # 	(lambda s: np.asarray([[1 + s, -s], [-s, 1 + s]]))(0.01)
    # ).flatten()
    # Manhattan south
    extent = [-74, -73.96, 40.73, 40.78]

    logger.debug("Getting the background OSM map")
    osmap = maps.get_map_by_bbox(maps.ax2mb(*extent),
                                 style=maps.MapBoxStyle.streets)

    fig: plt.Figure
    ax1: plt.Axes
    (fig, ax1) = plt.subplots()

    logger.debug("Drawing image")

    # The background map
    ax1.imshow(osmap, extent=extent, interpolation='quadric', zorder=-100)

    ax1.axis("off")

    edges.color[edges.color > 1.5] = 1.5
    edges.color[edges.color < 0.9] = 0.9

    g = nx.draw_networkx_edges(graph,
                               ax=ax1,
                               pos=nx.get_node_attributes(graph, name="pos"),
                               edge_list=list(edges.index),
                               edge_color=list(edges.color),
                               edge_cmap=cmap,
                               with_labels=False,
                               arrows=False,
                               node_size=0,
                               alpha=1,
                               width=2)

    # https://stackoverflow.com/questions/26739248/how-to-add-a-simple-colorbar-to-a-network-graph-plot-in-python
    (vmin, vmax) = (np.min(edges.color), np.max(edges.color))
    sm = plt.cm.ScalarMappable(cmap=cmap,
                               norm=plt.Normalize(vmin=vmin, vmax=vmax))
    sm._A = []
    plt.colorbar(sm)

    # https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_directed.html
    # pc = mpl.collections.PatchCollection(g, cmap=cmap)
    # pc.set_array(list(edges.color))
    # plt.colorbar(pc)

    # cax = fig.add_axes([ax1.get_position().x1 + 0.01, ax1.get_position().y0, 0.02, ax1.get_position().height])
    # cbar = fig.colorbar(im, cax=cax)

    ax1.set_xlim(extent[0:2])
    ax1.set_ylim(extent[2:4])

    logger.debug("OK")

    try:
        yield (fig, ax1)
    finally:
        plt.close(fig)
Example #10
0
def make_transit_img(J, backend='Agg') -> bytes:
    import matplotlib as mpl
    mpl.use(backend)
    mpl.rcParams['figure.max_open_warning'] = 10

    import dateutil.parser as dateparser
    import matplotlib.pyplot as plt

    ax: plt.Axes
    (fig, ax) = plt.subplots()

    origin = {
        'x': J['origin']['x'],
        't': dateparser.parse(J['origin']['t']),
        'desc': J['origin'].get('desc'),
    }

    # Location --> Transit time in minutes ; keep track of duplicates
    gohere = commons.index_dicts_by_key(J['gohere'],
                                        key_func=(lambda __: tuple(__['x'])),
                                        collapse_repetitive=False)
    # Keep only the minimal reach time, convert to minutes
    gohere = {x: (min(attr['s']) / 60) for (x, attr) in gohere.items()}

    # # Cut-off (and normalize)
    T = 60  # Minutes
    # gohere = { p : s for (p, s) in gohere.items() if (s <= T) }

    # Reindex datapoints by (x, y) pairs
    contour_pts = dict(zip(map(ll2xy, gohere.keys()), gohere.values()))

    #boxes = dict(boxify(gohere, maxinbox=10))

    # "Inner" Kaohsiung
    bbox = (120.2593, 22.5828, 120.3935, 22.6886)

    # Set plot view to the bbox
    ax.axis(maps.mb2ax(*bbox))
    ax.autoscale(enable=False)

    try:
        background_map = maps.get_map_by_bbox(bbox, **PARAM['mapbox'])
        ax.imshow(background_map,
                  interpolation='quadric',
                  extent=maps.mb2ax(*bbox),
                  zorder=-100)
    except Exception as e:
        commons.logger.warning("No background map ({})".format(e))

    # Cross at the origin
    ((ox, oy), ot) = (ll2xy(origin['x']),
                      origin['t'].strftime("%Y-%m-%d / %H:%M"))
    ax.plot(ox, oy, 'gx', ms=3, mew=0.2)
    ax.text(ox, oy, s="\n{}".format(ot), ha='center', va='top', fontsize=2)

    # Show all datapoints
    #ax.scatter(*zip(*contour_pts), marker='o', c='k', s=0.1, lw=0, edgecolor='none')

    # # Hack! for corners
    # for (x, y) in product(ax.axis()[:2], ax.axis()[2:]) :
    # 	contour_pts[(x, y)] = max(gohere.values())

    cmap = plt.get_cmap('Purples')
    cmap.set_over('k')

    # https://stackoverflow.com/questions/37327308/add-alpha-to-an-existing-matplotlib-colormap
    from matplotlib.colors import ListedColormap
    cmap = ListedColormap(
        np.vstack(
            [cmap(np.arange(cmap.N))[:, 0:3].T,
             np.linspace(0, 0.5, cmap.N)]).T)

    (x, y) = zip(*contour_pts)
    levels = list(range(0, T, 5))
    c = ax.tricontourf(x,
                       y,
                       list(contour_pts.values()),
                       levels=levels,
                       zorder=100,
                       cmap=cmap,
                       extent=maps.mb2ax(*bbox),
                       extend='max')

    # Reduce fontsize
    ax.tick_params(axis='both', which='both', labelsize='xx-small')
    fig.colorbar(c).ax.tick_params(axis='both',
                                   which='both',
                                   labelsize='xx-small')

    # import matplotlib.patches as patches
    # boxes = dict(boxify(contour_pts, maxinbox=20))
    # for (bb, gohere_part) in list(boxes.items()) :
    # 	(y, x) = bb[0:2]
    # 	(h, w) = (bb[2]-bb[0], bb[3]-bb[1])
    # 	ax.add_patch(patches.Rectangle((x, y), w, h, linewidth=0.5, edgecolor='k', facecolor='none'))
    # # 	for (latlon, s) in list(gohere_part.items()) :
    # # 		ax.plot(*ll2xy(latlon), 'o', c=plt.get_cmap('Purples')(s), markersize=3)

    buffer = io.BytesIO()
    fig.savefig(buffer, bbox_inches='tight', pad_inches=0, dpi=300)

    buffer.seek(0)

    if backend.lower() in ["tkagg"]:
        plt.ion()
        plt.show()
        plt.pause(0.1)

    plt.close(fig)

    return buffer.read()