def test_SaveGraph_0_normal_graph(self): saved_g = Graph() v1 = Vertex("1", "black") v2 = Vertex("2", "white") v3 = Vertex("3", "black") v4 = Vertex("4", "white") v5 = Vertex("5", "black") saved_g.add_vert(v1) saved_g.add_vert(v2) saved_g.add_vert(v3) saved_g.add_vert(v4) saved_g.add_vert(v5) saved_g.add_edge(v1.id, v2.id) saved_g.add_edge(v1.id, v3.id) saved_g.add_edge(v2.id, v3.id) saved_g.add_edge(v3.id, v4.id) saved_xml_str = save_graph(saved_g, "SaveGraph_0_saved_normal_graph.xml") # Note: testing the string against a model is tedious because the order # of edges varies. # Test loaded graph loaded_g = load_graph("SaveGraph_0_saved_normal_graph.xml") self.assertEqual(loaded_g.get_vert_count(), 5) self.assertEqual(loaded_g.get_edge_count(), 4) self.assertTrue(loaded_g.has_vert(v1.id)) self.assertTrue(loaded_g.has_vert(v2.id)) self.assertTrue(loaded_g.has_vert(v3.id)) self.assertTrue(loaded_g.has_vert(v4.id)) self.assertTrue(loaded_g.has_vert(v5.id)) self.assertTrue(loaded_g.has_edge(v1.id, v2.id)) self.assertTrue(loaded_g.has_edge(v1.id, v3.id)) self.assertTrue(loaded_g.has_edge(v2.id, v3.id)) self.assertTrue(loaded_g.has_edge(v3.id, v4.id)) self.assertFalse(loaded_g.has_edge(v2.id, v4.id)) self.assertEqual(loaded_g.get_vert(v1.id).color, "black") self.assertEqual(loaded_g.get_vert(v2.id).color, "white") self.assertEqual(loaded_g.get_vert(v3.id).color, "black") self.assertEqual(loaded_g.get_vert(v4.id).color, "white") self.assertEqual(loaded_g.get_vert(v5.id).color, "black") self.assertEqual(loaded_g.neighbors(v1.id), {v2, v3}) self.assertEqual(loaded_g.neighbors(v2.id), {v3, v1}) self.assertEqual(loaded_g.neighbors(v3.id), {v1, v2, v4}) self.assertEqual(loaded_g.neighbors(v4.id), {v3}) self.assertEqual(loaded_g.neighbors(v5.id), set())
def test_SaveGraph_1_empty_graph(self): saved_g = Graph() saved_xml_str = save_graph(saved_g, "SaveGraph_1_empty_graph.xml") empty_graph_xml_str = '<?xml version="1.0" ?>\n<graph/>\n' self.assertEqual(saved_xml_str, empty_graph_xml_str) loaded_g = load_graph("SaveGraph_1_empty_graph.xml") self.assertEqual(loaded_g.get_vert_count(), 0) self.assertEqual(loaded_g.get_edge_count(), 0) # Add some stuff to the graph v1 = Vertex("1", "black") v2 = Vertex("2", "white") loaded_g.add_vert(v1) loaded_g.add_vert(v2) loaded_g.add_edge("1", "2") saved_xml_str = save_graph(loaded_g, "SaveGraph_1_empty_graph.xml") reloaded_g = load_graph("SaveGraph_1_empty_graph.xml") self.assertEqual(reloaded_g.get_vert_count(), 2) self.assertEqual(reloaded_g.get_edge_count(), 1) self.assertTrue(reloaded_g.has_vert("1")) self.assertTrue(reloaded_g.has_vert("2")) self.assertTrue(reloaded_g.has_edge("2", "1")) self.assertEqual(reloaded_g.get_vert("1").color, "black") self.assertEqual(reloaded_g.get_vert("2").color, "white") # Remove the stuff reloaded_g.del_vert("1") reloaded_g.del_vert("2") saved_xml_str = save_graph(reloaded_g, "SaveGraph_1_empty_graph.xml") self.assertEqual(saved_xml_str, empty_graph_xml_str)
def test_LoadGoodXML_1_good_colors(self): correct_g = Graph() v1 = Vertex("1", "white") v2 = Vertex("2", "black") v3 = Vertex("3", "white") v4 = Vertex("4", "white") v5 = Vertex("5", "white") v6 = Vertex("6", "white") v7 = Vertex("7", "white") v8 = Vertex("8", "black") v9 = Vertex("9", "black") correct_g.add_vert(v1) correct_g.add_vert(v2) correct_g.add_vert(v3) correct_g.add_vert(v4) correct_g.add_vert(v5) correct_g.add_vert(v6) correct_g.add_vert(v7) correct_g.add_vert(v8) correct_g.add_vert(v9) correct_g.add_edge(v1.id, v2.id) correct_g.add_edge(v1.id, v3.id) correct_g.add_edge(v2.id, v3.id) correct_g.add_edge(v3.id, v4.id) parsed_g = load_graph("LoadGoodXML_1_good_color.xml") self.assertTrue(parsed_g == correct_g) self.assertEqual(parsed_g.get_vert(v1.id).color, "white") self.assertEqual(parsed_g.get_vert(v2.id).color, "black") self.assertEqual(parsed_g.get_vert(v3.id).color, "white") self.assertEqual(parsed_g.get_vert(v4.id).color, "white") self.assertEqual(parsed_g.get_vert(v5.id).color, "white") self.assertEqual(parsed_g.get_vert(v6.id).color, "white") self.assertEqual(parsed_g.get_vert(v7.id).color, "white") self.assertEqual(parsed_g.get_vert(v8.id).color, "black") self.assertEqual(parsed_g.get_vert(v9.id).color, "black")
def clicked_vertex_or_go_or_turn_button(clickData, go_clicks, load_clicks, displayed_fig, k_curr, path, curr_turn): """ This callback handles reaction to clicking: - a vertex in the graph - the "GO" button to advance the turn - the "Load" button to import a graph All three events are lumped together in this callback because all share Output("displayed-graph", "figure"), and an Output can only be assigned to one callback. """ global loaded_hr_graph global clicked_verts_this_turn global node_positions ctx = dash.callback_context thing_clicked = ctx.triggered[0]["prop_id"].split(".")[0] k_nums = [int(i) for i in k_curr.split("/")] turn_num = int(curr_turn[6:]) normal_k_color = { "display": "inline-block", "color": "black", "background": "white" } inverted_k_color = { "display": "inline-block", "color": "white", "background": "black" } ret_color = normal_k_color # Clicked graph vertex ################################################### if (thing_clicked == "displayed-graph") and (clickData is not None): clicked_vert_color = clickData["points"][0]["marker.color"] clicked_vert_id = clickData["points"][0]["text"] ############################# # Flip clicked vertex color # ############################# if clicked_vert_color == "black": new_color = "white" else: new_color = "black" displayed_fig["data"][0]["marker"]["color"][clickData["points"][0]["pointNumber"]] = new_color #################### # Update k readout # #################### if clicked_vert_id not in clicked_verts_this_turn: # If clicking this vertex for the first time (its id is not in # clicked_verts_this_turn), increment k # and store new_color (for reference when recoloring # loaded_hr_graph later) clicked_verts_this_turn[clicked_vert_id] = new_color if len(k_nums) == 1: # Still on first turn; k is shown as a single number with # no denominator. ret_str = f"{k_nums[0] + 1}" elif len(k_nums) == 2: # On turn 2 or greater; show k as n/d where n is the d from # previous turn and n is verts clicked this turn ret_str = f"{k_nums[0] + 1} / {k_nums[1]}" # Invert k background and color if n != d # (This serves as a visual aid so the user clicks the # same number of vertices each turn) if (k_nums[0] + 1) != k_nums[1]: ret_color = inverted_k_color else: ret_color = normal_k_color else: # If this vertex was already clicked (its id is already in # clicked_verts_this_turn), decrement k. clicked_verts_this_turn.pop(clicked_vert_id) if len(k_nums) == 1: ret_str = f"{k_nums[0] - 1}" elif len(k_nums) == 2: ret_str = f"{k_nums[0] - 1 } / {k_nums[1]}" if (k_nums[0] - 1) != k_nums[1]: ret_color = inverted_k_color else: ret_color = normal_k_color return displayed_fig, ret_str, ret_color, False, "", curr_turn # Clicked GO button ######################################################## elif (thing_clicked == "go-button") and (go_clicks): ################################ # Recolor graph and update fig # ################################ for id in clicked_verts_this_turn.keys(): loaded_hr_graph.get_vert(id).color = clicked_verts_this_turn[id] hr_logic.recolor(loaded_hr_graph) new_fig = figure_from_hr_graph(loaded_hr_graph) #################### # Update k readout # #################### clicked_verts_this_turn.clear() # Show n/d where n is 0 and d is n from previous turn if k_nums[0] == 0: ret_color = normal_k_color else: ret_color = inverted_k_color return new_fig, f"0 / {k_nums[0]}", ret_color, False, "", f"Turn: {turn_num + 1}" # Clicked Load button ###################################################### elif (thing_clicked == "load-button") and (load_clicks): try: loaded_hr_graph = hr_io.load_graph(path) except Exception as e: return displayed_fig, k_curr, normal_k_color, True, repr(e), curr_turn node_positions = None # Wipe positions to regenerate for new graph new_fig = figure_from_hr_graph(loaded_hr_graph) return new_fig, k_curr, normal_k_color, False, "", "Turn: 0" # Nothing clicked; standard Dash trigger of all callbacks at startup ####### else: return displayed_fig, k_curr, normal_k_color, False, "", curr_turn
def test_LoadBadXML_9_bad_color(self): with self.assertRaises(RuntimeError): parsed_g = load_graph("LoadBadXML_9_bad_color.xml")
def test_LoadBadXML_6_invalid_elt(self): with self.assertRaises(RuntimeError): parsed_g = load_graph("LoadBadXML_6_invalid_elt.xml")
def test_LoadBadXML_8_bad_vert_elt(self): with self.assertRaises(RuntimeError): parsed_g = load_graph("LoadBadXML_8_bad_edge_elt.xml")
def test_LoadBadXML_3_bad_edge_id_att(self): with self.assertRaises(RuntimeError): parsed_g = load_graph("LoadBadXML_3_bad_edge_id_att.xml")
def test_LoadBadXML_4_missing_file(self): with self.assertRaises(FileNotFoundError): parsed_g = load_graph("thisfiledoesnotexist")
def test_LoadBadXML_0_missing_vert_id_att(self): with self.assertRaises(RuntimeError): parsed_g = load_graph("LoadBadXML_0_missing_vert_id_att.xml")
def test_LoadGoodXML_5_bad_graph_edge_to_missing_vert(self): parsed_g = load_graph("LoadGoodXML_5_empty.xml") self.assertEqual(parsed_g.get_vert_count(), 0) self.assertEqual(parsed_g.get_edge_count(), 0)
def test_LoadGoodXML_0_normal(self): correct_g = Graph() v1 = Vertex("1", 0) v2 = Vertex("2", 0) v3 = Vertex("3", 0) v4 = Vertex("4", 0) v5 = Vertex("5", 0) correct_g.add_vert(v1) correct_g.add_vert(v2) correct_g.add_vert(v3) correct_g.add_vert(v4) correct_g.add_vert(v5) correct_g.add_edge(v1.id, v2.id) correct_g.add_edge(v1.id, v3.id) correct_g.add_edge(v2.id, v3.id) correct_g.add_edge(v3.id, v4.id) # Sanity check self.assertEqual(correct_g.get_vert_count(), 5) self.assertEqual(correct_g.get_edge_count(), 4) self.assertTrue(correct_g.has_vert(v1.id)) self.assertTrue(correct_g.has_vert(v2.id)) self.assertTrue(correct_g.has_vert(v3.id)) self.assertTrue(correct_g.has_vert(v4.id)) self.assertTrue(correct_g.has_vert(v5.id)) self.assertTrue(correct_g.has_edge(v1.id, v2.id)) self.assertTrue(correct_g.has_edge(v1.id, v3.id)) self.assertTrue(correct_g.has_edge(v2.id, v3.id)) self.assertTrue(correct_g.has_edge(v3.id, v4.id)) self.assertFalse(correct_g.has_edge(v2.id, v4.id)) self.assertEqual(correct_g.get_vert(v1.id).color, 0) self.assertEqual(correct_g.get_vert(v2.id).color, 0) self.assertEqual(correct_g.get_vert(v3.id).color, 0) self.assertEqual(correct_g.get_vert(v4.id).color, 0) self.assertEqual(correct_g.get_vert(v5.id).color, 0) self.assertEqual(correct_g.neighbors(v1.id), {v2, v3}) self.assertEqual(correct_g.neighbors(v2.id), {v3, v1}) self.assertEqual(correct_g.neighbors(v3.id), {v1, v2, v4}) self.assertEqual(correct_g.neighbors(v4.id), {v3}) self.assertEqual(correct_g.neighbors(v5.id), set()) # Parse XML file parsed_g = load_graph("LoadGoodXML_0_normal.xml") # g1 == g2 if they have same edges and same vertices by id # (even if vertices differ otherwise) self.assertTrue(parsed_g == correct_g) self.assertEqual(parsed_g.get_vert(v1.id).color, "black") self.assertEqual(parsed_g.get_vert(v2.id).color, "black") self.assertEqual(parsed_g.get_vert(v3.id).color, "black") self.assertEqual(parsed_g.get_vert(v4.id).color, "black") self.assertEqual(parsed_g.get_vert(v5.id).color, "black") # Modify edge or vert inventory to break equality correct_g.del_edge("1", "2") self.assertTrue(parsed_g != correct_g)
def test_LoadGoodXML_4_bad_graph_edge_to_missing_vert(self): with self.assertRaises(KeyError): parsed_g = load_graph("LoadGoodXML_4_bad_graph_edge_to_missing_vert.xml")
def test_LoadGoodXML_3_bad_graph_repeated_edge(self): with self.assertRaises(ValueError): parsed_g = load_graph("LoadGoodXML_3_bad_graph_repeated_edge.xml")