@@ -776,82 +776,112 @@ def plot_two_variants_comparison_3d(
776776 metrics2 ,
777777 name1 = "Standard MOEA/D" ,
778778 name2 = "Variant" ,
779+ metrics_dict1 = None ,
780+ metrics_dict2 = None ,
779781 save_path = None ,
780782):
781783 """
782- Compares two algorithm variants against ECM in two separate 3D subplots.
784+ Compares two algorithm variants against ECM in two separate 3D subplots,
785+ including performance metrics in the legend.
783786 """
784- fig = plt .figure (figsize = (18 , 8 ))
787+ fig = plt .figure (figsize = (20 , 9 ))
785788 ax1 = fig .add_subplot (121 , projection = "3d" )
786789 ax2 = fig .add_subplot (122 , projection = "3d" )
790+
791+ # Define color palette
792+ c_ref = "#333333"
793+ c1 = "#1f77b4"
794+ c2 = "#ff7f0e"
795+
796+ def format_label (name , m_dict ):
797+ if not m_dict :
798+ return name
799+ m_str = "\n " .join ([f"{ k } : { v :.4f} " for k , v in m_dict .items ()])
800+ return f"{ name } \n { m_str } "
787801
788802 # Subplot 1: Variant 1 vs ECM
789803 if ref_metrics_3d is not None :
790804 ax1 .scatter (
791805 ref_metrics_3d ["Return" ],
792806 ref_metrics_3d ["Risk" ],
793807 ref_metrics_3d ["Diversification" ],
794- c = "black" ,
808+ c = c_ref ,
795809 marker = "x" ,
796- s = 20 ,
810+ s = 25 ,
797811 label = "Reference (ECM)" ,
798- alpha = 0.2 ,
812+ alpha = 0.25 ,
813+ zorder = 1 ,
799814 )
815+
816+ label1 = format_label (name1 , metrics_dict1 )
800817 ax1 .scatter (
801818 metrics1 ["Return" ],
802819 metrics1 ["Risk" ],
803820 metrics1 ["Diversification" ],
804821 marker = "o" ,
805- s = 40 ,
806- label = name1 ,
807- alpha = 0.6 ,
808- color = "#1f77b4" ,
822+ s = 50 ,
823+ label = label1 ,
824+ alpha = 0.7 ,
825+ color = c1 ,
826+ edgecolors = "white" ,
827+ linewidth = 0.5 ,
828+ zorder = 2 ,
809829 )
810- ax1 .set_title (f"{ name1 } vs ECM" )
811- ax1 .set_xlabel ("Return" )
812- ax1 .set_ylabel ("Risk" )
813- ax1 .set_zlabel ("Diversification" )
814- ax1 .legend ()
830+ ax1 .set_title (f"{ name1 } Performance (3D)" , fontsize = 14 , pad = 20 )
831+ ax1 .set_xlabel ("Return" , fontsize = 11 )
832+ ax1 .set_ylabel ("Risk" , fontsize = 11 )
833+ ax1 .set_zlabel ("Diversification" , fontsize = 11 )
834+ ax1 .legend (loc = "upper left" , frameon = True , shadow = True , fontsize = 10 )
835+ ax1 .view_init (elev = 25 , azim = 45 )
815836
816837 # Subplot 2: Variant 2 vs ECM
817838 if ref_metrics_3d is not None :
818839 ax2 .scatter (
819840 ref_metrics_3d ["Return" ],
820841 ref_metrics_3d ["Risk" ],
821842 ref_metrics_3d ["Diversification" ],
822- c = "black" ,
843+ c = c_ref ,
823844 marker = "x" ,
824- s = 20 ,
845+ s = 25 ,
825846 label = "Reference (ECM)" ,
826- alpha = 0.2 ,
847+ alpha = 0.25 ,
848+ zorder = 1 ,
827849 )
850+
851+ label2 = format_label (name2 , metrics_dict2 )
828852 ax2 .scatter (
829853 metrics2 ["Return" ],
830854 metrics2 ["Risk" ],
831855 metrics2 ["Diversification" ],
832856 marker = "o" ,
833- s = 40 ,
834- label = name2 ,
835- alpha = 0.6 ,
836- color = "#ff7f0e" ,
857+ s = 50 ,
858+ label = label2 ,
859+ alpha = 0.7 ,
860+ color = c2 ,
861+ edgecolors = "white" ,
862+ linewidth = 0.5 ,
863+ zorder = 2 ,
837864 )
838- ax2 .set_title (f"{ name2 } vs ECM" )
839- ax2 .set_xlabel ("Return" )
840- ax2 .set_ylabel ("Risk" )
841- ax2 .set_zlabel ("Diversification" )
842- ax2 .legend ()
843-
844- # Animation function: Rotate both views
845- def update (frame ):
846- ax1 .view_init (elev = 20 , azim = frame )
847- ax2 .view_init (elev = 20 , azim = frame )
848- return (fig ,)
865+ ax2 .set_title (f"{ name2 } Performance (3D)" , fontsize = 14 , pad = 20 )
866+ ax2 .set_xlabel ("Return" , fontsize = 11 )
867+ ax2 .set_ylabel ("Risk" , fontsize = 11 )
868+ ax2 .set_zlabel ("Diversification" , fontsize = 11 )
869+ ax2 .legend (loc = "upper left" , frameon = True , shadow = True , fontsize = 10 )
870+ ax2 .view_init (elev = 25 , azim = 45 )
849871
850- ani = FuncAnimation (fig , update , frames = np .arange (0 , 360 , 4 ), interval = 50 )
872+ plt .suptitle ("3D Pareto Front Quality Comparison (AWA Optimization)" , fontsize = 16 , y = 0.98 )
873+ plt .tight_layout (rect = [0 , 0.03 , 1 , 0.95 ])
851874
852875 if save_path :
853876 if save_path .endswith (".gif" ):
854- ani .save (save_path , writer = "pillow" , fps = 15 )
877+ def update (frame ):
878+ ax1 .view_init (elev = 25 , azim = frame )
879+ ax2 .view_init (elev = 25 , azim = frame )
880+ return fig ,
881+
882+ from matplotlib .animation import FuncAnimation
883+ ani = FuncAnimation (fig , update , frames = np .arange (0 , 360 , 5 ), interval = 100 )
884+ ani .save (save_path , writer = "pillow" , dpi = 100 )
855885 else :
856- plt .savefig (save_path )
886+ plt .savefig (save_path , dpi = 150 , bbox_inches = "tight" )
857887 plt .close ()
0 commit comments