Repository: Qiskit/qiskit

Issue:
Better DAGDependency visualization 
### What should we add?

## Description

We need a better way to visualize `DAGDependency` objects, so that we can understand from the picture on which qubits each gate acts on.

As an example, consider the following:

```
from qiskit.circuit import QuantumCircuit
from qiskit.dagcircuit import DAGCircuit, DAGDependency
from qiskit.converters import circuit_to_dag, circuit_to_dagdependency
from qiskit.visualization import dag_drawer

qc = QuantumCircuit(4)
qc.h(0)
qc.cx(0, 1)
qc.cx(0, 2)
qc.cx(0, 3)
qc.x(3)
qc.h(3)
```

Drawing this `QuantumCircuit` with
```
qc.draw()
```
produces
![image](https://user-images.githubusercontent.com/7372836/199675922-84d966b3-fdf1-4ef3-9138-49ba2b93b17f.png)
from which it's very clear to understand on which qubits each gate acts on.

Converting `QuantumCircuit` to `DAGCircuit` and drawing it with
```
dag = circuit_to_dag(qc)
dag_drawer(dag)
```
produces
![image](https://user-images.githubusercontent.com/7372836/199676309-d0b68130-4bd6-4306-bd87-5262c39141f4.png)
and again it's easy to figure out on which qubits each gate acts on.

However, converting `QuantumCircuit` to `DAGDependency` and drawing it with
```
dagdep = circuit_to_dagdependency(qc)
dag_drawer(dagdep)
```
produces
![image](https://user-images.githubusercontent.com/7372836/199676607-656be349-b575-4df3-81de-82407af897f6.png)
from which it's almost impossible to figure out which gate is which, or what set of qubits it acts on.

## Request

It would be nice if for each gate in the last picture we could explicitly see the qubits it acts on.




Discussion:
Could you define the purpose of having a DAGDependency graph? (Given that we can have circuits printed and we can have the flowchart of unitary action printed out in the DAGCircuit)
The `DAGDependency` is an internal representation used in some transpiler passes as it far better represents the commutation relations between various gates than the `DAGCircuit` which is a data-flow graph.  The object already exists in Qiskit - this issue is just about improving its drawing.

Sasha: I think a lot of improvement could be achieved quite simply; the actual graph drawing right now is passable, it's just that the description function for nodes (and edges) makes some suboptimal choices.  The nodes could be improved a lot, for example, by just changing these lines: https://github.com/Qiskit/qiskit-terra/blob/3ce1737b2cb1b417c5bf9c41073d3ab4a664a732/qiskit/visualization/dag_visualization.py#L78-L103
I played around with the code a bit and I believe there is a fundamental problem here. There appears to be no "in" or "out" DAGDepNodes, which means no red or green nodes can be shown. And the edge that's passed to the edge display function is a dict that has {"commute": False} in it instead of a qubit or clbit. So none of the qubits shown in the DAG version on the wires are available.

I was able to make the output a little prettier, but no additional information can be shown without significant changes to the DAGDependency and DAGDepNode classes.

 
![image](https://github.com/Qiskit/qiskit/assets/16268251/fb2baeb5-809e-4b87-bc36-74bd8dcc14f8)

@alexanderivrii Actually after thinking this over, I realized there is a lot more info available, mainly in the qargs and cargs with each node. Also I didn't realize that these graphviz ovals would automatically expand with the size of the label. So I came up with this for this circuit.
```
from qiskit.circuit import QuantumCircuit
from qiskit.dagcircuit import DAGCircuit, DAGDependency
from qiskit.converters import circuit_to_dag, circuit_to_dagdependency
from qiskit.visualization import dag_drawer

qr = QuantumRegister(4, 'qr')
cr = ClassicalRegister(4, 'cr')
qc = QuantumCircuit(qr, cr)
qc.h(0)
qc.cx(0, 1)
qc.cx(0, 2)
qc.cx(0, 3)
qc.x(3).c_if(cr[0], 1)
qc.h(3)
qc.barrier(0, 1)
qc.measure(0, 1)
print(qc)
dag = circuit_to_dagdependency(qc)
dag.draw()
```
```
      ┌───┐                    ░           ┌─┐
qr_0: ┤ H ├──■────■────■───────░───────────┤M├
      └───┘┌─┴─┐  │    │       ░           └╥┘
qr_1: ─────┤ X ├──┼────┼───────░────────────╫─
           └───┘┌─┴─┐  │       ░            ║ 
qr_2: ──────────┤ X ├──┼────────────────────╫─
                └───┘┌─┴─┐   ┌───┐    ┌───┐ ║ 
qr_3: ───────────────┤ X ├───┤ X ├────┤ H ├─╫─
                     └───┘   └─╥─┘    └───┘ ║ 
                          ┌────╨─────┐      ║ 
cr: 4/════════════════════╡ cr_0=0x1 ╞══════╩═
                          └──────────┘      1 
```
![image](https://github.com/Qiskit/qiskit/assets/16268251/075cfa5c-72d2-4703-a9b4-fc17f65c98e5)

Let me know if there are any changes you'd like in color, info, etc.

Resolving patch:
diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py
--- a/qiskit/visualization/dag_visualization.py
+++ b/qiskit/visualization/dag_visualization.py
@@ -18,7 +18,9 @@
 from rustworkx.visualization import graphviz_draw
 
 from qiskit.dagcircuit.dagnode import DAGOpNode, DAGInNode, DAGOutNode
-from qiskit.circuit import Qubit
+from qiskit.circuit import Qubit, Clbit, ClassicalRegister
+from qiskit.circuit.classical import expr
+from qiskit.converters import dagdependency_to_circuit
 from qiskit.utils import optionals as _optionals
 from qiskit.exceptions import InvalidFileError
 from .exceptions import VisualizationError
@@ -72,32 +74,83 @@ def dag_drawer(dag, scale=0.7, filename=None, style="color"):
     # the two tradeoffs ere that it will not handle subclasses and it is
     # slower (which doesn't matter for a visualization function)
     type_str = str(type(dag))
+    register_bit_labels = {
+        bit: f"{reg.name}[{idx}]"
+        for reg in list(dag.qregs.values()) + list(dag.cregs.values())
+        for (idx, bit) in enumerate(reg)
+    }
     if "DAGDependency" in type_str:
+        # pylint: disable=cyclic-import
+        from qiskit.visualization.circuit._utils import get_bit_reg_index
+
+        qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)}
+        clbit_indices = {bit: index for index, bit in enumerate(dag.clbits)}
         graph_attrs = {"dpi": str(100 * scale)}
+        dag_dep_circ = dagdependency_to_circuit(dag)
 
         def node_attr_func(node):
             if style == "plain":
                 return {}
             if style == "color":
                 n = {}
-                n["label"] = str(node.node_id) + ": " + str(node.name)
-                if node.name == "measure":
-                    n["color"] = "blue"
-                    n["style"] = "filled"
-                    n["fillcolor"] = "lightblue"
+                args = []
+                for count, arg in enumerate(node.qargs + node.cargs):
+                    if count > 4:
+                        args.append("...")
+                        break
+                    if isinstance(arg, Qubit):
+                        f_str = f"q_{qubit_indices[arg]}"
+                    elif isinstance(arg, Clbit):
+                        f_str = f"c_{clbit_indices[arg]}"
+                    else:
+                        f_str = f"{arg.index}"
+                    arg_str = register_bit_labels.get(arg, f_str)
+                    args.append(arg_str)
+
+                n["color"] = "black"
+                n["label"] = (
+                    str(node.node_id)
+                    + ": "
+                    + str(node.name)
+                    + " ("
+                    + str(args)[1:-1].replace("'", "")
+                    + ")"
+                )
                 if node.name == "barrier":
-                    n["color"] = "black"
                     n["style"] = "filled"
-                    n["fillcolor"] = "green"
-                if getattr(node.op, "_directive", False):
-                    n["color"] = "black"
+                    n["fillcolor"] = "grey"
+                elif getattr(node.op, "_directive", False):
                     n["style"] = "filled"
                     n["fillcolor"] = "red"
-                if getattr(node.op, "condition", None):
-                    n["label"] = str(node.node_id) + ": " + str(node.name) + " (conditional)"
-                    n["color"] = "black"
+                elif getattr(node.op, "condition", None):
+                    condition = node.op.condition
+                    if isinstance(condition, expr.Expr):
+                        cond_txt = " (cond: [Expr]) ("
+                    elif isinstance(condition[0], ClassicalRegister):
+                        cond_txt = f" (cond: {condition[0].name}, {int(condition[1])}) ("
+                    else:
+                        register, bit_index, reg_index = get_bit_reg_index(
+                            dag_dep_circ, condition[0]
+                        )
+                        if register is not None:
+                            cond_txt = (
+                                f" (cond: {register.name}[{reg_index}], {int(condition[1])}) ("
+                            )
+                        else:
+                            cond_txt = f" (cond: {bit_index}, {int(condition[1])}) ("
                     n["style"] = "filled"
-                    n["fillcolor"] = "lightgreen"
+                    n["fillcolor"] = "green"
+                    n["label"] = (
+                        str(node.node_id)
+                        + ": "
+                        + str(node.name)
+                        + cond_txt
+                        + str(args)[1:-1].replace("'", "")
+                        + ")"
+                    )
+                elif node.name != "measure":  # measure is unfilled
+                    n["style"] = "filled"
+                    n["fillcolor"] = "lightblue"
                 return n
             else:
                 raise VisualizationError("Unrecognized style %s for the dag_drawer." % style)
@@ -105,12 +158,6 @@ def node_attr_func(node):
         edge_attr_func = None
 
     else:
-        register_bit_labels = {
-            bit: f"{reg.name}[{idx}]"
-            for reg in list(dag.qregs.values()) + list(dag.cregs.values())
-            for (idx, bit) in enumerate(reg)
-        }
-
         graph_attrs = {"dpi": str(100 * scale)}
 
         def node_attr_func(node):


Test patch:
diff --git a/test/python/visualization/references/dag_dep.png b/test/python/visualization/references/dag_dep.png
new file mode 100644
Binary files /dev/null and b/test/python/visualization/references/dag_dep.png differ
diff --git a/test/python/visualization/references/dag_no_reg.png b/test/python/visualization/references/dag_no_reg.png
Binary files a/test/python/visualization/references/dag_no_reg.png and b/test/python/visualization/references/dag_no_reg.png differ
diff --git a/test/python/visualization/test_dag_drawer.py b/test/python/visualization/test_dag_drawer.py
--- a/test/python/visualization/test_dag_drawer.py
+++ b/test/python/visualization/test_dag_drawer.py
@@ -16,11 +16,11 @@
 import tempfile
 import unittest
 
-from qiskit.circuit import QuantumRegister, QuantumCircuit, Qubit, Clbit
+from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit, Qubit, Clbit
 from qiskit.visualization import dag_drawer
 from qiskit.exceptions import InvalidFileError
 from qiskit.visualization import VisualizationError
-from qiskit.converters import circuit_to_dag
+from qiskit.converters import circuit_to_dag, circuit_to_dagdependency
 from qiskit.utils import optionals as _optionals
 from .visualization import path_to_diagram_reference, QiskitVisualizationTestCase
 
@@ -79,7 +79,34 @@ def test_dag_drawer_no_register(self):
             dag_drawer(dag, filename=tmp_path)
             image_ref = path_to_diagram_reference("dag_no_reg.png")
             image = Image.open(tmp_path)
-            self.assertImagesAreEqual(image, image_ref, 0.2)
+            self.assertImagesAreEqual(image, image_ref, 0.1)
+
+    @unittest.skipUnless(_optionals.HAS_GRAPHVIZ, "Graphviz not installed")
+    @unittest.skipUnless(_optionals.HAS_PIL, "PIL not installed")
+    def test_dag_drawer_with_dag_dep(self):
+        """Test dag dependency visualization."""
+        from PIL import Image  # pylint: disable=import-error
+
+        bits = [Qubit(), Clbit()]
+        qr = QuantumRegister(4, "qr")
+        cr = ClassicalRegister(4, "cr")
+        qc = QuantumCircuit(qr, bits, cr)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+        qc.cx(0, 3)
+        qc.x(3).c_if(cr[1], 1)
+        qc.h(3)
+        qc.x(4)
+        qc.barrier(0, 1)
+        qc.measure(0, 0)
+        dag = circuit_to_dagdependency(qc)
+        with tempfile.TemporaryDirectory() as tmpdirname:
+            tmp_path = os.path.join(tmpdirname, "dag_d.png")
+            dag_drawer(dag, filename=tmp_path)
+            image_ref = path_to_diagram_reference("dag_dep.png")
+            image = Image.open(tmp_path)
+            self.assertImagesAreEqual(image, image_ref, 0.1)
 
 
 if __name__ == "__main__":