3

How can I draw a “braced” line between two arbitrary points with Pyx?

It would look something like this:

Brace example http://tof.canardpc.com/view/d16770a8-0fc6-4e9d-b43c-a11eaa09304d

woot
  • 7,406
  • 2
  • 36
  • 55
Bastien Léonard
  • 60,478
  • 20
  • 78
  • 95
  • Looks like you create a canvas, find the point between the two endpoints, draw a vertical line for p1 to halfway, draw some sort of nipple at the halfway point, and then draw a vertical line from halfway to p2. Use c.stroke(path.line(p1.x, p2.y, halfway.x, halfway.y))) and c.stroke(path.line(halfway.x, halfway.y, p1.x, p2.y))). Or something. I'm not installing this package to answer this question. – hughdbrown Aug 17 '09 at 18:55
  • 2
    @hughbrown: Why didn't you post your answer as an answer? – S.Lott Aug 17 '09 at 19:00
  • I don't have this package installed. I figured I could sketch out an approximate answer and Bastien could run with it. – hughdbrown Aug 17 '09 at 19:53

2 Answers2

11

You can draw pretty braces using sigmoidals. I don't have Pyx installed so I'll just plot these using matplotlib (pylab here). Here beta controls the sharpness of the curves in the braces.

import numpy as nx
import pylab as px


def half_brace(x, beta):
    x0, x1 = x[0], x[-1]
    y = 1/(1.+nx.exp(-1*beta*(x-x0))) + 1/(1.+nx.exp(-1*beta*(x-x1)))
    return y

xmax, xstep = 20, .01
xaxis = nx.arange(0, xmax/2, xstep)
y0 = half_brace(xaxis, 10.)
y = nx.concatenate((y0, y0[::-1]))

px.plot(nx.arange(0, xmax, xstep), y)
px.show()

enter image description here

I plotted this along the x-axis to save screen space, but to get braces along the y-axis just swap x and y. Finally, Pyx has plenty of path drawing functionality built-in which coould also work for your needs.

tom10
  • 67,082
  • 10
  • 127
  • 137
  • Hey, I am particularly interested in knowing how did you wrote half_brace method. Is the math you do is a common standard stuff? Can you help pointing me out some resources to learn this? – asyncwait Aug 28 '09 at 09:10
  • @Vadi - I added a link to sigmoid functions in the first line of the answer. The half_brace function is the sum of two sigmoidals, and I just thought it was easier to do the sum than break this into four parts. Sigmoidals themselves are fairly common, but this depends on your perspective. In physics, for example (but in topics more advanced than basic undergrad), these appear directly as the Fermi-Dirac distribution function. – tom10 Aug 28 '09 at 21:26
5

tom10 provides a good solution, but could use some improvement.
The key is creates a brace over the range [0,1],[0,1] and then scale it.
This version also lets you tweak the shape a bit. For bonus points, it uses the second derivative to figure out how densely to space the points.

mid sets the balance between the lower and upper parts.
beta1 and beta2 control how sharp the curves (lower and upper) are.
You can change the height (or just multiply y by a scalar).
Making it vertical instead of horizontal just involves swapping x and y.
initial_divisions and resolution_factor govern how the x values are chosen, but should generally be ignorable.

import numpy as NP

def range_brace(x_min, x_max, mid=0.75, 
                beta1=50.0, beta2=100.0, height=1, 
                initial_divisions=11, resolution_factor=1.5):
    # determine x0 adaptively values using second derivitive
    # could be replaced with less snazzy:
    #   x0 = NP.arange(0, 0.5, .001)
    x0 = NP.array(())
    tmpx = NP.linspace(0, 0.5, initial_divisions)
    tmp = beta1**2 * (NP.exp(beta1*tmpx)) * (1-NP.exp(beta1*tmpx)) / NP.power((1+NP.exp(beta1*tmpx)),3)
    tmp += beta2**2 * (NP.exp(beta2*(tmpx-0.5))) * (1-NP.exp(beta2*(tmpx-0.5))) / NP.power((1+NP.exp(beta2*(tmpx-0.5))),3)
    for i in range(0, len(tmpx)-1):
        t = int(NP.ceil(resolution_factor*max(NP.abs(tmp[i:i+2]))/float(initial_divisions)))
        x0 = NP.append(x0, NP.linspace(tmpx[i],tmpx[i+1],t))
    x0 = NP.sort(NP.unique(x0)) # sort and remove dups
    # half brace using sum of two logistic functions
    y0 = mid*2*((1/(1.+NP.exp(-1*beta1*x0)))-0.5)
    y0 += (1-mid)*2*(1/(1.+NP.exp(-1*beta2*(x0-0.5))))
    # concat and scale x
    x = NP.concatenate((x0, 1-x0[::-1])) * float((x_max-x_min)) + x_min
    y = NP.concatenate((y0, y0[::-1])) * float(height)
    return (x,y)

Usage is simple:

import pylab as plt

fig = plt.figure()
ax = fig.add_subplot(111)

x,y = range_brace(0, 100)
ax.plot(x, y,'-')

plt.show()

Plot produced by example

PS: Don't forget that you can pass clip_on=False to plot and put it outside of the axis.

travc
  • 1,788
  • 1
  • 18
  • 9