Skip to content

Commit

Permalink
fixes #26
Browse files Browse the repository at this point in the history
  • Loading branch information
WolfgangFahl committed Jan 18, 2024
1 parent e5120fd commit 1229db7
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 46 deletions.
2 changes: 1 addition & 1 deletion dcm/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.1"
__version__ = "0.1.2"
20 changes: 13 additions & 7 deletions dcm/dcm_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
@author: wf
"""
import copy
from typing import List, Optional
import textwrap
from typing import List, Optional

from dcm.dcm_core import (
CompetenceElement,
CompetenceFacet,
Expand Down Expand Up @@ -134,6 +135,7 @@ def generate_svg(
filename: Optional[str] = None,
learner: Optional[Learner] = None,
config: Optional[SVGConfig] = None,
text_mode: str = "none"
) -> str:
"""
Generate the SVG markup and optionally save it to a file. If a filename is given, the method
Expand All @@ -143,14 +145,17 @@ def generate_svg(
filename (str, optional): The path to the file where the SVG should be saved. Defaults to None.
learner(Learner): the learner to show the achievements for
config (SVGConfig, optional): The configuration for the SVG canvas and legend. Defaults to default values.
text_mode(str): text display mode
Returns:
str: The SVG markup.
"""
if config is None:
config = SVGConfig() # Use default configuration if none provided
svg_markup = self.generate_svg_markup(
self.dcm.competence_tree, learner=learner, config=config
self.dcm.competence_tree,
learner=learner,
config=config,
text_mode=text_mode
)
if filename:
self.save_svg_to_file(svg_markup, filename)
Expand Down Expand Up @@ -201,8 +206,8 @@ def add_donut_segment(
segment.outer_radius = segment.inner_radius + relative_radius

result = svg.add_donut_segment(config=element_config, segment=segment)
if self.text_mode!="none":
text=textwrap.fill(element.short_name,width=20)
if self.text_mode != "none":
text = textwrap.fill(element.short_name, width=20)
self.svg.add_text_to_donut_segment(segment, text, direction=self.text_mode)
return result

Expand Down Expand Up @@ -305,7 +310,7 @@ def generate_svg_markup(
selected_paths: List = [],
config: SVGConfig = None,
with_java_script: bool = True,
text_mode: str="none",
text_mode: str = "none",
lookup_url: str = "",
) -> str:
"""
Expand All @@ -328,6 +333,7 @@ def generate_svg_markup(
If None, default configuration settings are used. Defaults to None.
with_java_script (bool, optional): Indicates whether to include JavaScript
in the SVG for interactivity. Defaults to True.
text_mode(str): text display mode
lookup_url (str, optional): Base URL for linking to detailed descriptions
or information about the competence elements. If not provided, links
will not be generated. Defaults to an empty string.
Expand All @@ -343,7 +349,7 @@ def generate_svg_markup(
competence_tree = self.dcm.competence_tree
self.selected_paths = selected_paths
self.levels = ["aspects", "areas", "facets"]
self.text_mode=text_mode
self.text_mode = text_mode

svg = self.prepare_and_add_inner_circle(config, competence_tree, lookup_url)

Expand Down
13 changes: 10 additions & 3 deletions dcm/dcm_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class CompetenceElement:
Attributes:
name (str): The name of the competence element.
short_name(Optional[str]): the label to be displayed
short_name(Optional[str]): the label to be displayed
id (Optional[str]): An optional identifier for the competence element will be set to the name if id is None.
url (Optional[str]): An optional URL for more information about the competence element.
description (Optional[str]): An optional description of the competence element.
Expand Down Expand Up @@ -152,6 +152,7 @@ class CompetenceTree(CompetenceElement, YamlAble["CompetenceTree"]):
competence_levels (List[CompetenceLevel]): A list of CompetenceLevel objects representing the different levels in the competence hierarchy.
element_names (Dict[str, str]): A dictionary holding the names for tree, aspects, facets, and levels. The key is the type ("tree", "aspect", "facet", "level").
"""

lookup_url: Optional[str] = None
aspects: List[CompetenceAspect] = field(default_factory=list)
levels: List[CompetenceLevel] = field(default_factory=list)
Expand All @@ -162,6 +163,12 @@ def __post_init__(self):
initalize the path variables of my hierarchy
"""
super().__post_init__()
self.update_paths()

def update_paths(self):
"""
update my paths
"""
self.path = self.id
# Loop through each competence aspect and set their paths and parent references
for aspect in self.aspects:
Expand Down Expand Up @@ -443,12 +450,12 @@ class DynamicCompetenceMap:
a visualization of a competence map
"""

def __init__(self, competence_tree: CompetenceTree):
def __init__(self, competence_tree: CompetenceTree,svg:SVG=None):
"""
constructor
"""
self.competence_tree = competence_tree
self.svg = None
self.svg = svg

@property
def main_id(self):
Expand Down
28 changes: 18 additions & 10 deletions dcm/dcm_webserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def __init__(self):
self.examples = DynamicCompetenceMap.get_examples(markup="yaml")
self.dcm = None
self.assessment = None
self.text_mode="none"
self.text_mode = "none"

@app.post("/svg/")
async def render_svg(svg_render_request: SVGRenderRequest) -> HTMLResponse:
Expand Down Expand Up @@ -184,7 +184,7 @@ async def render_svg(self, svg_render_request: SVGRenderRequest) -> HTMLResponse
)
dcm_chart = DcmChart(dcm)
svg_markup = dcm_chart.generate_svg_markup(
config=r.config, with_java_script=True,text_mode=self.text_mode
config=r.config, with_java_script=True, text_mode=self.text_mode
)
response = HTMLResponse(content=svg_markup)
return response
Expand Down Expand Up @@ -256,10 +256,10 @@ def render_dcm(
self.assessment_button.enable()
dcm_chart = DcmChart(dcm)
svg = dcm_chart.generate_svg_markup(
learner=learner,
selected_paths=selected_paths,
learner=learner,
selected_paths=selected_paths,
with_java_script=False,
text_mode=self.text_mode
text_mode=self.text_mode,
)
# Use the new get_java_script method to get the JavaScript
self.svg_view.content = svg
Expand Down Expand Up @@ -289,9 +289,8 @@ async def home(self, _client: Client):
extensions=extensions,
handler=self.read_and_optionally_render,
)
selection=["none","curved","horizontal","angled"]
self.text_mode="curved"
self.add_select("text", selection).bind_value(self,"text_mode")
selection = ["none", "curved", "horizontal", "angled"]
self.add_select("text", selection, value=self.text_mode,on_change=self.on_text_mode_change)
with ui.grid(columns=1).classes("w-full") as self.left_grid:
with ui.row() as self.input_row:
self.input_input = ui.input(
Expand Down Expand Up @@ -340,8 +339,8 @@ def new_assess(self):
"""
run a new assessment for a new learner
"""
learner = Learner(learner_id="?")
self.assess_learner(self.dcm, learner)
self.learner = Learner(learner_id="?")
self.assess_learner(self.dcm, self.learner)

def assess(self, learner: Learner, tree_id: str = None):
"""
Expand All @@ -364,6 +363,15 @@ def assess(self, learner: Learner, tree_id: str = None):
# assess_learner will render ...
# self.render_dcm(dcm,learner=learner)
self.assess_learner(dcm, learner)

async def on_text_mode_change(self,args):
"""
handle changes in the text_mode
"""
new_text_mode=args.value
if new_text_mode!=self.text_mode:
self.text_mode=new_text_mode
await self.render()

def configure_run(self):
"""
Expand Down
46 changes: 31 additions & 15 deletions dcm/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,43 +444,59 @@ def add_donut_segment(
level=2,
comment=config.comment,
)

def get_text_rotation(self,rotation_angle: float) -> float:
"""
Adjusts the rotation angle for SVG text elements to ensure that the text
is upright and readable in a circular chart. The text will be rotated
by 180 degrees if it is in the lower half of the chart (between 90 and 270 degrees).
Args:
rotation_angle (float): The initial rotation angle of the text element.
Returns:
float: The adjusted rotation angle for the text element.
"""
# In the bottom half of the chart (90 to 270 degrees), the text
# would appear upside down, so we rotate it by 180 degrees.
if 90 <= rotation_angle < 270:
rotation_angle -= 180

# Return the adjusted angle. No adjustment is needed for the
# top half of the chart as the text is already upright.
return rotation_angle

def add_text_to_donut_segment(
self,
segment: DonutSegment,
text: str,
direction: str = "horizontal",
color: str = "white"
) -> None:
self, segment: DonutSegment, text: str, direction: str = "horizontal", color: str = "white"
) -> None:
"""
Add text to a donut segment with various direction options.
Args:
segment (DonutSegment): The donut segment to which text will be added.
text (str): The text content to be added.
direction (str): The direction in which the text should be drawn.
Options are "horizontal", "angled", or "curved".
color (str): The color of the text. Default is "white".
"""
# Common calculations
mid_angle = (segment.start_angle + segment.end_angle) / 2
mid_angle_rad = radians(mid_angle)
mid_radius = (segment.inner_radius + segment.outer_radius) / 2
cx, cy = self.config.width / 2, self.config.height / 2

if direction in ["horizontal", "angled"]:
# Calculate position for horizontal or angled text
text_x = cx + mid_radius * cos(mid_angle_rad)
text_y = cy + mid_radius * sin(mid_angle_rad)

# Adjust text anchor and rotation
# Adjust text anchor and rotation for better readability
text_anchor = "middle"
transform = ""
if direction == "angled":
rotation_angle = mid_angle if mid_angle <= 180 else mid_angle - 180
rotation_angle=self.get_text_rotation(mid_angle)
transform = f"rotate({rotation_angle}, {text_x}, {text_y})"

# Add text element
escaped_text = html.escape(text)
text_element = (
Expand All @@ -492,7 +508,7 @@ def add_text_to_donut_segment(
f"{escaped_text}</text>"
)
self.add_element(text_element)

elif direction == "curved":
# Create a path for the text to follow
path_id = f"path{segment.start_angle}-{segment.end_angle}"
Expand All @@ -511,7 +527,7 @@ def add_text_to_donut_segment(
f'<path id="{path_id}" d="{path_d}" fill="none" stroke="none" />'
)
self.add_element(path_element)

# Add text along the path
text_path_element = (
f'<text fill="{color}" font-family="{self.config.font}" font-size="{self.config.font_size}">'
Expand Down
2 changes: 1 addition & 1 deletion dcm/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Version:
name = "dcm"
version = dcm.__version__
date = "2023-11-06"
updated = "2024-01-16"
updated = "2024-01-18"
description = "python based visualization of dynamic competence maps"

authors = "Wolfgang Fahl"
Expand Down
Loading

0 comments on commit 1229db7

Please sign in to comment.