bluesceada
bluesceada

Reputation: 131

MATLAB/Octave plot markers above the line rather than on the line

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: markers shown on the line

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.

markers above line, as is desired

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

Answers (3)

Singon
Singon

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

Plot with annotated peaks

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

Drawbacks

While this approach works fine regardless of the size of the markers or your plot, there are some drawbacks:

  • First, the annotations are fixed relative to the figure window, not the plot. This is fine when you display the plot for the first time, but once you zoom or pan, the alignment is lost: The markes stay in place while the plot moves. If you don't need an interactive plot (eg, you just want to export it to image), just be sure to set the plot limits before adding the annotations and you should be fine.
  • Second, this method is very slow compared to plotting the points using the 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

Adiel
Adiel

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

Alexander Büse
Alexander Büse

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

Related Questions