From 88f5809da0c409f73f3332d78be4a8b2d7132709 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 7 Jun 2021 14:28:33 +0800 Subject: [PATCH 01/47] move plot config to a separate json file --- pathpy/visualisations/config.json | 129 ++++++++++++++++++++++++ pathpy/visualisations/plot.py | 157 ++---------------------------- 2 files changed, 139 insertions(+), 147 deletions(-) create mode 100644 pathpy/visualisations/config.json diff --git a/pathpy/visualisations/config.json b/pathpy/visualisations/config.json new file mode 100644 index 0000000..f0c149b --- /dev/null +++ b/pathpy/visualisations/config.json @@ -0,0 +1,129 @@ +{ + "width": 800, + "height": 550, + "unit": "px", + "dpi": 96, + "margin": null, + "layout": "force", + "temporal": false, + "coordinates": false, + "euclidean": false, + "min_max_node_size": null, + "min_max_edge_size": null, + "keep_aspect_ratio": true, + "forceCharge": -20, + "forceRepel": -300, + "forceAlpha": 0.1, + "restartAlpha": 1, + "alphaMin": 0.001, + "targetAlpha": 0, + "chargeDistance": 800, + "repelDistance": 200, + "velocityDecay": 0.4, + "lookoutStrokeWidth": 1, + "lookoutOpacity": 0.5, + "lookoutWeight": 0.0, + "radiusMinSize": 4, + "radiusMaxSize": 16, + "nodeTransitionDuration": 100, + "defaultEdgeWeight": 1, + "targetAlphaDragStarted": 0.3, + "targetAlphaDragEnd": 0.0, + "linkStrengthMin": 0.0, + "linkStrengthMax": 0.45, + "template": null, + "css": null, + "backend": [ + "tikz" + ], + "fileformat": [ + "tex" + ], + "latex_class_options": "", + "interactiv": { + "backend": [ + "d3js" + ], + "fileformat": [ + "html" + ] + }, + "animation": { + "enabled": false, + "start": null, + "end": null, + "steps": 20, + "speed": 100, + "unit": "seconds" + }, + "label": { + "centered": true, + "enabled": true, + "color": "white" + }, + "label_centered": true, + "label_enabled": true, + "label_color": "white", + "node": { + "size": 15, + "color": "CornflowerBlue", + "opacity": 0.2, + "id_as_label": true + }, + "curved": false, + "directed": false, + "edge": { + "size": 2, + "color": "black", + "opacity": 1, + "directed": false, + "curved": 0.5 + }, + "widgets": { + "tooltip": { + "enabled": false, + "size": "100px" + }, + "save": { + "title": "Save", + "enabled": true, + "tooltip": "Save the network as [svg] or [png]." + }, + "zoom": { + "title": "Zoom", + "enabled": true, + "tooltip": "Zoom-in with [+]
zoom-out with [-]
or reset zoom with [Reset].
Furthermore, with [Shift+mouse wheel] you can also zoom." + }, + "filter": { + "title": "Filter", + "enabled": false, + "tooltip": "Filter the nodes base on given groups.", + "groups": [ + "all" + ] + }, + "search": { + "title": "Search", + "enabled": true, + "tooltip": "Search for a node in the network." + }, + "layout": { + "title": "Layout", + "enabled": false, + "tooltip": "Change the layout of the Network. Per default a [Force] directed layout is used. If x and y coordinates are given, an [Coord] layout can be used." + }, + "animation": { + "title": "Animation", + "enabled": true, + "tooltip": "Play and pause animation of the temproal network." + }, + "aggregation": { + "title": "Aggregation", + "enabled": true, + "tooltip": "Aggregate time steps.", + "past": 2, + "future": 2, + "aggregation": 1 + } + } +} \ No newline at end of file diff --git a/pathpy/visualisations/plot.py b/pathpy/visualisations/plot.py index ddb9844..a71acb6 100755 --- a/pathpy/visualisations/plot.py +++ b/pathpy/visualisations/plot.py @@ -4,11 +4,12 @@ # ============================================================================= # File : plot.py -- Module to plot pathoy networks # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2019 Pathpy Developers # ============================================================================= from __future__ import annotations +import os from typing import Any, List, Optional, Union, Dict from collections import defaultdict from copy import deepcopy @@ -16,6 +17,7 @@ from datetime import datetime import numpy as np import pandas as pd +import json from pathpy import logger, config from pathpy.visualisations.utils import UnitConverter @@ -35,152 +37,6 @@ LOG = logger(__name__) TIMESTAMP = config['temporal']['timestamp'] -# config: defaultdict = defaultdict(dict) -# config['environment']['interactive'] = False - -# General config -config['plot']['width'] = 800 -config['plot']['height'] = 550 -config['plot']['unit'] = 'px' -config['plot']['dpi'] = 96 -config['plot']['margin'] = None -config['plot']['layout'] = 'force' -config['plot']['temporal'] = False -config['plot']['coordinates'] = False -config['plot']['euclidean'] = False -config['plot']['min_max_node_size'] = None -config['plot']['min_max_edge_size'] = None -config['plot']['keep_aspect_ratio'] = True - -config['plot']['forceCharge'] = -20 # -30 -config['plot']['forceRepel'] = -300 # -100 -config['plot']['forceAlpha'] = 0.1 -config['plot']['restartAlpha'] = 1 -config['plot']['alphaMin'] = 0.001 # 0.1 -config['plot']['targetAlpha'] = 0 # 0.2 -config['plot']['chargeDistance'] = config['plot']['width'] -config['plot']['repelDistance'] = 200 -config['plot']['velocityDecay'] = 0.4 # .2 -config['plot']['lookoutStrokeWidth'] = 1 -config['plot']['lookoutOpacity'] = .5 -config['plot']['lookoutWeight'] = 0. -config['plot']['radiusMinSize'] = 4 -config['plot']['radiusMaxSize'] = 16 -config['plot']['nodeTransitionDuration'] = 100 -config['plot']['nodeTransitionDuration'] = 100 -config['plot']['defaultEdgeWeight'] = 1 - -config['plot']['targetAlphaDragStarted'] = 0.3 -config['plot']['targetAlphaDragEnd'] = 0.0 - - -config['plot']['linkStrengthMin'] = 0.0 -config['plot']['linkStrengthMax'] = .45 - - -config['plot']['template'] = None -config['plot']['css'] = None - -config['plot']['backend'] = ['tikz'] -config['plot']['fileformat'] = ['tex'] -config['plot']['latex_class_options'] = '' - -config['plot']['interactiv'] = {} -config['plot']['interactiv']['backend'] = ['d3js'] -config['plot']['interactiv']['fileformat'] = ['html'] - -# Animation config -config['plot']['animation'] = {} -config['plot']['animation']["enabled"] = False -config['plot']['animation']["start"] = None -config['plot']['animation']["end"] = None -config['plot']['animation']["steps"] = 20 -config['plot']['animation']["speed"] = 100 -config['plot']['animation']["unit"] = "seconds" - -# Label config -config['plot']['label'] = {} -config['plot']['label']['centered'] = True -config['plot']['label']['enabled'] = True -config['plot']['label']['color'] = 'white' - -config['plot']['label_centered'] = True -config['plot']['label_enabled'] = True -config['plot']['label_color'] = 'white' - - -# Node config -config['plot']['node'] = {} -config['plot']['node']['size'] = 15 -config['plot']['node']['color'] = 'CornflowerBlue' -config['plot']['node']['opacity'] = .2 -config['plot']['node']['id_as_label'] = True - -config['plot']['curved'] = False -config['plot']['directed'] = False - -# Edge config -config['plot']['edge'] = {} -config['plot']['edge']['size'] = 2 -config['plot']['edge']['color'] = 'black' -config['plot']['edge']['opacity'] = 1 -config['plot']['edge']['directed'] = False -config['plot']['edge']['curved'] = .5 - -# Widges config -config['plot']['widgets'] = {} - -# tooltip -config['plot']['widgets']['tooltip'] = {} -config['plot']['widgets']['tooltip']['enabled'] = False -config['plot']['widgets']['tooltip']['size'] = '100px' - -# save -config['plot']['widgets']['save'] = {} -config['plot']['widgets']['save']['title'] = 'Save' -config['plot']['widgets']['save']['enabled'] = True -config['plot']['widgets']['save']['tooltip'] = "Save the network as [svg] or [png]." - -# zoom -config['plot']['widgets']['zoom'] = {} -config['plot']['widgets']['zoom']['title'] = 'Zoom' -config['plot']['widgets']['zoom']['enabled'] = True -config['plot']['widgets']['zoom']['tooltip'] = "Zoom-in with [+]
zoom-out with [-]
or reset zoom with [Reset].
Furthermore, with [Shift+mouse wheel] you can also zoom." - -# filter -config['plot']['widgets']['filter'] = {} -config['plot']['widgets']['filter']['title'] = 'Filter' -config['plot']['widgets']['filter']['enabled'] = False -config['plot']['widgets']['filter']['tooltip'] = "Filter the nodes base on given groups." -config['plot']['widgets']['filter']['groups'] = ["all"] - -# search -config['plot']['widgets']['search'] = {} -config['plot']['widgets']['search']['title'] = 'Search' -config['plot']['widgets']['search']['enabled'] = True -config['plot']['widgets']['search']['tooltip'] = "Search for a node in the network." - -# layout -config['plot']['widgets']['layout'] = {} -config['plot']['widgets']['layout']['title'] = 'Layout' -config['plot']['widgets']['layout']['enabled'] = False -config['plot']['widgets']['layout']['tooltip'] = "Change the layout of the Network. Per default a [Force] directed layout is used. If x and y coordinates are given, an [Coord] layout can be used." - -# animation -config['plot']['widgets']['animation'] = {} -config['plot']['widgets']['animation']['title'] = 'Animation' -config['plot']['widgets']['animation']['enabled'] = True -config['plot']['widgets']['animation']['tooltip'] = "Play and pause animation of the temproal network." - -# aggregation -config['plot']['widgets']['aggregation'] = {} -config['plot']['widgets']['aggregation']['title'] = 'Aggregation' -config['plot']['widgets']['aggregation']['enabled'] = True -config['plot']['widgets']['aggregation']['tooltip'] = "Aggregate time steps." -config['plot']['widgets']['aggregation']['past'] = 2 -config['plot']['widgets']['aggregation']['future'] = 2 -config['plot']['widgets']['aggregation']['aggregation'] = 1 - def plot(obj, filename: Optional[str] = None, backend: Optional[str] = None, **kwargs) -> None: @@ -188,6 +44,13 @@ def plot(obj, filename: Optional[str] = None, # initialize variables figure: Any + # template directory + plot_config = str(os.path.join( + os.path.dirname(os.path.dirname(__file__)), 'visualisations/config.json')) + + with open(plot_config, 'r') as config_file: + config['plot'] = json.load(config_file) + # supported backends backends: Dict[str, object] = { 'd3js': D3js, From 39935b68d8f5da4c597714b4f61cffb25221f810 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 7 Jun 2021 14:32:17 +0800 Subject: [PATCH 02/47] clean up imports --- pathpy/visualisations/plot.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/pathpy/visualisations/plot.py b/pathpy/visualisations/plot.py index a71acb6..5988f57 100755 --- a/pathpy/visualisations/plot.py +++ b/pathpy/visualisations/plot.py @@ -4,7 +4,7 @@ # ============================================================================= # File : plot.py -- Module to plot pathoy networks # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2019 Pathpy Developers # ============================================================================= @@ -13,24 +13,15 @@ from typing import Any, List, Optional, Union, Dict from collections import defaultdict from copy import deepcopy -from singledispatchmethod import singledispatchmethod # remove for python 3.8 +import json from datetime import datetime +from singledispatchmethod import singledispatchmethod # remove for python 3.8 import numpy as np -import pandas as pd -import json from pathpy import logger, config from pathpy.visualisations.utils import UnitConverter - -from pathpy.visualisations.backends import (D3js, - Tikz, - Matplotlib) - -from pathpy.visualisations.fileformats import (HTML, - TEX, - PDF, - PNG) - +from pathpy.visualisations.backends import (D3js, Tikz, Matplotlib) +from pathpy.visualisations.fileformats import (HTML, TEX, PDF, PNG) from pathpy.models.classes import (BaseNetwork, BaseTemporalNetwork) # create logger for the Plot class From 58eb578f2ec239fd1654283f4a63d483fcdb8d04 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 7 Jun 2021 17:18:57 +0800 Subject: [PATCH 03/47] start working on the new plot functions --- pathpy/visualisations/network.py | 43 +++++++++++++++ pathpy/visualisations/new_plot.py | 88 +++++++++++++++++++++++++++++++ pathpy/visualisations/plot.py | 4 +- 3 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 pathpy/visualisations/network.py create mode 100644 pathpy/visualisations/new_plot.py diff --git a/pathpy/visualisations/network.py b/pathpy/visualisations/network.py new file mode 100644 index 0000000..86570f6 --- /dev/null +++ b/pathpy/visualisations/network.py @@ -0,0 +1,43 @@ +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# ============================================================================= +# File : network.py -- network plot of pathpy +# Author : Jürgen Hackl +# Time-stamp: +# +# Copyright (c) 2016-2021 Pathpy Developers +# ============================================================================= + +class PlanePlot: + """Class with an empty Plot""" + + def __init__(self, data, **kwargs): + self.data = data + + @property + def _kind(self): + """Specify kind str. Must be overridden in child class""" + raise NotImplementedError + + def _make_plot(self): + raise NotImplementedError + + +class NetworkPlot(PlanePlot): + """Plot of a static network""" + _kind = "network" + + def __init__(self, obj, **kwargs): + self.data = 0 + + def _make_plot(self): + pass +# ============================================================================= +# eof +# +# Local Variables: +# mode: python +# mode: linum +# mode: auto-fill +# fill-column: 79 +# End: diff --git a/pathpy/visualisations/new_plot.py b/pathpy/visualisations/new_plot.py new file mode 100644 index 0000000..d652b6c --- /dev/null +++ b/pathpy/visualisations/new_plot.py @@ -0,0 +1,88 @@ +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# ============================================================================= +# File : plot.py -- Plotting function for pathpy objects +# Author : Jürgen Hackl +# Time-stamp: +# +# Copyright (c) 2016-2021 Pathpy Developers +# ============================================================================= +import os +import importlib +from typing import Any, Optional, Dict +from pathpy import logger + +# create logger for the Plot class +LOG = logger(__name__) + + +# supported backends +_backends: set = {'d3js', 'tikz', 'matplotlib'} + +# supported file formats +_formats: Dict = {'.html': 'd3js', '.tex': 'tikz', + '.pdf': 'tikz', '.png': 'matplotlib'} + + +def plot(obj, filename, backend, **kwargs): + pass + + +# def plot(data, kind, **kwargs): +# # Importing pyplot at the top of the file (before the converters are +# # registered) causes problems in matplotlib 2 (converters seem to not +# # work) +# import matplotlib.pyplot as plt + +# if kwargs.pop("reuse_plot", False): +# ax = kwargs.get("ax") +# if ax is None and len(plt.get_fignums()) > 0: +# with plt.rc_context(): +# ax = plt.gca() +# kwargs["ax"] = getattr(ax, "left_ax", ax) +# plot_obj = PLOT_CLASSES[kind](data, **kwargs) +# plot_obj.generate() +# plot_obj.draw() +# return plot_obj.result + + +def network_plot(obj, filename: str, backend, **kwargs: Any): + """Plot a static network""" + plot_backend = _get_plot_backend(backend, filename) + plot_config = {filename} + plot_data = {obj} + return plot_backend.network_plot(plot_data, plot_config, **kwargs) + + +def _get_plot_backend(backend: Optional[str] = None, filename: str = None): + """ Return the plotting backend to use. """ + + if isinstance(filename, str): + _backend = _formats.get(os.path.splitext(filename)[1], '3djs') + + # if no backend was found use the backend suggested for the file format + if backend is not None and backend not in _backends and filename is not None: + LOG.warning('The backend %s was not found.', backend) + + # if no backend was given use the backend suggested for the file format + elif backend in _backends: + _backend = backend + + try: + module = importlib.import_module('pathpy.visualisations._%s' % _backend) + except ImportError: + LOG.error('The %s backend could not be imported.', _backend) + raise ImportError from None + + return module + + +# ============================================================================= +# eof +# +# Local Variables: +# mode: python +# mode: linum +# mode: auto-fill +# fill-column: 79 +# End: diff --git a/pathpy/visualisations/plot.py b/pathpy/visualisations/plot.py index 5988f57..3628224 100755 --- a/pathpy/visualisations/plot.py +++ b/pathpy/visualisations/plot.py @@ -4,7 +4,7 @@ # ============================================================================= # File : plot.py -- Module to plot pathoy networks # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2019 Pathpy Developers # ============================================================================= @@ -53,9 +53,9 @@ def plot(obj, filename: Optional[str] = None, figures: Dict[str, Dict[str, object]] = { 'html': {'fileformat': HTML, 'backend': D3js}, 'tex': {'fileformat': TEX, 'backend': Tikz}, - # 'csv': {'fileformat': CSV, 'backend': Tikz}, 'pdf': {'fileformat': PDF, 'backend': Tikz}, 'png': {'fileformat': PNG, 'backend': Matplotlib}, + # 'csv': {'fileformat': CSV, 'backend': Tikz}, } # initialize object parser From bf070f9d84cbe2ec1c46f53d63d6701b938107c3 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 10 Jun 2021 12:29:36 +0800 Subject: [PATCH 04/47] update time-stamp --- pathpy/visualisations/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pathpy/visualisations/plot.py b/pathpy/visualisations/plot.py index 3628224..7ca3855 100755 --- a/pathpy/visualisations/plot.py +++ b/pathpy/visualisations/plot.py @@ -4,7 +4,7 @@ # ============================================================================= # File : plot.py -- Module to plot pathoy networks # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2019 Pathpy Developers # ============================================================================= From 35ba6ecf6f8d010bb49e9687094a0c1c63b95934 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 10 Jun 2021 16:44:38 +0800 Subject: [PATCH 05/47] set up new plotting structure with submodules --- pathpy/tests/test_visualisations.py | 49 +++++++++++++++++++ pathpy/visualisations/_d3js/__init__.py | 25 ++++++++++ pathpy/visualisations/_d3js/network_plots.py | 27 ++++++++++ pathpy/visualisations/_matplotlib/__init__.py | 25 ++++++++++ .../_matplotlib/network_plots.py | 27 ++++++++++ pathpy/visualisations/_tikz/__init__.py | 25 ++++++++++ pathpy/visualisations/_tikz/network_plots.py | 27 ++++++++++ pathpy/visualisations/new_plot.py | 13 +++-- 8 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 pathpy/tests/test_visualisations.py create mode 100644 pathpy/visualisations/_d3js/__init__.py create mode 100644 pathpy/visualisations/_d3js/network_plots.py create mode 100644 pathpy/visualisations/_matplotlib/__init__.py create mode 100644 pathpy/visualisations/_matplotlib/network_plots.py create mode 100644 pathpy/visualisations/_tikz/__init__.py create mode 100644 pathpy/visualisations/_tikz/network_plots.py diff --git a/pathpy/tests/test_visualisations.py b/pathpy/tests/test_visualisations.py new file mode 100644 index 0000000..f36e7c8 --- /dev/null +++ b/pathpy/tests/test_visualisations.py @@ -0,0 +1,49 @@ +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# ============================================================================= +# File : test_visualisations.py -- Test environment for the plotting +# Author : Jürgen Hackl +# Time-stamp: +# +# Copyright (c) 2016-2021 Pathpy Developers +# ============================================================================= + +import pytest +from pathpy.visualisations.new_plot import _get_plot_backend, network_plot + + +def test_get_backend(): + """Test to load the plotting backends""" + module = _get_plot_backend(backend=None, filename=None) + d3js = _get_plot_backend(backend='d3js', filename=None) + tikz = _get_plot_backend(backend='tikz', filename=None) + matplotlib = _get_plot_backend(backend='matplotlib', filename=None) + + +def test_network_plot_d3js(): + """Test the plot function of a static network with d3js""" + plot = network_plot('net', backend='d3js') + print(plot) + + +def test_network_plot_tikz(): + """Test the plot function of a static network with tikz""" + plot = network_plot('net', backend='tikz') + print(plot) + + +def test_network_plot_matplotlib(): + """Test the plot function of a static network with matplotlib""" + plot = network_plot('net', backend='matplotlib') + print(plot) + + +# ============================================================================= +# eof +# +# Local Variables: +# mode: python +# mode: linum +# mode: auto-fill +# fill-column: 79 +# End: diff --git a/pathpy/visualisations/_d3js/__init__.py b/pathpy/visualisations/_d3js/__init__.py new file mode 100644 index 0000000..5c3ec7e --- /dev/null +++ b/pathpy/visualisations/_d3js/__init__.py @@ -0,0 +1,25 @@ +"""Initialize d3js plotting functions""" +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# ============================================================================= +# File : __init__.py -- d3js plotting cunctions +# Author : Jürgen Hackl +# Time-stamp: +# +# Copyright (c) 2016-2021 Pathpy Developers +# ============================================================================= +# flake8: noqa +# pylint: disable=unused-import + +from pathpy.visualisations._d3js.network_plots import network_plot + + +# ============================================================================= +# eof +# +# Local Variables: +# mode: python +# mode: linum +# mode: auto-fill +# fill-column: 79 +# End: diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py new file mode 100644 index 0000000..9bdb228 --- /dev/null +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -0,0 +1,27 @@ +"""Network plots with d3js""" +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# ============================================================================= +# File : network_plots.py -- Network plots with d3js +# Author : Jürgen Hackl +# Time-stamp: +# +# Copyright (c) 2016-2021 Pathpy Developers +# ============================================================================= +from typing import Any, Optional + + +def network_plot(obj, filename: Optional[str] = None, + backend: Optional[str] = None, **kwargs: Any): + """Plot a static network with d3js""" + return 'd3js' + +# ============================================================================= +# eof +# +# Local Variables: +# mode: python +# mode: linum +# mode: auto-fill +# fill-column: 79 +# End: diff --git a/pathpy/visualisations/_matplotlib/__init__.py b/pathpy/visualisations/_matplotlib/__init__.py new file mode 100644 index 0000000..d35718a --- /dev/null +++ b/pathpy/visualisations/_matplotlib/__init__.py @@ -0,0 +1,25 @@ +"""Initialize matplotlib plotting functions""" +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# ============================================================================= +# File : __init__.py -- matplotlib plotting cunctions +# Author : Jürgen Hackl +# Time-stamp: +# +# Copyright (c) 2016-2021 Pathpy Developers +# ============================================================================= +# flake8: noqa +# pylint: disable=unused-import + +from pathpy.visualisations._matplotlib.network_plots import network_plot + + +# ============================================================================= +# eof +# +# Local Variables: +# mode: python +# mode: linum +# mode: auto-fill +# fill-column: 79 +# End: diff --git a/pathpy/visualisations/_matplotlib/network_plots.py b/pathpy/visualisations/_matplotlib/network_plots.py new file mode 100644 index 0000000..c0cd44c --- /dev/null +++ b/pathpy/visualisations/_matplotlib/network_plots.py @@ -0,0 +1,27 @@ +"""Network plots with matplotlib""" +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# ============================================================================= +# File : network_plots.py -- Network plots with matplotlib +# Author : Jürgen Hackl +# Time-stamp: +# +# Copyright (c) 2016-2021 Pathpy Developers +# ============================================================================= +from typing import Any, Optional + + +def network_plot(obj, filename: Optional[str] = None, + backend: Optional[str] = None, **kwargs: Any): + """Plot a static network with matplotlib""" + return 'matplotlib' + +# ============================================================================= +# eof +# +# Local Variables: +# mode: python +# mode: linum +# mode: auto-fill +# fill-column: 79 +# End: diff --git a/pathpy/visualisations/_tikz/__init__.py b/pathpy/visualisations/_tikz/__init__.py new file mode 100644 index 0000000..b2e38cb --- /dev/null +++ b/pathpy/visualisations/_tikz/__init__.py @@ -0,0 +1,25 @@ +"""Initialize tikz plotting functions""" +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# ============================================================================= +# File : __init__.py -- tikz plotting cunctions +# Author : Jürgen Hackl +# Time-stamp: +# +# Copyright (c) 2016-2021 Pathpy Developers +# ============================================================================= +# flake8: noqa +# pylint: disable=unused-import + +from pathpy.visualisations._tikz.network_plots import network_plot + + +# ============================================================================= +# eof +# +# Local Variables: +# mode: python +# mode: linum +# mode: auto-fill +# fill-column: 79 +# End: diff --git a/pathpy/visualisations/_tikz/network_plots.py b/pathpy/visualisations/_tikz/network_plots.py new file mode 100644 index 0000000..9af14d0 --- /dev/null +++ b/pathpy/visualisations/_tikz/network_plots.py @@ -0,0 +1,27 @@ +"""Network plots with tikz""" +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# ============================================================================= +# File : network_plots.py -- Network plots with tikz +# Author : Jürgen Hackl +# Time-stamp: +# +# Copyright (c) 2016-2021 Pathpy Developers +# ============================================================================= +from typing import Any, Optional + + +def network_plot(obj, filename: Optional[str] = None, + backend: Optional[str] = None, **kwargs: Any): + """Plot a static network with tikz""" + return 'tikz' + +# ============================================================================= +# eof +# +# Local Variables: +# mode: python +# mode: linum +# mode: auto-fill +# fill-column: 79 +# End: diff --git a/pathpy/visualisations/new_plot.py b/pathpy/visualisations/new_plot.py index d652b6c..68077b7 100644 --- a/pathpy/visualisations/new_plot.py +++ b/pathpy/visualisations/new_plot.py @@ -3,7 +3,7 @@ # ============================================================================= # File : plot.py -- Plotting function for pathpy objects # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -46,7 +46,8 @@ def plot(obj, filename, backend, **kwargs): # return plot_obj.result -def network_plot(obj, filename: str, backend, **kwargs: Any): +def network_plot(obj, filename: Optional[str] = None, + backend: Optional[str] = None, **kwargs: Any): """Plot a static network""" plot_backend = _get_plot_backend(backend, filename) plot_config = {filename} @@ -54,18 +55,20 @@ def network_plot(obj, filename: str, backend, **kwargs: Any): return plot_backend.network_plot(plot_data, plot_config, **kwargs) -def _get_plot_backend(backend: Optional[str] = None, filename: str = None): +def _get_plot_backend(backend: Optional[str] = None, filename: str = None, + default: str = 'd3js'): """ Return the plotting backend to use. """ + _backend: str = default if isinstance(filename, str): - _backend = _formats.get(os.path.splitext(filename)[1], '3djs') + _backend = _formats.get(os.path.splitext(filename)[1], default) # if no backend was found use the backend suggested for the file format if backend is not None and backend not in _backends and filename is not None: LOG.warning('The backend %s was not found.', backend) # if no backend was given use the backend suggested for the file format - elif backend in _backends: + elif isinstance(backend, str) and backend in _backends: _backend = backend try: From fcdc6f38f460dca0dbf5b6942d34d93df39eef0a Mon Sep 17 00:00:00 2001 From: = Date: Thu, 10 Jun 2021 17:05:40 +0800 Subject: [PATCH 06/47] add abstract plot class --- pathpy/tests/test_visualisations.py | 6 +++--- pathpy/visualisations/_d3js/network_plots.py | 13 +++++++++++-- pathpy/visualisations/new_plot.py | 13 ++++++++++++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/pathpy/tests/test_visualisations.py b/pathpy/tests/test_visualisations.py index f36e7c8..cf5d07f 100644 --- a/pathpy/tests/test_visualisations.py +++ b/pathpy/tests/test_visualisations.py @@ -3,7 +3,7 @@ # ============================================================================= # File : test_visualisations.py -- Test environment for the plotting # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -29,13 +29,13 @@ def test_network_plot_d3js(): def test_network_plot_tikz(): """Test the plot function of a static network with tikz""" plot = network_plot('net', backend='tikz') - print(plot) + assert plot == 'tikz' def test_network_plot_matplotlib(): """Test the plot function of a static network with matplotlib""" plot = network_plot('net', backend='matplotlib') - print(plot) + assert plot == 'matplotlib' # ============================================================================= diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py index 9bdb228..fff92b1 100644 --- a/pathpy/visualisations/_d3js/network_plots.py +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -4,18 +4,27 @@ # ============================================================================= # File : network_plots.py -- Network plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= from typing import Any, Optional +from pathpy.visualisations.new_plot import PathPyPlot def network_plot(obj, filename: Optional[str] = None, backend: Optional[str] = None, **kwargs: Any): """Plot a static network with d3js""" - return 'd3js' + result = NetworkPlot(obj, **kwargs) + return result + +class NetworkPlot(PathPyPlot): + """Base network plot""" + + def __init__(self, data, **kwargs): + """Initialize network plot class""" + self.data = data # ============================================================================= # eof # diff --git a/pathpy/visualisations/new_plot.py b/pathpy/visualisations/new_plot.py index 68077b7..86c6057 100644 --- a/pathpy/visualisations/new_plot.py +++ b/pathpy/visualisations/new_plot.py @@ -3,7 +3,7 @@ # ============================================================================= # File : plot.py -- Plotting function for pathpy objects # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -80,6 +80,17 @@ def _get_plot_backend(backend: Optional[str] = None, filename: str = None, return module +class PathPyPlot: + """Base class for assemblig plots.""" + + def to_html(self): + """Function to convert plot to html.""" + raise NotImplementedError + + def to_png(self): + """Function to convert plot to png.""" + raise NotImplementedError + # ============================================================================= # eof # From 26e3b15f6b87e39122f1b117624e25c45646284f Mon Sep 17 00:00:00 2001 From: = Date: Thu, 10 Jun 2021 17:16:09 +0800 Subject: [PATCH 07/47] add typing notations --- pathpy/visualisations/_d3js/network_plots.py | 18 +++++++++++------- pathpy/visualisations/new_plot.py | 7 +++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py index fff92b1..443fdfc 100644 --- a/pathpy/visualisations/_d3js/network_plots.py +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -4,27 +4,31 @@ # ============================================================================= # File : network_plots.py -- Network plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= -from typing import Any, Optional +from __future__ import annotations +from typing import TYPE_CHECKING, Any, Optional from pathpy.visualisations.new_plot import PathPyPlot +# pseudo load class for type checking +if TYPE_CHECKING: + from pathpy.models.network import Network -def network_plot(obj, filename: Optional[str] = None, - backend: Optional[str] = None, **kwargs: Any): + +def network_plot(network: Network, filename: Optional[str] = None, **kwargs: Any): """Plot a static network with d3js""" - result = NetworkPlot(obj, **kwargs) + result = NetworkPlot(network, **kwargs) return result class NetworkPlot(PathPyPlot): """Base network plot""" - def __init__(self, data, **kwargs): + def __init__(self, network: Network, **kwargs: Any): """Initialize network plot class""" - self.data = data + self.network = network # ============================================================================= # eof # diff --git a/pathpy/visualisations/new_plot.py b/pathpy/visualisations/new_plot.py index 86c6057..3798645 100644 --- a/pathpy/visualisations/new_plot.py +++ b/pathpy/visualisations/new_plot.py @@ -3,7 +3,7 @@ # ============================================================================= # File : plot.py -- Plotting function for pathpy objects # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -50,9 +50,8 @@ def network_plot(obj, filename: Optional[str] = None, backend: Optional[str] = None, **kwargs: Any): """Plot a static network""" plot_backend = _get_plot_backend(backend, filename) - plot_config = {filename} - plot_data = {obj} - return plot_backend.network_plot(plot_data, plot_config, **kwargs) + plot_data = obj + return plot_backend.network_plot(plot_data, **kwargs) def _get_plot_backend(backend: Optional[str] = None, filename: str = None, From 0913c583a050bd8cfad0e73517a34a85fd5f382b Mon Sep 17 00:00:00 2001 From: = Date: Thu, 10 Jun 2021 17:28:39 +0800 Subject: [PATCH 08/47] add abstract methods to PathPyPlot --- pathpy/visualisations/_d3js/network_plots.py | 8 ++++++-- pathpy/visualisations/new_plot.py | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py index 443fdfc..6e875e1 100644 --- a/pathpy/visualisations/_d3js/network_plots.py +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -4,7 +4,7 @@ # ============================================================================= # File : network_plots.py -- Network plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -17,7 +17,7 @@ from pathpy.models.network import Network -def network_plot(network: Network, filename: Optional[str] = None, **kwargs: Any): +def network_plot(network: Network, **kwargs: Any): """Plot a static network with d3js""" result = NetworkPlot(network, **kwargs) return result @@ -26,9 +26,13 @@ def network_plot(network: Network, filename: Optional[str] = None, **kwargs: Any class NetworkPlot(PathPyPlot): """Base network plot""" + _kind = 'network' + def __init__(self, network: Network, **kwargs: Any): """Initialize network plot class""" self.network = network + + # ============================================================================= # eof # diff --git a/pathpy/visualisations/new_plot.py b/pathpy/visualisations/new_plot.py index 3798645..4654d1c 100644 --- a/pathpy/visualisations/new_plot.py +++ b/pathpy/visualisations/new_plot.py @@ -3,7 +3,7 @@ # ============================================================================= # File : plot.py -- Plotting function for pathpy objects # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -82,6 +82,23 @@ def _get_plot_backend(backend: Optional[str] = None, filename: str = None, class PathPyPlot: """Base class for assemblig plots.""" + @property + def _kind(self) -> str: + """Specify kind str. Must be overridden in child class""" + raise NotImplementedError + + def generate(self): + """Function to generate the plot""" + raise NotImplementedError + + def save(self): + """Function to save the plot""" + raise NotImplementedError + + def show(self): + """Function to show the plot""" + raise NotImplementedError + def to_html(self): """Function to convert plot to html.""" raise NotImplementedError From 2278e5dcab471f428c77e16f003a68eba37f0bf9 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 10 Jun 2021 17:47:39 +0800 Subject: [PATCH 09/47] add abstract d3js plot class --- pathpy/visualisations/_d3js/network_plots.py | 31 ++++++++++++++++++-- pathpy/visualisations/new_plot.py | 15 ++++------ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py index 6e875e1..654c9bf 100644 --- a/pathpy/visualisations/_d3js/network_plots.py +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -4,7 +4,7 @@ # ============================================================================= # File : network_plots.py -- Network plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -23,15 +23,42 @@ def network_plot(network: Network, **kwargs: Any): return result -class NetworkPlot(PathPyPlot): +class D3jsPlot(PathPyPlot): + """Abstract class for plotting d3js objects""" + + def __init__(self, **kwargs: Any): + """Initialize plot class""" + super().__init__() + if kwargs: + self.config = kwargs + + def generate(self): + """Function to generate the plot""" + raise NotImplementedError + + def save(self): + """Function to save the plot""" + print('Save the plot') + + def show(self): + """Function to show the plot""" + print('Show the plot') + + +class NetworkPlot(D3jsPlot): """Base network plot""" _kind = 'network' def __init__(self, network: Network, **kwargs: Any): """Initialize network plot class""" + super().__init__(**kwargs) self.network = network + def generate(self): + """Function to generate the plot""" + raise NotImplementedError + # ============================================================================= # eof diff --git a/pathpy/visualisations/new_plot.py b/pathpy/visualisations/new_plot.py index 4654d1c..58b6234 100644 --- a/pathpy/visualisations/new_plot.py +++ b/pathpy/visualisations/new_plot.py @@ -3,7 +3,7 @@ # ============================================================================= # File : plot.py -- Plotting function for pathpy objects # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -82,6 +82,11 @@ def _get_plot_backend(backend: Optional[str] = None, filename: str = None, class PathPyPlot: """Base class for assemblig plots.""" + def __init__(self): + """Initialize plot class""" + self.data: dict = {} + self.config: dict = {} + @property def _kind(self) -> str: """Specify kind str. Must be overridden in child class""" @@ -99,14 +104,6 @@ def show(self): """Function to show the plot""" raise NotImplementedError - def to_html(self): - """Function to convert plot to html.""" - raise NotImplementedError - - def to_png(self): - """Function to convert plot to png.""" - raise NotImplementedError - # ============================================================================= # eof # From ceffee47a4aaa2cb14e39183c94f5a0008c6fa51 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 11 Jun 2021 12:23:30 +0800 Subject: [PATCH 10/47] update doc strings --- pathpy/visualisations/_d3js/network_plots.py | 6 +++--- pathpy/visualisations/new_plot.py | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py index 654c9bf..4e1b131 100644 --- a/pathpy/visualisations/_d3js/network_plots.py +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -4,7 +4,7 @@ # ============================================================================= # File : network_plots.py -- Network plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -24,7 +24,7 @@ def network_plot(network: Network, **kwargs: Any): class D3jsPlot(PathPyPlot): - """Abstract class for plotting d3js objects""" + """Base class for plotting d3js objects""" def __init__(self, **kwargs: Any): """Initialize plot class""" @@ -46,7 +46,7 @@ def show(self): class NetworkPlot(D3jsPlot): - """Base network plot""" + """Network plot class for a static network.""" _kind = 'network' diff --git a/pathpy/visualisations/new_plot.py b/pathpy/visualisations/new_plot.py index 58b6234..21b45bc 100644 --- a/pathpy/visualisations/new_plot.py +++ b/pathpy/visualisations/new_plot.py @@ -3,7 +3,7 @@ # ============================================================================= # File : plot.py -- Plotting function for pathpy objects # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -49,8 +49,13 @@ def plot(obj, filename, backend, **kwargs): def network_plot(obj, filename: Optional[str] = None, backend: Optional[str] = None, **kwargs: Any): """Plot a static network""" + # load aprorpoate backend plot_backend = _get_plot_backend(backend, filename) + + # convert given object to needed plot data plot_data = obj + + # return the plot return plot_backend.network_plot(plot_data, **kwargs) @@ -80,7 +85,7 @@ def _get_plot_backend(backend: Optional[str] = None, filename: str = None, class PathPyPlot: - """Base class for assemblig plots.""" + """Abstract class for assemblig plots.""" def __init__(self): """Initialize plot class""" From 3098b74ffe8da70e70f35d056eeb0cecf5e4780d Mon Sep 17 00:00:00 2001 From: = Date: Fri, 11 Jun 2021 13:05:02 +0800 Subject: [PATCH 11/47] add dataclasses for node and edge plot objects --- pathpy/tests/test_visualisations.py | 11 +++-- pathpy/visualisations/_d3js/network_plots.py | 47 ++++++++++++++++++-- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/pathpy/tests/test_visualisations.py b/pathpy/tests/test_visualisations.py index cf5d07f..6e2346f 100644 --- a/pathpy/tests/test_visualisations.py +++ b/pathpy/tests/test_visualisations.py @@ -3,12 +3,13 @@ # ============================================================================= # File : test_visualisations.py -- Test environment for the plotting # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= import pytest +import pathpy as pp from pathpy.visualisations.new_plot import _get_plot_backend, network_plot @@ -22,8 +23,12 @@ def test_get_backend(): def test_network_plot_d3js(): """Test the plot function of a static network with d3js""" - plot = network_plot('net', backend='d3js') - print(plot) + net = pp.Network() + net.add_edge('a', 'b') + net.add_edge('b', 'c') + + plot = network_plot(net, backend='d3js') + # print(plot) def test_network_plot_tikz(): diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py index 4e1b131..2b09ab3 100644 --- a/pathpy/visualisations/_d3js/network_plots.py +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -4,12 +4,14 @@ # ============================================================================= # File : network_plots.py -- Network plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= from __future__ import annotations from typing import TYPE_CHECKING, Any, Optional +from dataclasses import dataclass + from pathpy.visualisations.new_plot import PathPyPlot # pseudo load class for type checking @@ -20,6 +22,7 @@ def network_plot(network: Network, **kwargs: Any): """Plot a static network with d3js""" result = NetworkPlot(network, **kwargs) + result.generate() return result @@ -45,6 +48,31 @@ def show(self): print('Show the plot') +@dataclass +class NodeData: + """Class to store nodes for plotting""" + uid: str + size: Optional[float] = None + color: Optional[str] = None + opacity: float = 1.0 + x: Optional[float] = None + y: Optional[float] = None + + +@dataclass +class EdgeData: + """Class to store nodes for plotting""" + uid: str + source: str + target: str + size: Optional[float] = None + color: Optional[str] = None + opacity: float = 1.0 + weight: float = 1.0 + # directed: bool = True + # curved: bool = True + + class NetworkPlot(D3jsPlot): """Network plot class for a static network.""" @@ -57,9 +85,20 @@ def __init__(self, network: Network, **kwargs: Any): def generate(self): """Function to generate the plot""" - raise NotImplementedError - - + self._compute_edge_data() + self._compute_node_data() + + def _compute_node_data(self): + """Generate the data structure for the nodes""" + print('test') + n = NodeData('uid') + e = EdgeData('uid', 'u', 'v') + print(n) + print(e) + + def _compute_edge_data(self): + """Generate the data structure for the edges""" + pass # ============================================================================= # eof # From 56f2dd10933f7b90d31582a61818b051506123f9 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 11 Jun 2021 13:34:40 +0800 Subject: [PATCH 12/47] add basic functions to read node and edge properties --- pathpy/visualisations/_d3js/network_plots.py | 38 ++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py index 2b09ab3..2736faa 100644 --- a/pathpy/visualisations/_d3js/network_plots.py +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -4,13 +4,13 @@ # ============================================================================= # File : network_plots.py -- Network plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= from __future__ import annotations from typing import TYPE_CHECKING, Any, Optional -from dataclasses import dataclass +from dataclasses import dataclass, asdict from pathpy.visualisations.new_plot import PathPyPlot @@ -54,7 +54,7 @@ class NodeData: uid: str size: Optional[float] = None color: Optional[str] = None - opacity: float = 1.0 + opacity: Optional[float] = None x: Optional[float] = None y: Optional[float] = None @@ -67,7 +67,7 @@ class EdgeData: target: str size: Optional[float] = None color: Optional[str] = None - opacity: float = 1.0 + opacity: Optional[float] = None weight: float = 1.0 # directed: bool = True # curved: bool = True @@ -90,15 +90,33 @@ def generate(self): def _compute_node_data(self): """Generate the data structure for the nodes""" - print('test') - n = NodeData('uid') - e = EdgeData('uid', 'u', 'v') - print(n) - print(e) + nodes: dict = {} + for uid, node in self.network.nodes.items(): + nodes[uid] = NodeData( + uid, + size=node['size'], + color=node['color'], + opacity=node['opacity'], + x=node['x'], + y=node['y'], + ) + self.data['nodes'] = nodes def _compute_edge_data(self): """Generate the data structure for the edges""" - pass + edges: dict = {} + for uid, edge in self.network.edges.items(): + edges[uid] = EdgeData( + uid, + edge.v.uid, + edge.w.uid, + size=edge['size'], + color=edge['color'], + opacity=edge['opacity'], + weight=edge.weight('weight'), + ) + self.data['edges'] = edges + # ============================================================================= # eof # From 490578d232807045d481b60bcc77cd4dbaa026d2 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 11 Jun 2021 13:56:48 +0800 Subject: [PATCH 13/47] add basic functionality to generate html files --- pathpy/tests/test_visualisations.py | 5 +-- pathpy/visualisations/_d3js/network_plots.py | 33 +++++++++++++++++--- pathpy/visualisations/new_plot.py | 4 +-- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/pathpy/tests/test_visualisations.py b/pathpy/tests/test_visualisations.py index 6e2346f..708df2d 100644 --- a/pathpy/tests/test_visualisations.py +++ b/pathpy/tests/test_visualisations.py @@ -3,7 +3,7 @@ # ============================================================================= # File : test_visualisations.py -- Test environment for the plotting # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -28,7 +28,8 @@ def test_network_plot_d3js(): net.add_edge('b', 'c') plot = network_plot(net, backend='d3js') - # print(plot) + print(plot) + plot.save('test.html') def test_network_plot_tikz(): diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py index 2736faa..8ae35da 100644 --- a/pathpy/visualisations/_d3js/network_plots.py +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -4,20 +4,28 @@ # ============================================================================= # File : network_plots.py -- Network plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= from __future__ import annotations + +import webbrowser +import tempfile + from typing import TYPE_CHECKING, Any, Optional from dataclasses import dataclass, asdict +from pathpy import logger, config from pathpy.visualisations.new_plot import PathPyPlot # pseudo load class for type checking if TYPE_CHECKING: from pathpy.models.network import Network +# create logger +LOG = logger(__name__) + def network_plot(network: Network, **kwargs: Any): """Plot a static network with d3js""" @@ -39,13 +47,28 @@ def generate(self): """Function to generate the plot""" raise NotImplementedError - def save(self): + def save(self, filename: str) -> None: """Function to save the plot""" - print('Save the plot') + with open(filename, 'w+') as new: + new.write(self.to_html()) - def show(self): + def show(self) -> None: """Function to show the plot""" - print('Show the plot') + + if config['environment']['interactive']: + from IPython.core.display import display, HTML + display(HTML(self.to_html())) + else: + # create temporal file + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + # save html + self.save(temp_file.name) + # open the file + webbrowser.open(r'file:///'+temp_file.name) + + def to_html(self) -> str: + """Convert data to html""" + return "

Test

" @dataclass diff --git a/pathpy/visualisations/new_plot.py b/pathpy/visualisations/new_plot.py index 21b45bc..566e16f 100644 --- a/pathpy/visualisations/new_plot.py +++ b/pathpy/visualisations/new_plot.py @@ -3,7 +3,7 @@ # ============================================================================= # File : plot.py -- Plotting function for pathpy objects # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -101,7 +101,7 @@ def generate(self): """Function to generate the plot""" raise NotImplementedError - def save(self): + def save(self, filename: str): """Function to save the plot""" raise NotImplementedError From cf4a362e9b56a693e0730f82a30db2d9d8fefc58 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 11 Jun 2021 18:19:40 +0800 Subject: [PATCH 14/47] add functionality to create d3js plots --- pathpy/tests/test_visualisations.py | 5 +- pathpy/visualisations/_d3js/network_plots.py | 58 ++++++++- .../visualisations/_d3js/templates/network.js | 116 ++++++++++++++++++ .../visualisations/_d3js/templates/setup.html | 17 +++ .../visualisations/_d3js/templates/styles.css | 22 ++++ 5 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 pathpy/visualisations/_d3js/templates/network.js create mode 100644 pathpy/visualisations/_d3js/templates/setup.html create mode 100644 pathpy/visualisations/_d3js/templates/styles.css diff --git a/pathpy/tests/test_visualisations.py b/pathpy/tests/test_visualisations.py index 708df2d..f73fed0 100644 --- a/pathpy/tests/test_visualisations.py +++ b/pathpy/tests/test_visualisations.py @@ -3,7 +3,7 @@ # ============================================================================= # File : test_visualisations.py -- Test environment for the plotting # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -24,11 +24,10 @@ def test_get_backend(): def test_network_plot_d3js(): """Test the plot function of a static network with d3js""" net = pp.Network() - net.add_edge('a', 'b') + net.add_edge('a', 'b', color='red') net.add_edge('b', 'c') plot = network_plot(net, backend='d3js') - print(plot) plot.save('test.html') diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py index 8ae35da..33775c6 100644 --- a/pathpy/visualisations/_d3js/network_plots.py +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -4,7 +4,7 @@ # ============================================================================= # File : network_plots.py -- Network plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -12,9 +12,13 @@ import webbrowser import tempfile +import json +import os +import uuid from typing import TYPE_CHECKING, Any, Optional from dataclasses import dataclass, asdict +from collections import defaultdict from pathpy import logger, config from pathpy.visualisations.new_plot import PathPyPlot @@ -68,7 +72,57 @@ def show(self) -> None: def to_html(self) -> str: """Convert data to html""" - return "

Test

" + + # generate unique dom uids + network_id = "#x"+uuid.uuid4().hex + + # get path to the pathpy templates + template_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + os.path.normpath('_d3js/templates')) + + # get template files + with open(os.path.join(template_dir, "network.js")) as template: + js_template = template.read() + + with open(os.path.join(template_dir, "setup.html")) as template: + setup_template = template.read() + + with open(os.path.join(template_dir, "styles.css")) as template: + css_template = template.read() + + # initialize variables + data: defaultdict = defaultdict(list) + + # convert data to json format + for key, objects in self.data.items(): + for obj in objects.values(): + data[key].append( + {k: v for k, v in asdict(obj).items() if v is not None}) + + self.config['selector'] = network_id + + # generate html file + html = '\n' + + # div environment for the plot object + html += f'\n
\n' + + # add setup code + html += setup_template + + # add JavaScript + html += '' + + return html @dataclass diff --git a/pathpy/visualisations/_d3js/templates/network.js b/pathpy/visualisations/_d3js/templates/network.js new file mode 100644 index 0000000..b0301a3 --- /dev/null +++ b/pathpy/visualisations/_d3js/templates/network.js @@ -0,0 +1,116 @@ +require(['d3'], function(d3){ //START + console.log("Network Template Loaded"); + /* Resources + + https://bl.ocks.org/mapio/53fed7d84cd1812d6a6639ed7aa83868 + https://codepen.io/smlo/pen/JdMOej + */ + + // variables from the config file + const selector = config.selector; + const width = config.width || 400; + const height = config.height || 400; + + /* Create a svg element to display the network */ + var svg = d3.select(selector) + .append('svg') + .attr('width', width) + .attr('height', height) + + // add container to store the elements + var container = svg.append("g"); + + /*Add zoom function to the container */ + svg.call( + d3.zoom() + .scaleExtent([.1, 4]) + .on("zoom", function() { container.attr("transform", d3.event.transform); }) + ); + + /*Load nodes and links from the data */ + var nodes = data.nodes + var links = data.edges + + /*Link creation template */ + var link = container.append("g").attr("class", "links") + .selectAll(".link") + .data(links) + .enter() + .append("line") + .attr("class", "link") + .style("stroke", function(d) { return d.color; }) + .style("stroke-opacity", function(d) { return d.opacity; }) + .style("stroke-width", function(d){ return d.size }); + + /*Node creation template */ + var node = container.append("g").attr("class", "nodes") + .selectAll("circle.node") + .data(nodes) + .enter().append("circle") + .attr("class", "node") + .attr("x", function(d) { return d.x; }) + .attr("y", function(d) { return d.y; }) + .style("fill", function(d) { return d.color; }) + .style("opacity", function(d) { return d.opacity; }) + .style("r", function(d){ return d.size }); + + /*Simulation of the forces*/ + var simulation = d3.forceSimulation(nodes) + .force("charge", d3.forceManyBody().strength(-3000)) + .force("center", d3.forceCenter(width / 2, height / 2)) + .force("x", d3.forceX(width / 2).strength(1)) + .force("y", d3.forceY(height / 2).strength(1)) + .force("links", d3.forceLink(links) + .id(function(d) {return d.uid; }) + .distance(50).strength(1)) + .on("tick", ticked); + + /*Update of the node and edge objects*/ + function ticked() { + node.call(updateNode); + link.call(updateLink); + }; + + /*Update link positions */ + function updateLink(link) { + link + .attr("x1", function(d) { return d.source.x; }) + .attr("y1", function(d) { return d.source.y; }) + .attr("x2", function(d) { return d.target.x; }) + .attr("y2", function(d) { return d.target.y; }); + }; + + /*Update node positions */ + function updateNode(node) { + node.attr("transform", function(d) { + return "translate(" + d.x + "," + d.y + ")"; + }); + }; + + /*Add drag functionality to the node objects*/ + node.call( + d3.drag() + .on("start", dragstarted) + .on("drag", dragged) + .on("end", dragended) + ); + + function dragstarted(d) { + d3.event.sourceEvent.stopPropagation(); + if (!d3.event.active) simulation.alphaTarget(0.3).restart(); + d.fx = d.x; + d.fy = d.y; + }; + + function dragged(d) { + d.fx = d3.event.x; + d.fy = d3.event.y; + }; + + function dragended(d) { + if (!d3.event.active) simulation.alphaTarget(0); + d.fx = null; + d.fy = null; + }; + +}); //END diff --git a/pathpy/visualisations/_d3js/templates/setup.html b/pathpy/visualisations/_d3js/templates/setup.html new file mode 100644 index 0000000..5082439 --- /dev/null +++ b/pathpy/visualisations/_d3js/templates/setup.html @@ -0,0 +1,17 @@ + + + diff --git a/pathpy/visualisations/_d3js/templates/styles.css b/pathpy/visualisations/_d3js/templates/styles.css new file mode 100644 index 0000000..23d736e --- /dev/null +++ b/pathpy/visualisations/_d3js/templates/styles.css @@ -0,0 +1,22 @@ +body { + margin: 0; +} + +svg { + position: fixed; + font: 12px sans-serif; +} + +svg circle.node { + fill: #3b5998; + stroke: #1b3978; + stroke-width: 5.5px; + r: 15px; + opacity: 1; +} + +svg line.link { + stroke: #222; + stroke-opacity: .6; + stroke-width: 4px; +} From c5b606ada445c279e9d02ab0a134ec581da960ba Mon Sep 17 00:00:00 2001 From: = Date: Thu, 17 Jun 2021 15:11:39 +0800 Subject: [PATCH 15/47] move general D3jsPlot class to new file --- pathpy/visualisations/_d3js/core.py | 124 +++++++++++++++++++ pathpy/visualisations/_d3js/network_plots.py | 102 +-------------- 2 files changed, 128 insertions(+), 98 deletions(-) create mode 100644 pathpy/visualisations/_d3js/core.py diff --git a/pathpy/visualisations/_d3js/core.py b/pathpy/visualisations/_d3js/core.py new file mode 100644 index 0000000..49bf819 --- /dev/null +++ b/pathpy/visualisations/_d3js/core.py @@ -0,0 +1,124 @@ +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# ============================================================================= +# File : core.py -- Plots with d3js +# Author : Jürgen Hackl +# Time-stamp: +# +# Copyright (c) 2016-2021 Pathpy Developers +# ============================================================================= +from __future__ import annotations + +import os +import json +import uuid +import tempfile +import webbrowser + +from typing import Any +from collections import defaultdict +from dataclasses import asdict + +from pathpy import logger, config +from pathpy.visualisations.new_plot import PathPyPlot + +# create logger +LOG = logger(__name__) + + +class D3jsPlot(PathPyPlot): + """Base class for plotting d3js objects""" + + def __init__(self, **kwargs: Any): + """Initialize plot class""" + super().__init__() + if kwargs: + self.config = kwargs + + def generate(self): + """Function to generate the plot""" + raise NotImplementedError + + def save(self, filename: str) -> None: + """Function to save the plot""" + with open(filename, 'w+') as new: + new.write(self.to_html()) + + def show(self) -> None: + """Function to show the plot""" + + if config['environment']['interactive']: + from IPython.core.display import display, HTML + display(HTML(self.to_html())) + else: + # create temporal file + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + # save html + self.save(temp_file.name) + # open the file + webbrowser.open(r'file:///'+temp_file.name) + + def to_html(self) -> str: + """Convert data to html""" + + # generate unique dom uids + network_id = "#x"+uuid.uuid4().hex + + # get path to the pathpy templates + template_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + os.path.normpath('_d3js/templates')) + + # get template files + with open(os.path.join(template_dir, "network.js")) as template: + js_template = template.read() + + with open(os.path.join(template_dir, "setup.html")) as template: + setup_template = template.read() + + with open(os.path.join(template_dir, "styles.css")) as template: + css_template = template.read() + + # initialize variables + data: defaultdict = defaultdict(list) + + # convert data to json format + for key, objects in self.data.items(): + for obj in objects.values(): + data[key].append( + {k: v for k, v in asdict(obj).items() if v is not None}) + + self.config['selector'] = network_id + + # generate html file + html = '\n' + + # div environment for the plot object + html += f'\n
\n' + + # add setup code + html += setup_template + + # add JavaScript + html += '' + + return html + + +# ============================================================================= +# eof +# +# Local Variables: +# mode: python +# mode: linum +# mode: auto-fill +# fill-column: 79 +# End: diff --git a/pathpy/visualisations/_d3js/network_plots.py b/pathpy/visualisations/_d3js/network_plots.py index 33775c6..0afcad6 100644 --- a/pathpy/visualisations/_d3js/network_plots.py +++ b/pathpy/visualisations/_d3js/network_plots.py @@ -4,24 +4,17 @@ # ============================================================================= # File : network_plots.py -- Network plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= from __future__ import annotations -import webbrowser -import tempfile -import json -import os -import uuid - from typing import TYPE_CHECKING, Any, Optional -from dataclasses import dataclass, asdict -from collections import defaultdict +from dataclasses import dataclass -from pathpy import logger, config -from pathpy.visualisations.new_plot import PathPyPlot +from pathpy import logger +from pathpy.visualisations._d3js.core import D3jsPlot # pseudo load class for type checking if TYPE_CHECKING: @@ -38,93 +31,6 @@ def network_plot(network: Network, **kwargs: Any): return result -class D3jsPlot(PathPyPlot): - """Base class for plotting d3js objects""" - - def __init__(self, **kwargs: Any): - """Initialize plot class""" - super().__init__() - if kwargs: - self.config = kwargs - - def generate(self): - """Function to generate the plot""" - raise NotImplementedError - - def save(self, filename: str) -> None: - """Function to save the plot""" - with open(filename, 'w+') as new: - new.write(self.to_html()) - - def show(self) -> None: - """Function to show the plot""" - - if config['environment']['interactive']: - from IPython.core.display import display, HTML - display(HTML(self.to_html())) - else: - # create temporal file - with tempfile.NamedTemporaryFile(delete=False) as temp_file: - # save html - self.save(temp_file.name) - # open the file - webbrowser.open(r'file:///'+temp_file.name) - - def to_html(self) -> str: - """Convert data to html""" - - # generate unique dom uids - network_id = "#x"+uuid.uuid4().hex - - # get path to the pathpy templates - template_dir = os.path.join( - os.path.dirname(os.path.dirname(__file__)), - os.path.normpath('_d3js/templates')) - - # get template files - with open(os.path.join(template_dir, "network.js")) as template: - js_template = template.read() - - with open(os.path.join(template_dir, "setup.html")) as template: - setup_template = template.read() - - with open(os.path.join(template_dir, "styles.css")) as template: - css_template = template.read() - - # initialize variables - data: defaultdict = defaultdict(list) - - # convert data to json format - for key, objects in self.data.items(): - for obj in objects.values(): - data[key].append( - {k: v for k, v in asdict(obj).items() if v is not None}) - - self.config['selector'] = network_id - - # generate html file - html = '\n' - - # div environment for the plot object - html += f'\n
\n' - - # add setup code - html += setup_template - - # add JavaScript - html += '' - - return html - - @dataclass class NodeData: """Class to store nodes for plotting""" From 70affc98a3ae6855708b051b20aeb426e68a8b8a Mon Sep 17 00:00:00 2001 From: = Date: Thu, 17 Jun 2021 16:27:10 +0800 Subject: [PATCH 16/47] simplify data generation --- pathpy/visualisations/_d3js/core.py | 16 +----- pathpy/visualisations/_d3js/network_plots.py | 58 ++++---------------- 2 files changed, 14 insertions(+), 60 deletions(-) diff --git a/pathpy/visualisations/_d3js/core.py b/pathpy/visualisations/_d3js/core.py index 49bf819..4dc0255 100644 --- a/pathpy/visualisations/_d3js/core.py +++ b/pathpy/visualisations/_d3js/core.py @@ -3,7 +3,7 @@ # ============================================================================= # File : core.py -- Plots with d3js # Author : Jürgen Hackl -# Time-stamp: +# Time-stamp: # # Copyright (c) 2016-2021 Pathpy Developers # ============================================================================= @@ -16,8 +16,6 @@ import webbrowser from typing import Any -from collections import defaultdict -from dataclasses import asdict from pathpy import logger, config from pathpy.visualisations.new_plot import PathPyPlot @@ -79,15 +77,7 @@ def to_html(self) -> str: with open(os.path.join(template_dir, "styles.css")) as template: css_template = template.read() - # initialize variables - data: defaultdict = defaultdict(list) - - # convert data to json format - for key, objects in self.data.items(): - for obj in objects.values(): - data[key].append( - {k: v for k, v in asdict(obj).items() if v is not None}) - + # update config self.config['selector'] = network_id # generate html file @@ -103,7 +93,7 @@ def to_html(self) -> str: html += '