【MATLAB学习笔记】绘图——自定义标记(Marker)形状,实现与MATLAB自带标记基本一致的功能(自适应缩放、自适应裁剪)

目录

  • 前言
  • 自定义标记函数
  • 自定义标记函数的说明
    • 纵横比调整
    • 将图形大小按磅数设置
    • 平移标记点
    • 绘制标记点
    • 边界标记点不裁剪
  • 拓展功能——标记点自适应绘图区的缩放
    • 绘图区缩放回调函数
    • 标记点大小自适应
    • 标记点裁剪自适应
  • 示例
    • 基本绘图
    • 自定义标记函数的使用
  • 总代码
    • 主函数
    • 自定义标记函数
    • 回调函数
    • 其他函数
  • 总结

前言

  在MATLAB中,使用plot、scatter和patch等函数进行绘图时,很多时候都需要使用标记(Marker)对数据点进行标记,以便更清晰地展示数据。在MATLAB只能使用默认的标记形状(如下图),但有时觉得MATLAB自带的标记不好看或者不够用时就需要自定义标记形状,而MATLAB中并没有提供自定义标记这样一个功能或者函数。
在这里插入图片描述

  本文章提供了一个可以自定义标记形状的函数,只需要输入标记形状的点坐标和连接方式即可绘制出与MATLAB自带标记基本一致的自定义标记,包括随着绘图区的缩放,标记在屏幕上的大小不会改变,以及绘图区边缘的标记可以超出绘图区等功能,同时可以设置该标记的颜色、磅数、线宽等基本属性。

自定义标记函数

  自定义标记的主函数如下,该函数目前只能绘制没有交叉线的图形。前5个参数为必须输入,包括ax绘图区对象,曲线的x和y坐标,标记形状的点坐标GPoints(第一列为x坐标,第二列为y坐标),形状点的连接顺序(不需要首尾相连)GSeq。
  其他可设置参数为填充的面颜色faceColor,填充的线颜色edgeColor,形状的大小pounds,形状的线宽度lineWidth,形状的数量NMarker(近似数量, 采用等间距, 如不能整除则四舍五入,输入0则表示绘制所有点的标记)。同时,若不需要则以空数组[ ]代替, 如不需要形状磅数pounds, 则pounds = [ ]。

function setMarker(ax, x, y, GPoints, GSeq, faceColor, edgeColor, pounds, lineWidth, NMarker)
% 自定义设置标记形状(目前只能绘制没有交叉线的图形), 前5个参数为必须输入, 其他参数为可选参数
% 若不需要则以空数组[]代替, 如不需要形状磅数pounds, 则pounds = []
%
% ax        绘图区
% x,y       绘图的数据点
% GPoints   形状的点坐标
% GSeq      形状点的连接顺序(不需要首尾相连)
% faceColor 填充的面颜色  默认:none
% edgeColor 填充的线颜色  默认:k
% pounds    形状的大小(磅数)  默认:6
% lineWidth 形状的线宽度  % 默认:0.5
% NMarker   形状的数量(近似数量, 采用等间距, 如不能整除则四舍五入)  
%           默认:0, 0则代表全部点都绘制if nargin < 6, faceColor = 'none'; end
if nargin < 7, edgeColor = 'k'; end
if nargin < 8, pounds = 6; end
if nargin < 9, lineWidth = 0.5; end
if nargin < 10, NMarker = 0; endif isempty(faceColor), faceColor = 'none'; end
if isempty(edgeColor), edgeColor = 'k'; end
if isempty(pounds), pounds = 6; end
if isempty(lineWidth), lineWidth = 0.5; end
if isempty(NMarker), NMarker = 0; endif NMarker == 0, NMarker = length(x); endhold(ax,"on") NG = length(GSeq);  % 点数量% 将图形的比例拉正
xl = xlim;
yl = ylim;
lenx = xl(2)-xl(1); % x轴的长度
leny = yl(2)-yl(1); % y轴的长度
yRatio = leny/lenx; % 纵横比比例
GPoints(:,2) = GPoints(:,2)*yRatio; % 修正坐标 % 获取绘图区的每单位在屏幕上的长度(磅)
unitLength = getUnitLength();% 计算水平距离矩阵
W = zeros(NG);
for i = 1:NGfor j = 1:NGif i ~= jW(i,j) = sqrt((GPoints(i,1)-GPoints(j,1))^2);endend
end% 缩放比例
scale = 2*pounds/(max(max(W))*unitLength);
% 缩放
GPoints = GPoints*scale;PM = 1:round(length(x)/NMarker):length(x);   % 需要绘制的点位置
NMarker = length(PM);                        % 绘制标记的真实数量% 重心坐标
[Cx, Cy] = polygon_centroid(GPoints);% 初始化所有标记的坐标和连接顺序
GPointsAll = zeros(NMarker*NG,2);
GSeqAll = zeros(NMarker,NG);
% 边界点判断初始化
BJudge = false(NMarker,1);
for k = 1:NMarker% 取出当前点坐标xk = x(PM(k));yk = y(PM(k));% 平移displacement = [xk-Cx,yk-Cy];% 更新坐标GPointsNew = GPoints;GPointsNew = GPointsNew + repmat(displacement,size(GPoints,1),1);% 保存所有标记点的坐标和连接顺序GPointsAll(NG*(k-1)+1:NG*k,:) = GPointsNew;GSeqAll(k,:) =  GSeq + NG*(k-1);%%% 判断当前标记点是否处于边界 %%%xkk = GPointsNew(:, 1);  % 当前标记点的x坐标ykk = GPointsNew(:, 2);  % 当前标记点的y坐标% 标记形状坐标判断BLeft = xkk - xl(1);    % 左边界BRight = xkk - xl(2);   % 右边界BDown = ykk - yl(1);    % 下边界BUp = ykk - yl(2);      % 上边界% 标记中心坐标判断BLeft2 = xk - xl(1);    % 左边界BRight2 = xl(2)-xk;     % 右边界BDown2 = yk - yl(1);    % 下边界BUp2 = yl(2) - yk;      % 上边界% 判断形状坐标是否同号以及中心点是否在绘图区if (~judgeSign(BLeft) || ~judgeSign(BRight) || ~judgeSign(BDown) || ~judgeSign(BUp)) && ...all([BLeft2,BRight2,BDown2,BUp2]>=0)BJudge(k) = true;  % 记录处于边界的标记点endend
% 绘制标记点
pa = patch('Faces', GSeqAll, 'Vertices', GPointsAll, 'FaceColor', faceColor,...'EdgeColor', edgeColor,'lineWidth',lineWidth);% 设置绘图区边缘图形不裁剪
pa2 = patch('Faces', GSeqAll(BJudge,:), 'Vertices', GPointsAll, 'FaceColor', faceColor,...'EdgeColor', edgeColor,'LineWidth',lineWidth, 'Clip', 'off');%%%% 控制图形放大缩小 %%%%
zm = zoom(ax);
zm.Enable = 'on'; % 启用缩放% 初始化xy轴范围
xl = xlim;
yl = ylim;
setappdata(ax,'lxl',xl) 
setappdata(ax,'lyl',yl) % 绘图区缩放回调函数
set(zm, 'ActionPostCallback', @(src, event) updateZoomInfo(ax,x,y,PM,NG,NMarker,pa,pa2)); end

自定义标记函数的说明

纵横比调整

  在自定义形状时,如果x轴和y轴的刻度一致,则绘制出来的图形与实际一致。大多数情况下,MATLAB绘制图形时一般x轴和y轴的刻度并不一致,比如x轴一格长度为2,y轴一格长度为500,这时绘制出来的图形看起来会呈现上下被压扁的性质。比如,原本需要绘制的形状是下图这个四边形,但是因为x轴和y轴的刻度不一致,会出现绘制出来的形状只呈现出一条线(下面第二张图)。
在这里插入图片描述
在这里插入图片描述
  为了避免这种情况,首先就要对图形比例进行修正,即获取x轴和y轴刻度之间的比例,对标记图形的x或者y坐标进行拉伸或者收缩。

% 将图形的比例拉正
xl = xlim;
yl = ylim;
lenx = xl(2)-xl(1); % x轴的长度
leny = yl(2)-yl(1); % y轴的长度
yRatio = leny/lenx; % 纵横比比例
GPoints(:,2) = GPoints(:,2)*yRatio; % 修正坐标 

下面是调整后的效果图。
在这里插入图片描述

将图形大小按磅数设置

  放缩了x轴或者y轴的比例后,会出现标记图形过大或者过小的情况,这时就需要对标记的坐标进行放缩。在MATLAB中,标记的大小(MarkerSize)通常以磅数为单位,默认大小为6磅。同样参考MATLAB的方式,我们绘制图形也可以用磅数设置图形大小,但并不知道绘图区单位长度对应的磅数大小是多少。因此,首先要计算绘图区中的单位磅数。
  下面的函数中,Position(3)对应的就是x轴的总磅数,除x轴的总长度即可得到绘图区中的单位磅数。在上图中,绘图区中的单位磅数为50.9091磅。

function unitLength = getUnitLength()
% 获取绘图区的每单位在屏幕上的长度(磅)% 获取图形窗口的大小和位置  
Po = get(gcf, 'Position'); % 返回的单位是点 (points),每个点约为 1/72 英寸  
figWidth = Po(3); % 图形的宽度 % X轴范围  
xl = xlim;
rangeX = xl(2) - xl(1); % 绘图区的每单位在屏幕上的长度(磅)
unitLength = figWidth/rangeX;end

  得到了绘图区中的单位磅数后就可以以某一个标准(比如图形的水平长度、竖直长度和最长边的长度等)对图形的坐标进行放缩。本文采用图形的水平长度L为标准,将长度L乘单位长度unitLength可以得到图形当前的磅数,由于MATLAB中图形的大小是以半径为参考的,故需要在前面乘2。

% 获取绘图区的每单位在屏幕上的长度(磅)
unitLength = getUnitLength();% 水平长度矩阵
L = max(GPoints(:,1)) - min(GPoints(:,1));% 缩放比例
scale = 2*pounds/(L*unitLength);% 缩放
GPoints = GPoints*scale;

  设置磅数pounds = 6,可以得到下面的效果图(红色为自定义标记,黑色为MATLAB自带的三角形标记),可以看到,自定义标记与MATLAB自带标记的大小一致。
在这里插入图片描述

平移标记点

  已知标记点初始的点坐标和连接顺序后,还需要将标记点平移到相应的位置。
  NMarker为需要绘制的标记点数量,为得到标记点的位置,需要将整个数据区间均分成NMarker份。但是,输入的NMarker并不一定能均分,因此文章采用一种四舍五入的方法,即下面的round(length(x)/NMarker),以及更新NMarker的数量。

% 计算绘制的点位置
PM = 1:round(length(x)/NMarker):length(x);   % 需要绘制的点位置
NMarker = length(PM);                        % 绘制标记的真实数量

  得到了绘制标记点的位置后,需要将标记平移到该位置。本文以标记图形的重心作为参考,计算重心到该位置的位移,再到坐标进行更新。
  计算多边形的重心方法如下:

  1. 计算面积 A A A
      假设一个多边形的顶点坐标为 ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x n , y n ) (x_1, y_1), (x_2, y_2), \ldots, (x_n, y_n) (x1,y1),(x2,y2),,(xn,yn),连接顺序为 1 − n − 1 1-n-1 1n1,则该多边形的面积 A A A
    A = 1 2 ∣ ∑ i = 1 n ( x i y i + 1 − x i + 1 y i ) ∣ A = \frac{1}{2} \left| \sum_{i=1}^{n} (x_i y_{i+1} - x_{i+1} y_i) \right| A=21 i=1n(xiyi+1xi+1yi) 其中 ( x n + 1 , y n + 1 ) (x_{n+1}, y_{n+1}) (xn+1,yn+1)被定义为 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)
  2. 计算重心的坐标 ( C x , C y ) (C_x, C_y) (Cx,Cy)
      重心 C C C的坐标 ( C x , C y ) (C_x, C_y) (Cx,Cy)可通过以下公式计算:
    C x = 1 6 A ∑ i = 1 n ( x i + x i + 1 ) ( x i y i + 1 − x i + 1 y i ) C y = 1 6 A ∑ i = 1 n ( y i + y i + 1 ) ( x i y i + 1 − x i + 1 y i ) C_x = \frac{1}{6A} \sum_{i=1}^{n} (x_i + x_{i+1})(x_i y_{i+1} - x_{i+1} y_i) \\ C_y = \frac{1}{6A} \sum_{i=1}^{n} (y_i + y_{i+1})(x_i y_{i+1} - x_{i+1} y_i) Cx=6A1i=1n(xi+xi+1)(xiyi+1xi+1yi)Cy=6A1i=1n(yi+yi+1)(xiyi+1xi+1yi)根据上面的计算过程,计算重心的函数polygonCentroid如下:
