Skip to content
Snippets Groups Projects
Commit 84f107b5 authored by Allen, Bruce (CIV)'s avatar Allen, Bruce (CIV)
Browse files

experiment with multiple graph scene objects

parent f2264853
No related branches found
No related tags found
No related merge requests found
......@@ -91,17 +91,17 @@ def at_and_above_in_uncollapsed(node):
return node_set
# get the set of removable_edges and addable_edge_pairs
def collapsed_edges(graph_item):
def collapsed_edges(graph_scene):
print("collapsed_edges.a")
# make sure there are edges to work with
if not graph_item.edges:
if not graph_scene.edges:
return (list(), list())
# identify the existing collapsed source_node, dest_node edge pairs
existing_edge_pairs = set()
existing_edges = dict()
for edge in graph_item.edges:
for edge in graph_scene.edges:
if edge.relation == "COLLAPSED_FOLLOWS":
existing_edge_pairs.add((edge.source_node, edge.dest_node))
existing_edges[(edge.source_node, edge.dest_node)] = edge
......@@ -110,7 +110,7 @@ def collapsed_edges(graph_item):
# calculate the set of source_node, dest_node edge pairs
edge_pairs = set()
for edge in graph_item.edges:
for edge in graph_scene.edges:
if edge.relation == "FOLLOWS" and (edge.source_node.collapse
or edge.dest_node.collapse):
print("will add COLLAPSED_FOLLOWS to bridge: %s to %s" % (
......
from PyQt5.QtCore import QRectF
# global constants and defaults
GRAPHICS_RECT = QRectF(-50, -50, 25000, 25000)
from PyQt5.QtCore import QRect
from graph_constants import GRAPHICS_RECT
from graph_collapse_helpers import collapsed_edges
from edge import Edge
from edge_point_placer import EdgePointPlacer
......@@ -12,7 +11,7 @@ class GraphItem():
* nodes (list<Node>)
* edges (list<Edge>)
NOTE: Optimization: call initialize_items and appearance just-in-time.
NOTE: Optimization: call initialize_items and set_appearance just-in-time.
Set appearance when graph should be painted differently.
"""
......@@ -64,23 +63,7 @@ class GraphItem():
# perform any just-in-time initialization
self.initialize_items()
self.set_appearance()
# find the corners of this graph
min_x = GRAPHICS_RECT.x() + GRAPHICS_RECT.width()
max_x = GRAPHICS_RECT.x()
min_y = GRAPHICS_RECT.y() + GRAPHICS_RECT.height()
max_y = GRAPHICS_RECT.y()
for node in self.nodes:
if node.x() - node.w/2 < min_x:
min_x = node.x() - node.w/2
if node.x() + node.w/2 > max_x:
max_x = node.x() + node.w/2
if node.y() - node.h/2 < min_y:
min_y = node.y() - node.h/2
if node.y() + node.h/2 > max_y:
max_y = node.y() + node.h/2
return QRect(min_x, min_y, max_x-min_x, max_y-min_y)
return self.itemsBoundingRect()
# uncollapse all nodes of node type
def uncollapse(self, node_type):
......
......@@ -26,14 +26,12 @@ from edge_grip import EdgeGrip
# GraphicsView
class GraphMainView(QGraphicsView):
"""GraphMainWidget provides the main QGraphicsView. It manages signals
and wraps these:
* GraphMainView
* GraphMainScene
"""GraphMainWidget provides the main QGraphicsView.
It manages signals.
GraphMainWidget also issues signal:
* signal_graph_item_view_changed = pyqtSignal(GraphItem,
name='graphItemViewChanged')
* signal_graph_item_view_changed = pyqtSignal(dict,
name='graphViewChanged')
"""
def __init__(self):
......
import math
from PyQt5.QtCore import QRectF, Qt
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtCore import QObject
from PyQt5.QtGui import QBrush, QColor, QLinearGradient, QPainter, QPen
from PyQt5.QtGui import QTransform
from PyQt5.QtWidgets import QGraphicsView
from PyQt5.QtWidgets import QGraphicsScene
from settings_manager import settings
from graph_collapse_helpers import collapse_below, uncollapse_below
#from node import Node # for Node detection
from edge_grip import EdgeGrip
# QGraphicsScene for a trace graph
class GraphScene(QGraphicsScene):
def __init__(self, trace):
super(GraphScene, self).__init__()
self.index = trace["index"]
self.mark = trace["mark"]
self.probability = trace["probability"]
self.nodes = trace["nodes"]
self.edges = trace["edges"]
self.graph_views = trace["trace_views"] # zz future
# edge highlight state, grips show when edges are highlighted
self._highlighted_edge = None
self._source_edge_grip = None
self._dest_edge_grip = None
self.initialize_items()
self.set_appearance()
# set_scene, useful in hide/collapse nodes
def set_scene(self):
self.clear_scene()
# optimization: just-in-time for display
self.initialize_items()
self.set_appearance()
# add node items
for node in self.nodes:
self.addItem(node)
# add edge items
for edge in self.edges:
self.addItem(edge)
# set initial size
self.setSceneRect(self.itemsBoundingRect())
# clear_scene, useful in hide/collapse nodes
def clear_scene(self):
# turn off grip selection and grips
if self._highlighted_edge:
self.unhighlight_edge()
# we must call removeItem so QGraphicsScene does not delete these
for item in self.items():
item.prepareGeometryChange()
self.removeItem(item)
# # hack to remove cached residue from edges
# self.clear()
# perform orderly just-in-time initialization of graph item components
def initialize_items(self):
if not self._items_initialized:
# initialize items
for node in self.nodes:
node.initialize_node(self)
placer = EdgePointPlacer()
for edge in self.edges:
edge.initialize_edge(placer)
self._items_initialized = True
# reset appearance when graph color or shape changes
def set_appearance(self):
if not self.appearance_set:
for node in self.nodes:
node.set_appearance()
for edge in self.edges:
edge.set_appearance()
self.appearance_set = True
def change_h_spacing(self, old_spacing, old_indent,
new_spacing, new_indent):
for node in self.nodes:
x = (node.x() - old_indent) / old_spacing * new_spacing + new_indent
node.setPos(x, node.y())
def change_v_spacing(self, old_spacing, old_indent,
new_spacing, new_indent):
for node in self.nodes:
y = (node.y() - old_indent) / old_spacing * new_spacing + new_indent
node.setPos(node.x(), y)
def bounding_rect(self):
# perform any just-in-time initialization
self.set_appearance()
return self.itemsBoundingRect()
# highlight edge and create its two bezier edge grips
def highlight_edge(self, edge):
if self._highlighted_edge:
raise Exceiption("Bad")
edge.set_highlighted(True)
self._source_edge_grip = EdgeGrip(edge, "source")
self.addItem(self._source_edge_grip)
self._dest_edge_grip = EdgeGrip(edge, "dest")
self.addItem(self._dest_edge_grip)
self._highlighted_edge = edge
# unhighlight edge and remove its two bezier edge grips
def unhighlight_edge(self):
if not self._highlighted_edge:
raise Exceiption("Bad")
self._highlighted_edge.set_highlighted(False)
# be careful with scene internal indexes else Qt crashes when deleting
self._source_edge_grip.prepareGeometryChange()
self.removeItem(self._source_edge_grip)
self._source_edge_grip = None
self._dest_edge_grip.prepareGeometryChange()
self.removeItem(self._dest_edge_grip)
self._dest_edge_grip = None
self._highlighted_edge = None
# uncollapse all nodes of node type
def uncollapse(self, node_type):
for node in self.nodes:
if node.node_type == node_type:
node.do_uncollapse()
self.appearance_set = False
self.set_appearance()
self.set_collapsed_edges()
# diagnostics
def _print_totals(self):
if not self.edges:
print("Totals: None, empty graph.")
return
# total items in scene
scene_item_total = len(self.items())
# nodes + edges in this scene
graph_item_total = len(self.nodes) + len(self.edges)
# edge_list_total
edge_list_total = 0
for node in self.nodes:
edge_list_total += len(node.edge_list)
print("Totals: scene: %d, graph: %d, graph nodes: %d, "
"graph edges: %d, edge list total: %d" % (
scene_item_total, graph_item_total,
len(self.nodes), len(self.edges), edge_list_total))
# redo the list of collapsed edges in edges[] based on what is collapsed
def set_collapsed_edges(self):
print("set_collapsed_edges.a")
self._print_totals()
removable_edges, addable_edge_pairs = collapsed_edges(self)
# a reference to node_lookup
node_lookup = self.edges[0].node_lookup
# create the addable edges
addable_edges = list()
for source_node, dest_node in addable_edge_pairs:
addable_edge = Edge(source_node.node_id, "COLLAPSED_FOLLOWS",
dest_node.node_id, "", node_lookup)
addable_edges.append(addable_edge)
# remove any discontinued FOLLOWS edges
for removable_edge in removable_edges:
removable_edge.source_node.edge_list.remove(removable_edge)
removable_edge.dest_node.edge_list.remove(removable_edge)
self.edges.remove(removable_edge)
# add any new FOLLOWS edges
placer = EdgePointPlacer()
for addable_edge in addable_edges:
self.edges.append(addable_edge)
addable_edge.initialize_edge(placer)
addable_edge.set_appearance()
# change the COLLAPSED_FOLLOWS edges
self.clear_scene()
self.set_scene(self)
# reset appearance
self.appearance_set = False
self.set_appearance()
print("set_collapsed_edges.b")
self._print_totals()
# remove grips on mouse down unless on grip
def mousePressEvent(self, event):
if self._highlighted_edge:
if not self._source_edge_grip.isUnderMouse() \
and not self._dest_edge_grip.isUnderMouse():
self.unhighlight_edge()
super(GraphScene, self).mousePressEvent(event)
# allow QGraphicsScene size to shrink back
def mouseMoveEvent(self, _event):
self.setSceneRect(self.itemsBoundingRect())
super(GraphScene, self).mouseMoveEvent(_event)
......@@ -3,15 +3,15 @@ from PyQt5.QtCore import pyqtSignal # for signal/slot support
from PyQt5.QtCore import pyqtSlot # for signal/slot support
class GraphsManager(QObject):
"""Provides graphs (list<GraphItem>) and signals when the graph list
"""Provides graphs (dict<dict>) and signals when the graph dict
is loaded or cleared.
Register with signal_graphs_loaded to provide current graph list.
Register with signal_graphs_loaded to provide current graph dict.
Data structures:
* schema_name (str)
* scope (int)
* graphs (list<GraphItem>)
* graphs (dict<dict>)
Signals:
* signal_graphs_loaded() - graphs were loaded or cleared
......@@ -48,17 +48,18 @@ class GraphsManager(QObject):
def appearance_changed(self, old_settings, new_settings):
# make all graphs need appearance_set
for graph in self.graphs:
graph.appearance_set = False
if "graph_item" in graph:
graph.appearance_set = False
# maybe change spacing
if old_settings["graph_h_spacing"] != new_settings["graph_h_spacing"]:
for graph in self.graphs:
# maybe change spacing
if old_settings["graph_h_spacing"] \
!= new_settings["graph_h_spacing"]:
graph.change_h_spacing(old_settings["graph_h_spacing"],
old_settings["node_width"]/2,
new_settings["graph_h_spacing"],
new_settings["node_width"]/2)
if old_settings["graph_v_spacing"] != new_settings["graph_v_spacing"]:
for graph in self.graphs:
if old_settings["graph_v_spacing"]
!= new_settings["graph_v_spacing"]:
graph.change_v_spacing(old_settings["graph_v_spacing"],
old_settings["node_height"]/2,
new_settings["graph_v_spacing"],
......@@ -69,10 +70,8 @@ class GraphsManager(QObject):
# return graph associated with graph_index else None
def find_graph(self, graph_index):
for graph in self.graphs:
if graph.index == graph_index:
# return subscript
return graph
if graph_index in self.graphs:
return self.graphs[graph_index]
# not found
return None
......@@ -631,8 +631,8 @@ class GUIManager(QObject):
else:
# accept request
status, graphs = mp_code_io_manager.read_generated_json(
generated_json_text)
status, global_view_specification, graphs = \
mp_code_io_manager.read_generated_json(generated_json_text)
if status:
# log failure
......
......@@ -31,8 +31,9 @@ read_generated_json(generated_json):
import_gry_file(gry_filename):
import JGF Gryphon file. Return (status, metadata fields and graphs)
NOTE: RIGAL functions read/write in same directory, so we change to the
scratch RIGAL work directory to work, then move back when done.
export_gry_file(gry_filename, mp_code_text, scope,
selected_index, scale, x_slider, y_slider, graphs):
export JGF Gryphon file. Return status
"""
# read MP Code file. Return (status, mp_code_text)
......@@ -69,7 +70,8 @@ def save_mp_code_file(mp_code_text, mp_code_filename):
# NOTE: per request, removes underscores from node label
def read_generated_json(generated_json_text):
graphs = list()
graphs = dict()
global_view_specification = dict() # zz not populated yet
recommended_node_width = settings["node_width"]
recommended_node_height = settings["node_height"]
graph_h_spacing = settings["graph_h_spacing"]
......@@ -87,10 +89,11 @@ def read_generated_json(generated_json_text):
if "GLOBAL" in generated_json:
print("Implementation TBD for GLOBAL:%s"%generated_json["GLOBAL"])
i = 1
trace_index = 1
for trace in generated_json["traces"]:
nodes = list()
edges = list()
trace_views = dict()
# build graph from trace
# item 0: graph mark, "U" or "M"
......@@ -101,25 +104,25 @@ def read_generated_json(generated_json_text):
# item 2: event list of nodes
node_lookup = dict() # helper for connecting edges
json_nodes = trace[2]
json_nodes = trace[2] # event list
y_extra_say = 0
for json_node in json_nodes:
for json_node in json_nodes: # event in event list
# prepare label without underscores
label = json_node[0].replace('_'," ")
# prepare event name without underscores
event_name = json_node[0].replace('_'," ")
# get x and y, not available in older JSON
# compute x and y
x=json_node[3] * graph_h_spacing + recommended_node_width/2
y=json_node[4] * graph_v_spacing + recommended_node_height/2
if json_node[1] == "T": # SAY
# spread out SAY boxes more
y_extra_say += 22 * (len(label)//20)
y_extra_say += 22 * (len(event_name)//20)
y += y_extra_say
# y=json_node[4]*1.8*graph_v_spacing+recommended_node_height
# node_id, type, name, x, y, hide, collapse, collapse_below
node = Node("%s"%json_node[2], json_node[1],
label, x, y, False, False, False)
event_name, x, y, False, False, False)
nodes.append(node)
node_lookup[node.node_id] = node
......@@ -139,30 +142,39 @@ def read_generated_json(generated_json_text):
"%s"%json_edge[0], "",
node_lookup))
# items 5...n-1: the user-defined named relations
for user_defined_json_edges in trace[5:]:
if isinstance(user_defined_json_edges, list):
label = user_defined_json_edges[0]
for json_edge in user_defined_json_edges[1:]:
# source_id, relation, target_id, label
# items 5...: list of user-defined named relations and dict of views
for type_specific_element in trace[5:]:
if isinstance(type_specific_element, list):
relation_name = type_specific_element[0]
for json_edge in type_specific_element[1:]:
# source_id, relation, target_id, relation_name
edges.append(Edge("%s"%json_edge[0],
"USER_DEFINED",
"%s"%json_edge[1], label,
"%s"%json_edge[1], relation_name,
node_lookup))
elif isinstance(user_defined_json_edges, dict):
elif isinstance(type_specific_element, dict):
# VIEWS
for key, value in user_defined_json_edges.items():
for key, value in type_specific_element.items():
if value:
views[key] = value
print("Implementation TBD for %s:%s"%(key, value))
# build graph item from this
graph_item = GraphItem(i, graph_mark, trace_probability,
nodes, edges)
graphs.append(graph_item)
i += 1
return ("", graphs)
# # build graph item from this
# graph_item = GraphItem(trace_index, graph_mark, trace_probability,
# nodes, edges)
graph = {"index":trace_index,
"mark":graph_mark,
"probability":trace_probability,
"nodes":nodes,
"edges":edges,
"trace_views":trace_views,
"graph_item":None # calculated just-in-time
}
graphs[trace_index] = graph
trace_index += 1
return ("", global_view_specification, graphs)
except Exception as e:
print("Error reading generated JSON text: %s" % (repr(e)))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment