pgfplots 中的三维直方图

pgfplots 中的三维直方图

我有一个简单的三维直方图

在此处输入图片描述

我想将其导入 pgfplots,例如使用 matlab2tikz 或手动导入。

PGFplots 不提供 3d 直方图。

是否有捷径可寻?

答案1

您可以使用\addplot3 graphics在 中包含图像pgfplots。这允许您使用pgfplots绘制轴并使用数据坐标系添加注释。

如果您已将 Matlab 中的绘图另存为名为 的图像3dcolumnchart.png,例如以下代码

\addplot3 graphics[points={%
(-4.4449,4.6547,0) => (110.814,167.827)
(-4.633,-4.5186,0) => (264.187,74.679)
(4.5829,-4.5216,0) => (470.558,145.343)
(-0.45821,-0.43355,1157) => (287.474,379.016)
}] {3dcolumnchart.png};
\node at (axis cs:-1.5,0.5,490) [inner sep=0pt, pin={[pin edge={thick,black},align=left]145:Interesting\\Data Point}] {};

将产生

为此,您需要为四个点提供从数据坐标系到图形坐标系的映射。您可以使用 Matlab 中的“数据光标”查找点的数据坐标,然后在ptGIMP 等图像编辑器中查找相同点的图形坐标(以 为单位)。但是,这很快就会变得有点乏味。

我编写了一个名为的 Matlab 脚本pgfplotscsconversion.m,它允许您单击 Matlab 图中四个点,然后将映射写入 Matlab 命令提示符。

下面是我如何得出上图的一个例子。

  1. 创建 Matlab 图

    hist3(randn(10000,2)) % some random data
    set(get(gca,'child'),'FaceColor','interp','CDataMode','auto'); % colors
    set(gcf,'PaperPositionMode','auto') % make sure the "print" paper format is the same as the screen paper format
    
  2. 保存以下代码为pgfplotscsconversion.m

    function pgfplotscsconversion
    
    % Hook into the Data Cursor "click" event
    h = datacursormode(gcf);
    set(h,'UpdateFcn',@myupdatefcn,'SnapToDataVertex','off');
    datacursormode on
    
    % select four points in plot using mouse
    
    
    % The function that gets called on each Data Cursor click
    function [txt] = myupdatefcn(obj,event_obj)
    
    % Get the screen resolution, in dots per inch
    dpi = get(0,'ScreenPixelsPerInch');
    
    % Get the click position in pixels, relative to the lower left of the
    % screen
    screen_location=get(0,'PointerLocation');
    
    % Get the position of the plot window, relative to the lower left of
    % the screen
    figurePos = get(gcf,'Position');
    
    % Get the data coordinates of the cursor
    pos = get(event_obj,'Position');
    
    % Format the data and figure coordinates. The factor "72.27/dpi" is
    % necessary to convert from pixels to TeX points (72.27 poins per inch)
    display(['(',num2str(pos(1)),',',num2str(pos(2)),',',num2str(pos(3)),') => (',num2str((screen_location(1)-figurePos(1))*72.27/dpi),',',num2str((screen_location(2)-figurePos(2))*72.27/dpi),')'])
    
    % Format the tooltip display
    txt = {['X: ',num2str(pos(1))],['Y: ',num2str(pos(2))],['Z: ',num2str(pos(3))]};
    

    运行pgfplotscsconversion,单击绘图中的四个点。最好选择绘图边缘附近的非共线点。复制并粘贴写入 Matlab 命令窗口的四行。

  3. 将图导出为图像

    axis off
    print -dpng matlabout -r400 % PNG called "matlabout.png" with 400 dpi resolution
    

    如果您想使用矢量 PDF 输出,您必须自己设置纸张尺寸以匹配图形尺寸,因为 PDF 驱动程序不会自动调整尺寸:

    currentScreenUnits=get(gcf,'Units')     % Get current screen units
    currentPaperUnits=get(gcf,'PaperUnits') % Get current paper units
    set(gcf,'Units',currentPaperUnits)      % Set screen units to paper units
    plotPosition=get(gcf,'Position')        % Get the figure position and size
    set(gcf,'PaperSize',plotPosition(3:4))  % Set the paper size to the figure size
    set(gcf,'Units',currentScreenUnits)     % Restore the screen units
    
    print -dpdf matlabout      % PDF called "matlabout.pdf"
    
  4. 删除图像的白色背景,例如使用 ImageMagick 命令

    convert matlabout.png -transparent white 3dcolumnchart.png
    
  5. 将图像包含在pgfplots轴中。如果您选择了绘图角上的点,则您的、 和xmin应该xmax会自动设置,否则您必须自己提供这些。此外,您需要调整绘图的和以获得绘图的正确垂直位置。yminymaxwidthheight

    \documentclass[border=5mm]{standalone}
    \usepackage{pgfplots}
    
    \begin{document}
    \begin{tikzpicture}
    \begin{axis}[3d box,xmin=-5,xmax=5,ymin=-5,ymax=5,width=9cm,height=9.25cm,grid=both, minor z tick num=1]
    \addplot3 graphics[points={%
    (-4.4449,4.6547,0) => (110.814,167.827)
    (-4.633,-4.5186,0) => (264.187,74.679)
    (4.5829,-4.5216,0) => (470.558,145.343)
    (-0.45821,-0.43355,1157) => (287.474,379.016)
    }]
    {3dcolumnchart.png};
    \node at (axis cs:-1.5,0.5,490) [inner sep=0pt, pin={[pin edge={thick,black},align=left]145:Interesting\\Data Point}] {};
    \end{axis}
    \end{tikzpicture}
    \end{document}
    

