Reputation: 131
I want to visualize the peaks of a function, and I want to have markers for it appear above the line they are associated with.
I fabricated a minimum example where I already have the peaks, the question is just how to visualize the markers correctly:
y = [0.1 0.3 10.0 1.0 0.5 0.1 24.0 0.6 0.1 0.2]
x = (1:length(y))
plot(x,y);
hold on;
peaks = [3 7];
plot(x(peaks), y(peaks), 'v', 'MarkerSize', 24);
print('-dpng', 'example.png', '-S640,480');
So, as a result, the markers appear centered on the line like this:
The result that I want could be achieved by carefully tuning a parameter OFFSET
like this:
plot(x(peaks), y(peaks)+OFFSET, 'v', 'MarkerSize', 24);
As shown in the following figure, for this exact example OFFSET=2.56
works for the exported png, but with the interactive plot and exporting vector graphics, it's wrong again.
Can anyone recommend a way to get this result without having to manually doing trial/error?
Currently I am using Octave with gnuplot to export to latex+tikz, and it would be good if the solution would work there.
In my actual (more complicated) use case I am plotting multiple lines after each other into the same figure, and the y limits change, so the offsets can not just be calculated easily, as the markersize doesn't change with the y limits.
Edit: Additionally I am using a semilogx plot, so drawing lines inside the diagram in the x/y-Axis scales would look distorted.
Upvotes: 1
Views: 1399
Reputation: 21
One way to do this is with annotations, but there are some drawbacks (see below).
Annotations enable you to place various graphic objects into your figure. One very annoying thing about them is that they work in so-called normalized coordinates, which span the whole figure window (not just the plot area) and go from [0,0] to [1,1], forcing you to convert to these coordinates first. I wrote a simple function to do this, provided your plot scale is linear (if you want logarithmic, you will have to modify this function):
## Convert from data coordinates to normalized figure coordinates.
function [xf yf] = figcoords(xa, ya)
axp = get(gca, "position");
lf = axp(1);
bf = axp(2);
rf = lf + axp(3);
tf = bf + axp(4);
xl = xlim();
yl = ylim();
la = xl(1);
ra = xl(2);
ba = yl(1);
ta = yl(2);
xf = lf + (xa-la).*(rf-lf)./(ra-la);
yf = bf + (ya-ba).*(tf-bf)./(ta-ba);
endfunction
With this out of your way, you can proceed to annotating the plot using the annotation
function:
y = [0.1 0.3 10.0 1.0 0.5 0.1 24.0 0.6 0.1 0.2];
x = (1:length(y));
peaks = [3 7];
## Plot the data as you would normally
plot(x,y);
## Plot peak markers (no `hold on` needed)
[xp yp] = figcoords(peaks, y(peaks)); # Transform to figure coordinates
for coords = [xp; yp]
xpi = coords(1);
ypi = coords(2);
annotation("arrow", [xpi xpi], [ypi+eps ypi]);
endfor
Here, we actually draw little arrows pointing from top onto the peaks.
As their height is very small, we only see the arrowheads.
The arguments to the annotation
function are the x and y coordinates
of the endpoints of the arrow. Note that we added a small number (eps
)
to the y-value of the starting point to make the arrow point downward.
If you want, you can tweak the appearance of the markers to make them more visually appealing:
y = [0.1 0.3 10.0 1.0 0.5 0.1 24.0 0.6 0.1 0.2];
x = (1:length(y));
peaks = [3 7];
coloridx = get(gca, "ColorOrderIndex")
peakcolor = get(gca, "ColorOrder")(coloridx,:); # Save current plot colour
plot(x,y);
## Plot peak markers
[xp yp] = figcoords(peaks, y(peaks));
for coords = [xp; yp]
xpi = coords(1);
ypi = coords(2);
annotation("arrow", [xpi xpi], [ypi+eps ypi], "headstyle", "plain",...
"color", peakcolor);
endfor
Plot with annotated peaks in the same color
While this approach works fine regardless of the size of the markers or your plot, there are some drawbacks:
plot
function. On my computer, for example, when drawing a simple example with
seven annotated peaks, it takes about a second before the markers appear.
Plotting a signal with thousands of peaks is near impossible.Upvotes: 2
Reputation: 3071
What about drawing the little triangles?
y = [0.1 0.3 10.0 1.0 0.5 0.1 24.0 0.6 0.1 0.2];
x = (1:length(y));
peaks = [3 7];
plot(x,y);
hold on; line([peaks(1) peaks(1)+0.2], [y(x==peaks(1)) y(x==peaks(1))+1], 'color','b')
hold on; line([peaks(1) peaks(1)-0.2], [y(x==peaks(1)) y(x==peaks(1))+1], 'color','b')
hold on; line([peaks(1)+0.2 peaks(1)-0.2], [y(x==peaks(1))+1 y(x==peaks(1))+1], 'color','b')
hold on; line([peaks(2) peaks(2)+0.2], [y(x==peaks(2)) y(x==peaks(2))+1], 'color','b')
hold on; line([peaks(2) peaks(2)-0.2], [y(x==peaks(2)) y(x==peaks(2))+1], 'color','b')
hold on; line([peaks(2)+0.2 peaks(2)-0.2], [y(x==peaks(2))+1 y(x==peaks(2))+1], 'color','b')
There can be a problem if the y-values of the peaks exists in other locations on the vector. If so, you can specify first
or other matching specs for the find
function.
Upvotes: 1
Reputation: 564
Concerning the Matlab part, you could draw the peak markers yourself. Somewhere along these lines (extending your example):
y = [0.1 0.3 10.0 1.0 0.5 0.1 24.0 0.6 0.1 0.2]
x = (1:length(y))
figure, plot(x,y);
leglengthx=0.2;
leglengthy=0.5;
hold on;
peaks = [3 7];
peaks_max=[10 24];
for ii=1:2
line([peaks(ii) peaks(ii)+leglengthx],[peaks_max(ii) peaks_max(ii)+leglengthy]);
line([peaks(ii) peaks(ii)-leglengthx],[peaks_max(ii) peaks_max(ii)+leglengthy]);
line([peaks(ii)-leglengthx peaks(ii)+leglengthx],[peaks_max(ii)+leglengthy peaks_max(ii)+leglengthy]);
end
plot(x(peaks), y(peaks), 'v', 'MarkerSize', 24);
I have added the maxima of the peaks, which should not be an issue to automatically extract and two variables that control the triangle size of the marker. And then its just drawing three lines for every peak.
I don't know how this will translate to Octave.
Upvotes: 1