"""Parallel coordinate plot visualization."""
import numpy as np
from pymoo.core.plot import Plot
from pymoo.util.misc import set_if_none, set_if_none_from_tuples
from pymoo.visualization.matplotlib import plt
from pymoo.visualization.util import normalize, parse_bounds
[docs]
class PCP(Plot):
"""Parallel Coordinate Plot for multi-objective visualization."""
def __init__(
self,
bounds=None,
show_bounds=True,
n_ticks=5,
normalize_each_axis=True,
bbox=False,
**kwargs,
):
"""Initialize PCP.
Args:
bounds: Bounds for normalization.
show_bounds: Whether the value of the boundaries are shown in the plot.
n_ticks: Number of ticks to be shown on each parallel axis.
normalize_each_axis: Whether the values should be normalized either by bounds
or implicitly.
bbox: Whether to use bounding boxes for labels.
**kwargs: Additional keyword arguments passed to parent Plot class.
"""
super().__init__(bounds=bounds, **kwargs)
self.show_bounds = show_bounds
self.n_ticks = n_ticks
self.bbox = bbox
self.normalize_each_axis = normalize_each_axis
set_if_none_from_tuples(
self.axis_style, ("color", "red"), ("linewidth", 2), ("alpha", 0.75)
)
def _do(self):
# initial a figure with a single plot
self.init_figure()
# if no normalization of each axis the bounds are based on the overall min and max
if not self.normalize_each_axis and self.bounds is None:
_F = np.vstack([e[0] for e in self.to_plot])
self.bounds = [_F.min(), _F.max()]
# normalize the input
bounds = parse_bounds(self.bounds, self.n_dim)
to_plot_norm, bounds = normalize(self.to_plot, bounds, return_bounds=True)
# plot for each set the lines
for k, (F, kwargs) in enumerate(to_plot_norm):
_kwargs = kwargs.copy()
set_if_none(_kwargs, "color", self.colors[k % len(self.colors)])
for i in range(len(F)):
plt.plot(np.arange(F.shape[1]), F[i, :], **_kwargs)
# Plot the parallel coordinate axes
for i in range(self.n_dim):
self.ax.axvline(i, **self.axis_style)
bottom, top = -0.1, 1.075
margin_left = 0.08
if self.show_bounds:
lower = self.ax.text(
i - margin_left, bottom, self.func_number_to_text(bounds[0][i])
)
upper = self.ax.text(
i - margin_left, top, self.func_number_to_text(bounds[1][i])
)
if self.bbox:
lower.set_bbox(dict(facecolor="white", alpha=0.8))
upper.set_bbox(dict(facecolor="white", alpha=0.8))
if self.n_ticks is not None:
n_length = 0.03
for y in np.linspace(0, 1, self.n_ticks):
self.ax.hlines(y, i - n_length, i + n_length, **self.axis_style)
# if bounds are shown, then move them to the bottom
if self.show_bounds:
self.ax.tick_params(axis="x", which="major", pad=25)
self.ax.spines["right"].set_visible(False)
self.ax.spines["left"].set_visible(False)
self.ax.set_yticklabels([])
self.ax.set_yticks([])
self.ax.set_ylim((-0.05, 1.05))
self.ax.set_xticks(np.arange(self.n_dim))
self.ax.set_xticklabels(self.get_labels())
return self