function [Cx, Cy] = polygonCentroid(points)  
% 计算形状的重心坐标
% points 一个n x 2 的矩阵, 表示n个点的坐标, 第一列为x值, 第二列为y值  % 点的数量  
n = size(points, 1);  % 闭合多边形  
points = [points; points(1, :)];  % 初始化面积和重心坐标  
A = 0;  
Cx = 0;  
Cy = 0;  % 计算面积和重心坐标  
for i = 1:n  xi = points(i, 1);  yi = points(i, 2);  xi1 = points(i+1, 1);  yi1 = points(i+1, 2);  % 计算有向面积  A_temp = xi * yi1 - xi1 * yi;  A = A + A_temp;  % 计算重心坐标  Cx = Cx + (xi + xi1) * A_temp;  Cy = Cy + (yi + yi1) * A_temp;  
end  A = A / 2; % 最终面积取绝对值  
Cx = Cx / (6 * A);  
Cy = Cy / (6 * A);  end  

  得到了重心坐标后,接着进行平移。GPointsAll和GSeqAll用于存储所有标记点的坐标和连接方式,方便一次性绘图,在循环中绘图会影响绘图速度。

% 重心坐标
[Cx, Cy] = polygonCentroid(GPoints);% 初始化所有标记的坐标和连接顺序
GPointsAll = zeros(NMarker*NG,2);
GSeqAll = zeros(NMarker,NG);
for k = 1:NMarker% 取出当前点坐标xk = x(PM(k));yk = y(PM(k));% 平移displacement = [xk-Cx,yk-Cy];% 更新坐标GPointsNew = GPoints;GPointsNew = GPointsNew + repmat(displacement,size(GPoints,1),1);% 保存所有标记点的坐标和连接顺序GPointsAll(NG*(k-1)+1:NG*k,:) = GPointsNew;GSeqAll(k,:) =  GSeq + NG*(k-1);end

绘制标记点

  绘制标记点采用patch函数,patch函数其中一个用法如下:

  • patch(‘Faces’,F,‘Vertices’,V) 创建一个或多个多边形,其中 V 指定顶点的值,F 定义要连接的顶点。

  FaceColor、EdgeColor和LineWidth分别表示面的颜色、线的颜色和线的宽度。此外,patch是MATLAB中绘制多边形非常强大的函数,其他用途可以参考下面这几篇文章。
【MATLAB学习笔记】绘图——errorbar误差图+patch误差填充图
【有限元学习笔记】二维四边形单元位移云图可视化(有限元后处理)——MATLAB程序
【有限元学习笔记】二维四边形单元应力动画云图可视化(高斯点应力外推、应力绕节点平均)——MATLAB程序

% 绘制标记点
pa = patch('Faces', GSeqAll, 'Vertices', GPointsAll, 'FaceColor', faceColor,...'EdgeColor', edgeColor,'LineWidth',lineWidth);

在这里插入图片描述

边界标记点不裁剪

  此外,注意到处于边界处的标记点(红色)被边界裁剪了,而MATLAB自带的标记点(黑色)并没有被裁剪。
在这里插入图片描述
  要想实现这个效果只需要将patch中的Clipping属性设置为 “off” 即可。

% 绘制标记点
pa = patch('Faces', GSeqAll, 'Vertices', GPointsAll, 'FaceColor', faceColor,...'EdgeColor', edgeColor,'LineWidth',lineWidth, 'Clipping', 'off');

在这里插入图片描述

拓展功能——标记点自适应绘图区的缩放

绘图区缩放回调函数

  要想标记点自适应绘图区的缩放而变化,就需要采用回调函数对一些参数进行更改。简单来说,回调函数就是用户在执行某一项操作时自动执行的函数。
  下面利用回调函数监控了绘图区的缩放,当绘图区进行缩放时就会自动执行updateZoomInfo这个函数。setappdata函数可以将变量数据动态保存,方便在updateZoomInfo函数中使用这个数据,下面代码中保存了x轴范围xl和y轴范围yl。

%%%% 控制图形放大缩小 %%%%
zm = zoom(ax);
zm.Enable = 'on'; % 启用缩放% 初始化xy轴范围
xl = xlim;
yl = ylim;
setappdata(ax,'lxl',xl) 
setappdata(ax,'lyl',yl) % 绘图区缩放回调函数
set(zm, 'ActionPostCallback', @(src, event) updateZoomInfo(ax,x,y,PM,NG,NMarker,pa,pa2)); 

回调函数updateZoomInfo的代码如下:

function updateZoomInfo(ax,x,y,PM,NG,NMarker,pa,pa2)  
% 监控绘图区缩放并自动更新标记图形的回调函数
%
% ax        绘图区
% x,y       绘图的数据点
% PM        需要绘制的点位置
% NG        % 点数量
% NMarker   形状的数量
% pa        patch对象
% pa2       边缘点的patch对象% 当前xy轴范围   
cxl = xlim(ax); 
cyl = ylim(ax);% 获取上一次缩放的xy轴范围
lxl = getappdata(ax,'lxl');
lyl = getappdata(ax,'lyl');
% 保存当前的xy轴范围
setappdata(ax,'lxl',cxl)
setappdata(ax,'lyl',cyl)% 计算xy轴放大因子
xScaleFactor = (lxl(2) - lxl(1))/(cxl(2) - cxl(1));  
yScaleFactor = (lyl(2) - lyl(1))/(cyl(2) - cyl(1));% 获取当前的节点坐标和连接顺序
GPoints = pa.Vertices;
GSeq = pa.Faces;
GPoints = GPoints(1:NG,:);% 缩放坐标
GPoints(:,1) = GPoints(:,1)/xScaleFactor;
GPoints(:,2) = GPoints(:,2)/yScaleFactor;% 重心坐标
[Cx, Cy] = polygonCentroid(GPoints);% 初始化所有标记的坐标
GPointsAll = zeros(NMarker*NG,2);
% 边界点判断初始化
BJudge = false(NMarker,1);
for k = 1:NMarker% 取出当前点坐标xk = x(PM(k));yk = y(PM(k));% 平移displacement = [xk-Cx,yk-Cy];% 更新坐标GPointsNew = GPoints;GPointsNew = GPointsNew + repmat(displacement,size(GPoints,1),1);% 保存所有标记点的坐标GPointsAll(NG*(k-1)+1:NG*k,:) = GPointsNew;%%% 判断当前标记点是否处于边界 %%%% 标记形状坐标判断BLeft = GPointsNew(:, 1) - cxl(1);    % 左边界BRight = GPointsNew(:, 1) - cxl(2);   % 右边界BDown = GPointsNew(:, 2) - cyl(1);    % 下边界BUp = GPointsNew(:, 2) - cyl(2);      % 上边界% 标记中心坐标判断BLeft2 = xk - cxl(1);    % 左边界BRight2 = cxl(2)-xk;     % 右边界BDown2 = yk - cyl(1);    % 下边界BUp2 = cyl(2) - yk;      % 上边界% 判断形状坐标是否同号以及中心点是否在绘图区if (~judgeSign(BLeft) || ~judgeSign(BRight) || ~judgeSign(BDown) || ~judgeSign(BUp)) && ...all([BLeft2,BRight2,BDown2,BUp2]>0)BJudge(k) = true;  % 记录处于边界的标记点endend% 更新坐标
pa.Vertices = GPointsAll;% 设置绘图区边缘图形不裁剪
pa2.Vertices = GPointsAll;
pa2.Faces = GSeq(BJudge,:);end  

标记点大小自适应

  在缩放绘图区时,自定义标记的大小并不会自己改变,而MATLAB自带的标记大小会自动更新,如下图。
在这里插入图片描述
  要想实现这种效果就需要在回调函数中计算当前x轴和y轴的放大比例,在对标记点的坐标进行缩放。其中getappdata表示获取上一次缩放的x轴和y轴范围,setappdata保存当前的x轴和y轴范围。计算得到了放大因子后,对标记图形的x坐标和y坐标分别缩放即可。

% 当前xy轴范围   
cxl = xlim(ax); 
cyl = ylim(ax);% 获取上一次缩放的xy轴范围
lxl = getappdata(ax,'lxl');
lyl = getappdata(ax,'lyl');
% 保存当前的xy轴范围
setappdata(ax,'lxl',cxl)
setappdata(ax,'lyl',cyl)% 计算xy轴放大因子
xScaleFactor = (lxl(2) - lxl(1))/(cxl(2) - cxl(1));  
yScaleFactor = (lyl(2) - lyl(1))/(cyl(2) - cyl(1));% 获取当前的节点坐标和连接顺序
GPoints = pa.Vertices;
GSeq = pa.Faces;
GPoints = GPoints(1:NG,:);% 缩放坐标
GPoints(:,1) = GPoints(:,1)/xScaleFactor;
GPoints(:,2) = GPoints(:,2)/yScaleFactor;

  得到了缩放后的坐标后,需要重新计算新的平移位置。其中%%% 判断当前标记点是否处于边界 %%%部分见下一小节。

% 重心坐标
[Cx, Cy] = polygonCentroid(GPoints);% 初始化所有标记的坐标
GPointsAll = zeros(NMarker*NG,2);
% 边界点判断初始化
BJudge = false(NMarker,1);
for k = 1:NMarker% 取出当前点坐标xk = x(PM(k));yk = y(PM(k));% 平移displacement = [xk-Cx,yk-Cy];% 更新坐标GPointsNew = GPoints;GPointsNew = GPointsNew + repmat(displacement,size(GPoints,1),1);% 保存所有标记点的坐标GPointsAll(NG*(k-1)+1:NG*k,:) = GPointsNew;%%% 判断当前标记点是否处于边界 %%%% 标记形状坐标判断BLeft = GPointsNew(:, 1) - cxl(1);    % 左边界BRight = GPointsNew(:, 1) - cxl(2);   % 右边界BDown = GPointsNew(:, 2) - cyl(1);    % 下边界BUp = GPointsNew(:, 2) - cyl(2);      % 上边界% 标记中心坐标判断BLeft2 = xk - cxl(1);    % 左边界BRight2 = cxl(2)-xk;     % 右边界BDown2 = yk - cyl(1);    % 下边界BUp2 = cyl(2) - yk;      % 上边界% 判断形状坐标是否同号以及中心点是否在绘图区if (~judgeSign(BLeft) || ~judgeSign(BRight) || ~judgeSign(BDown) || ~judgeSign(BUp)) && ...all([BLeft2,BRight2,BDown2,BUp2]>0)BJudge(k) = true;  % 记录处于边界的标记点endend

  最后,更新一下patch中的点数据。

% 更新坐标
pa.Vertices = GPointsAll;

  放大后的效果图如下。可见,随着绘图区的缩放,自定义标记在屏幕上的大小并不会发生改变,与MATLAB自带的标记一致。
在这里插入图片描述

标记点裁剪自适应

  将原本patch中的Clipping属性设置为 “off”,虽然在初始图形中显示为不裁剪,但是在图形放大时会出现标记点偏离绘图区很多的情况,如下图。
应
  因此,不能直接将原来的patch函数中的Clipping属性设置为 “off” ,而是需要创建一个新的patch函数,只针对边界处的标记点设置成不裁剪模式。首先,需要判断标记点是否处于边界处,判断准则有两个:

  1. 重心点位于绘图区内;
  2. 标记图形的任意点超出绘图区。

  基于此准则,将平移标记点部分的最后一个代码块修改如下(回调函数部分的也是如此,只不过该部分在上一小节中已经提前改好了)。其中BJudge用于判断该标记点是否处于边界处,若处于边界处则为true,反之则为false。

