3

edit (14/06): Thanks to the hint of marmot, and based on this answer. I define \markopts in scatter/@pre marker code/.code like this:

scatter/@pre marker code/.code={
  \pgfmathtruncatemacro{\lasttwomarks}{%
    or((\coordindex==(\numcoords-2)), (\coordindex==(\numcoords-1)))%
  }
  \ifnum\lasttwomarks=1
    \def\markopts{mark=none}
  \else
    \def\markopts{mark=*,mark size=1pt}
  \fi
  \ifnum\coordindex=0
     \def\markopts{mark=*,fill=white,mark size=2pt}
  \fi
  \expandafter\scope\expandafter[\markopts]
}
,scatter/@post marker code/.code={
  \endscope
}

... which hides the last two marks and removes my need to append \draw to the plot. I now just add two "dummy" coordinates to the data for the arrow end. See this gist.

original question:

I'd like to mimic this graph:

graph showing arrows plots with markings

So far I've bee stuck on adding plots that start with a * mark for the first datapoint that is not filled, filled marks for the other data points, and arrow ends that point somewhere approximately in the right direction. I know that pgfplots treats coordinates given by \draw as one would expect withing the axis environment, but I wounder if I can adjust my command so that it can be used there.

\documentclass[border=5mm,tikz]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.15}
\usetikzlibrary{arrows.meta}
\begin{document}
\tikz\draw[{Circle[fill=none]}-Stealth,thick] (0, 0) --
plot[smooth,mark=*] coordinates {(1, .24) (2, .42) (3, .54) (4, .59)} -- (5, .6);
% \begin{tikzpicture}[baseline]
%   \begin{axis}[xtick=\empty,xmin=-1,xmax=6,ymin=-1,ymax=1]
%     \addplot+ [yshift=2pt,red,smooth,mark=*] coordinates {(1, .24) (2, .42) (3, .54) (4, .59) (5, .6)};
%   \end{axis}
% \end{tikzpicture}
\end{document}

mwe arrow plot

edit:

some background:

Basically, the graph represents the piano tuner's choice when applying stretched tuning: the bass strings are usually thick and have a lot more spectral components. Each plot represents the prominent partials in the harmonic series of a single recorded note from a 1923 Steinway piano. The aligned keyboard and the grid lines (12 notes per octave => xtick distance=12) indicate to which note the partials are closest, and the y axis shows deviation in cents. The aural compromise chosen by the tuner often depends on the resonating partials in different pianos. I have measurements of a different piano that I would like to represent in such a graph.

The notes used are all (on different octaves) A's. The unfilled marks represent the fundamental pitch (the first value of a plot), the filled ones are prominent partials, and the pinned labels always point to intersecting plots.

In the meantime I wrote this:

working draft

\documentclass[border=5mm]{standalone}
\usepackage{pgfplots}
\usetikzlibrary{arrows.meta,plotmarks,decorations.markings,intersections}
\tikzset{%
  every pin/.style={black,fill=white,rectangle,outer sep=1pt,inner sep=1pt, rounded corners=3pt,font=\tiny}%
  ,every mark/.append style={scale=.5}%
  ,n/.style={xshift=-1ex,yshift=1em,font=\bfseries}
}
\pgfplotsset{%
  width=16cm%
  ,height=7cm%
  ,scale only axis%
  ,compat=1.15%
  ,major grid style={dotted,thin,gray}
  ,tick label style={font=\small}
  ,tickwidth=0pt
  ,label style={font=\small}
  ,axis x line=bottom
  ,axis y line=left
}
\begin{document}
\begin{tikzpicture}[baseline,pin distance=4ex]
  \begin{axis}[%
    name=main plot
    ,clip=false
    ,xtick distance=12
    ,xticklabels={0, A0, A1, A2, A3, A4, A5, A6, A7},
    ,ylabel={Deviation (cents)}
    ,ytick={-25,-20,...,45}
    ,ymin=-34
    ,ymax=50
    ,xmin=-4
    ,xmax=90
    ,grid=major
    ,major grid style={black!50}
    ,axis y discontinuity=crunch
    ,enlargelimits=false
  ]
    % A0
    \draw[blue,{Circle[fill=none]}-Stealth,thick] (axis cs:-.55, -26) node[n](a0) {A0} --
    plot[smooth,mark=*] coordinates {
      (axis cs:12,-25) (axis cs:19,-23) (axis cs:24,-19.5) (axis cs:28,-14) (axis cs:31,-13)
      (axis cs:34,-7) (axis cs:36,-3) (axis cs:38,3) (axis cs:40,7) (axis cs:42,14) (axis cs:43,19)
      (axis cs:44,25) (axis cs:45,33)
    } -- (axis cs:45.5,39);
    % A1
    \draw[black,{Circle[fill=none]}-Stealth,thick] (axis cs:11.55, -6) node[n](a1) {A1} --
    plot[smooth,mark=*] coordinates {
      (axis cs:24, -5)   (axis cs:31, -3.5)   (axis cs:36, -3) (axis cs:40, -2) (axis cs:43, -1)
      (axis cs:46, 1)   (axis cs:48, 2)  (axis cs:50, 4) (axis cs:52, 6) (axis cs:54, 9) (axis cs:55, 11)
    } -- (axis cs:58, 18);
    % A2
    \draw[cyan,{Circle[fill=none]}-Stealth,thick] (axis cs:23.55, -2) node[n](a2) {A2} --
    plot[smooth,mark=*] coordinates {
      (axis cs:36, -1.4) (axis cs:43, -1) (axis cs:48, -1) (axis cs:52, -.8) (axis cs:55, -.6)
      (axis cs:58, 0) (axis cs:60, 1) (axis cs:62, 2.5) (axis cs:64, 4.5) (axis cs:66, 6.5) (axis cs:68, 9)
    } -- (axis cs:71, 14);
    % A3
    \draw[darkgray,{Circle[fill=none]}-Stealth,thick] (axis cs:35.55, -1.4) node[n](a3) {A3} --
    plot[smooth,mark=*] coordinates {
      (axis cs:48, -1) (axis cs:55, -.5) (axis cs:60, 1) (axis cs:64, 3) (axis cs:67, 5.2)
      (axis cs:70, 8.6) (axis cs:72, 11)
    } -- (axis cs:74.5, 17);
    % A4
    \draw[purple,{Circle[fill=none]}-Stealth,thick] (axis cs:47.55, 0) node[n](a4) {A4} --
    plot[smooth,mark=*] coordinates {
      (axis cs:60, .5) (axis cs:67, 5) (axis cs:72, 12) (axis cs:75, 18)
    } -- (axis cs:78, 26);
    % A5
    % .. controls (axis cs:66,3.6) and (axis cs:67,4.2) .. 
    \draw[orange,{Circle[fill=none]}-Stealth,thick] (axis cs:59.55,1.2) node[n](a5) {A5}
    to[out=10, in=210] (axis cs:72, 12) --
    plot[smooth,mark=*] coordinates {
      (axis cs:72, 12) (axis cs:79, 26) (axis cs:84, 39)
    } -- (axis cs:85.5, 46);
    % A6
    % how to include a marking at (84, 38)? this breaks the path for some reason: (axis cs:84, 38) circle(2pt) --
    % .. controls (axis cs:73,13) and (axis cs:77,20) ..
    \draw[teal,{Circle[fill=none]}-Stealth,thick] (axis cs:71.6, 11.55) node[n](a6) {A6}
    to[out=35, in=240] (axis cs:84, 39) to[out=60, in=250] (axis cs:87, 54);
    % A7
    % .. controls (axis cs:85,43) and (axis cs:86,45) ..
    \draw[gray,{Circle[fill=none]}-Stealth,thick] (axis cs:83.75, 38) node[n](a7) {A7}
    to[out=60, in=250] (axis cs:88, 58);
    \node [pin=320:$A0^8-A1^4$] at (36,-3)  {};
    \node [pin=85:$A1^6-A2^3$]       at (43, -1) {};
    \node [pin=below right:$A2^4-A3^2$] at (48, -1) {};
    \node [pin=below right:$A3^4-A4^2$] at (60, 1)  {};
    \node [pin=above left:$A4^4-A5^2$]  at (72, 12) {};
    \node [pin=below right:$A5^2-A6^1$] at (72, 12) {};
    \node [pin=below right:$A6^2-A7^1$] at (84, 38) {};
  \end{axis}
\end{tikzpicture}
\end{document}

Note that:

  • I made ugly tweaks to the starting point to fake a centered anchor for the Circle arrows (the "fundamentals" which should indeed be positioned on the grid)
  • I couldn't get the inline to[] and plot[smooth] to result in a smooth curve, especially for the orange path
  • I don't know how to include a * mark shape manually with circle in the A6 path (see comment) without breaking the path (the arrows disappear or jump to funky places), and scaling the other markings means that I'm not sure about which diameter to hardcode

I wounder how one would overwrite or discard specific markings. Can this be done with forget plot and postaction?

edit:

per request of @marmot, plot data in files:

\begin{filecontents*}{A0.txt}
0  -26
12 -25
19 -23
24 -19.5
28 -14
31 -13
34 -7
36 -3
38 3
40 7
42 14
43 19
44 25
45 33
45 38
\end{filecontents*}

\begin{filecontents*}{A1.txt}
12 -6
24 -5
31 -3.5
36 -3
40 -2
43 -1
46 1
48 2
50 4
52 6
54 9
55 11
58 18
\end{filecontents*}

\begin{filecontents*}{A2.txt}
24 -2
36 -1.4
43 -1
48 -1
52 -.8
55 -.6
58 0
60 1
62 2.5
64 4.5
66 6.5
68 9
71 14
\end{filecontents*}

\begin{filecontents*}{A3.txt}
36 -1.4
48 -1
55 -.5
60 1
64 3
67 5.2
70 8.6
72 11
74 16
\end{filecontents*}

\begin{filecontents*}{A4.txt}
48 0
60 .5
67 5
72 12
75 18
78 26
\end{filecontents*}

\begin{filecontents*}{A5.txt}
60 1.2
72 12
72 12
79 26
84 39
86 44
\end{filecontents*}

\begin{filecontents*}{A6.txt}
72 11.55
84 39
87 54
\end{filecontents*}

\begin{filecontents*}{A7.txt}
84 38
88 58
\end{filecontents*}

(my data still needs to be processed, so these values are incomplete and illustrative only)

4
  • Please provide us with more details. Which aspect of the above screen shot do you want to reproduce? What determines the locations of the filled vs. unfilled marks? Do you have their coordinates, or are there, as one may infer from the screen shot, unfilled marks whenever the plot intersects with a vertical grid line?
    – user121799
    Jun 7, 2019 at 1:29
  • @marmot All the coordinates are given (the first mark is not derived from intersection) see update.
    – Bart
    Jun 7, 2019 at 18:28
  • If you have the coordinates in lists/tables/data files, I believe that there will be a straightforward solution using scatter and @pre marker code and/or visualization depends on.
    – user121799
    Jun 7, 2019 at 18:52
  • @marmot thanks for your response. Could you please give a hint to your solution and/or point me to documentation?
    – Bart
    Jun 10, 2019 at 17:37

1 Answer 1

1

This is not a complete answer but could conceivably become one. The problem is that I do not know what the criteria are to fill or not fill the mark. So this is something that fills every third mark, but can be modified to do what you want once the criteria are clear. The reason why I was asking for a data file is that I was hoping you will indicate there which marks should be filled. This information could be used then.

\documentclass[border=5mm]{standalone}
\usepackage{filecontents}


\begin{filecontents*}{A0.txt}
0  -26
12 -25
19 -23
24 -19.5
28 -14
31 -13
34 -7
36 -3
38 3
40 7
42 14
43 19
44 25
45 33
45 38
\end{filecontents*}

\begin{filecontents*}{A1.txt}
12 -6
24 -5
31 -3.5
36 -3
40 -2
43 -1
46 1
48 2
50 4
52 6
54 9
55 11
58 18
\end{filecontents*}

\begin{filecontents*}{A2.txt}
24 -2
36 -1.4
43 -1
48 -1
52 -.8
55 -.6
58 0
60 1
62 2.5
64 4.5
66 6.5
68 9
71 14
\end{filecontents*}

\begin{filecontents*}{A3.txt}
36 -1.4
48 -1
55 -.5
60 1
64 3
67 5.2
70 8.6
72 11
74 16
\end{filecontents*}

\begin{filecontents*}{A4.txt}
48 0
60 .5
67 5
72 12
75 18
78 26
\end{filecontents*}

\begin{filecontents*}{A5.txt}
60 1.2
72 12
72 12
79 26
84 39
86 44
\end{filecontents*}

\usepackage{pgfplots}
\pgfplotsset{compat=1.16}
\usetikzlibrary{arrows.meta,plotmarks,decorations.markings,intersections}
\tikzset{%
  every pin/.style={black,fill=white,rectangle,outer sep=1pt,inner sep=1pt, rounded corners=3pt,font=\tiny}%
  ,every mark/.append style={scale=.5}%
  ,n/.style={xshift=-1ex,yshift=1em,font=\bfseries}
}
\pgfplotsset{%
  width=16cm%
  ,height=7cm%
  ,scale only axis%
  ,compat=1.15%
  ,major grid style={dotted,thin,gray}
  ,tick label style={font=\small}
  ,tickwidth=0pt
  ,label style={font=\small}
  ,axis x line=bottom
  ,axis y line=left
}
\begin{document}
\begin{tikzpicture}[baseline,pin distance=4ex]
  \begin{axis}[%
    name=main plot
    ,clip=false
    ,xtick distance=12
    ,xticklabels={0, A0, A1, A2, A3, A4, A5, A6, A7},
    ,ylabel={Deviation (cents)}
    ,ytick={-25,-20,...,45}
    ,ymin=-34
    ,ymax=50
    ,xmin=-4
    ,xmax=90
    ,grid=major
    ,major grid style={black!50}
    ,axis y discontinuity=crunch
    ,enlargelimits=false,
    scatter/@pre marker code/.code={
       \pgfmathtruncatemacro{\itest}{mod(\coordindex,3)}
       \ifnum\itest=0
         \def\markopts{mark=*,fill=white,mark size=3pt}
       \else
         \def\markopts{mark=*}
       \fi  
       \expandafter\scope\expandafter[\markopts]
       },
       scatter/@post marker code/.code={
    \endscope
    },  
  ]
  \addplot[scatter,color=blue] table {A1.txt};
  \end{axis}
\end{tikzpicture}
\end{document}

enter image description here

1
  • Thanks, it looks good! As I explained above, "The unfilled marks represent the fundamental pitch (the first value of a plot)". The last mark on the plot should probably be hidden though, so that we can apply a postaction decoration for the arrow.
    – Bart
    Jun 11, 2019 at 13:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .