Skip to content

Commit

Permalink
Potential fix to the tooltip with special characters bug (#1203)
Browse files Browse the repository at this point in the history
* Potential solution

* Push update

* Fixed linting issues

* Fix to linting issues

* minor fix

* using serde_json instead of manually escaping

* fix import, and added test case

* Updated test logic

* updated test to check to_dot

* fix clippy issues

* created release notes

* fix release notes

* fix linting issue in test

* remove unused import

* fixing quotes issue

* fixing "\" issue and updating tests

* updating more test to have quotes

* Update dot_utils.rs

* Apply suggestions from code review

* Collect vector of results in Rust

* Fix error handling

* Update releasenotes/notes/fix-graphviz-draw-tooltip-3f697d71c4b79e60.yaml

* Update releasenotes/notes/fix-graphviz-draw-tooltip-3f697d71c4b79e60.yaml

* Update releasenotes/notes/fix-graphviz-draw-tooltip-3f697d71c4b79e60.yaml

---------

Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com>
  • Loading branch information
anushkrishnav and IvanIsCoding committed Jun 11, 2024
1 parent 77474a2 commit 70b7126
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 20 deletions.
14 changes: 14 additions & 0 deletions releasenotes/notes/fix-graphviz-draw-tooltip-3f697d71c4b79e60.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
fixes:
- |
:func:`.graphviz_draw` can now handle special characters
.. jupyter-execute::
import rustworkx as rx
from rustworkx.visualization import graphviz_draw
graphviz_draw(
rx.generators.path_graph(2),
node_attr_fn=lambda x: {"label": "the\nlabel", "tooltip": "the\ntooltip"},
)
15 changes: 8 additions & 7 deletions src/dot_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,18 @@ fn attr_map_to_string<'a>(
if attrs.is_empty() {
return Ok("".to_string());
}

let attr_string = attrs
.iter()
.map(|(key, value)| {
if key == "label" {
format!("{}=\"{}\"", key, value)
} else {
format!("{}={}", key, value)
}
let escaped_value = serde_json::to_string(value).map_err(|_err| {
pyo3::exceptions::PyValueError::new_err("could not escape character")
})?;
let escaped_value = &escaped_value.get(1..escaped_value.len() - 1).ok_or(
pyo3::exceptions::PyValueError::new_err("could not escape character"),
)?;
Ok(format!("{}=\"{}\"", key, escaped_value))
})
.collect::<Vec<String>>()
.collect::<PyResult<Vec<String>>>()?
.join(", ");
Ok(format!("[{}]", attr_string))
}
6 changes: 3 additions & 3 deletions tests/digraph/test_dot.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ def test_digraph_to_dot_to_file(self):
)
graph.add_edge(0, 1, dict(label="1", name="1"))
expected = (
'digraph {\n0 [color=black, fillcolor=green, label="a", '
'style=filled];\n1 [color=black, fillcolor=red, label="a", '
'style=filled];\n0 -> 1 [label="1", name=1];\n}\n'
'digraph {\n0 [color="black", fillcolor="green", label="a", '
'style="filled"];\n1 [color="black", fillcolor="red", label="a", '
'style="filled"];\n0 -> 1 [label="1", name="1"];\n}\n'
)
res = graph.to_dot(lambda node: node, lambda edge: edge, filename=self.path)
self.addCleanup(os.remove, self.path)
Expand Down
18 changes: 9 additions & 9 deletions tests/graph/test_dot.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ def test_graph_to_dot(self):
)
graph.add_edge(0, 1, dict(label="1", name="1"))
expected = (
'graph {\n0 [color=black, fillcolor=green, label="a", style=filled'
'];\n1 [color=black, fillcolor=red, label="a", style=filled];'
'\n0 -- 1 [label="1", name=1];\n}\n'
'graph {\n0 [color="black", fillcolor="green", label="a", style="filled"'
'];\n1 [color="black", fillcolor="red", label="a", style="filled"];'
'\n0 -- 1 [label="1", name="1"];\n}\n'
)
res = graph.to_dot(lambda node: node, lambda edge: edge)
self.assertEqual(expected, res)
Expand All @@ -70,9 +70,9 @@ def test_digraph_to_dot(self):
)
graph.add_edge(0, 1, dict(label="1", name="1"))
expected = (
'digraph {\n0 [color=black, fillcolor=green, label="a", '
'style=filled];\n1 [color=black, fillcolor=red, label="a", '
'style=filled];\n0 -> 1 [label="1", name=1];\n}\n'
'digraph {\n0 [color="black", fillcolor="green", label="a", '
'style="filled"];\n1 [color="black", fillcolor="red", label="a", '
'style="filled"];\n0 -> 1 [label="1", name="1"];\n}\n'
)
res = graph.to_dot(lambda node: node, lambda edge: edge)
self.assertEqual(expected, res)
Expand All @@ -97,9 +97,9 @@ def test_graph_to_dot_to_file(self):
)
graph.add_edge(0, 1, dict(label="1", name="1"))
expected = (
'graph {\n0 [color=black, fillcolor=green, label="a", '
'style=filled];\n1 [color=black, fillcolor=red, label="a", '
'style=filled];\n0 -- 1 [label="1", name=1];\n}\n'
'graph {\n0 [color="black", fillcolor="green", label="a", '
'style="filled"];\n1 [color="black", fillcolor="red", label="a", '
'style="filled"];\n0 -- 1 [label="1", name="1"];\n}\n'
)
res = graph.to_dot(lambda node: node, lambda edge: edge, filename=self.path)
self.addCleanup(os.remove, self.path)
Expand Down
34 changes: 33 additions & 1 deletion tests/visualization/test_graphviz.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import subprocess
import tempfile
import unittest

import rustworkx
from rustworkx.visualization import graphviz_draw

Expand Down Expand Up @@ -150,3 +149,36 @@ def test_filename(self):
self.assertTrue(os.path.isfile("test_graphviz_filename.svg"))
if not SAVE_IMAGES:
self.addCleanup(os.remove, "test_graphviz_filename.svg")

def test_escape_sequences(self):
# Create a simple graph
graph = rustworkx.generators.path_graph(2)

escape_sequences = {
"\\n": "\n", # Newline
"\\t": "\t", # Horizontal tab
"\\'": "'", # Single quote
'\\"': '"', # Double quote
"\\\\": "\\", # Backslash
"\\r": "\r", # Carriage return
"\\b": "\b", # Backspace
"\\f": "\f", # Form feed
}

for escaped_seq, raw_seq in escape_sequences.items():

def node_attr(node):
"""
Define node attributes including escape sequences for labels and tooltips.
"""
label = f"label{escaped_seq}"
tooltip = f"tooltip{escaped_seq}"
return {"label": label, "tooltip": tooltip}

# Draw the graph using graphviz_draw
dot_str = graph.to_dot(node_attr)

# Assert that the escape sequences are correctly placed and escaped in the dot string
self.assertIn(
escaped_seq, dot_str, f"Escape sequence {escaped_seq} not found in dot output"
)

0 comments on commit 70b7126

Please sign in to comment.