假设我们想用绘制一个漂亮的热pgfplots
图。date
x-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 个不同位置的简单x
、y
、z
坐标。
但是,我获得了大量由以下结构组成的数据:
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.txt
pgfplots
完整代码(包括矩阵数据)在这里:
%!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}
该数据的输出如下: