pgfplots:如何创建以日期为 x 轴的热图?

pgfplots:如何创建以日期为 x 轴的热图?

假设我们想用绘制一个漂亮的热pgfplots图。datex-axis


最小工作示例(MWE):

\documentclass[tikz]{standalone}
\usepackage{pgfplots}
\usetikzlibrary{positioning,calc}%

\begin{document}
\begin{tikzpicture}

\begin{scope}[local bounding box=plots]

    \begin{axis}[
                 view={0}{90},
                 shader=interp,
                 mesh/ordering=x varies,
                 mesh/cols=3,
                ]

    \addplot3[surf] coordinates {
        (0,0,50)  (1,0,50)  (2,0,50)
        (0,1,50)  (1,1,100)  (2,1,50)
        (0,2,50)  (1,2,50)  (2,2,50)
    };

    \end{axis}

\end{scope}

\end{tikzpicture}
\end{document}

结果截图:

当前状态的截图


问题说明:

如您所见,目前这些值基于 9 个不同位置的简单xyz坐标。

但是,我获得了大量由以下结构组成的数据:

Date;                   Date_decimal;   1;2;3;4;5;6;7;8;9;10
2019-06-01 12:00:00;    0.000;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;2;2
2019-06-01 12:05:00;    0.083;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;3;3
2019-06-01 12:10:00;    0.167;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;4;4
2019-06-01 12:15:00;    0.250;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;5;5
2019-06-01 12:20:00;    0.333;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;6;6
2019-06-01 12:30:00;    0.417;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;7;7
2019-06-01 12:35:00;    0.500;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;8;8
2019-06-01 12:40:00;    0.583;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;9;9
2019-06-01 12:45:00;    0.667;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;10;10
2019-06-01 12:50:00;    0.750;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;11;11

表格说明:第一列为实际的date,第二列为日期decimal_date(单位:小时),表格的其余部分仅y-columns包含z-value每个热图坐标的。此外,第一行中包含的数字1, 2, 3, ... , 10提供了每列的实际索引号。

因此我想使用以下方案:

  • date-值或可选date_decimal(单位:十进制小时)应用于x-轴;如果更容易实现date_decimal,只需跳过或丢弃date-列(= 10 件)
  • y-axis 应该是每个表列的列索引号(1、2、...、10);为了更好地理解,我将其写到第一行(= 10 个)
  • z-axis(彩色)应为热图坐标中每个单元格的对应值(= 100 个)

我该如何修改上面的图表,以便x在保留原始数据的矩阵结构的同时将其视为一个图表date axis?实际数据表包含 2000 行和 2000 列,因此不可能轻易重新排列它。:-)

date如果使用真实列作为似乎太复杂x-axis,可以简单地使用decimal_date-column 代替,同时丢弃 -column date。我已发布了另一个关于此的问题这里

我担心最大的挑战是将当前的数据结构移交给pgfplots


所需状态的屏幕截图:

最后它可能看起来像这样:

所需状态的屏幕截图

答案1

我已经适应了我的简单答案适用于此场景。它依赖于使用lualatex、启用shell-escape以及手动调整脚本中的一些参数。这些参数是

  • ycols是文件开头具有相同长度(不同) y 数据的列数。在本例中为 1。它可以为零,然后y输出数据从 1 计数到值的数量y
  • xrows是具有不同类型的 x 值的行数。在本例中为 2。它可以为零,然后x输出数据从 1 计数到值的数量x
  • delimiter是用于分隔矩阵条目的模式匹配变量。我不太熟悉lua,但[^;]+它适用于分号和%S+空格。
  • del_out是写入新输出文件的分隔符,这里是分号;

此外,还会检测数据条目太少或太多的行。假设第一个数据行是正确的。

读取输入数据的第ycols+1行以提取y值的数量。数据的表标题x是可选的。因此,输入数据可以采用原始格式(即使有附加条目)。

Date; nonsense;entries; Date_decimal;   1;2;3;4;5;6;7;8;9;10
2019-06-01 12:00:00;    0.000;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;2;2
2019-06-01 12:05:00;    0.083;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;3;3

但它也可以包含纯y值(这样做只是为了看起来更好)。两种方法都可以。

                                        1;2;3;4;5;6;7;8;9;10
2019-06-01 12:00:00;    0.000;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;2;2
2019-06-01 12:05:00;    0.083;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;3;3

重新格式化的数据如下所示:

x1;x2;y1;z
2019-06-01 12:00:00;0.000;1;0.083
2019-06-01 12:00:00;0.000;2;0.25
2019-06-01 12:00:00;0.000;3;0.25
2019-06-01 12:00:00;0.000;4;0.2
2019-06-01 12:00:00;0.000;5;0.22

x请注意,为了使文件更紧凑,所有前导和尾随空格都被删除。此数据中存在两种不同的数据类型,之后可以选择更合适的类型。为简单起见,我选择了行中的数值x2,但日期也可以访问,x1并可以使用dateplot库进行绘制。

通过调用\directlua{rewrite_mat("data2.dat","out.txt")},将中的矩阵以兼容的向量格式data2.txt重写为。out.txtpgfplots

完整代码(包括矩阵数据)在这里:

%!Tex program = lualatex
\documentclass[tikz]{standalone}
\usepackage{pgfplots,filecontents,luacode}
\pgfplotsset{compat=1.16}

\begin{filecontents*}{data2.dat}
some;data;doesn't;matter;               1;2;3;4;5;6;7;8;9;10
2019-06-01 12:00:00;    0.000;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;2;2
2019-06-01 12:05:00;    0.083;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;3;3
2019-06-01 12:10:00;    0.167;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;4;4
2019-06-01 12:15:00;    0.250;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;5;5
2019-06-01 12:20:00;    0.333;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;6;6
2019-06-01 12:30:00;    0.417;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;7;7
2019-06-01 12:35:00;    0.500;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;8;8
2019-06-01 12:40:00;    0.583;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;9;9
2019-06-01 12:45:00;    0.667;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;10;10
2019-06-01 12:50:00;    0.750;          0.083;0.25;0.25;0.2;0.22;0.45;0.6;0.5;11;11
\end{filecontents*}

\begin{filecontents}{mat-rewrite.lua}
\begin{luacode}
function rewrite_mat(infilename, outfilename)
  -- open input and output files
  infile  = io.open( infilename, "r")
  outfile = io.open(outfilename, "w")

  -- rest-of-line comments and table delimiters
  -- commentchars='#!' not yet implemented
  delimiter="[^;]+"
  del_out = ";"
  --delimiter="%S+"
  --del_out  =" "

  -- number of columns with y values and rows with x values
  ycols = 1
  xrows = 2

  -- initialize arrays for x/y/z values
  yvals={}
  xvals={}
  zvals={}
  xvals[1] = {}
  yvals[1] = {}
  for i=2,xrows do
    xvals[i] = {}
  end
  for i=2,ycols do
    yvals[i] = {}
  end

  -- initialize counters for numbers of x/y values (matrix size)
  -- can be used later for 'mesh rows' and 'mesh cols'
  ynum = 0
  xnum = 0 - ycols

  -- count the number of y values first
  i = 0
  for line in infile:lines() do
    i = i+1
    if i == ycols +1 then
      t={}                             -- make a table from the current line
      length=0
      for v in line:gmatch(delimiter) do
        length = length + 1
        t[length]=v:gsub("^%s*(.-)%s*$", "%1") -- leading and trailing whitespaces removed
      end

      ynum = length - xrows

      break
    end
  end

  infile:close()
  infile  = io.open( infilename, "r")

  -- read the matrix
  for line in infile:lines() do
    t={}                               -- make a table from the current line
    length=0
    for v in line:gmatch(delimiter) do
      length = length + 1
      t[length]=v:gsub("^%s*(.-)%s*$", "%1") -- leading and trailing whitespaces removed
    end 

    xnum = xnum + 1                    -- count number of x values (number of lines minus ycols)
                                       -- first lines: store y values
    if (xnum < 1) or ((xnum == 1) and (ycols == 0)) then        
      if length < ynum then
        tex.error("Not enough y values in line " .. xnum + ycols .. ": expected " .. ynum .. ", but got " .. length) -- invoke error
      end
      for y=1,ynum do
        if ycols == 0 then
          yvals[1][y] = t[length-ynum+y]
        else 
          yvals[xnum+ycols][y] = t[length-ynum+y]
        end
      end
    end  

    if xnum >= 1 then                                -- all other lines: store x and z values
      if length < (xrows+ynum) then
        tex.error("Not enough x/z values in line " .. xnum + ycols .. ": expected " .. xrows .. "+" .. ynum .. ", but got " .. length) -- invoke error
      end
      if length > (xrows+ynum) then
        tex.error("Too many x/z values in line " .. xnum + ycols .. ": expected " .. xrows .. "+" .. ynum .. ", but got " .. length) -- invoke error
      end

      if xrows == 0 then
        xvals[1][xnum]=xnum
      else
        for i=1,xrows do
          xvals[i][xnum] = t[i]
        end
      end
      for y=1,ynum do
        index = ynum*(xnum-1)+y
        zvals[index]=t[y+xrows]
      end
    end
  end

  infile:close()

  -- write data
  if xrows == 0 then
    outfile:write("x1" .. del_out)
  else
    for i=1,xrows do
      outfile:write("x" .. i .. del_out)
    end
  end
  if ycols == 0 then
    outfile:write("y1" .. del_out)
  else
    for i=1,ycols do
      outfile:write("y" .. i .. del_out)
    end
  end
  outfile:write("z" .. "\string\n")

  -- write data
  for x = 1,xnum do
    for y = 1,ynum do
        print(xnum)
        print(xvals)
        print(xvals[1])
        print(xvals[1][x])
      if xrows == 0 then
        outfile:write(xvals[1][x] .. del_out)
      else
        for i=1,xrows do
          outfile:write(xvals[i][x] .. del_out)
        end
      end
        print(xnum)
      if ycols == 0 then
        outfile:write(yvals[1][y] .. del_out)
      else
        for i=1,ycols do
          outfile:write(yvals[i][y] .. del_out)
        end
      end
      outfile:write(zvals[(x-1)*ynum+y] .. "\string\n")
    end
  end

  outfile:close()

end  
\end{luacode}
\end{filecontents}
% load function
\input{mat-rewrite.lua}
\begin{document}
\directlua{rewrite_mat("data2.dat","out.txt")}
\begin{tikzpicture}
\begin{axis}[mesh/ordering=y varies, unbounded coords=jump,colorbar,title={data from infrared measurements},view={0}{90},xlabel=$x$,ylabel=$y$,colorbar style={xlabel=$^\circ\mathrm{C}$,xticklabel pos=upper,xlabel style={yshift=.22cm}}]
\addplot3[surf,mesh/rows=10,mesh/cols=10] 
  table[col sep = semicolon,
        x = x2, y = y1, z = z
       ] {out.txt};
\end{axis}
\end{tikzpicture}
\end{document}

该数据的输出如下:

示例输出图像

相关内容