'''
PM4Py – A Process Mining Library for Python
Copyright (C) 2024 Process Intelligence Solutions UG (haftungsbeschränkt)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see this software project's root or
visit <https://www.gnu.org/licenses/>.
Website: https://processintelligence.solutions
Contact: info@processintelligence.solutions
'''
import base64
import os
import re
import shutil
import tempfile
import webbrowser
from pm4py.visualization.powl.variants import basic
from pm4py.visualization.powl.variants import net
from enum import Enum
from pm4py.util import exec_utils, constants
from typing import Optional, Dict, Any
from pm4py.objects.powl.obj import POWL
[docs]
class POWLVisualizationVariants(Enum):
BASIC = basic
NET = net
[docs]
class Parameters(Enum):
FORMAT = "format"
ENCODING = "encoding"
DEFAULT_VARIANT = POWLVisualizationVariants.BASIC
[docs]
def apply(
powl: POWL,
variant=DEFAULT_VARIANT,
frequency_tags=True,
parameters: Optional[Dict[Any, Any]] = None,
) -> str:
"""
Method for POWL model representation
Parameters
-----------
powl
POWL model
parameters
Possible parameters of the algorithm:
Parameters.FORMAT -> Format of the image (PDF, PNG, SVG; default PNG)
variant
Variant of the algorithm to use:
- POWLVisualizationVariants.BASIC (default)
- POWLVisualizationVariants.NET: BPMN-like visualization with decision gates
frequency_tags
Simplify the visualization using frequency tags
Returns
-----------
str
SVG Content
"""
if parameters is None:
parameters = {}
if frequency_tags:
powl = powl.simplify_using_frequent_transitions()
viz = exec_utils.get_variant(variant).apply(powl, parameters=parameters)
svg_content = viz.pipe().decode("utf-8")
def inline_images_and_svgs(svg_content):
img_pattern = re.compile(
r'<image[^>]+xlink:href=["\'](.*?)["\'][^>]*>'
)
def encode_file_to_base64(file_path):
with open(file_path, "rb") as file:
return base64.b64encode(file.read()).decode("utf-8")
def read_file_content_and_viewbox(file_path):
with open(file_path, "r") as file:
content = file.read()
content = re.sub(r"<\?xml.*?\?>", "", content, flags=re.DOTALL)
content = re.sub(
r"<!DOCTYPE.*?>", "", content, flags=re.DOTALL
)
viewBox_match = re.search(r'viewBox="([^"]*)"', content)
viewBox = (
viewBox_match.group(1) if viewBox_match else "0 0 1 1"
)
svg_content_match = re.search(
r"<svg[^>]*>(.*?)</svg>", content, re.DOTALL
)
svg_content = (
svg_content_match.group(1)
if svg_content_match
else content
)
return svg_content, viewBox
def replace_with_inline_content(match):
file_path = match.group(1)
if file_path.lower().endswith(".svg"):
svg_data, viewBox = read_file_content_and_viewbox(file_path)
viewBox_values = [float(v) for v in viewBox.split()]
actual_width, actual_height = (
viewBox_values[2],
viewBox_values[3],
)
intended_width = float(
match.group(0)
.split('width="')[1]
.split('"')[0]
.replace("px", "")
)
intended_height = float(
match.group(0)
.split('height="')[1]
.split('"')[0]
.replace("px", "")
)
x = float(match.group(0).split('x="')[1].split('"')[0])
y = float(match.group(0).split('y="')[1].split('"')[0])
scale_x = intended_width / actual_width
scale_y = intended_height / actual_height
return f'<g transform="translate({x},{y}) scale({scale_x},{scale_y})">{svg_data}</g>'
else:
base64_data = encode_file_to_base64(file_path)
return match.group(0).replace(
file_path, f"data:image/png;base64,{base64_data}"
)
return img_pattern.sub(replace_with_inline_content, svg_content)
svg_content_with_inline_images = inline_images_and_svgs(svg_content)
return svg_content_with_inline_images
[docs]
def save(
svg_content: str,
output_file_path: str,
parameters: Optional[Dict[Any, Any]] = None,
):
"""
Save the diagram in a specified format.
Parameters
-----------
svg_content : str
SVG content
output_file_path : str
Path where the output file should be saved
"""
if parameters is None:
parameters = {}
encoding = exec_utils.get_param_value(
Parameters.ENCODING, parameters, "utf-8"
)
with tempfile.NamedTemporaryFile(
delete=False, mode="w+", suffix=".svg"
) as tmpfile:
tmpfile.write(svg_content)
tmpfile_path = tmpfile.name
if output_file_path.endswith("svg"):
shutil.move(tmpfile_path, output_file_path)
elif output_file_path.endswith("png"):
import cairosvg as cairosvg
cairosvg.svg2png(
bytestring=svg_content.encode(encoding), write_to=output_file_path
)
elif output_file_path.endswith("pdf"):
import cairosvg as cairosvg
cairosvg.svg2pdf(
bytestring=svg_content.encode(encoding), write_to=output_file_path
)
else:
raise Exception(
f"Unsupported format! Please use 'svg', 'png', or 'pdf'."
)
if os.path.exists(tmpfile_path):
os.remove(tmpfile_path)
[docs]
def view(svg_content: str, parameters: Optional[Dict[Any, Any]] = None):
"""
View the diagram
Parameters
-----------
svg_content
SVG content
image_format
image format
"""
if parameters is None:
parameters = {}
if constants.DEFAULT_ENABLE_VISUALIZATIONS_VIEW:
image_format = str(
exec_utils.get_param_value(Parameters.FORMAT, parameters, "png")
).lower()
encoding = exec_utils.get_param_value(
Parameters.ENCODING, parameters, "utf-8"
)
with tempfile.NamedTemporaryFile(
delete=False, mode="w+", suffix=".svg"
) as tmpfile:
tmpfile.write(svg_content)
tmpfile_path = tmpfile.name
if image_format == "svg":
absolute_path = os.path.abspath(tmpfile_path)
return webbrowser.open("file://" + absolute_path)
elif image_format == "png":
import cairosvg as cairosvg
cairosvg.svg2png(
bytestring=svg_content.encode(encoding),
write_to=tmpfile_path + ".png",
)
webbrowser.open(
"file://" + os.path.abspath(tmpfile_path + ".png")
)
elif image_format == "pdf":
import cairosvg as cairosvg
cairosvg.svg2pdf(
bytestring=svg_content.encode(encoding),
write_to=tmpfile_path + ".pdf",
)
webbrowser.open(
"file://" + os.path.abspath(tmpfile_path + ".pdf")
)
else:
raise Exception(
f"Unsupported format: {image_format}. Please use 'svg', 'png', or 'pdf'.")