"""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