% 重心坐标
[Cx, Cy] = polygonCentroid(GPoints);% 初始化所有标记的坐标和连接顺序
GPointsAll = zeros(NMarker*NG,2);
GSeqAll = zeros(NMarker,NG);% 边界点判断初始化
BJudge = false(NMarker,1);
for k = 1:NMarker% 取出当前点坐标xk = x(PM(k));yk = y(PM(k));% 平移displacement = [xk-Cx,yk-Cy];% 更新坐标GPointsNew = GPoints;GPointsNew = GPointsNew + repmat(displacement,size(GPoints,1),1);% 保存所有标记点的坐标和连接顺序GPointsAll(NG*(k-1)+1:NG*k,:) = GPointsNew;GSeqAll(k,:) =  GSeq + NG*(k-1);%%% 判断当前标记点是否处于边界 %%%   % 标记形状坐标判断BLeft = GPointsNew(:, 1) - xl(1);    % 左边界BRight = GPointsNew(:, 1) - xl(2);   % 右边界BDown = GPointsNew(:, 2) - yl(1);    % 下边界BUp = GPointsNew(:, 2) - yl(2);      % 上边界% 标记中心坐标判断BLeft2 = xk - xl(1);    % 左边界BRight2 = xl(2)-xk;     % 右边界BDown2 = yk - yl(1);    % 下边界BUp2 = yl(2) - yk;      % 上边界% 判断形状坐标是否同号以及中心点是否在绘图区if (~judgeSign(BLeft) || ~judgeSign(BRight) || ~judgeSign(BDown) || ~judgeSign(BUp)) && ...all([BLeft2,BRight2,BDown2,BUp2]>=0)BJudge(k) = true;  % 记录处于边界的标记点end end

  上面代码中的judgeSign为判断数组中的元素是否为同号的函数,如果是则返回ture,反之则返回false。

function flag = judgeSign(arr) 
% 判断数组中所有元素是否同号(包含0也算同号)% 计算数组每个元素的符号  signs = sign(arr(arr~=0));  % 获取唯一符号  uniqueSigns = unique(signs);  % 判断是否只有一个唯一符号,并且该符号不为零  if length(uniqueSigns) == 1 && uniqueSigns ~= 0  flag = true; % 同号  else  flag = false; % 不同号或包含零  end  
end

  得到BJudge数组后,将边界处的标记点的’Clipping’设置为’off’。

% 设置绘图区边缘图形不裁剪
pa2 = patch('Faces', GSeqAll(BJudge,:), 'Vertices', GPointsAll, 'FaceColor', faceColor,...'EdgeColor', edgeColor,'LineWidth',lineWidth, 'Clipping', 'off');

  最后,在回调函数中更新点数据和连接顺序。

% 设置绘图区边缘图形不裁剪
pa2.Vertices = GPointsAll;
pa2.Faces = GSeq(BJudge,:);

效果图如下,可见图形放大时不会导致出现标记点偏离绘图区很多的情况了。
在这里插入图片描述

示例

基本绘图

  下面这是基本的绘图代码,具体细节可以参考前面的文章(点击转跳)。

clc;clear;close all
set(0,'defaultfigurecolor','w');%% 数据
x = -1:1:10;   % 产生0到1, 步长为0.01的序列
y = 2*x.^3 + 1;    % y为x一次函数%% 绘图
f = figure(1);
ax = gca;   % 将当前坐标区实例化
plot(x,y,'-k','LineWidth',1.3)
xlim([-1,10])set(gca,'FontName','Times New Roman','FontSize',13)
xlabel('\fontname{宋体} 位移\fontname{Times New Roman} \it x/\rm mm')
ylabel('\fontname{宋体} 力\fontname{Times New Roman} \it y/\rm N') % 去除上边框、右边框刻度线
box off     % 取消边框
ax1 = axes('Position',get(ax,'Position'),'XAxisLocation','top',...'YAxisLocation','right','Color','none','XColor','k','YColor','k');  % 设置坐标区
set(ax1,'XTick', [],'YTick', []);   % 去掉xy轴刻度
hold off% 保存图片
print(f,'-dpng','FigName','-r600')%保存图像

运行代码后得到下面的结果图。
在这里插入图片描述

自定义标记函数的使用

  自定义标记函数的使用非常简单,一共有两个步骤。

  1. 定义点和连接顺序
      首先,创建自定义标记的点坐标和连接顺序(不需要首尾相连),下面提供了上文中自定义多边形标记的数据。
GPoints = [-1,-1;0, 1;1,-1;0,-0.5];
GSeq = 1:4;

在这里插入图片描述
2. 定义标记属性
  接着定义该形状的一些属性,不需要定义的属性则以[ ]代替。

faceColor = 'r';
edgeColor = 'r';
pounds = 6;
lineWidth = [];
NMarker = [];
setMarker(ax, x, y, GPoints, GSeq, faceColor, edgeColor, pounds, lineWidth, NMarker)

  效果图如下,最右侧的标记被右框线覆盖了一部分,解决办法可以查看下面这篇文章。
【MATLAB学习笔记】绘图——去除上、右边框刻度后图被框线覆盖解决方案
在这里插入图片描述

  • 更换形状
      形状可以自己定义,下面是一个不同于前面的形状,同样也可以绘制成功。
GPoints = [-1,-1;1, -0.3;0, 0;1,0.7;-1,1];
GSeq = 1:5;

在这里插入图片描述
在这里插入图片描述

总代码

  总代码如下,后续还会继续更新一些MATLAB绘图的技巧和细节,制作不易,别忘了关注和点赞喔

主函数

clc;clear;close all
set(0,'defaultfigurecolor','w');%% 数据
x = -1:1:10;   % 产生0到1, 步长为0.01的序列
y = 2*x.^3 + 1;    % y为x一次函数%%% 形状1
GPoints = [-1,-1;0, 1;1,-1;0,-0.5];
GSeq = 1:4;% %%% 形状2
% GPoints = [-1,-1;
%            1, -0.3;
%            0, 0;
%            1,0.7;
%            -1,1];
% GSeq = 1:5;%% 绘图
f = figure(1);
ax = gca;   % 将当前坐标区实例化
plot(x,y,'-k','LineWidth',1.3)
xlim([-1,10])faceColor = 'r';
edgeColor = 'r';
pounds = 6;
lineWidth = [];
NMarker = [];
setMarker(ax, x, y, GPoints, GSeq, faceColor, edgeColor, pounds, lineWidth, NMarker)set(gca,'FontName','Times New Roman','FontSize',13)
xlabel('\fontname{宋体} 位移\fontname{Times New Roman} \it x/\rm mm')
ylabel('\fontname{宋体} 力\fontname{Times New Roman} \it y/\rm N') % 去除上边框、右边框刻度线
box off     % 取消边框
ax1 = axes('Position',get(ax,'Position'),'XAxisLocation','top',...'YAxisLocation','right','Color','none','XColor','k','YColor','k');  % 设置坐标区
set(ax1,'XTick', [],'YTick', []);   % 去掉xy轴刻度
hold off% 保存图片
print(f,'-dpng','图14','-r600')%保存图像

自定义标记函数

function setMarker(ax, x, y, GPoints, GSeq, faceColor, edgeColor, pounds, lineWidth, NMarker)
% 自定义设置标记形状(目前只能绘制没有交叉线的图形), 前5个参数为必须输入, 其他参数为可选参数
% 若不需要则以空数组[]代替, 如不需要形状磅数pounds, 则pounds = []
%
% ax        绘图区
% x,y       绘图的数据点
% GPoints   形状的点坐标
% GSeq      形状点的连接顺序(不需要首尾相连)
% faceColor 填充的面颜色  默认:none
% edgeColor 填充的线颜色  默认:k
% pounds    形状的大小(磅数)  默认:6
% lineWidth 形状的线宽度  % 默认:0.5
% NMarker   形状的数量(近似数量, 采用等间距, 如不能整除则四舍五入)  
%           默认:0, 0则代表全部点都绘制if nargin < 6, faceColor = 'none'; end
if nargin < 7, edgeColor = 'k'; end
if nargin < 8, pounds = 6; end
if nargin < 9, lineWidth = 0.5; end
if nargin < 10, NMarker = 0; endif isempty(faceColor), faceColor = 'none'; end
if isempty(edgeColor), edgeColor = 'k'; end
if isempty(pounds), pounds = 6; end
if isempty(lineWidth), lineWidth = 0.5; end
if isempty(NMarker), NMarker = 0; endif NMarker == 0, NMarker = length(x); endhold(ax,"on") NG = length(GSeq);  % 点数量% 将图形的比例拉正
xl = xlim;
yl = ylim;
lenx = xl(2)-xl(1); % x轴的长度
leny = yl(2)-yl(1); % y轴的长度
yRatio = leny/lenx; % 纵横比比例
GPoints(:,2) = GPoints(:,2)*yRatio; % 修正坐标 % 获取绘图区的每单位在屏幕上的长度(磅)
unitLength = getUnitLength();% 水平长度矩阵
L = max(GPoints(:,1)) - min(GPoints(:,1));% 缩放比例
scale = 2*pounds/(L*unitLength);% 缩放
GPoints = GPoints*scale;% 计算绘制的点位置
PM = 1:round(length(x)/NMarker):length(x);   % 需要绘制的点位置
NMarker = length(PM);                        % 绘制标记的真实数量% 重心坐标
[Cx, Cy] = polygonCentroid(GPoints);% 初始化所有标记的坐标和连接顺序
GPointsAll = zeros(NMarker*NG,2);
GSeqAll = zeros(NMarker,NG);% 边界点判断初始化
BJudge = false(NMarker,1);
for k = 1:NMarker% 取出当前点坐标xk = x(PM(k));yk = y(PM(k));% 平移displacement = [xk-Cx,yk-Cy];% 更新坐标GPointsNew = GPoints;GPointsNew = GPointsNew + repmat(displacement,size(GPoints,1),1);% 保存所有标记点的坐标和连接顺序GPointsAll(NG*(k-1)+1:NG*k,:) = GPointsNew;GSeqAll(k,:) =  GSeq + NG*(k-1);%%% 判断当前标记点是否处于边界 %%%   % 标记形状坐标判断BLeft = GPointsNew(:, 1) - xl(1);    % 左边界BRight = GPointsNew(:, 1) - xl(2);   % 右边界BDown = GPointsNew(:, 2) - yl(1);    % 下边界BUp = GPointsNew(:, 2) - yl(2);      % 上边界% 标记中心坐标判断BLeft2 = xk - xl(1);    % 左边界BRight2 = xl(2)-xk;     % 右边界BDown2 = yk - yl(1);    % 下边界BUp2 = yl(2) - yk;      % 上边界% 判断形状坐标是否同号以及中心点是否在绘图区if (~judgeSign(BLeft) || ~judgeSign(BRight) || ~judgeSign(BDown) || ~judgeSign(BUp)) && ...all([BLeft2,BRight2,BDown2,BUp2]>=0)BJudge(k) = true;  % 记录处于边界的标记点end end
% 绘制标记点
pa = patch('Faces', GSeqAll, 'Vertices', GPointsAll, 'FaceColor', faceColor,...'EdgeColor', edgeColor,'LineWidth',lineWidth);% 设置绘图区边缘图形不裁剪
pa2 = patch('Faces', GSeqAll(BJudge,:), 'Vertices', GPointsAll, 'FaceColor', faceColor,...'EdgeColor', edgeColor,'LineWidth',lineWidth, 'Clipping', 'off');%%%% 控制图形放大缩小 %%%%
zm = zoom(ax);
zm.Enable = 'on'; % 启用缩放% 初始化xy轴范围
xl = xlim;
yl = ylim;
setappdata(ax,'lxl',xl) 
setappdata(ax,'lyl',yl) % 绘图区缩放回调函数
set(zm, 'ActionPostCallback', @(src, event) updateZoomInfo(ax,x,y,PM,NG,NMarker,pa,pa2)); end

回调函数

