We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Show title in figure for contour plot.
I noticed `optuna.visualization.plot_contour` show a plot without its title.

<img width="913" alt="Screen Shot 2020-04-28 at 10 49 53" src="https://user-images.githubusercontent.com/5164000/80437965-01d5fc00-893e-11ea-896a-343b2e51bc90.png">

I think it's better to show a title to be consistent with other plot features such as `optuna.visualization.plot_optimization_history` and `optuna.visualization.plot_slice`.

We can reproduce this behavior in the [official quickstart](https://colab.research.google.com/github/optuna/optuna/blob/master/examples/quickstart.ipynb)

## Expected behavior

Show title of a figure in the contour plot.

## Environment

- Optuna version: 1.3.0
- Python version: 3.6.9
- OS: Ubuntu 18.04.3 LTS

## Steps to reproduce

Run notebook on [Google Colab](https://colab.research.google.com/github/optuna/optuna/blob/master/examples/quickstart.ipynb).

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `optuna/visualization/contour.py`
Content:
```
1 import math
2 
3 from optuna.logging import get_logger
4 from optuna.study import StudyDirection
5 from optuna.trial import TrialState
6 from optuna import type_checking
7 from optuna.visualization.utils import _check_plotly_availability
8 from optuna.visualization.utils import _is_log_scale
9 from optuna.visualization.utils import is_available
10 
11 if type_checking.TYPE_CHECKING:
12     from typing import List  # NOQA
13     from typing import Optional  # NOQA
14     from typing import Tuple  # NOQA
15 
16     from optuna.study import Study  # NOQA
17     from optuna.trial import FrozenTrial  # NOQA
18     from optuna.visualization.plotly_imports import Contour  # NOQA
19     from optuna.visualization.plotly_imports import Scatter  # NOQA
20 
21 if is_available():
22     from optuna.visualization.plotly_imports import go
23     from optuna.visualization.plotly_imports import make_subplots
24     from optuna.visualization.plotly_imports import plotly
25 
26 logger = get_logger(__name__)
27 
28 
29 def plot_contour(study, params=None):
30     # type: (Study, Optional[List[str]]) -> go.Figure
31     """Plot the parameter relationship as contour plot in a study.
32 
33     Note that, If a parameter contains missing values, a trial with missing values is not plotted.
34 
35     Example:
36 
37         The following code snippet shows how to plot the parameter relationship as contour plot.
38 
39         .. testcode::
40 
41             import optuna
42 
43             def objective(trial):
44                 x = trial.suggest_uniform('x', -100, 100)
45                 y = trial.suggest_categorical('y', [-1, 0, 1])
46                 return x ** 2 + y
47 
48             study = optuna.create_study()
49             study.optimize(objective, n_trials=10)
50 
51             optuna.visualization.plot_contour(study, params=['x', 'y'])
52 
53         .. raw:: html
54 
55             <iframe src="../_static/plot_contour.html" width="100%" height="500px" frameborder="0">
56             </iframe>
57 
58     Args:
59         study:
60             A :class:`~optuna.study.Study` object whose trials are plotted for their objective
61             values.
62         params:
63             Parameter list to visualize. The default is all parameters.
64 
65     Returns:
66         A :class:`plotly.graph_objs.Figure` object.
67     """
68 
69     _check_plotly_availability()
70     return _get_contour_plot(study, params)
71 
72 
73 def _get_contour_plot(study, params=None):
74     # type: (Study, Optional[List[str]]) -> go.Figure
75 
76     layout = go.Layout(title="Contour Plot",)
77 
78     trials = [trial for trial in study.trials if trial.state == TrialState.COMPLETE]
79 
80     if len(trials) == 0:
81         logger.warning("Your study does not have any completed trials.")
82         return go.Figure(data=[], layout=layout)
83 
84     all_params = {p_name for t in trials for p_name in t.params.keys()}
85     if params is None:
86         sorted_params = sorted(list(all_params))
87     elif len(params) <= 1:
88         logger.warning("The length of params must be greater than 1.")
89         return go.Figure(data=[], layout=layout)
90     else:
91         for input_p_name in params:
92             if input_p_name not in all_params:
93                 raise ValueError("Parameter {} does not exist in your study.".format(input_p_name))
94         sorted_params = sorted(list(set(params)))
95 
96     param_values_range = {}
97     for p_name in sorted_params:
98         values = [t.params[p_name] for t in trials if p_name in t.params]
99         param_values_range[p_name] = (min(values), max(values))
100 
101     if len(sorted_params) == 2:
102         x_param = sorted_params[0]
103         y_param = sorted_params[1]
104         sub_plots = _generate_contour_subplot(trials, x_param, y_param, study.direction)
105         figure = go.Figure(data=sub_plots)
106         figure.update_xaxes(title_text=x_param, range=param_values_range[x_param])
107         figure.update_yaxes(title_text=y_param, range=param_values_range[y_param])
108         if _is_log_scale(trials, x_param):
109             log_range = [math.log10(p) for p in param_values_range[x_param]]
110             figure.update_xaxes(range=log_range, type="log")
111         if _is_log_scale(trials, y_param):
112             log_range = [math.log10(p) for p in param_values_range[y_param]]
113             figure.update_yaxes(range=log_range, type="log")
114     else:
115         figure = make_subplots(
116             rows=len(sorted_params), cols=len(sorted_params), shared_xaxes=True, shared_yaxes=True
117         )
118         showscale = True  # showscale option only needs to be specified once
119         for x_i, x_param in enumerate(sorted_params):
120             for y_i, y_param in enumerate(sorted_params):
121                 if x_param == y_param:
122                     figure.add_trace(go.Scatter(), row=y_i + 1, col=x_i + 1)
123                 else:
124                     sub_plots = _generate_contour_subplot(
125                         trials, x_param, y_param, study.direction
126                     )
127                     contour = sub_plots[0]
128                     scatter = sub_plots[1]
129                     contour.update(showscale=showscale)  # showscale's default is True
130                     if showscale:
131                         showscale = False
132                     figure.add_trace(contour, row=y_i + 1, col=x_i + 1)
133                     figure.add_trace(scatter, row=y_i + 1, col=x_i + 1)
134                 figure.update_xaxes(range=param_values_range[x_param], row=y_i + 1, col=x_i + 1)
135                 figure.update_yaxes(range=param_values_range[y_param], row=y_i + 1, col=x_i + 1)
136                 if _is_log_scale(trials, x_param):
137                     log_range = [math.log10(p) for p in param_values_range[x_param]]
138                     figure.update_xaxes(range=log_range, type="log", row=y_i + 1, col=x_i + 1)
139                 if _is_log_scale(trials, y_param):
140                     log_range = [math.log10(p) for p in param_values_range[y_param]]
141                     figure.update_yaxes(range=log_range, type="log", row=y_i + 1, col=x_i + 1)
142                 if x_i == 0:
143                     figure.update_yaxes(title_text=y_param, row=y_i + 1, col=x_i + 1)
144                 if y_i == len(sorted_params) - 1:
145                     figure.update_xaxes(title_text=x_param, row=y_i + 1, col=x_i + 1)
146 
147     return figure
148 
149 
150 def _generate_contour_subplot(trials, x_param, y_param, direction):
151     # type: (List[FrozenTrial], str, str, StudyDirection) -> Tuple[Contour, Scatter]
152 
153     x_indices = sorted(list({t.params[x_param] for t in trials if x_param in t.params}))
154     y_indices = sorted(list({t.params[y_param] for t in trials if y_param in t.params}))
155     if len(x_indices) < 2:
156         logger.warning("Param {} unique value length is less than 2.".format(x_param))
157         return go.Contour(), go.Scatter()
158     if len(y_indices) < 2:
159         logger.warning("Param {} unique value length is less than 2.".format(y_param))
160         return go.Contour(), go.Scatter()
161     z = [[float("nan") for _ in range(len(x_indices))] for _ in range(len(y_indices))]
162 
163     x_values = []
164     y_values = []
165     for trial in trials:
166         if x_param not in trial.params or y_param not in trial.params:
167             continue
168         x_values.append(trial.params[x_param])
169         y_values.append(trial.params[y_param])
170         x_i = x_indices.index(trial.params[x_param])
171         y_i = y_indices.index(trial.params[y_param])
172         if isinstance(trial.value, int):
173             value = float(trial.value)
174         elif isinstance(trial.value, float):
175             value = trial.value
176         else:
177             raise ValueError(
178                 "Trial{} has COMPLETE state, but its value is non-numeric.".format(trial.number)
179             )
180         z[y_i][x_i] = value
181 
182     # TODO(Yanase): Use reversescale argument to reverse colorscale if Plotly's bug is fixed.
183     # If contours_coloring='heatmap' is specified, reversesecale argument of go.Contour does not
184     # work correctly. See https://github.com/pfnet/optuna/issues/606.
185     colorscale = plotly.colors.PLOTLY_SCALES["Blues"]
186     if direction == StudyDirection.MINIMIZE:
187         colorscale = [[1 - t[0], t[1]] for t in colorscale]
188         colorscale.reverse()
189 
190     contour = go.Contour(
191         x=x_indices,
192         y=y_indices,
193         z=z,
194         colorbar={"title": "Objective Value"},
195         colorscale=colorscale,
196         connectgaps=True,
197         contours_coloring="heatmap",
198         hoverinfo="none",
199         line_smoothing=1.3,
200     )
201 
202     scatter = go.Scatter(
203         x=x_values, y=y_values, marker={"color": "black"}, mode="markers", showlegend=False
204     )
205 
206     return (contour, scatter)
207
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/optuna/visualization/contour.py b/optuna/visualization/contour.py
--- a/optuna/visualization/contour.py
+++ b/optuna/visualization/contour.py
@@ -102,7 +102,7 @@
         x_param = sorted_params[0]
         y_param = sorted_params[1]
         sub_plots = _generate_contour_subplot(trials, x_param, y_param, study.direction)
-        figure = go.Figure(data=sub_plots)
+        figure = go.Figure(data=sub_plots, layout=layout)
         figure.update_xaxes(title_text=x_param, range=param_values_range[x_param])
         figure.update_yaxes(title_text=y_param, range=param_values_range[y_param])
         if _is_log_scale(trials, x_param):
@@ -115,6 +115,7 @@
         figure = make_subplots(
             rows=len(sorted_params), cols=len(sorted_params), shared_xaxes=True, shared_yaxes=True
         )
+        figure.update_layout(layout)
         showscale = True  # showscale option only needs to be specified once
         for x_i, x_param in enumerate(sorted_params):
             for y_i, y_param in enumerate(sorted_params):

{"golden_diff": "diff --git a/optuna/visualization/contour.py b/optuna/visualization/contour.py\n--- a/optuna/visualization/contour.py\n+++ b/optuna/visualization/contour.py\n@@ -102,7 +102,7 @@\n         x_param = sorted_params[0]\n         y_param = sorted_params[1]\n         sub_plots = _generate_contour_subplot(trials, x_param, y_param, study.direction)\n-        figure = go.Figure(data=sub_plots)\n+        figure = go.Figure(data=sub_plots, layout=layout)\n         figure.update_xaxes(title_text=x_param, range=param_values_range[x_param])\n         figure.update_yaxes(title_text=y_param, range=param_values_range[y_param])\n         if _is_log_scale(trials, x_param):\n@@ -115,6 +115,7 @@\n         figure = make_subplots(\n             rows=len(sorted_params), cols=len(sorted_params), shared_xaxes=True, shared_yaxes=True\n         )\n+        figure.update_layout(layout)\n         showscale = True  # showscale option only needs to be specified once\n         for x_i, x_param in enumerate(sorted_params):\n             for y_i, y_param in enumerate(sorted_params):\n", "issue": "Show title in figure for contour plot.\nI noticed `optuna.visualization.plot_contour` show a plot without its title.\r\n\r\n<img width=\"913\" alt=\"Screen Shot 2020-04-28 at 10 49 53\" src=\"https://user-images.githubusercontent.com/5164000/80437965-01d5fc00-893e-11ea-896a-343b2e51bc90.png\">\r\n\r\nI think it's better to show a title to be consistent with other plot features such as `optuna.visualization.plot_optimization_history` and `optuna.visualization.plot_slice`.\r\n\r\nWe can reproduce this behavior in the [official quickstart](https://colab.research.google.com/github/optuna/optuna/blob/master/examples/quickstart.ipynb)\r\n\r\n## Expected behavior\r\n\r\nShow title of a figure in the contour plot.\r\n\r\n## Environment\r\n\r\n- Optuna version: 1.3.0\r\n- Python version: 3.6.9\r\n- OS: Ubuntu 18.04.3 LTS\r\n\r\n## Steps to reproduce\r\n\r\nRun notebook on [Google Colab](https://colab.research.google.com/github/optuna/optuna/blob/master/examples/quickstart.ipynb).\n", "before_files": [{"content": "import math\n\nfrom optuna.logging import get_logger\nfrom optuna.study import StudyDirection\nfrom optuna.trial import TrialState\nfrom optuna import type_checking\nfrom optuna.visualization.utils import _check_plotly_availability\nfrom optuna.visualization.utils import _is_log_scale\nfrom optuna.visualization.utils import is_available\n\nif type_checking.TYPE_CHECKING:\n    from typing import List  # NOQA\n    from typing import Optional  # NOQA\n    from typing import Tuple  # NOQA\n\n    from optuna.study import Study  # NOQA\n    from optuna.trial import FrozenTrial  # NOQA\n    from optuna.visualization.plotly_imports import Contour  # NOQA\n    from optuna.visualization.plotly_imports import Scatter  # NOQA\n\nif is_available():\n    from optuna.visualization.plotly_imports import go\n    from optuna.visualization.plotly_imports import make_subplots\n    from optuna.visualization.plotly_imports import plotly\n\nlogger = get_logger(__name__)\n\n\ndef plot_contour(study, params=None):\n    # type: (Study, Optional[List[str]]) -> go.Figure\n    \"\"\"Plot the parameter relationship as contour plot in a study.\n\n    Note that, If a parameter contains missing values, a trial with missing values is not plotted.\n\n    Example:\n\n        The following code snippet shows how to plot the parameter relationship as contour plot.\n\n        .. testcode::\n\n            import optuna\n\n            def objective(trial):\n                x = trial.suggest_uniform('x', -100, 100)\n                y = trial.suggest_categorical('y', [-1, 0, 1])\n                return x ** 2 + y\n\n            study = optuna.create_study()\n            study.optimize(objective, n_trials=10)\n\n            optuna.visualization.plot_contour(study, params=['x', 'y'])\n\n        .. raw:: html\n\n            <iframe src=\"../_static/plot_contour.html\" width=\"100%\" height=\"500px\" frameborder=\"0\">\n            </iframe>\n\n    Args:\n        study:\n            A :class:`~optuna.study.Study` object whose trials are plotted for their objective\n            values.\n        params:\n            Parameter list to visualize. The default is all parameters.\n\n    Returns:\n        A :class:`plotly.graph_objs.Figure` object.\n    \"\"\"\n\n    _check_plotly_availability()\n    return _get_contour_plot(study, params)\n\n\ndef _get_contour_plot(study, params=None):\n    # type: (Study, Optional[List[str]]) -> go.Figure\n\n    layout = go.Layout(title=\"Contour Plot\",)\n\n    trials = [trial for trial in study.trials if trial.state == TrialState.COMPLETE]\n\n    if len(trials) == 0:\n        logger.warning(\"Your study does not have any completed trials.\")\n        return go.Figure(data=[], layout=layout)\n\n    all_params = {p_name for t in trials for p_name in t.params.keys()}\n    if params is None:\n        sorted_params = sorted(list(all_params))\n    elif len(params) <= 1:\n        logger.warning(\"The length of params must be greater than 1.\")\n        return go.Figure(data=[], layout=layout)\n    else:\n        for input_p_name in params:\n            if input_p_name not in all_params:\n                raise ValueError(\"Parameter {} does not exist in your study.\".format(input_p_name))\n        sorted_params = sorted(list(set(params)))\n\n    param_values_range = {}\n    for p_name in sorted_params:\n        values = [t.params[p_name] for t in trials if p_name in t.params]\n        param_values_range[p_name] = (min(values), max(values))\n\n    if len(sorted_params) == 2:\n        x_param = sorted_params[0]\n        y_param = sorted_params[1]\n        sub_plots = _generate_contour_subplot(trials, x_param, y_param, study.direction)\n        figure = go.Figure(data=sub_plots)\n        figure.update_xaxes(title_text=x_param, range=param_values_range[x_param])\n        figure.update_yaxes(title_text=y_param, range=param_values_range[y_param])\n        if _is_log_scale(trials, x_param):\n            log_range = [math.log10(p) for p in param_values_range[x_param]]\n            figure.update_xaxes(range=log_range, type=\"log\")\n        if _is_log_scale(trials, y_param):\n            log_range = [math.log10(p) for p in param_values_range[y_param]]\n            figure.update_yaxes(range=log_range, type=\"log\")\n    else:\n        figure = make_subplots(\n            rows=len(sorted_params), cols=len(sorted_params), shared_xaxes=True, shared_yaxes=True\n        )\n        showscale = True  # showscale option only needs to be specified once\n        for x_i, x_param in enumerate(sorted_params):\n            for y_i, y_param in enumerate(sorted_params):\n                if x_param == y_param:\n                    figure.add_trace(go.Scatter(), row=y_i + 1, col=x_i + 1)\n                else:\n                    sub_plots = _generate_contour_subplot(\n                        trials, x_param, y_param, study.direction\n                    )\n                    contour = sub_plots[0]\n                    scatter = sub_plots[1]\n                    contour.update(showscale=showscale)  # showscale's default is True\n                    if showscale:\n                        showscale = False\n                    figure.add_trace(contour, row=y_i + 1, col=x_i + 1)\n                    figure.add_trace(scatter, row=y_i + 1, col=x_i + 1)\n                figure.update_xaxes(range=param_values_range[x_param], row=y_i + 1, col=x_i + 1)\n                figure.update_yaxes(range=param_values_range[y_param], row=y_i + 1, col=x_i + 1)\n                if _is_log_scale(trials, x_param):\n                    log_range = [math.log10(p) for p in param_values_range[x_param]]\n                    figure.update_xaxes(range=log_range, type=\"log\", row=y_i + 1, col=x_i + 1)\n                if _is_log_scale(trials, y_param):\n                    log_range = [math.log10(p) for p in param_values_range[y_param]]\n                    figure.update_yaxes(range=log_range, type=\"log\", row=y_i + 1, col=x_i + 1)\n                if x_i == 0:\n                    figure.update_yaxes(title_text=y_param, row=y_i + 1, col=x_i + 1)\n                if y_i == len(sorted_params) - 1:\n                    figure.update_xaxes(title_text=x_param, row=y_i + 1, col=x_i + 1)\n\n    return figure\n\n\ndef _generate_contour_subplot(trials, x_param, y_param, direction):\n    # type: (List[FrozenTrial], str, str, StudyDirection) -> Tuple[Contour, Scatter]\n\n    x_indices = sorted(list({t.params[x_param] for t in trials if x_param in t.params}))\n    y_indices = sorted(list({t.params[y_param] for t in trials if y_param in t.params}))\n    if len(x_indices) < 2:\n        logger.warning(\"Param {} unique value length is less than 2.\".format(x_param))\n        return go.Contour(), go.Scatter()\n    if len(y_indices) < 2:\n        logger.warning(\"Param {} unique value length is less than 2.\".format(y_param))\n        return go.Contour(), go.Scatter()\n    z = [[float(\"nan\") for _ in range(len(x_indices))] for _ in range(len(y_indices))]\n\n    x_values = []\n    y_values = []\n    for trial in trials:\n        if x_param not in trial.params or y_param not in trial.params:\n            continue\n        x_values.append(trial.params[x_param])\n        y_values.append(trial.params[y_param])\n        x_i = x_indices.index(trial.params[x_param])\n        y_i = y_indices.index(trial.params[y_param])\n        if isinstance(trial.value, int):\n            value = float(trial.value)\n        elif isinstance(trial.value, float):\n            value = trial.value\n        else:\n            raise ValueError(\n                \"Trial{} has COMPLETE state, but its value is non-numeric.\".format(trial.number)\n            )\n        z[y_i][x_i] = value\n\n    # TODO(Yanase): Use reversescale argument to reverse colorscale if Plotly's bug is fixed.\n    # If contours_coloring='heatmap' is specified, reversesecale argument of go.Contour does not\n    # work correctly. See https://github.com/pfnet/optuna/issues/606.\n    colorscale = plotly.colors.PLOTLY_SCALES[\"Blues\"]\n    if direction == StudyDirection.MINIMIZE:\n        colorscale = [[1 - t[0], t[1]] for t in colorscale]\n        colorscale.reverse()\n\n    contour = go.Contour(\n        x=x_indices,\n        y=y_indices,\n        z=z,\n        colorbar={\"title\": \"Objective Value\"},\n        colorscale=colorscale,\n        connectgaps=True,\n        contours_coloring=\"heatmap\",\n        hoverinfo=\"none\",\n        line_smoothing=1.3,\n    )\n\n    scatter = go.Scatter(\n        x=x_values, y=y_values, marker={\"color\": \"black\"}, mode=\"markers\", showlegend=False\n    )\n\n    return (contour, scatter)\n", "path": "optuna/visualization/contour.py"}], "after_files": [{"content": "import math\n\nfrom optuna.logging import get_logger\nfrom optuna.study import StudyDirection\nfrom optuna.trial import TrialState\nfrom optuna import type_checking\nfrom optuna.visualization.utils import _check_plotly_availability\nfrom optuna.visualization.utils import _is_log_scale\nfrom optuna.visualization.utils import is_available\n\nif type_checking.TYPE_CHECKING:\n    from typing import List  # NOQA\n    from typing import Optional  # NOQA\n    from typing import Tuple  # NOQA\n\n    from optuna.study import Study  # NOQA\n    from optuna.trial import FrozenTrial  # NOQA\n    from optuna.visualization.plotly_imports import Contour  # NOQA\n    from optuna.visualization.plotly_imports import Scatter  # NOQA\n\nif is_available():\n    from optuna.visualization.plotly_imports import go\n    from optuna.visualization.plotly_imports import make_subplots\n    from optuna.visualization.plotly_imports import plotly\n\nlogger = get_logger(__name__)\n\n\ndef plot_contour(study, params=None):\n    # type: (Study, Optional[List[str]]) -> go.Figure\n    \"\"\"Plot the parameter relationship as contour plot in a study.\n\n    Note that, If a parameter contains missing values, a trial with missing values is not plotted.\n\n    Example:\n\n        The following code snippet shows how to plot the parameter relationship as contour plot.\n\n        .. testcode::\n\n            import optuna\n\n            def objective(trial):\n                x = trial.suggest_uniform('x', -100, 100)\n                y = trial.suggest_categorical('y', [-1, 0, 1])\n                return x ** 2 + y\n\n            study = optuna.create_study()\n            study.optimize(objective, n_trials=10)\n\n            optuna.visualization.plot_contour(study, params=['x', 'y'])\n\n        .. raw:: html\n\n            <iframe src=\"../_static/plot_contour.html\" width=\"100%\" height=\"500px\" frameborder=\"0\">\n            </iframe>\n\n    Args:\n        study:\n            A :class:`~optuna.study.Study` object whose trials are plotted for their objective\n            values.\n        params:\n            Parameter list to visualize. The default is all parameters.\n\n    Returns:\n        A :class:`plotly.graph_objs.Figure` object.\n    \"\"\"\n\n    _check_plotly_availability()\n    return _get_contour_plot(study, params)\n\n\ndef _get_contour_plot(study, params=None):\n    # type: (Study, Optional[List[str]]) -> go.Figure\n\n    layout = go.Layout(title=\"Contour Plot\",)\n\n    trials = [trial for trial in study.trials if trial.state == TrialState.COMPLETE]\n\n    if len(trials) == 0:\n        logger.warning(\"Your study does not have any completed trials.\")\n        return go.Figure(data=[], layout=layout)\n\n    all_params = {p_name for t in trials for p_name in t.params.keys()}\n    if params is None:\n        sorted_params = sorted(list(all_params))\n    elif len(params) <= 1:\n        logger.warning(\"The length of params must be greater than 1.\")\n        return go.Figure(data=[], layout=layout)\n    else:\n        for input_p_name in params:\n            if input_p_name not in all_params:\n                raise ValueError(\"Parameter {} does not exist in your study.\".format(input_p_name))\n        sorted_params = sorted(list(set(params)))\n\n    param_values_range = {}\n    for p_name in sorted_params:\n        values = [t.params[p_name] for t in trials if p_name in t.params]\n        param_values_range[p_name] = (min(values), max(values))\n\n    if len(sorted_params) == 2:\n        x_param = sorted_params[0]\n        y_param = sorted_params[1]\n        sub_plots = _generate_contour_subplot(trials, x_param, y_param, study.direction)\n        figure = go.Figure(data=sub_plots, layout=layout)\n        figure.update_xaxes(title_text=x_param, range=param_values_range[x_param])\n        figure.update_yaxes(title_text=y_param, range=param_values_range[y_param])\n        if _is_log_scale(trials, x_param):\n            log_range = [math.log10(p) for p in param_values_range[x_param]]\n            figure.update_xaxes(range=log_range, type=\"log\")\n        if _is_log_scale(trials, y_param):\n            log_range = [math.log10(p) for p in param_values_range[y_param]]\n            figure.update_yaxes(range=log_range, type=\"log\")\n    else:\n        figure = make_subplots(\n            rows=len(sorted_params), cols=len(sorted_params), shared_xaxes=True, shared_yaxes=True\n        )\n        figure.update_layout(layout)\n        showscale = True  # showscale option only needs to be specified once\n        for x_i, x_param in enumerate(sorted_params):\n            for y_i, y_param in enumerate(sorted_params):\n                if x_param == y_param:\n                    figure.add_trace(go.Scatter(), row=y_i + 1, col=x_i + 1)\n                else:\n                    sub_plots = _generate_contour_subplot(\n                        trials, x_param, y_param, study.direction\n                    )\n                    contour = sub_plots[0]\n                    scatter = sub_plots[1]\n                    contour.update(showscale=showscale)  # showscale's default is True\n                    if showscale:\n                        showscale = False\n                    figure.add_trace(contour, row=y_i + 1, col=x_i + 1)\n                    figure.add_trace(scatter, row=y_i + 1, col=x_i + 1)\n                figure.update_xaxes(range=param_values_range[x_param], row=y_i + 1, col=x_i + 1)\n                figure.update_yaxes(range=param_values_range[y_param], row=y_i + 1, col=x_i + 1)\n                if _is_log_scale(trials, x_param):\n                    log_range = [math.log10(p) for p in param_values_range[x_param]]\n                    figure.update_xaxes(range=log_range, type=\"log\", row=y_i + 1, col=x_i + 1)\n                if _is_log_scale(trials, y_param):\n                    log_range = [math.log10(p) for p in param_values_range[y_param]]\n                    figure.update_yaxes(range=log_range, type=\"log\", row=y_i + 1, col=x_i + 1)\n                if x_i == 0:\n                    figure.update_yaxes(title_text=y_param, row=y_i + 1, col=x_i + 1)\n                if y_i == len(sorted_params) - 1:\n                    figure.update_xaxes(title_text=x_param, row=y_i + 1, col=x_i + 1)\n\n    return figure\n\n\ndef _generate_contour_subplot(trials, x_param, y_param, direction):\n    # type: (List[FrozenTrial], str, str, StudyDirection) -> Tuple[Contour, Scatter]\n\n    x_indices = sorted(list({t.params[x_param] for t in trials if x_param in t.params}))\n    y_indices = sorted(list({t.params[y_param] for t in trials if y_param in t.params}))\n    if len(x_indices) < 2:\n        logger.warning(\"Param {} unique value length is less than 2.\".format(x_param))\n        return go.Contour(), go.Scatter()\n    if len(y_indices) < 2:\n        logger.warning(\"Param {} unique value length is less than 2.\".format(y_param))\n        return go.Contour(), go.Scatter()\n    z = [[float(\"nan\") for _ in range(len(x_indices))] for _ in range(len(y_indices))]\n\n    x_values = []\n    y_values = []\n    for trial in trials:\n        if x_param not in trial.params or y_param not in trial.params:\n            continue\n        x_values.append(trial.params[x_param])\n        y_values.append(trial.params[y_param])\n        x_i = x_indices.index(trial.params[x_param])\n        y_i = y_indices.index(trial.params[y_param])\n        if isinstance(trial.value, int):\n            value = float(trial.value)\n        elif isinstance(trial.value, float):\n            value = trial.value\n        else:\n            raise ValueError(\n                \"Trial{} has COMPLETE state, but its value is non-numeric.\".format(trial.number)\n            )\n        z[y_i][x_i] = value\n\n    # TODO(Yanase): Use reversescale argument to reverse colorscale if Plotly's bug is fixed.\n    # If contours_coloring='heatmap' is specified, reversesecale argument of go.Contour does not\n    # work correctly. See https://github.com/pfnet/optuna/issues/606.\n    colorscale = plotly.colors.PLOTLY_SCALES[\"Blues\"]\n    if direction == StudyDirection.MINIMIZE:\n        colorscale = [[1 - t[0], t[1]] for t in colorscale]\n        colorscale.reverse()\n\n    contour = go.Contour(\n        x=x_indices,\n        y=y_indices,\n        z=z,\n        colorbar={\"title\": \"Objective Value\"},\n        colorscale=colorscale,\n        connectgaps=True,\n        contours_coloring=\"heatmap\",\n        hoverinfo=\"none\",\n        line_smoothing=1.3,\n    )\n\n    scatter = go.Scatter(\n        x=x_values, y=y_values, marker={\"color\": \"black\"}, mode=\"markers\", showlegend=False\n    )\n\n    return (contour, scatter)\n", "path": "optuna/visualization/contour.py"}]}