Source code for jmetal.lab.visualization.chord_plot

import colorsys
from typing import List

import numpy as np
from matplotlib import patches
from matplotlib import pyplot as plt
from matplotlib.path import Path
from tqdm import tqdm

from jmetal.core.solution import FloatSolution


[docs] def polar_to_cartesian(r, theta): return np.array([r * np.cos(theta), r * np.sin(theta)])
[docs] def draw_sector( start_angle=0, end_angle=60, radius=1.0, width=0.2, lw=2, ls="-", ax=None, fc=(1, 0, 0), ec=(0, 0, 0), z_order=1 ): if start_angle > end_angle: start_angle, end_angle = end_angle, start_angle start_angle *= np.pi / 180.0 end_angle *= np.pi / 180.0 # https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves opt = 4.0 / 3.0 * np.tan((end_angle - start_angle) / 4.0) * radius inner = radius * (1 - width) vertsPath = [ polar_to_cartesian(radius, start_angle), polar_to_cartesian(radius, start_angle) + polar_to_cartesian(opt, start_angle + 0.5 * np.pi), polar_to_cartesian(radius, end_angle) + polar_to_cartesian(opt, end_angle - 0.5 * np.pi), polar_to_cartesian(radius, end_angle), polar_to_cartesian(inner, end_angle), polar_to_cartesian(inner, end_angle) + polar_to_cartesian(opt * (1 - width), end_angle - 0.5 * np.pi), polar_to_cartesian(inner, start_angle) + polar_to_cartesian(opt * (1 - width), start_angle + 0.5 * np.pi), polar_to_cartesian(inner, start_angle), polar_to_cartesian(radius, start_angle), ] codesPaths = [ Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CLOSEPOLY, ] if ax is None: return vertsPath, codesPaths else: path = Path(vertsPath, codesPaths) patch = patches.PathPatch(path, facecolor=fc, edgecolor=ec, lw=lw, linestyle=ls, zorder=z_order) ax.add_patch(patch) return patch
[docs] def draw_chord( start_angle1=0, end_angle1=60, start_angle2=180, end_angle2=240, radius=1.0, chord_width=0.7, ax=None, color=(1, 0, 0), z_order=1, ): if start_angle1 > end_angle1: start_angle1, end_angle1 = end_angle1, start_angle1 if start_angle2 > end_angle2: start_angle2, end_angle2 = end_angle2, start_angle2 start_angle1 *= np.pi / 180.0 end_angle1 *= np.pi / 180.0 start_angle2 *= np.pi / 180.0 end_angle2 *= np.pi / 180.0 optAngle1 = 4.0 / 3.0 * np.tan((end_angle1 - start_angle1) / 4.0) * radius optAngle2 = 4.0 / 3.0 * np.tan((end_angle2 - start_angle2) / 4.0) * radius rchord = radius * (1 - chord_width) vertsPath = [ polar_to_cartesian(radius, start_angle1), polar_to_cartesian(radius, start_angle1) + polar_to_cartesian(optAngle1, start_angle1 + 0.5 * np.pi), polar_to_cartesian(radius, end_angle1) + polar_to_cartesian(optAngle1, end_angle1 - 0.5 * np.pi), polar_to_cartesian(radius, end_angle1), polar_to_cartesian(rchord, end_angle1), polar_to_cartesian(rchord, start_angle2), polar_to_cartesian(radius, start_angle2), polar_to_cartesian(radius, start_angle2) + polar_to_cartesian(optAngle2, start_angle2 + 0.5 * np.pi), polar_to_cartesian(radius, end_angle2) + polar_to_cartesian(optAngle2, end_angle2 - 0.5 * np.pi), polar_to_cartesian(radius, end_angle2), polar_to_cartesian(rchord, end_angle2), polar_to_cartesian(rchord, start_angle1), polar_to_cartesian(radius, start_angle1), ] codesPath = [ Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, ] if ax == None: return vertsPath, codesPath else: path = Path(vertsPath, codesPath) patch = patches.PathPatch(path, facecolor=color + (0.5,), edgecolor=color + (0.4,), lw=2, alpha=0.5) ax.add_patch(patch) return patch
[docs] def hover_over_bin(event, handle_tickers, handle_plots, colors, fig): is_found = False for iobj in range(len(handle_tickers)): for ibin in range(len(handle_tickers[iobj])): cont = False if not is_found: cont, ind = handle_tickers[iobj][ibin].contains(event) if cont: is_found = True if cont: plt.setp(handle_tickers[iobj][ibin], facecolor=colors[iobj]) [h.set_visible(True) for h in handle_plots[iobj][ibin]] is_found = True fig.canvas.draw_idle() else: plt.setp(handle_tickers[iobj][ibin], facecolor=(1, 1, 1)) for h in handle_plots[iobj][ibin]: h.set_visible(False) fig.canvas.draw_idle()
[docs] def chord_diagram( solutions: List[FloatSolution], nbins="auto", ax=None, obj_labels=None, prop_labels=dict(fontsize=13, ha="center", va="center"), pad=6, ): points_matrix = np.array([s.objectives for s in solutions]) (NPOINTS, NOBJ) = np.shape(points_matrix) HSV_tuples = [(x * 1.0 / NOBJ, 0.5, 0.5) for x in range(NOBJ)] colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), HSV_tuples)) if ax is None: fig = plt.figure(figsize=(6, 6)) ax = plt.axes([0, 0, 1, 1], aspect="equal") ax.set_xlim(-2.3, 2.3) ax.set_ylim(-2.3, 2.3) ax.axis("off") y = np.array([1.0 / NOBJ] * NOBJ) * (360 - pad * NOBJ) sector_angles = [] labels_pos_and_ros = [] start_angle = 0 for i in range(NOBJ): end_angle = start_angle + y[i] sector_angles.append((start_angle, end_angle)) angle_diff = 0.5 * (start_angle + end_angle) if -30 <= angle_diff <= 210: angle_diff -= 90 else: angle_diff -= 270 angleText = start_angle - 2.5 if -30 <= angleText <= 210: angleText -= 90 else: angleText -= 270 labels_pos_and_ros.append( tuple(polar_to_cartesian(1.0, 0.5 * (start_angle + end_angle) * np.pi / 180.0)) + (angle_diff,) + tuple(polar_to_cartesian(0.725, (start_angle - 2.5) * np.pi / 180.0)) + (angleText,) + tuple(polar_to_cartesian(0.85, (start_angle - 2.5) * np.pi / 180.0)) + (angleText,) ) start_angle = end_angle + pad arc_points = [] for point in points_matrix: arc_points.append([]) idim = 0 for _ in point: anglePoint = sector_angles[idim][0] + (sector_angles[idim][1] - sector_angles[idim][0]) * point[idim] arc_points[-1].append((anglePoint, anglePoint)) idim = idim + 1 max_hist_values = [] handle_tickers = [] handle_plots = [] for iobj in tqdm(range(NOBJ), ascii=True, desc="Chord diagram"): draw_sector( start_angle=sector_angles[iobj][0], end_angle=sector_angles[iobj][1], radius=0.925, width=0.225, ax=ax, fc=(1, 1, 1, 0.0), ec=(0, 0, 0), lw=2, z_order=10, ) draw_sector( start_angle=sector_angles[iobj][0], end_angle=sector_angles[iobj][1], radius=0.925, width=0.05, ax=ax, fc=colors[iobj], ec=(0, 0, 0), lw=2, z_order=10, ) draw_sector( start_angle=sector_angles[iobj][0], end_angle=sector_angles[iobj][1], radius=0.7 + 0.15, width=0.0, ax=ax, fc=colors[iobj], ec=colors[iobj], lw=2, ls=":", z_order=5, ) histValues, binsDim = np.histogram(points_matrix[:, iobj], bins=nbins) relativeHeightBinPre = 0.025 max_hist_values.append(max(histValues)) handle_tickers.append([]) handle_plots.append([]) for indexBin in range(len(histValues)): startAngleBin = ( sector_angles[iobj][0] + (sector_angles[iobj][1] - sector_angles[iobj][0]) * binsDim[indexBin] ) endAngleBin = ( sector_angles[iobj][0] + (sector_angles[iobj][1] - sector_angles[iobj][0]) * binsDim[indexBin + 1] ) relativeHeightBin = 0.15 * histValues[indexBin] / max(histValues) handle_tickers[-1].append( draw_sector( start_angle=startAngleBin, end_angle=endAngleBin, radius=0.69, width=0.08, ax=ax, lw=1, fc=(1, 1, 1), ec=(0, 0, 0), ) ) handle_plots[-1].append([]) if histValues[indexBin] > 0: draw_sector( start_angle=startAngleBin, end_angle=endAngleBin, radius=0.7 + relativeHeightBin, width=0, ax=ax, lw=1, fc=colors[iobj], ec=colors[iobj], ) plotPoint1 = polar_to_cartesian(0.7 + relativeHeightBinPre, startAngleBin * np.pi / 180.0) plotPoint2 = polar_to_cartesian(0.7 + relativeHeightBin, startAngleBin * np.pi / 180.0) plt.plot([plotPoint1[0], plotPoint2[0]], [plotPoint1[1], plotPoint2[1]], c=colors[iobj], lw=1) relativeHeightBinPre = relativeHeightBin else: plotPoint1 = polar_to_cartesian(0.7 + relativeHeightBinPre, startAngleBin * np.pi / 180.0) plotPoint2 = polar_to_cartesian(0.725 + relativeHeightBin, startAngleBin * np.pi / 180.0) plt.plot([plotPoint1[0], plotPoint2[0]], [plotPoint1[1], plotPoint2[1]], c=colors[iobj], lw=1) relativeHeightBinPre = 0.025 if indexBin == len(histValues) - 1: plotPoint1 = polar_to_cartesian(0.7 + relativeHeightBin, endAngleBin * np.pi / 180.0) plotPoint2 = polar_to_cartesian(0.725, endAngleBin * np.pi / 180.0) plt.plot([plotPoint1[0], plotPoint2[0]], [plotPoint1[1], plotPoint2[1]], c=colors[iobj], lw=1) for ipoint in range(len(points_matrix)): plotPoint1 = polar_to_cartesian(0.6, arc_points[ipoint][iobj][0] * np.pi / 180.0) plotPoint2 = polar_to_cartesian(0.6, arc_points[ipoint][iobj][0] * np.pi / 180.0) plt.plot( [plotPoint1[0], plotPoint2[0]], [plotPoint1[1], plotPoint2[1]], marker="o", markersize=3, c=colors[iobj], lw=2, ) if binsDim[indexBin] < points_matrix[ipoint, iobj] <= binsDim[indexBin + 1]: for jdim in range(NOBJ): if jdim >= 1: handle_plots[iobj][indexBin].append( draw_chord( arc_points[ipoint][jdim - 1][0], arc_points[ipoint][jdim - 1][1], arc_points[ipoint][jdim][0], arc_points[ipoint][jdim][1], radius=0.55, color=colors[iobj], chord_width=1, ax=ax, ) ) handle_plots[iobj][indexBin][-1].set_visible(False) handle_plots[iobj][indexBin].append( draw_chord( arc_points[ipoint][-1][0], arc_points[ipoint][-1][1], arc_points[ipoint][0][0], arc_points[ipoint][0][1], radius=0.55, color=colors[iobj], chord_width=1, ax=ax, ) ) handle_plots[iobj][indexBin][-1].set_visible(False) if obj_labels is None: obj_labels = ["$f_{" + str(i) + "}(\mathbf{x})$" for i in range(NOBJ)] prop_legend_bins = dict(fontsize=9, ha="center", va="center") for i in range(NOBJ): p0, p1 = polar_to_cartesian(0.975, sector_angles[i][0] * np.pi / 180.0) ax.text(p0, p1, "0", **prop_legend_bins) p0, p1 = polar_to_cartesian(0.975, sector_angles[i][1] * np.pi / 180.0) ax.text(p0, p1, "1", **prop_legend_bins) ax.text( labels_pos_and_ros[i][0], labels_pos_and_ros[i][1], obj_labels[i], rotation=labels_pos_and_ros[i][2], **prop_labels ) ax.text(labels_pos_and_ros[i][3], labels_pos_and_ros[i][4], "0", **prop_legend_bins, color=colors[i]) ax.text( labels_pos_and_ros[i][6], labels_pos_and_ros[i][7], str(max_hist_values[i]), **prop_legend_bins, color=colors[i] ) plt.axis([-1.2, 1.2, -1.2, 1.2]) fig.canvas.mpl_connect( "motion_notify_event", lambda event: hover_over_bin(event, handle_tickers, handle_plots, colors, fig) ) plt.show()