function updateZoomInfo(ax,x,y,PM,NG,NMarker,pa,pa2)  
% 监控绘图区缩放并自动更新标记图形的回调函数
%
% ax        绘图区
% x,y       绘图的数据点
% PM        需要绘制的点位置
% NG        % 点数量
% NMarker   形状的数量
% pa        patch对象
% pa2       边缘点的patch对象% 当前xy轴范围   
cxl = xlim(ax); 
cyl = ylim(ax);% 获取上一次缩放的xy轴范围
lxl = getappdata(ax,'lxl');
lyl = getappdata(ax,'lyl');
% 保存当前的xy轴范围
setappdata(ax,'lxl',cxl)
setappdata(ax,'lyl',cyl)% 计算xy轴放大因子
xScaleFactor = (lxl(2) - lxl(1))/(cxl(2) - cxl(1));  
yScaleFactor = (lyl(2) - lyl(1))/(cyl(2) - cyl(1));% 获取当前的节点坐标和连接顺序
GPoints = pa.Vertices;
GSeq = pa.Faces;
GPoints = GPoints(1:NG,:);% 缩放坐标
GPoints(:,1) = GPoints(:,1)/xScaleFactor;
GPoints(:,2) = GPoints(:,2)/yScaleFactor;% 重心坐标
[Cx, Cy] = polygonCentroid(GPoints);% 初始化所有标记的坐标
GPointsAll = zeros(NMarker*NG,2);
% 边界点判断初始化
BJudge = false(NMarker,1);
for k = 1:NMarker% 取出当前点坐标xk = x(PM(k));yk = y(PM(k));% 平移displacement = [xk-Cx,yk-Cy];% 更新坐标GPointsNew = GPoints;GPointsNew = GPointsNew + repmat(displacement,size(GPoints,1),1);% 保存所有标记点的坐标GPointsAll(NG*(k-1)+1:NG*k,:) = GPointsNew;%%% 判断当前标记点是否处于边界 %%%% 标记形状坐标判断BLeft = GPointsNew(:, 1) - cxl(1);    % 左边界BRight = GPointsNew(:, 1) - cxl(2);   % 右边界BDown = GPointsNew(:, 2) - cyl(1);    % 下边界BUp = GPointsNew(:, 2) - cyl(2);      % 上边界% 标记中心坐标判断BLeft2 = xk - cxl(1);    % 左边界BRight2 = cxl(2)-xk;     % 右边界BDown2 = yk - cyl(1);    % 下边界BUp2 = cyl(2) - yk;      % 上边界% 判断形状坐标是否同号以及中心点是否在绘图区if (~judgeSign(BLeft) || ~judgeSign(BRight) || ~judgeSign(BDown) || ~judgeSign(BUp)) && ...all([BLeft2,BRight2,BDown2,BUp2]>0)BJudge(k) = true;  % 记录处于边界的标记点endend% 更新坐标
pa.Vertices = GPointsAll;% 设置绘图区边缘图形不裁剪
pa2.Vertices = GPointsAll;
pa2.Faces = GSeq(BJudge,:);end  

其他函数

  • 计算标记图形重心函数
function [Cx, Cy] = polygonCentroid(points)  
% 计算形状的重心坐标
% points 一个n x 2 的矩阵, 表示n个点的坐标, 第一列为x值, 第二列为y值  % 点的数量  
n = size(points, 1);  % 闭合多边形  
points = [points; points(1, :)];  % 初始化面积和重心坐标  
A = 0;  
Cx = 0;  
Cy = 0;  % 计算面积和重心坐标  
for i = 1:n  xi = points(i, 1);  yi = points(i, 2);  xi1 = points(i+1, 1);  yi1 = points(i+1, 2);  % 计算有向面积  A_temp = xi * yi1 - xi1 * yi;  A = A + A_temp;  % 计算重心坐标  Cx = Cx + (xi + xi1) * A_temp;  Cy = Cy + (yi + yi1) * A_temp;  
end  A = A / 2; % 最终面积取绝对值  
Cx = Cx / (6 * A);  
Cy = Cy / (6 * A);  end  
  • 获取绘图区单位长度磅数函数
function unitLength = getUnitLength()
% 获取绘图区的每单位在屏幕上的长度(磅)% 获取图形窗口的大小和位置  
Po = get(gcf, 'Position'); % 返回的单位是点 (points),每个点约为 1/72 英寸  
figWidth = Po(3); % 图形的宽度 % X轴范围  
xl = xlim;
rangeX = xl(2) - xl(1); % 绘图区的每单位在屏幕上的长度(磅)
unitLength = figWidth/rangeX;end
  • 判断数组中所有元素是否同号函数
function flag = judgeSign(arr) 
% 判断数组中所有元素是否同号(包含0也算同号)% 计算数组每个元素的符号  signs = sign(arr(arr~=0));  % 获取唯一符号  uniqueSigns = unique(signs);  % 判断是否只有一个唯一符号,并且该符号不为零  if length(uniqueSigns) == 1 && uniqueSigns ~= 0  flag = true; % 同号  else  flag = false; % 不同号或包含零  end  
end

总结

  这只是一个基础的示例,实际中还会有更具体的、更细致的要求,这就需要再做额外调整;另外本人也仍在学习中,这只是个人的学习笔记,可能还有一些不足之处,欢迎指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/52625.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

语言的基本运算

编程语言基本数据类型的加减乘除&#xff0c;看起来都很像。它们都和数学公示很像&#xff0c;除了乘法不能用X或x&#xff0c;这个是字母&#xff0c;除法不能用&#xff0c;因为这个字符在键盘上看不到。 除法的余数? C/C整数除法默认会丢弃余数&#xff0c;Java/C#一样。P…

入门STM32--按键输入

上一篇博客我们介绍了如何使用GPIO配置跑马灯&#xff0c;根据GPIO的基本结构图&#xff0c;我们能够发现&#xff0c;他肯定不单单有输出的功能&#xff0c;肯定可以检测IO上的电平变化&#xff0c;实际上就是输入的功能。 1.按键 在大多数情况下&#xff0c;按键是一种简单的…

【第54课】XSS跨站Cookie盗取表单劫持网络钓鱼溯源分析项目平台框架

免责声明 本文发布的工具和脚本&#xff0c;仅用作测试和学习研究&#xff0c;禁止用于商业用途&#xff0c;不能保证其合法性&#xff0c;准确性&#xff0c;完整性和有效性&#xff0c;请根据情况自行判断。 如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利&#xff0…

1259:【例9.3】求最长不下降序列 动态规划

1259&#xff1a;【例9.3】求最长不下降序列 题目链接 【输入样例】 【输入样例】 14 13 7 9 16 38 24 37 18 44 19 21 22 63 15【输出样例】 max8 7 9 16 18 19 21 22 63思路&#xff1a; 确定状态&#xff1a; a[n]数组放数据&#xff0c; dp[n]数组放第i个位子前最长子序…

kafka发送消息-生产者发送消息的分区策略(消息发送到哪个分区中?是什么策略)

生产者发送消息的分区策略&#xff08;消息发送到哪个分区中&#xff1f;是什么策略&#xff09; 1、默认策略&#xff0c;程序自动计算并指定分区1.1、指定key&#xff0c;不指定分区1.2、不指定key&#xff0c;不指定分区 2、轮询分配策略RoundRobinPartitioner2.1、创建配置…

Linux网络:网络基础

Linux网络&#xff1a;网络基础 一、网络诞生背景及产生的诸多问题1. 1 网络诞生背景1.2 网络诞生面临的困境 二、网络协议栈&#xff08;OSI七层模型、CP/IP五层模型&#xff09;2.1 TCP/IP五层(或四层)模型 三、网络和系统关系四、网络传输流程4.1 同一个局域网中的两台主机进…

折腾 Quickwit,Rust 编写的分布式搜索引擎-官方教程

快速上手 在本快速入门指南中&#xff0c;我们将安装 Quickwit&#xff0c;创建一个索引&#xff0c;添加文档&#xff0c;最后执行搜索查询。本指南中使用的所有 Quickwit 命令都在 CLI 参考文档 中进行了记录。 https://quickwit.io/docs/main-branch/reference/cli 使用 Qui…

如何在Ubuntu 16.04上更新Firefox版本

如何在Ubuntu 16.04上更新Firefox版本 在Ubuntu 16.04上更新Firefox版本有多种方法&#xff0c;每种方法都有其优点。下面我们将介绍几种常见的方法&#xff0c;帮助您确保浏览器保持最新状态。 1. 使用官方PPA&#xff08;个人包档案&#xff09; 官方PPA提供了最新版本的F…

ubuntu22.04安装redis

更新包管理器的索引&#xff1a; sudo apt update安装Redis&#xff1a; sudo apt install redis-server确认Redis已经安装并且正在运行&#xff1a; sudo systemctl status redis-server

flutter 中 ssl 双向证书校验

SSL 证书&#xff1a; 在处理 https 请求的时候&#xff0c;通常可以使用 中间人攻击的方式 获取 https 请求以及响应参数。应为通常我们是 SSL 单向认证&#xff0c;服务器并没有验证我们的客户端的证书。为了防止这种中间人攻击的情况。我么可以通过 ssl 双向认证的方式。即…

用Python实现时间序列模型实战——Day1:时间序列的基本概念

一、学习内容 1. 时间序列数据的定义与特点 定义&#xff1a; 时间序列数据是一组按时间顺序排列的观测值。时间序列的每个观测值都与特定时间点相关联。例如&#xff0c;气温每天的记录、股票每日的收盘价等。 特点&#xff1a; 时间依赖性&#xff1a;时间序列数据的一个…

Eureka的生命周期管理:服务注册、续约与下线的完整流程解析

Eureka的生命周期管理&#xff1a;服务注册、续约与下线的完整流程解析 引言 在分布式系统中&#xff0c;服务发现是微服务架构的核心问题之一。Eureka是Netflix开源的一个服务发现框架&#xff0c;它能够有效地管理微服务的生命周期&#xff0c;包括服务注册、续约和下线。这…

8.27-dockerfile的应用+私有仓库的创建

一、dockerfile应用 通过dockerfile创建⼀个在启动容器时&#xff0c;就可以启动httpd服务的镜像 1.步骤 : 1.创建⼀个⽬录&#xff0c;⽤于存储Docker file所使⽤的⽂件2.在此⽬录中创建Docker file⽂件&#xff0c;以及镜像制作所使⽤的⽂件3.使⽤docker build创建镜像4.使…

基于x86 平台opencv的图像采集和seetaface6的图像质量评估功能

目录 一、概述二、环境要求2.1 硬件环境2.2 软件环境三、开发流程3.1 编写测试3.2 配置资源文件3.3 验证功能一、概述 本文档是针对x86 平台opencv的图像采集和seetaface6的图像质量评估功能,opencv通过摄像头采集视频图像,将采集的视频图像送给seetaface6的图像质量评估模块…

全新的大语言模型Grok-2,最新测评!!

埃隆马斯克再次引发轰动&#xff0c;他旗下的xAI公司推出了全新的大语言模型Grok-2&#xff01; 最新的Grok-2测试版已经发布&#xff0c;用户可以在&#x1d54f;平台上体验小版本的Grok-2 mini。 马斯克还通过一种谜语般的方式揭开了困扰大模型社区一个多月的谜团&#xff1a…

Java笔试面试题AI答之面向对象(2)

文章目录 7. Java中的组合、聚合和关联有什么区别&#xff1f;1. 关联&#xff08;Association&#xff09;2. 聚合&#xff08;Aggregation&#xff09;3. 组合&#xff08;Composition&#xff09;总结 8. 请设计一个符合开闭原则的设计模式的例子&#xff1f;策略模式示例1.…

每日刷力扣SQL(九)

1484.按日期分组销售产品 转载 首先&#xff0c;根据题目的描述以及给出的示例。我们能得到结果集中各个字段的含义如下&#xff1a; ① sell_date&#xff1a;卖出产品的日期&#xff08;应该不用解释了&#xff09; ② num_sold&#xff1a;当前这个日期下&…

工业软件架构2:(QT和C++实现)

工业软件架构 - 事件驱动 - 2 1. 命令模式的使用命令模式(Command Pattern)命令模式的基本概念命令模式的运作机制1. 定义命令接口2. 实现具体命令3. 调用者类4.扩展命令模式的功能撤销命令:宏命令:总结2. MVVM 模式的使用View(界面)部分则通过绑定与 ViewModel 交互:3.…

FFmpeg的入门实践系列三(基础知识)

欢迎诸位来阅读在下的博文~ 在这里&#xff0c;在下会不定期发表一些浅薄的知识和经验&#xff0c;望诸位能与在下多多交流&#xff0c;共同努力 文章目录 前期博客一、音视频常用术语二、FFmpeg库的结构介绍三、FFmpeg的常用函数初始化封装格式编解码器相关 四、FFmpeg常用的数…

FastCGI简述

FastCGI (FCGI) 是一种协议&#xff0c;用于改善 Web 服务器和应用程序之间的通信效率。它是在 CGI&#xff08;Common Gateway Interface&#xff09;的基础上发展起来的&#xff0c;旨在解决 CGI 在处理大量并发请求时存在的性能问题。 CGI的由来 最早的Web服务器只能简单地…