Source code for cosapp.tools.views.d3js

"""Build a D3js view of a System."""

import os
from typing import Dict
from cosapp.base import System
from cosapp.tools import templates
from .baseRenderer import BaseRenderer


[docs] class D3JsRenderer(BaseRenderer): """ Utility class to export a system as HTML using D3.JS library. Parameters ---------- system : System System to export embeddable: bool, optional Is the HTML to be embedded in an existing page? Default: False """ __globals = None def __init__(self, system: System, embeddable=False): super().__init__(system, embeddable)
[docs] @staticmethod def get_level(syst) -> int: """ Return the number of child levels of input system """ if len(syst.children) == 0: return 1 else: return 1 + max(map(D3JsRenderer.get_level, syst.children.values()))
[docs] def get_data(self) -> Dict: """ Convert `self.system` into a dictionary with 4 keys: `name`, `full_name`, `children` and `size`. - `name` key is a string with the system name. - `full_name` key is a string with Module contextual name. - `children` key is a list containing dictionaries of same format for children system . - `size` key is the display size of system in D3 plot. Returns ------- Dict Dictionary containing elements to draw the `System` using D3JS library """ def build_d3_json( system: System, parent_name: str = None, nlevels: int = 0 ) -> Dict[str, Dict]: """ Builds the dictionary of system structure for D3 plot. Parameters ---------- system : System Current input system parent_name : str, optional Name of parent system, default None nlevels : int, optional Number of system levels, default 0 Returns ------- Dict[str, Dict] The structure of input system in D3 format """ if parent_name is None: full_name = system.name else: full_name = f"{parent_name}.{system.name}" if nlevels == 0: # Number of System levels nlevels = D3JsRenderer.get_level(system) level = len(full_name.split(".")) rtn = {"name": system.name, "full_name": full_name} if system.children: rtn["children"] = [ build_d3_json(child, parent_name=full_name, nlevels=nlevels) for child in system.children.values() ] else: rtn["size"] = (nlevels / level) ** 5 return rtn return build_d3_json(self.system)
[docs] @classmethod def html_resources(cls) -> Dict[str, str]: """ Return a dictionary of three keys. - `d3_js` key holds the content of D3.js library. - `draw_js` key holds the content of the draw function implemented in `d3_draw.js`. - `d3_styles` key holds the css style of D3 plot. Returns ------- Dict[str, Dict] The necessary resources to render Jinja template """ ressource_dir = os.path.dirname(os.path.abspath(templates.__file__)) libs_dir = os.path.join(ressource_dir, "lib") src_dir = os.path.join(ressource_dir, "src") # Grab the resources with open(os.path.join(libs_dir, "d3.min.js"), "r", encoding="utf-8") as f: d3_js = f.read() with open(os.path.join(src_dir, "d3_draw.js"), "r", encoding="utf-8") as f: draw_js = f.read() with open(os.path.join(src_dir, "d3_styles.css"), "r", encoding="utf-8") as f: d3_styles = f.read() return {"d3_js": d3_js, "draw_js": draw_js, "d3_styles": d3_styles}
[docs] @classmethod def html_template(cls) -> "Template": """ Return the Jinja template used to create HTML file. Returns ------- Template """ try: from jinja2 import Environment, PackageLoader except ImportError: raise ImportError("jinja2 needs to be installed to export to d3 JS.") env = Environment( loader=PackageLoader("cosapp.tools", "templates"), trim_blocks=True, lstrip_blocks=True, ) template = env.get_template("system_d3.html") return template
[docs] def html_content(self) -> str: """ Return D3 HTML content of renderer's system as a character string. Returns ------- str """ common = self.get_globals() template = common["template"] html_begin_tags = common["html_begin_tags"] + "\n<div class='flexdiv'>" html_end_tags = common["html_end_tags"] d3_js = common["d3_js"] draw_js = common["draw_js"] d3_styles = common["d3_styles"] elements = { "title": "my viewer", "model_data": f"var modelData = {self.get_data()!s}", "d3_styles": d3_styles, "d3JS": d3_js, "draw": draw_js, } rendered_html = template.render(elements) if not self.embeddable: html_begin_tags = html_begin_tags.replace("{{title}}", elements["title"]) rendered_html = html_begin_tags + rendered_html + html_end_tags return rendered_html
[docs] @classmethod def get_globals(cls): cg = cls.__globals if cg is None: cg = super().get_globals() return cg
[docs] def d3_html(system: System, embeddable=False) -> str: """Return D3.JS HTML content of `system`. Parameters ---------- system : System System to export embeddable: bool, optional Is the HTML to be embedded in an existing page? Default: False """ renderer = D3JsRenderer(system, embeddable) return renderer.html_content()
[docs] def to_d3(system: System, show=True, size=435) -> None: """Return the representation of this system in HTML format. Returns ------- str HTML formatted representation """ filename = f"{system.name}_d3.html" rendered_html = d3_html(system, embeddable=False) with open(filename, mode="w", encoding="utf-8") as f: f.write(rendered_html) if show: try: from IPython.display import IFrame except ImportError: raise ImportError("IPython needs to be installed to display the d3 figure.") return IFrame(filename, "99.9%", f"{size}px") else: return None