Tension on a Graph, Part I: A Wavy String
Python + Pycairo, 727 708 608, + PyLab, 383
from pylab import*
def f(N):
 def P(u,w,N):
    T=lambda v,p:(C(v-u,p-u)&gt;0)==(C(w-v,p-v)&gt;0)==(C(u-w,p-w)&gt;0);M=[(i,n)for i,n in enumerate(N)if T(V([n[0],sin(pi*n[0])+sin(3*pi*n[0])/2]),n)]
    if M:i,n=max(M,key=lambda n:C(n[1]-u,w-u)**2);M=P(u,n,N[:i])+[n]+P(n,w,N[i+1:])
    return M
 V=array;C=cross;a=V([3,0]);plot(*zip(*([-a]+P(-a,a,map(V,sorted(N)))+[a])));N and scatter(*zip(*N));show()

Example
f([(-2.8,-0.7),(-2.5,-0.9),(-1.2,0.2),(-0.5,0.8),(0.5,0.4),(1.2,-0.9),(1.5, -0.6),(1.8, -0.8)])


How it works

 Suppose we know that the taut string passes through two points A and
 B (we can always start with  A = (-3, 0) and B = (3, 0).) As we pull the string, it &quot;wants&quot; to take the shortest path possible between
 A and B, that is, ideally, the segment AB. However, if there are any nails in the area bounded by the function (sin πx + ...)
 and AB, then at least one of them must block the string. In
 particular, the nail(s) farthest away from AB within the said area
 must block the string. Hence, if C is this nail, we know that the
 taut string must pass through C, in addition to A and B. We can
 now repeat the process for the segments AC and CB, and continue in
 this fashion until eventually there are no more intervening nails.
 
  
 This is a binary divide-and-conquer algorithm with a linear scan in
 each step, so it has a best-case complexity of O(n log n) and
 worst-case complexity of O(n2).