我看过类似的问题这里和这里,但我仍然卡住了。基本上我想实现的是这个加上条形标签:
我有两个问题:
1) 我的代码很烂。目前,y 轴标签和瀑布连接器都是手动放置的,这不是最理想的,因为我将制作 15 个左右版本的图表。
2) 我无法让条形标签显示在正确的位置。我以前使用过 Jake 的 Centered Nodes Near Coords 解决方案(目前找不到参考 stack exchange 问题),但出于某种原因,它们拒绝在本例中出现在正确的位置。
如有任何关于如何改进或使标签发挥作用的建议,我们将不胜感激。
[编辑] 我真的需要一个使用 PGFplots 的答案。这需要快速完成,我没有时间学习不同的 .cvs 到 latex 实现。
梅威瑟:
\documentclass{article}
\usepackage{pgfplots, pgfplotstable}
\usepackage{filecontents}
\pgfdeclareplotmark{waterfall bridge}{\pgfpathmoveto{\pgfpoint{0pt}{33pt}}\pgfpathlineto{\pgfpoint{0pt}{9pt}}\pgfusepathqstroke}
\pgfdeclareplotmark{waterfall bridge 2}{\pgfpathmoveto{\pgfpoint{0pt}{19pt}}\pgfpathlineto{\pgfpoint{0pt}{-5pt}}\pgfusepathqstroke}
\pgfdeclareplotmark{waterfall bridge 3}{\pgfpathmoveto{\pgfpoint{0pt}{5pt}}\pgfpathlineto{\pgfpoint{0pt}{-19pt}}\pgfusepathqstroke}
\pgfdeclareplotmark{waterfall bridge 4}{\pgfpathmoveto{\pgfpoint{0pt}{-9pt}}\pgfpathlineto{\pgfpoint{0pt}{-33pt}}\pgfusepathqstroke}
%~~ALL OF THE MARK POSITIONING IS UGLY~~
% I would like them to use relative, rather than absolute, positioning
\begin{filecontents}{datatable.csv}
2 3 2 -6 -3
\end{filecontents}
\makeatletter
\pgfplotsset{
calculate xoffset/.code={
\pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}
\pgfmathsetmacro\labelshift{(\pgfplotspointmeta*10^\pgfplots@data@scale@trafo@EXPONENT@x)/2)*\pgfplots@x@veclength)}
\pgfkeys{/pgf/fpu=false}
},
nodes near coords horizontally centered/.style={
every node near coord/.append style={
/pgfplots/calculate xoffset,
xshift=-\labelshift,yshift=-.25pt
},
nodes near coords align=center
}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\begin{axis}[
xbar stacked,
point meta=explicit,
nodes near coords horizontally centered, %~~THIS ISN'T WORKING~~
bar width=10pt,
axis x line*=bottom,
axis on top=true,
ytick style={opacity=0},
yticklabel style={font=\small, text width=2cm, align=center},
x axis line style={opacity=0},
y axis line style={opacity=0},
y dir=reverse,
ytick={-.35,-.175,0,.175,.35}, %~~THIS IS UGLY!~~
yticklabels={
label1,label2,label3,label4,label5
},
]
\addplot[
fill=cyan,
draw=none,
bar shift=28pt,
mark options={
lightgray,
thick,
},
mark=waterfall bridge,
] table [y expr=\coordindex, x index=0, meta index=0] {datatable.csv};
\addplot[
fill=orange,
draw=none,
bar shift=14pt,
mark options={
lightgray,
thick,
},
mark=waterfall bridge 2,
] table [y expr=\coordindex, x index=1, meta index=1] {datatable.csv};
\addplot[
fill=blue,
draw=none,
bar shift=0pt,
mark options={
lightgray,
thick,
},
mark=waterfall bridge 3,
] table [y expr=\coordindex, x index=2, meta index=2] {datatable.csv};
\addplot[
fill=red,
draw=none,
bar shift=-14pt,
mark options={
lightgray,
thick,
},
mark=waterfall bridge 4,
] table [y expr=\coordindex, x index=3, meta index=3] {datatable.csv};
\addplot[
fill=yellow,
draw=none,
bar shift=-28pt,
] table [y expr=\coordindex, x index=4, meta index=4] {datatable.csv};
\draw[ultra thin,lightgray] (axis cs:0,\pgfkeysvalueof{/pgfplots/ymin}) -- (axis cs:0,\pgfkeysvalueof{/pgfplots/ymax}); % adds x=0 line
\end{axis}
\end{tikzpicture}
\end{document}
答案1
好吧,只是为了好玩(并且为了更好地学习新工具)我在 lua 中做了一个实现,可以通过 LuaLatex 运行。
lua 代码从外部读取.cvs
数据,其中数据预计位于不同的行中(每行一个数字),并生成一个 tikz 图形,该图形会根据读取的数据自适应其轴。此外,生成的 tikz 定义了一组名为 的坐标row1
,row2
等等,位于每个条形的中心,以及一个名为 的坐标,min
位于最左侧条形的 x 位置。这些坐标可用于在图表中放置标签,要么位于条形的中心,要么位于图形的左侧。
图形中使用的颜色可由用户定义。如果颜色数量少于条数,则颜色将循环使用。
这是 LaTeX 代码:
\documentclass{book}
\usepackage{xcolor}
\usepackage{tikz}
\usepackage{filecontents}
\directlua{dofile("luaFunctions.lua")}
%create a pair of datafiles
\begin{filecontents*}{datafile1.csv}
2
3
2
-6
-3
\end{filecontents*}
\begin{filecontents*}{datafile2.csv}
3
4
2
3
-4
-6
2
-5
\end{filecontents*}
% latex commands to execute the lua functions
\def\waterfallChart#1{\directlua{waterfallChart("#1")}}
\def\setColors#1{%
\directlua{emptyColors()}%
\foreach \c in {#1} {\directlua{addColor("\c")}}
}
% set some styles
\tikzset{bar connection/.style = {black!50, thick}}
\setColors{cyan!80!black!50, orange, blue!80!black, red, yellow}
\begin{document}
\begin{tikzpicture} % First graph
\waterfallChart{datafile1.csv} % This draws the chart
% Now, adding labels, centered at each bar
\foreach \label [count=\n from 1] in {foo, bar, bad, foobar, spam}
\node at (row\n) {\label};
\end{tikzpicture}
\vskip 2cm
\begin{tikzpicture} % Second graph
\setColors{brown,red,orange} % Different colors for this one
\waterfallChart{datafile2.csv} % Draw the chart
% Put labels (at the left of the figure in this case)
\foreach \label [count=\n from 1] in {foo, bar, bad, foobar, spam, eggs, lorem, ipsum}
\node[left] at (row\n-|min) {\label};
\end{tikzpicture}
\end{document}
结果如下:
这是该文件的内容luaFunctions.lua
:
colors = {"blue","green"} -- Default colors
function readDataFile(filename)
local input = io.open(filename, 'r')
local dataTable = {}
local n
for line in input:lines() do
table.insert(dataTable, line)
end
input:close()
return dataTable
end
function emptyColors()
colors = {}
end
function addColor(c)
table.insert(colors,c)
end
function computeExtremes(dataTable)
local max, min, x
x = 0.0
min = 0.0
max = 0.0
for i,p in ipairs(dataTable) do
x = x + p
if (x<min) then min = x end
if (x>max) then max = x end
end
return min, max
end
function waterfallChart(filename)
local data = readDataFile(filename)
local min, max, n_steps, step, color, xpos, ypos, barwidth, aux, spread
-- Configure here as required
barwidth = 0.5 -- Height of each bar in the chart
spread = 1.4 -- Distance among baselines of the bars (in barwidth units)
n_ticks = 6 -- Number of ticks in the x-axis
min, max = computeExtremes(data)
step = (max-min)/n_ticks
max = min + n_ticks*step
xpos = 0.0
ypos = 0.0
aux = 0
-- Draw axes
-- Vertical axis
tex.print(string.format("\\draw (0,%f) -- (0, %f);",
1.1*barwidth, -#data*spread*barwidth))
-- Horizontal axis
tex.print(string.format("\\foreach \\tick in {%d, %d, ..., %d}",
min, min+step, max))
tex.print(string.format(" \\draw (\\tick, %f) -- +(0, -2mm) node[below] {\\tick};",
-#data*spread*barwidth))
tex.print(string.format("\\coordinate (min) at (%f,%f);",
min+0.0, -#data*spread*barwidth))
-- Draw the bars
color = 1
for i,p in ipairs(data) do
tex.print(string.format("\\fill[%s] (%f,%f) rectangle +(%f, %f) coordinate[midway] (row%d);",
colors[color], xpos, ypos, p+0.0, barwidth, i))
tex.print(string.format("\\draw[bar connection] (%f, %f) -- +(0,%f);",
xpos, ypos, spread*aux*barwidth))
aux = 1
ypos = ypos - spread*barwidth
xpos = xpos + 1.0 * p
color = color + 1
if (color > #colors) then color = 1; end
end
end
答案2
我不知道pgfplots
nor pgfplotstable
,所以我看不懂你的代码。但是看了你的图,我写了一个纯 tikz 解决方案,也许可以更简单。主要缺点是你必须在循环中指定数据点\foreach
(而不是从 读取它们.csv
),还要指定条形的颜色。你会决定这是否对你有用。
\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}
\xdef\myypos{0}
\xdef\myxpos{0}
\xdef\mywidth{0.5}
\xdef\myyprev{0.0}
% Axes (ugly, some hardcoded numbers)
\draw (0,1.1*\mywidth) -- (0, -5*2.2*\mywidth);
\foreach \tick in {-2,0,...,8}
\draw (\tick, -5*2.2*\mywidth) -- +(0,-2mm) node[below] {\tick};
% bars and labels
\foreach \mylen/\mylab/\mycolor in
{2/label1/cyan!80!black!50,
3/label2/orange,
2/label3/blue!80!black,
-6/label4/red,
-3/label5/yellow}
{
\fill[\mycolor] (\myxpos,\myypos) rectangle +(\mylen, \mywidth) node[midway,black] {\mylab};
\draw[black!40,thick] (\myxpos, \myypos) -- +(0, 2.2*\myyprev*\mywidth);
\pgfmathparse{\myypos - 1.2*\mywidth}\xdef\myypos{\pgfmathresult}
\pgfmathparse{\myxpos + \mylen}\xdef\myxpos{\pgfmathresult}
\xdef\myyprev{1.0}
}
\end{tikzpicture}
\end{document}
结果: