qiskittutorialvisualizationcircuitstools

Qiskit Circuit Visualization: circuit.draw(), plot_histogram, and Bloch Sphere

quantumcomputer.dev
quantumcomputer.dev
May 27, 2026 · 23 views

Qiskit's visualization tools are one of its strongest features. circuit.draw() alone has three backends (text, matplotlib, LaTeX) and a dozen configuration options. plot_histogram turns measurement counts into readable bar charts. plot_bloch_multivector renders full statevector information as Bloch sphere projections per qubit. This guide covers each tool with actual API calls and explains what you see in the output.

Installation

pip install qiskit qiskit-aer matplotlib pylatexenc

pylatexenc is required only for the LaTeX circuit backend. matplotlib is required for all graphical output.

circuit.draw(): Three Backends

QuantumCircuit.draw() renders a circuit diagram. The three backends produce different output formats suitable for different contexts.

Text Backend

The default when no output format is specified. Returns ASCII art suitable for terminal output and log files.

from qiskit import QuantumCircuit

qc = QuantumCircuit(3, 3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure([0, 1, 2], [0, 1, 2])

print(qc.draw())

Output:

     ┌───┐           ┌─┐
q_0: ┤ H ├──■────────┤M├──────
     └───┘┌─┴─┐      └╥┘┌─┐
q_1: ─────┤ X ├──■───╫─┤M├───
          └───┘┌─┴─┐ ║ └╥┘┌─┐
q_2: ──────────┤ X ├─╫──╫─┤M├
               └───┘ ║  ║ └╥┘
c: 3/════════════════╩══╩══╩═
                     0  1  2

This is a GHZ state preparation circuit: Hadamard on q₀ followed by two CNOTs creating (|000⟩ + |111⟩)/√2.

Useful text backend options:

# Fold at 80 characters for wide circuits
qc.draw(output='text', fold=80)

# Show initial qubit states
qc.draw(output='text', initial_state=True)

# Show barrier markers
qc.draw(output='text', plot_barriers=True)

# Reverse qubit order (some physics conventions place q₀ at bottom)
qc.draw(output='text', reverse_bits=True)

Matplotlib Backend

Returns a matplotlib.figure.Figure. Use this for Jupyter notebooks, documentation, and saving to file.

fig = qc.draw(output='mpl')
fig.savefig('circuit.png', dpi=150, bbox_inches='tight')

Key configuration options:

fig = qc.draw(
    output='mpl',
    style={
        'backgroundcolor': '#FFFFFF',
        'fontsize': 12,
    },
    fold=20,            # Wrap circuit after 20 gate columns
    scale=0.8,          # Scale factor (default 1.0)
    initial_state=True  # Show |0> labels on qubits
)

Qiskit ships several built-in color styles:

# Available: 'default', 'bw', 'clifford', 'iqx', 'iqx-dark', 'textbook'
fig = qc.draw(output='mpl', style='iqx-dark')

The iqx-dark style matches IBM Quantum's dark UI and is well-suited for presentations.

LaTeX Backend

Renders to high-quality vector output using LaTeX. Requires a working LaTeX distribution (TeX Live, MiKTeX) and pylatexenc.

# Display inline in Jupyter as a rendered image
fig = qc.draw(output='latex')

# Save the LaTeX source
qc.draw(output='latex_source', filename='circuit.tex')

LaTeX output is the highest quality option for papers and presentations. The circuit symbols are vector graphics, scale cleanly, and match standard quantum circuit notation from Nielsen and Chuang.

Inspecting Circuit Properties Before Drawing

Before drawing, you often want to measure a circuit's key properties:

from qiskit import QuantumCircuit

qc = QuantumCircuit(3, 3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure([0, 1, 2], [0, 1, 2])

print(f"Qubits:    {qc.num_qubits}")
print(f"Depth:     {qc.depth()}")
print(f"Gate ops:  {qc.count_ops()}")
print(f"Width:     {qc.width()}")

Output:

Qubits:    3
Depth:     4
Gate ops:  OrderedDict([('h', 1), ('cx', 2), ('measure', 3)])
Width:     6

qc.depth() returns the critical path length, not the total gate count. Depth is the metric that matters for noise: longer depth means more time for decoherence to accumulate.

You can iterate over gates programmatically:

for instruction in qc.data:
    gate = instruction.operation
    qubits = [qc.find_bit(q).index for q in instruction.qubits]
    print(f"{gate.name:10s} on qubits {qubits}")

Output:

h          on qubits [0]
cx         on qubits [0, 1]
cx         on qubits [1, 2]
measure    on qubits [0]
measure    on qubits [1]
measure    on qubits [2]

plot_histogram: Visualizing Measurement Results

plot_histogram takes a dict of measurement outcome strings to counts and produces a bar chart.

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram

qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])

sim = AerSimulator()
counts = sim.run(qc, shots=2048).result().get_counts()
print(counts)
# {'00': 1018, '11': 1030}

fig = plot_histogram(counts, title='Bell State Measurement Results')
fig.savefig('histogram.png', dpi=150)

Comparing Multiple Result Sets

plot_histogram accepts a list of dicts to overlay multiple distributions. This is useful for comparing ideal simulation versus noisy simulation, or comparing circuits before and after optimization:

from qiskit_aer.noise import NoiseModel
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit import transpile