答案2

我通过重复坐标成功实现了三维直方图效果。我只是将每个 x,y 组合重复 4 次,每次重复一次可能出现的 4 个条形图顶部。

例如,代码

\documentclass{minimal}
\usepackage{pgfplots}

\begin{document}
\begin{tikzpicture}
    \begin{axis}[
    view = {120}{35},% important to draw x,y in increasing order
    xmin = 0,
    ymin = 0,
    xmax = 3,
    ymax = 3,
    zmin = 0,
    unbounded coords = jump,
    colormap={pos}{color(0cm)=(white); color(6cm)=(blue)}
    ]
    \addplot3[surf,mark=none] coordinates {
        (0,0,0) (0,0,0) (0,1,0) (0,1,0) (0,2,nan) (0,2,nan) (0,3,nan) (0,3,nan)

        (0,0,0) (0,0,2) (0,1,2) (0,1,3) (0,2,3) (0,2,1) (0,3,1) (0,3,0)

        (1,0,0) (1,0,2) (1,1,2) (1,1,3) (1,2,3) (1,2,1) (1,3,1) (1,3,0)

        (1,0,0) (1,0,0) (1,1,0) (1,1,6) (1,2,6) (1,2,0) (1,3,0) (1,3,0)

        (2,0,nan) (2,0,nan) (2,1,0) (2,1,6) (2,2,6) (2,2,0) (2,3,nan) (2,3,nan)

        (2,0,0) (2,0,1) (2,1,1) (2,1,0) (2,2,0) (2,2,0) (2,3,nan) (2,3,nan)

        (3,0,0) (3,0,1) (3,1,1) (3,1,0) (3,2,nan) (3,2,nan) (3,3,nan) (3,3,nan)

        (3,0,0) (3,0,0) (3,1,0) (3,1,0) (3,2,nan) (3,2,nan) (3,3,nan) (3,3,nan)
    };
    \end{axis}
\end{tikzpicture}
\end{document}

生产

上面的 0 z 坐标值意味着具有与相同的值,zmin并且必须设置视图,以便首先绘制具有较低 x 和 y 坐标的点。

我不知道如何使条形图的侧面颜色与顶部颜色相同,因此采用单色色彩图。

此处可以看到更大的示例

这是我在 Python 中生成的一些数据。为了将其保存到文件中,我必须添加几个 for 循环,以确保边界处的所有点都设置为 z 值等于 zmin。下面的函数采用存储在x和中的 x、y 网格y。相应的 z 值位于 中,这是一个长度为 的列表z列表。它写入可以包含在 中的文件。它假设没有 NaN 值并将沿边界的 z 值设置为。len(x) - 1len(y) - 1output\addplot3 file {}zmin

import csv

def make3dhistogram(x, y, z, zmin, output):
    writer = csv.writer(open(output, 'wb'), delimiter=' ')
    i = 0
    for j in range(len(y)):
        writer.writerow((x[i], y[j], zmin))
        writer.writerow((x[i], y[j], zmin))
    for i in range(len(x)-1):        
        writer.writerow((x[i], y[0], zmin))
        for j in range(len(y)-1):
            writer.writerow((x[i], y[j], z[i][j]))
            writer.writerow((x[i], y[j+1], z[i][j]))            
        writer.writerow((x[i], y[len(y)-1], zmin))
        writer.writerow([])
        writer.writerow((x[i+1], y[0], zmin))
        for j in range(len(y)-1):
            writer.writerow((x[i+1], y[j], z[i][j]))
            writer.writerow((x[i+1], y[j+1], z[i][j]))          
        writer.writerow((x[i+1], y[len(y)-1], zmin))
        writer.writerow([])

    i = len(x)-1
    for j in range(len(y)):
        writer.writerow((x[i], y[j], zmin))
        writer.writerow((x[i], y[j], zmin))

例如

x = [0,1,2,3]
y = [0,1,2,3]
z = [[2,3,1], [0, 6, 0], [1, 0, 0]]
make3dhistogram(x, y, z, 0.0, 'data')

生成上面的简单图,这次在 z 平面上有一个网格,因为没有跳过任何点。

答案3

matlab2tikz现在完全支持 3D 直方图。这

load seamount
dat = [-y,x]; % Grid corrected for negative y-values
hist3(dat) % Draw histogram in 2D
n = hist3(dat); % Extract histogram data;
                % default to 10x10 bins
view([-37.5, 30]);

给出

matlab2tikz 输出

答案4

无需外部程序:只需生成一个立方体的散点图,代码的高度取决于数据。(我从这里,但代码的作者同意我在这里分享它;-)

更新:@sporc 发现了一个错误并帮助我改进了代码。谢谢!请参阅这里以便进一步补充代码。

如果您运行此程序,LaTeX 将告诉您要插入哪个前因子。由于扩展问题,我无法以简单的方式执行此操作,本质上是因为 pgfplots 仅在完成绘图后才设置比例。在此示例中,可以通过先执行空绘图来避免这种情况。但随后代码会崩溃,用户会修改两次,或做一些其他疯狂的事情,因此我觉得调整一个数字是两害相权取其轻。

在此处输入图片描述

\documentclass[tikz,border=3.14pt]{standalone}
\usetikzlibrary{calc}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\pgfplotsset{compat=1.16}
% from https://tex.stackexchange.com/a/102770/121799
\def\pgfplotsinvokeiflessthan#1#2#3#4{%
    \pgfkeysvalueof{/pgfplots/iflessthan/.@cmd}{#1}{#2}{#3}{#4}\pgfeov
}%
\def\pgfplotsmulticmpthree#1#2#3#4#5#6\do#7#8{%
    \pgfplotsset{float <}%
    \pgfplotsinvokeiflessthan{#1}{#4}{%
        % first key <:
        #7%
    }{%
        \pgfplotsinvokeiflessthan{#4}{#1}{%
            % first key >:
            #8%
        }{%
            % first key ==:
            \pgfplotsset{float <}%
            \pgfplotsinvokeiflessthan{#2}{#5}{%
                % second key <
                #7%
            }{%
                \pgfplotsinvokeiflessthan{#5}{#2}{%
                    % second key >
                    #8%
                }{%
                    % second key ==
                    \pgfplotsset{float <}%
                    \pgfplotsinvokeiflessthan{#3}{#6}{%
                        % third key <
                        #7%
                    }{%
                        % third key >=
                        #8%
                    }%
                }%
            }%
        }%
    }%
}%

\begin{document}
\pgfplotstableread[col sep=comma,header=true]{%
x,y,color,myvalue
2,3,1,100
4,3,2,3
2,7,3,0.75
7,7,4,45
8,5,2,3
2,5,1,10
4,-4,2,1
4,1,3,75
5,-1,4,4
5,2,2,3
1,-2,1,10
2,5,2,5
3,-8,3,75
4,5,4,42
7,-2,2,2
}{\datatable}
%
%\pgfplotstablesort[col sep=comma,header=true]\resulttable{\datatable}
\pgfplotstablesort[create on use/sortkey/.style={
        create col/assign/.code={%
            \edef\entry{{\thisrow{x}}{\thisrow{y}}{\thisrow{myvalue}}}%
            \pgfkeyslet{/pgfplots/table/create col/next content}\entry
        }
    },
    sort key=sortkey,
    sort cmp={%
        iflessthan/.code args={#1#2#3#4}{%
            \edef\temp{#1#2}%
            \expandafter\pgfplotsmulticmpthree\temp\do{#3}{#4}%
        },
    },
    sort,
    columns/Mtx/.style={string type},
    columns/Kind/.style={string type},]\resulttable{\datatable}

\begin{tikzpicture}%[x={(0.866cm,-0.5cm)},y={(0.866cm,0.5cm)},z={(0cm,1 cm)}]
\pgfplotsset{set layers}
\begin{axis}[% from section 4.6.4 of the pgfplotsmanual
        view={120}{40},
        width=320pt,
        height=280pt,
        z buffer=none,
        xmin=-1,xmax=9,
        ymin=-10,ymax=8,
        zmin=0,zmax=200,
        enlargelimits=upper,
        ztick={0,100,200},
        zticklabels={0,50,100}, % here one has to "cheat"
        % meaning that one has to put labels which are the actual value 
        % divided by 2. This is because the bars will be centered at these
        % values
        xtick=data,
        extra tick style={grid=major},
        ytick=data,
        grid=minor,
        xlabel={$x$},
        ylabel={$y$},
        zlabel={$z$},
        minor tick num=1,
        point meta=explicit,
        colormap name=viridis,
        scatter/use mapped color={
            draw=mapped color,fill=mapped color!70},
        execute at begin plot={}            
        ]
\path let \p1=($(axis cs:0,0,1)-(axis cs:0,0,0)$) in 
\pgfextra{\pgfmathsetmacro{\conv}{2*\y1}
\typeout{Kindly\space\space consider\space setting\space the\space 
        prefactor\space of\space z\space to\space \conv}};      
\addplot3 [visualization depends on={
0.9952*z \as \myz}, % you'll get told how to adjust the prefactor
scatter/@pre marker code/.append style={/pgfplots/cube/size z=\myz pt},%
scatter,only marks,
mark=cube*,mark size=5,opacity=1]
 table[x expr={\thisrow{x}},y expr={\thisrow{y}},z
 expr={1*\thisrow{myvalue}},
 meta expr={\thisrow{color}}
        ] \resulttable;
    \end{axis}
\end{tikzpicture}
\end{document}

相关内容