-
Notifications
You must be signed in to change notification settings - Fork 61
font size slider for visualizer #256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Changes from all commits
b148e41
c09f189
6e1046b
480c3b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,18 +28,36 @@ document.addEventListener('DOMContentLoaded', function () { | |
| primary_view_name: primaryViewName, | ||
| secondary_view_name: secondaryViewName, | ||
| } = data; | ||
| const optionsObject = (typeof optionsRaw === 'string') ? JSON.parse(optionsRaw) : optionsRaw; | ||
| // --- State --- | ||
|
|
||
| const optionsObject = | ||
| typeof optionsRaw === "string" ? JSON.parse(optionsRaw) : optionsRaw; | ||
|
|
||
| // Expose for debugging only — disable in production. | ||
| if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production') { | ||
| window.__graph = { | ||
| data, | ||
| allNodesPrimary, | ||
| allEdgesPrimary, | ||
| allNodesSecondary, | ||
| allEdgesSecondary, | ||
| optionsObject, | ||
| }; | ||
| } | ||
| // --- Visual State --- | ||
| let currentView = 'primary'; | ||
| let primaryViewPositions = null; | ||
| let secondaryViewPositions = null; | ||
| let visualState = { | ||
| fontSize: 14 | ||
| }; | ||
|
Comment on lines
+50
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find and read the graph_script.js file
find . -name "graph_script.js" -type fRepository: TemoaProject/temoa Length of output: 162 🏁 Script executed: #!/bin/bash
# Read the graph_script.js file to understand the full context
wc -l ./temoa/utilities/network_vis_templates/graph_script.jsRepository: TemoaProject/temoa Length of output: 121 🏁 Script executed: #!/bin/bash
# Read the relevant sections of the file
cat -n ./temoa/utilities/network_vis_templates/graph_script.js | head -120 | tail -80Repository: TemoaProject/temoa Length of output: 3994 🏁 Script executed: #!/bin/bash
# Read the beginning of the file to understand how optionsObject is created
cat -n ./temoa/utilities/network_vis_templates/graph_script.js | head -50Repository: TemoaProject/temoa Length of output: 2122 🏁 Script executed: #!/bin/bash
# Search for font size references and how optionsObject is used
rg -n "fontSize|font.*size" ./temoa/utilities/network_vis_templates/graph_script.jsRepository: TemoaProject/temoa Length of output: 709 🏁 Script executed: #!/bin/bash
# Search for optionsObject usage and structure to see if it contains font settings
rg -n "optionsObject" ./temoa/utilities/network_vis_templates/graph_script.js | head -20Repository: TemoaProject/temoa Length of output: 350 🏁 Script executed: #!/bin/bash
# Look for where optionsRaw/options_json_str is generated, likely in a Python backend
find . -name "*.py" -type f | xargs rg -l "options_json_str|network_vis" | head -10Repository: TemoaProject/temoa Length of output: 93 🏁 Script executed: #!/bin/bash
# Read the visualizer.py file to understand how options_json_str is generated
wc -l ./temoa/utilities/visualizer.pyRepository: TemoaProject/temoa Length of output: 97 🏁 Script executed: #!/bin/bash
# Read the visualizer.py file to find options_json_str generation
rg -n "options_json_str|nodes.*font|edges.*font" ./temoa/utilities/visualizer.pyRepository: TemoaProject/temoa Length of output: 110 🏁 Script executed: #!/bin/bash
# Read the visualizer.py file around line 242 and look for options construction
cat -n ./temoa/utilities/visualizer.py | sed -n '200,280p'Repository: TemoaProject/temoa Length of output: 3792 🏁 Script executed: #!/bin/bash
# Look at the DEFAULT_VIS_OPTIONS to understand the structure
cat -n ./temoa/utilities/visualizer.py | sed -n '272,315p'Repository: TemoaProject/temoa Length of output: 1672 🏁 Script executed: #!/bin/bash
# Check if the slider has a default value in the HTML template
rg -n "font-size-slider" ./temoa/utilities/network_vis_templates/ -A 2 -B 2Repository: TemoaProject/temoa Length of output: 1411 🏁 Script executed: #!/bin/bash
# Verify scope - check that optionsObject is defined before line 50
cat -n ./temoa/utilities/network_vis_templates/graph_script.js | sed -n '30,55p'Repository: TemoaProject/temoa Length of output: 1021 Synchronize slider initial value with backend font size defaults to prevent UI/render mismatch.
🛠️ Suggested fix- let visualState = {
- fontSize: 14
- };
+ const parsedFontSize = parseInt(
+ optionsObject?.nodes?.font?.size ?? optionsObject?.edges?.font?.size ?? 14,
+ 10,
+ );
+ const defaultFontSize = Number.isFinite(parsedFontSize) ? parsedFontSize : 14;
+ let visualState = {
+ fontSize: defaultFontSize
+ };And at line 60: const fontSizeSlider = document.getElementById('font-size-slider');
+ if (fontSizeSlider) fontSizeSlider.value = String(defaultFontSize);🤖 Prompt for AI Agents |
||
|
|
||
| // --- DOM Elements --- | ||
| const configWrapper = document.getElementById('config-panel-wrapper'); | ||
| const configHeader = document.querySelector('.config-panel-header'); | ||
| const configToggleButton = document.querySelector('.config-toggle-btn'); | ||
| const advancedControlsToggle = document.getElementById('advanced-controls-toggle'); | ||
| const visConfigContainer = document.getElementById('vis-config-container'); | ||
| const fontSizeSlider = document.getElementById('font-size-slider'); | ||
| const searchInput = document.getElementById('search-input'); | ||
| const resetButton = document.getElementById('reset-view-btn'); | ||
| const sectorTogglesContainer = document.getElementById('sector-toggles'); | ||
|
|
@@ -61,9 +79,33 @@ document.addEventListener('DOMContentLoaded', function () { | |
| }); | ||
| } | ||
|
|
||
| // --- Visual Settings Sliders --- | ||
| function updateVisualSettings() { | ||
| if (fontSizeSlider) visualState.fontSize = parseInt(fontSizeSlider.value, 10); | ||
|
|
||
| // Use setOptions for global font size - works for edges with smooth enabled | ||
| // Note: Don't set per-edge font as it breaks rendering with smooth edges | ||
| network.setOptions({ | ||
| nodes: { font: { size: visualState.fontSize } }, | ||
| edges: { font: { size: visualState.fontSize, align: 'top' } } | ||
| }); | ||
|
|
||
| // Also update nodes individually since they have per-node font from addWithCurrentFontSize | ||
| const nodeUpdates = nodes.get().map(n => ({ | ||
| id: n.id, | ||
| font: { ...(n.font ?? {}), size: visualState.fontSize } | ||
| })); | ||
| nodes.update(nodeUpdates); | ||
|
|
||
| network.redraw(); | ||
| } | ||
|
|
||
| if (fontSizeSlider) fontSizeSlider.addEventListener('input', updateVisualSettings); | ||
|
|
||
|
|
||
| // --- Vis.js Network Initialization --- | ||
| const nodes = new vis.DataSet(allNodesPrimary); | ||
| const edges = new vis.DataSet(allEdgesPrimary); | ||
| const nodes = new vis.DataSet(); | ||
| const edges = new vis.DataSet(); | ||
| const network = new vis.Network(graphContainer, { nodes, edges }, optionsObject); | ||
|
|
||
| // --- Core Functions --- | ||
|
|
@@ -84,13 +126,13 @@ document.addEventListener('DOMContentLoaded', function () { | |
| nodes.clear(); edges.clear(); | ||
|
|
||
| if (currentView === 'primary') { | ||
| nodes.add(allNodesSecondary); edges.add(allEdgesSecondary); | ||
| addWithCurrentFontSize(allNodesSecondary, allEdgesSecondary); | ||
| currentView = 'secondary'; | ||
| viewToggleButton.textContent = `Switch to ${primaryViewName}`; | ||
| viewToggleButton.setAttribute('aria-pressed', 'true'); | ||
| applyPositions(secondaryViewPositions); | ||
| } else { | ||
| nodes.add(allNodesPrimary); edges.add(allEdgesPrimary); | ||
| addWithCurrentFontSize(allNodesPrimary, allEdgesPrimary); | ||
| currentView = 'primary'; | ||
| viewToggleButton.textContent = `Switch to ${secondaryViewName}`; | ||
| viewToggleButton.setAttribute('aria-pressed', 'false'); | ||
|
|
@@ -134,8 +176,8 @@ document.addEventListener('DOMContentLoaded', function () { | |
| const visibleNodeIds = new Set(visibleNodes.map(n => n.id)); | ||
| visibleEdges = activeEdgesData.filter(edge => visibleNodeIds.has(edge.from) && visibleNodeIds.has(edge.to)); | ||
| } | ||
| nodes.clear(); edges.clear(); | ||
| nodes.add(visibleNodes); edges.add(visibleEdges); | ||
|
|
||
| addWithCurrentFontSize(visibleNodes, visibleEdges); | ||
| applyPositions(currentPositions); | ||
| } | ||
|
|
||
|
|
@@ -205,6 +247,20 @@ document.addEventListener('DOMContentLoaded', function () { | |
| }); | ||
| } | ||
|
|
||
| function addWithCurrentFontSize(newNodes, newEdges) { | ||
| nodes.clear(); | ||
| edges.clear(); | ||
| nodes.add( | ||
| newNodes.map(n => ({ | ||
| ...n, | ||
| font: { ...(n.font ?? {}), size: visualState.fontSize }, | ||
| })), | ||
| ); | ||
| // Don't set per-edge font - let network.setOptions() handle it | ||
| // vis.js ignores global font options when edges have per-item font set | ||
| edges.add(newEdges); | ||
| } | ||
|
|
||
| function resetView() { | ||
| searchInput.value = ""; | ||
| primaryViewPositions = null; | ||
|
|
@@ -213,8 +269,7 @@ document.addEventListener('DOMContentLoaded', function () { | |
| switchView(); // This will switch back to primary and apply null positions | ||
| } else { | ||
| // If already on primary, just reload the original data | ||
| nodes.clear(); edges.clear(); | ||
| nodes.add(allNodesPrimary); edges.add(allEdgesPrimary); | ||
| addWithCurrentFontSize(allNodesPrimary, allEdgesPrimary); | ||
| applyPositions(primaryViewPositions); // Apply null to reset | ||
| network.fit(); | ||
| } | ||
|
|
@@ -233,9 +288,7 @@ document.addEventListener('DOMContentLoaded', function () { | |
| }); | ||
| const filteredNodes = activeNodes.filter(node => nodesToShow.has(node.id)); | ||
| const filteredEdges = activeEdges.filter(edge => nodesToShow.has(edge.from) && nodesToShow.has(edge.to)); | ||
| nodes.clear(); edges.clear(); | ||
| nodes.add(filteredNodes); | ||
| edges.add(filteredEdges); | ||
| addWithCurrentFontSize(filteredNodes, filteredEdges); | ||
| network.fit(); | ||
| } | ||
|
|
||
|
|
@@ -257,4 +310,6 @@ document.addEventListener('DOMContentLoaded', function () { | |
| createStyleLegend(); | ||
| createSectorLegend(); | ||
| createSectorToggles(); | ||
| // Initial data load with consistent font handling | ||
| addWithCurrentFontSize(allNodesPrimary, allEdgesPrimary); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,13 @@ <h3>Configuration & Legend</h3> | |
| aria-controls="config-container-content"></button> | ||
| </div> | ||
| <div id="config-container-content"> | ||
| <div class="legend-section"> | ||
| <h4>Visual Settings</h4> | ||
| <div class="control-group"> | ||
| <label for="font-size-slider">Label Font Size</label> | ||
| <input type="range" id="font-size-slider" min="6" max="100" step="1" value="14"> | ||
| </div> | ||
| </div> | ||
|
Comment on lines
20
to
26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial LGTM! Font size slider integration. The slider is well-integrated with proper labeling and accessibility attributes. The default value of 14 correctly matches One consideration: a max of 100 is quite large for label fonts. If most users will stay within 6-30, you could consider tightening the range for finer control, but the current range does allow for extreme zoom-out scenarios where larger labels might be useful. 🤖 Prompt for AI Agents |
||
| <div class="legend-section"> | ||
| <h4>Style Legend</h4> | ||
| <div id="style-legend-container" class="legend-container"></div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| from temoa.model_checking.commodity_graph import generate_commodity_graph | ||
| from temoa.model_checking.network_model_data import EdgeTuple, NetworkModelData | ||
| from temoa.types.core_types import Commodity, Period, Region, Sector, Technology, Vintage | ||
|
|
||
|
|
||
| def test_special_items_styling() -> None: | ||
| """ | ||
| Test that demand orphans, other orphans, and driven techs | ||
| are correctly styled in the commodity graph. | ||
| """ | ||
| region = Region('test_region') | ||
| period = Period(2025) | ||
|
|
||
| # Concrete NetworkModelData | ||
| network_data = NetworkModelData() | ||
| network_data.physical_commodities = {Commodity('comm_inter')} | ||
| network_data.source_commodities[(region, period)] = {Commodity('comm_source')} | ||
| network_data.demand_commodities[(region, period)] = {Commodity('comm_demand')} | ||
|
|
||
| # Define some special items | ||
| demand_orphans = [ | ||
| EdgeTuple( | ||
| region, | ||
| Commodity('comm_inter'), | ||
| Technology('tech_demand_orphan'), | ||
| Vintage(2020), | ||
| Commodity('comm_demand'), | ||
| sector=Sector('S1'), | ||
| ) | ||
| ] | ||
| other_orphans = [ | ||
| EdgeTuple( | ||
| region, | ||
| Commodity('comm_source'), | ||
| Technology('tech_other_orphan'), | ||
| Vintage(2020), | ||
| Commodity('comm_inter'), | ||
| sector=Sector('S2'), | ||
| ) | ||
| ] | ||
| driven_techs = [ | ||
| EdgeTuple( | ||
| region, | ||
| Commodity('comm_source'), | ||
| Technology('tech_driven'), | ||
| Vintage(2020), | ||
| Commodity('comm_demand'), | ||
| sector=Sector('S3'), | ||
| ) | ||
| ] | ||
|
|
||
| # Generate the graph | ||
| dg, _sector_colors = generate_commodity_graph( | ||
| region, | ||
| period, | ||
| network_data, | ||
| demand_orphans=demand_orphans, | ||
| other_orphans=other_orphans, | ||
| driven_techs=driven_techs, | ||
| ) | ||
|
|
||
| # 1. Check Node Styling | ||
| assert dg.nodes['comm_demand']['color']['border'] == '#d62728' | ||
| assert dg.nodes['comm_demand']['borderWidth'] == 4 | ||
| assert 'Connected to Demand Orphan' in dg.nodes['comm_demand']['title'] | ||
|
|
||
| assert dg.nodes['comm_inter']['color']['border'] == '#d62728' | ||
| assert dg.nodes['comm_inter']['borderWidth'] == 4 | ||
|
|
||
| assert dg.nodes['comm_source']['color']['border'] == '#ff7f0e' | ||
| assert dg.nodes['comm_source']['borderWidth'] == 4 | ||
|
|
||
| # 2. Check Edge Styling | ||
| edges = list(dg.edges(data=True)) | ||
|
|
||
| edge_do = next(e for e in edges if (e[0] == 'comm_inter' and e[1] == 'comm_demand')) | ||
| assert edge_do[2]['dashes'] is True | ||
| assert edge_do[2]['color'] == '#d62728' | ||
|
|
||
| edge_oo = next(e for e in edges if (e[0] == 'comm_source' and e[1] == 'comm_inter')) | ||
| assert edge_oo[2]['dashes'] is True | ||
| assert edge_oo[2]['color'] == '#ff7f0e' | ||
|
|
||
| edge_dt = next(e for e in edges if (e[0] == 'comm_source' and e[1] == 'comm_demand')) | ||
| assert edge_dt[2]['dashes'] is True | ||
| assert edge_dt[2]['color'] == '#1f77b4' | ||
|
Comment on lines
+76
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider using explicit assertions for edge lookup to improve test failure messages. Using ♻️ Suggested improvement for clearer test failures- edge_do = next(e for e in edges if (e[0] == 'comm_inter' and e[1] == 'comm_demand'))
+ edge_do_list = [e for e in edges if (e[0] == 'comm_inter' and e[1] == 'comm_demand')]
+ assert len(edge_do_list) == 1, f"Expected 1 edge from comm_inter to comm_demand, found {len(edge_do_list)}"
+ edge_do = edge_do_list[0]
assert edge_do[2]['dashes'] is True
assert edge_do[2]['color'] == '#d62728'
- edge_oo = next(e for e in edges if (e[0] == 'comm_source' and e[1] == 'comm_inter'))
+ edge_oo_list = [e for e in edges if (e[0] == 'comm_source' and e[1] == 'comm_inter')]
+ assert len(edge_oo_list) == 1, f"Expected 1 edge from comm_source to comm_inter, found {len(edge_oo_list)}"
+ edge_oo = edge_oo_list[0]
assert edge_oo[2]['dashes'] is True
assert edge_oo[2]['color'] == '#ff7f0e'
- edge_dt = next(e for e in edges if (e[0] == 'comm_source' and e[1] == 'comm_demand'))
+ edge_dt_list = [e for e in edges if (e[0] == 'comm_source' and e[1] == 'comm_demand')]
+ assert len(edge_dt_list) == 1, f"Expected 1 edge from comm_source to comm_demand, found {len(edge_dt_list)}"
+ edge_dt = edge_dt_list[0]
assert edge_dt[2]['dashes'] is True
assert edge_dt[2]['color'] == '#1f77b4'🤖 Prompt for AI Agents |
||
Uh oh!
There was an error while loading. Please reload this page.