# Ideal simulation
counts_ideal = AerSimulator().run(qc, shots=2048).result().get_counts()

# Noisy simulation using real device calibration
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")
noise_model = NoiseModel.from_backend(backend)
sim_noisy = AerSimulator(noise_model=noise_model)
counts_noisy = sim_noisy.run(
    transpile(qc, sim_noisy), shots=2048
).result().get_counts()

fig = plot_histogram(
    [counts_ideal, counts_noisy],
    legend=['Ideal', 'With device noise'],
    title='Bell State: Ideal vs Noisy Simulation',
    bar_labels=True,
    figsize=(10, 5),
)
fig.savefig('comparison.png', dpi=150)

The noisy simulation will show small |01⟩ and |10⟩ bars corresponding to CNOT gate errors and readout errors. This is a direct visualization of the noise budget for your circuit on that specific device.

plot_bloch_multivector: Statevector on the Bloch Sphere

The Bloch sphere maps a single qubit's state to a point on a unit sphere. |0⟩ sits at the north pole, |1⟩ at the south pole, and superpositions lie on the equatorial surface. plot_bloch_multivector renders one sphere per qubit.

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector

# Bell state (no measurement, to keep the statevector)
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)

sv = Statevector.from_instruction(qc)
print(sv)
# Statevector([0.707+0j, 0, 0, 0.707+0j])
# This is (|00> + |11>)/sqrt(2)

fig = plot_bloch_multivector(sv)
fig.savefig('bloch.png', dpi=150)

For the Bell state, the Bloch sphere visualization shows both qubits at the center of their respective spheres (zero Bloch vector). This is the signature of entanglement: individually, each qubit is in a maximally mixed state. The two-qubit system is in a pure state, but neither qubit has a definite spin direction when viewed in isolation.

For contrast, an unentangled qubit in the |+⟩ state:

qc = QuantumCircuit(1)
qc.h(0)

sv = Statevector.from_instruction(qc)
fig = plot_bloch_multivector(sv)

The sphere shows the qubit pointing along the +X axis, confirming the |+⟩ = (|0⟩ + |1⟩)/√2 state.

plot_state_city: Density Matrix Visualization

For mixed states (which arise from noise or from tracing out entangled qubits), the full density matrix carries the complete state information. plot_state_city renders it as a 3D bar chart:

from qiskit.quantum_info import DensityMatrix
from qiskit.visualization import plot_state_city

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)

dm = DensityMatrix.from_instruction(qc)
fig = plot_state_city(dm, title="Bell State Density Matrix")
fig.savefig('density_matrix.png', dpi=150)

The Bell state density matrix has four non-zero elements:

  • dm[0][0] = dm[3][3] = 0.5 (the diagonal, corresponding to |00⟩ and |11⟩ probabilities)

  • dm[0][3] = dm[3][0] = 0.5 (the off-diagonal coherences)

The off-diagonal coherences are what distinguish a genuine quantum superposition from a classical 50/50 mixture. Decoherence destroys these off-diagonal terms. A fully decohered Bell state has density matrix diag(0.5, 0, 0, 0.5), which is indistinguishable from a classical coin flip.

Transpiled Circuit Visualization

When you submit a circuit to real hardware, Qiskit transpiles it to the device's native gate set and qubit connectivity. Visualizing the transpiled circuit shows what actually executes on hardware:

from qiskit import transpile
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")

qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()

transpiled = transpile(qc, backend=backend, optimization_level=3)

print(f"Original:    {qc.count_ops()}, depth {qc.depth()}")
print(f"Transpiled:  {transpiled.count_ops()}, depth {transpiled.depth()}")

# Draw both for comparison
fig_original = qc.draw(output='mpl')
fig_transpiled = transpiled.draw(output='mpl', fold=30)

Transpilation replaces abstract gates with native gates specific to the device (ECR on IBM Eagle and Heron, for example). The transpiled circuit may have more gates than the original, but they directly correspond to physical pulses on the hardware.

Optimization level 3 applies the most aggressive rewriting rules and can significantly reduce circuit depth at the cost of longer compilation time. For hardware submission, always use optimization level 2 or 3.

Working with Circuits from the Registry

Every circuit in the quantumcomputer.dev registry is stored as OpenQASM 2.0. You can export any circuit from the registry and load it directly into Qiskit:

from qiskit import QuantumCircuit
from qiskit.visualization import plot_histogram
from qiskit_aer import AerSimulator

# Load OpenQASM exported from the registry
qasm_string = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0], q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];
"""

qc = QuantumCircuit.from_qasm_str(qasm_string)

# Draw it
print(qc.draw())

# Simulate and plot results
sim = AerSimulator()
counts = sim.run(qc, shots=2048).result().get_counts()
fig = plot_histogram(counts)

The playground gives you a visual circuit builder, and the Export button on any circuit detail page downloads the OpenQASM file. From there, QuantumCircuit.from_qasm_str() or QuantumCircuit.from_qasm_file() loads it into Qiskit in one line.

Good circuits to start with for visualization: the Bell State and GHZ circuits are ideal for plot_bloch_multivector (entanglement shows up as Bloch vectors at center). Grover's algorithm is interesting to visualize at each iteration step to see amplitude amplification build up incrementally.

quantumcomputer.dev
quantumcomputer.dev
The editorial team at quantumcomputer.dev

Comments (0)

Loading comments...