Skip to content

Commit

Permalink
fixes achievement coloring and segment handling
Browse files Browse the repository at this point in the history
  • Loading branch information
WolfgangFahl committed Jan 16, 2024
1 parent ab73a51 commit 2f26af6
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 41 deletions.
11 changes: 8 additions & 3 deletions dcm/dcm_assessment.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ def setup_ui(self):
total=self.total, desc="self assessment", unit="facets"
)
self.progress_bar.reset()
facet_element_name=self.competence_tree.element_names["facet"]
area_element_name=self.competence_tree.element_names["area"]
facet_element_name=self.competence_tree.element_names.get("facet") or "facet"
area_element_name=self.competence_tree.element_names.get("area") or "area"

with ui.row() as self.navigation_row:
ui.button("",
Expand Down Expand Up @@ -305,14 +305,19 @@ def update_achievement_view(self, step: int = 0):
display the active achievement as the step indicates
"""
self.show_progress()
self.webserver.render_dcm(self.dcm, self.learner, clear_assessment=False)
if self.achievement_index + step < 0:
ui.notify("first achievement reached!")
step = 0
if self.achievement_index + step < len(self.learner.achievements):
self.achievement_index += step
self.index_view.text = self.get_index_str()
achievement = self.current_achievement
self.webserver.render_dcm(
self.dcm,
self.learner,
selected_paths=[achievement.path],
clear_assessment=False
)
self.button_row.achievement = achievement
self.button_row.set_button_states(achievement)
competence_element = self.competence_tree.lookup_by_path(achievement.path)
Expand Down
132 changes: 110 additions & 22 deletions dcm/dcm_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
@author: wf
"""
from dataclasses import dataclass
from typing import List, Optional
import copy

from dcm.dcm_core import (
CompetenceElement,
Expand Down Expand Up @@ -53,19 +53,18 @@ def generate_svg(
if filename:
self.save_svg_to_file(svg_markup, filename)
return svg_markup

def generate_donut_segment_for_element(
self,
svg: SVG,
element: CompetenceElement,
learner: Learner,
segment: DonutSegment,
):
def add_donut_segment(self,
svg: SVG,
element: CompetenceElement,
segment: DonutSegment,
level_color=None,
achievement_level=None
)->DonutSegment:
"""
generate a donut segment for a given element of
the CompetenceTree
create a donut segment for the
given competence element and add it to the given SVG
"""
# Add the element segment as a donut segment
element_url = (
element.url
if element.url
Expand All @@ -80,13 +79,73 @@ def generate_donut_segment_for_element(
x=self.cx,
y=self.cy,
)

if level_color:
element_config.fill = level_color # Set the color
if element.path in self.selected_paths:
element_config.element_class = "selected"

if achievement_level is not None:
total_levels = self.dcm.competence_tree.total_valid_levels
relative_radius = (segment.outer_radius - segment.inner_radius) * (achievement_level / total_levels)
segment.outer_radius = segment.inner_radius + relative_radius

result=svg.add_donut_segment(config=element_config, segment=segment)
return result

def generate_donut_segment_for_achievement(
self,
svg: SVG,
learner: Learner,
element: CompetenceElement,
segment: DonutSegment,
)->DonutSegment:
"""
generate a donut segment for the
learner's achievements
corresponding to the given path and return it's segment definition
"""
achievement = learner.achievements_by_path.get(element.path, None)
result=None
if achievement and achievement.level:
# Retrieve the color for the achievement level
level_color = self.dcm.competence_tree.get_level_color(achievement.level)

if level_color:
# set the color and radius of
# the segment for achievement
# make sure we don't interfere with the segment calculations
segment=copy.deepcopy(segment)
result=self.add_donut_segment(svg, element, segment, level_color, achievement.level)
return result

def generate_donut_segment_for_element(
self,
svg: SVG,
element: CompetenceElement,
learner: Learner,
segment: DonutSegment,
)->DonutSegment:
"""
generate a donut segment for a given element of
the CompetenceTree
"""
# Simply create the donut segment without considering the achievement
result=self.add_donut_segment(
svg=svg,
element=element,
segment=segment
)
# check learner achievements
if learner:
achievement = learner.achievements_by_path.get(element.path, None)
if achievement and achievement.level:
element_config.element_class = "selected"
svg.add_donut_segment(config=element_config, segment=segment)

_learner_segment=self.generate_donut_segment_for_achievement(
svg=svg,
learner=learner,
element=element,
segment=segment
)
return result

def generate_pie_elements(
self,
level: int,
Expand Down Expand Up @@ -119,7 +178,10 @@ def generate_pie_elements(
end_angle,
)
self.generate_donut_segment_for_element(
svg, element, learner, segment=sub_segment
svg,
element,
learner,
segment=sub_segment
)
start_angle = end_angle
if level + 1 < len(self.levels):
Expand All @@ -135,19 +197,45 @@ def generate_svg_markup(
self,
competence_tree: CompetenceTree = None,
learner: Learner = None,
selected_paths: List=[],
config: SVGConfig = None,
with_java_script: bool = True,
lookup_url: str = "",
) -> str:
"""
generate the SVG markup for the given CompetenceTree and learner
Generate the SVG markup for the given CompetenceTree and Learner. This method
creates an SVG representation of the competence map, which visualizes the
structure and levels of competencies, along with highlighting the learner's
achievements if provided.
Args:
Args:
competence_tree (CompetenceTree, optional): The competence tree structure
to be visualized. If None, the competence tree of the DcmChart instance
will be used. Defaults to None.
learner (Learner, optional): The learner whose achievements are to be
visualized on the competence tree. If None, no learner-specific
information will be included in the SVG. Defaults to None.
selected_paths (List, optional): A list of paths that should be highlighted
in the SVG. These paths typically represent specific competencies or
achievements. Defaults to an empty list.
config (SVGConfig, optional): Configuration for the SVG canvas and legend.
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.
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.
"""
Returns:
str: A string containing the SVG markup for the competence map.
Raises:
ValueError: If there are inconsistencies or issues with the provided data
that prevent the creation of a valid SVG.
"""
if competence_tree is None:
competence_tree = self.dcm.competence_tree

self.selected_paths=selected_paths
svg = SVG(config)
self.svg = svg
config = svg.config
Expand Down
27 changes: 27 additions & 0 deletions dcm/dcm_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,33 @@ def handle_error(msg):
return facet
handle_error(f"invalid path for lookup {path}")
return None

@property
def total_valid_levels(self) -> int:
"""
Calculate the total number of levels excluding
levels with a level of 0.
Returns:
int: The total number of valid levels.
"""
level_count= len([level for level in self.levels if level.level != 0])
return level_count

def get_level_color(self, achievement_level: int) -> Optional[str]:
"""
Retrieve the color associated with a specific achievement level.
Args:
achievement_level (int): The level of achievement to get the color for.
Returns:
Optional[str]: The color code associated with the given level, or None if not found.
"""
for level in self.levels:
if level.level == achievement_level:
return level.color_code
return None

def to_pretty_json(self):
"""
Expand Down
49 changes: 34 additions & 15 deletions dcm/dcm_webserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
@author: wf
"""
import os
from typing import Optional
from typing import List,Optional
from urllib.parse import urlparse

from fastapi import HTTPException
Expand Down Expand Up @@ -229,23 +229,42 @@ async def render(self, _click_args=None):
except BaseException as ex:
self.handle_exception(ex, self.do_trace)

def render_dcm(self, dcm, learner: Learner = None, clear_assessment: bool = True):
def render_dcm(self,
dcm,
learner: Learner = None,
selected_paths: List=[],
clear_assessment: bool = True
):
"""
render the dynamic competence map
Args:
dcm(DynamicCompetenceMap)
selected_paths (List, optional): A list of paths that should be highlighted
in the SVG. These paths typically represent specific competencies or
achievements. Defaults to an empty list.
"""
if clear_assessment and self.assessment:
try:
self.assessment_row.clear()
except Exception as ex:
ui.notify(str(ex))
self.assessment = None
self.dcm = dcm
self.assessment_button.enable()
dcm_chart = DcmChart(dcm)
svg = dcm_chart.generate_svg_markup(learner=learner, with_java_script=False)
# Use the new get_java_script method to get the JavaScript
self.svg_view.content = svg
self.svg_view.update()
try:
if clear_assessment and self.assessment:
try:
self.assessment_row.clear()
except Exception as ex:
ui.notify(str(ex))
self.assessment = None
self.dcm = dcm
self.assessment_button.enable()
dcm_chart = DcmChart(dcm)
svg = dcm_chart.generate_svg_markup(
learner=learner,
selected_paths=selected_paths,
with_java_script=False
)
# Use the new get_java_script method to get the JavaScript
self.svg_view.content = svg
self.svg_view.update()
except Exception as ex:
self.handle_exception(ex, self.do_trace)

async def home(self, _client: Client):
"""Generates the home page with a selection of examples and
Expand Down
4 changes: 3 additions & 1 deletion tests/test_competence_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ def test_element_lookup(self):
self.assertTrue(example_name in examples)
example = examples[example_name]
path="greta/4/1/2"
facet = example.competence_tree.lookup_by_path(path)
ct=example.competence_tree
self.assertEqual(4,ct.total_valid_levels)
facet = ct.lookup_by_path(path)
self.assertIsNotNone(facet)
self.assertIsInstance(facet, CompetenceFacet)
html = facet.as_html()
Expand Down

0 comments on commit 2f26af6

Please sign in to comment.