diff --git a/qiskit/visualization/circuit/qcstyle.py b/qiskit/visualization/circuit/qcstyle.py index c0c374de2465..76f08bb66201 100644 --- a/qiskit/visualization/circuit/qcstyle.py +++ b/qiskit/visualization/circuit/qcstyle.py @@ -418,3 +418,75 @@ def set_style(current_style, new_style): UserWarning, 2, ) +<<<<<<< HEAD +======= + style_name = "default" + + if style_name in ["iqp", "default"]: + current_style = DefaultStyle().style + else: + # Search for file in 'styles' dir, then config_path, and finally the current directory + style_name = style_name + ".json" + style_paths = [] + + default_path = Path(__file__).parent / "styles" / style_name + style_paths.append(default_path) + + # check configured paths, if there are any + if config: + config_path = config.get("circuit_mpl_style_path", "") + if config_path: + for path in config_path: + style_paths.append(Path(path) / style_name) + + # check current directory + cwd_path = Path("") / style_name + style_paths.append(cwd_path) + + for path in style_paths: + # expand ~ to the user directory and check if the file exists + exp_user = path.expanduser() + if os.path.isfile(exp_user): + try: + with open(exp_user) as infile: + json_style = json.load(infile) + + current_style = StyleDict(json_style) + break + except json.JSONDecodeError as err: + warn( + f"Could not decode JSON in file '{path}': {str(err)}. " + "Will use default style.", + UserWarning, + 2, + ) + break + except (OSError, FileNotFoundError): + warn( + f"Error loading JSON file '{path}'. Will use default style.", + UserWarning, + 2, + ) + break + else: + warn( + f"Style JSON file '{style_name}' not found in any of these locations: " + f"{', '.join(map(str, style_paths))}. " + "Will use default style.", + UserWarning, + 2, + ) + current_style = DefaultStyle().style + + # if the style is a dictionary, update the defaults with the new values + # this _needs_ to happen after loading by name to cover cases like + # style = {"name": "bw", "edgecolor": "#FF0000"} + if isinstance(style, dict): + current_style.update(style) + + # this is the default font ratio + # if the font- or subfont-sizes are changed, the new size is based on this ratio + def_font_ratio = 13 / 8 + + return current_style, def_font_ratio +>>>>>>> 19c15b749 (bug loading MPL style from the configuration (#11750)) diff --git a/qiskit/visualization/circuit/styles/__init__.py b/qiskit/visualization/circuit/styles/__init__.py new file mode 100644 index 000000000000..edbac45d60af --- /dev/null +++ b/qiskit/visualization/circuit/styles/__init__.py @@ -0,0 +1,13 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2014. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Empty init for data directory.""" diff --git a/releasenotes/notes/fix_11536-c87d192a133b3dc3.yaml b/releasenotes/notes/fix_11536-c87d192a133b3dc3.yaml new file mode 100644 index 000000000000..a3600fb6a7d5 --- /dev/null +++ b/releasenotes/notes/fix_11536-c87d192a133b3dc3.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + A bug when loading MPL style from the configuration was fixed. diff --git a/test/python/visualization/test_circuit_drawer.py b/test/python/visualization/test_circuit_drawer.py index 9f2bd51d7f23..340db1e20d79 100644 --- a/test/python/visualization/test_circuit_drawer.py +++ b/test/python/visualization/test_circuit_drawer.py @@ -12,15 +12,22 @@ # pylint: disable=missing-docstring -import unittest import os +<<<<<<< HEAD +======= +import pathlib +import shutil +import tempfile +import unittest +import warnings +>>>>>>> 19c15b749 (bug loading MPL style from the configuration (#11750)) from unittest.mock import patch from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.test import QiskitTestCase from qiskit.utils import optionals from qiskit import visualization -from qiskit.visualization.circuit import text +from qiskit.visualization.circuit import text, styles from qiskit.visualization.exceptions import VisualizationError if optionals.HAS_MATPLOTLIB: @@ -49,6 +56,38 @@ def test_default_output(self): out = visualization.circuit_drawer(circuit) self.assertIsInstance(out, text.TextDrawing) + @unittest.skipUnless(optionals.HAS_MATPLOTLIB, "Skipped because matplotlib is not available") + def test_mpl_config_with_path(self): + # It's too easy to get too nested in a test with many context managers. + tempdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with + self.addCleanup(tempdir.cleanup) + + clifford_style = pathlib.Path(styles.__file__).parent / "clifford.json" + shutil.copyfile(clifford_style, pathlib.Path(tempdir.name) / "my_clifford.json") + + circuit = QuantumCircuit(1) + circuit.h(0) + + def config(style_name): + return { + "circuit_drawer": "mpl", + "circuit_mpl_style": style_name, + "circuit_mpl_style_path": [tempdir.name], + } + + with warnings.catch_warnings(): + warnings.filterwarnings("error", message="Style JSON file.*not found") + + # Test that a non-standard style can be loaded by name. + with patch("qiskit.user_config.get_config", return_value=config("my_clifford")): + self.assertIsInstance(visualization.circuit_drawer(circuit), figure.Figure) + + # Test that a non-existent style issues a warning, but still draws something. + with patch("qiskit.user_config.get_config", return_value=config("NONEXISTENT")): + with self.assertWarnsRegex(UserWarning, "Style JSON file.*not found"): + fig = visualization.circuit_drawer(circuit) + self.assertIsInstance(fig, figure.Figure) + @unittest.skipUnless(optionals.HAS_MATPLOTLIB, "Skipped because matplotlib is not available") def test_user_config_default_output(self): with patch("qiskit.user_config.get_config", return_value={"circuit_drawer": "mpl"}):