讲一点扒MATLAB底裤的事情叭,就是之前写的有一些绘图函数,比如阴影柱状图,想要把图例里的图标进行修改让其也带着阴影,我采取的是直接绘制一些会检测图例框移动的阴影图标来冒充图例的图标,那么有没有办法真正的自定义图例呢?
还真找到了方法,先给大家讲讲怎么办到的,之后可能会专门写一些相关的函数哈:
或者右上角心形图例:
或者直接把方形框改掉:
感谢@左右逢源
(https://www.zhihu.com/people/zuo-you-feng-yuan-4-38)
提供的思路和部分代码!!
原理
首先,众所周不知,MATLAB图例有很多隐藏属性,大概这样:
首先想的是怎么把图例图标里绑定的那个对象给取出来,我们研究发现办法还是很多的,例如:
hLegend.EntryContainer.NodeChildren(n).Icon.Transform.Children.Children
或者麻烦一点:
hLegend.EntryContainer.Children(n).Children(1).Transform.Children.Children
或者使用findobj
后直接选择对象:
objHdl=findobj(hLegend.EntryContainer)
objHdl(4).Transform.Children.Children
左右逢源
给出了一段批量获取的代码:
lgdEntryChild=hLegend.EntryContainer.NodeChildren;
iconSet=arrayfun(@(lgdEntryChild)lgdEntryChild.Icon.Transform.Children.Children,lgdEntryChild,UniformOutput=false)
获取了这个对象后其实就可以直接对其进行修改了:
clc;clear;close all
hold on;
plot(1:5,'LineWidth',2);
plot(2:6,'LineWidth',2);
hLegend=legend();
hLegend.Title.String='MyLegend';
% 批量获取全部图标对象
pause(1e-6)
lgdEntryChild=hLegend.EntryContainer.NodeChildren;
iconSet=arrayfun(@(lgdEntryChild)lgdEntryChild.Icon.Transform.Children.Children,lgdEntryChild,UniformOutput=false);
% 换颜色+加粗
iconSet{2}.ColorData=uint8([0;0;0;255]);
iconSet{2}.LineWidth=10;
那么有没有办法对其进行组合呢?,这个办法是左右逢源
想到的,大体就是copy一份对象并修改所属的组(父类)即可:
% 复制并改父类
tempIcon=copy(iconSet{1});
tempIcon.Parent=iconSet{2}.Parent;
very amazing 啊,于是我们就开始了各种尝试:
写着玩的代码
圆圈+十字
创造新的图例最简单的就是把几个老图例进行组合,因此我先试了一个圆圈+十字的组合:
clc;clear;close all
hold on
% 绘制等高线图为构造自定义图例提供素材
x=linspace(-2*pi,2*pi);
y=linspace(0,4*pi);
[X,Y]=meshgrid(x,y);
Z=sin(X)+cos(Y);
contour(X,Y,Z,'LineWidth',1);
% 绘制横线竖线为构造自定义图例提供素材
plot(1:5,'LineWidth',1);
scatter(1,1,'LineWidth',1,'Marker','|');
% 获取横线竖线素材,之所以分两次构建图例
% 且copy第一次构建图例的素材
% 是因为如果直接将图例素材移动再
% 删除无用素材的EntryContainer也依旧会保留空白
hLegend1=legend();
pause(1e-6)
lgdEntryChild=hLegend1.EntryContainer.NodeChildren;
iconSet=arrayfun(@(lgdEntryChild)copy(lgdEntryChild.Icon.Transform.Children.Children),lgdEntryChild,UniformOutput=false);
% 第二次构建图例且只保留等高线图的图例
cHdl=findobj(gca,'Type','Contour');
hLegend2=legend(cHdl,'Data');
pause(1e-6)
iconParent=hLegend2.EntryContainer.NodeChildren(1).Icon.Transform.Children;
icon=hLegend2.EntryContainer.NodeChildren(1).Icon.Transform.Children.Children;
% 简单修改素材组成(删除等高线图例的两个内圈环)
delete(icon(1))
delete(icon(2))
% 修改横线竖线素材的父类组
iconSet{1}.Parent=iconParent;
iconSet{2}.Parent=iconParent;
% 修饰素材颜色和大小利于拼接
iconSet{1}.EdgeColorData=uint8([0;0;0;255]);
iconSet{1}.Size=8;
iconSet{2}.ColorData=uint8([0;0;0;255]);
icon(3).ColorData=uint8([0;0;0;255]);
方形等高线?
这时候已经发现是通过VertexData
来控制形状,试了试将自身图例图标复制并缩小:
clc;clear;close all
hold on
% 绘制等高线图为构造自定义图例提供素材
fill([0,0,1,1],[1,0,0,1],[1,1,1],'LineWidth',.8)
hLegend=legend();
pause(1e-6)
icon=hLegend.EntryContainer.NodeChildren.Icon.Transform.Children.Children;
% 复制第一圈改颜色
LineIcon=copy(icon(1));
QuadIcon=copy(icon(2));
LineIcon.VertexData=(LineIcon.VertexData-.5).*.7+.5;
LineIcon.Parent=icon(1).Parent;
QuadIcon.VertexData=(QuadIcon.VertexData-.5).*.7+.5;
QuadIcon.ColorData=uint8([255;220;220;255]);
QuadIcon.Parent=icon(2).Parent;
% 复制第二圈改颜色
LineIcon=copy(icon(1));
QuadIcon=copy(icon(2));
LineIcon.VertexData=(LineIcon.VertexData-.5).*.4+.5;
LineIcon.Parent=icon(1).Parent;
QuadIcon.VertexData=(QuadIcon.VertexData-.5).*.4+.5;
QuadIcon.ColorData=uint8([210;220;225;255]);
QuadIcon.Parent=icon(2).Parent;
三角形
此时发现了LineLoop
以及Quadrilateral
对象调整不了点数,只能做有限的修改:
clc;clear;close all
hold on
% 绘制等高线图为构造自定义图例提供素材
fill([0,0,1,1],[1,0,0,1],[1,1,1],'LineWidth',.8)
hLegend=legend();
pause(1e-6)
icon=hLegend.EntryContainer.NodeChildren.Icon.Transform.Children.Children;
icon(1).VertexData=single([0 1 1 1
0 1 1 0
0 0 0 0]);
爱心
又经过尝试发现LineStrip
以及TriangleStrip
对象可以调整点数,尝试构造了一个心形图例图标:
clc;clear;close all
x = -1:1/100:1;
y1 = 0.6 * abs(x) .^ 0.5 + ((1 - x .^ 2) / 2) .^ 0.5;
y2 = 0.6 * abs(x) .^ 0.5 - ((1 - x .^ 2) / 2) .^ 0.5;
plot([x, flip(x)], [y1, y2], 'r')
hLegend=legend();
pause(1e-6)
icon=hLegend.EntryContainer.NodeChildren.Icon.Transform.Children.Children;
XX=[x, flip(x),x(1)]./2+.5;
YY=([y1, y2,y1(1)]-.2)./2+.5;
XYZ=single([XX;YY;0.*XX]);
icon.VertexData=XYZ;
icon.LineWidth=2;
看起来比较支持横平竖直的图例。
其他隐藏属性的利用
隐藏分割线
知道了那些隐藏属性能做到的尝试还是很多的,比如隐藏标题和图例图标之间的横线:
clc;clear;close all
hold on;
plot(1:5,'LineWidth',2);
plot(2:6,'LineWidth',2);
hLegend=legend();
hLegend.Title.String='MyLegend';
% 批量获取全部图标对象
pause(1e-6)
hLegend.TitleSeparator.Visible='off';
可以更好看一点。
平行四边形框
clc;clear;close all
hold on;
plot(1:5,'LineWidth',2);
plot(2:6,'LineWidth',2);
hLegend=legend();
hLegend.Title.String='MyLegend';
pause(1e-6)
hLegend.BoxEdge.VertexData=single([0 -.5 1 1.5
0 1 1 0
0 0 0 0]);
完
以上就是我们目前为止对其隐藏属性做的全部尝试,感觉能做出很多有意思的东西,未来也有可能开发的绘图函数真的用上了自己设计的图例图标,以上代码还不完善,还需要设计各种监听回调才能达到真正使用的级别,但给大家科普一下这些隐藏的玩意还是挺有意思的,下期见~