var tokenRegex = /\{([^\}]+)\}/g, objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, replacer = function( all, key, obj ) { var res = obj; key.replace( objNotationRegex, function( all, name, quote, quotedName, isFunc ) { name = name || quotedName; if ( res ) { if ( name in res ) { res = res[name]; } typeof res === 'function' && isFunc && ( res = res() ); } } ); res = ( res == null || res == obj ? all : res ) + ''; return res; }, fill = function( str, obj ) { return String( str ).replace( tokenRegex, function( all, key ) { return replacer( all, key, obj ); } ); }; Raphael.fn.popup = function( X, Y, set, pos, ret ) { pos = String( pos || 'top-middle' ).split( '-' ); pos[1] = pos[1] || 'middle'; var r = 5, bb = set.getBBox(), w = Math.round( bb.width ), h = Math.round( bb.height ), x = Math.round( bb.x ) - r, y = Math.round( bb.y ) - r, gap = Math.min( h / 2, w / 2, 10 ), shapes = { top: 'M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z', bottom: 'M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z', right: 'M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z', left: 'M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z', }, offset = { hx0: X - ( x + r + w - gap * 2 ), hx1: X - ( x + r + w / 2 - gap ), hx2: X - ( x + r + gap ), vhy: Y - ( y + r + h + r + gap ), '^hy': Y - ( y - gap ), }, mask = [ { x: x + r, y: y, w: w, w4: w / 4, h4: h / 4, right: 0, left: w - gap * 2, bottom: 0, top: h - gap * 2, r: r, h: h, gap: gap, }, { x: x + r, y: y, w: w, w4: w / 4, h4: h / 4, left: w / 2 - gap, right: w / 2 - gap, top: h / 2 - gap, bottom: h / 2 - gap, r: r, h: h, gap: gap, }, { x: x + r, y: y, w: w, w4: w / 4, h4: h / 4, left: 0, right: w - gap * 2, top: 0, bottom: h - gap * 2, r: r, h: h, gap: gap, } ][pos[1] == 'middle' ? 1 : ( pos[1] == 'top' || pos[1] == 'left' ) * 2]; var dx = 0, dy = 0, out = this.path( fill( shapes[pos[0]], mask ) ).insertBefore( set ); switch ( pos[0] ) { case 'top': dx = X - ( x + r + mask.left + gap ); dy = Y - ( y + r + h + r + gap ); break; case 'bottom': dx = X - ( x + r + mask.left + gap ); dy = Y - ( y - gap ); break; case 'left': dx = X - ( x + r + w + r + gap ); dy = Y - ( y + r + mask.top + gap ); break; case 'right': dx = X - ( x - gap ); dy = Y - ( y + r + mask.top + gap ); break; } out.translate( dx, dy ); if ( ret ) { ret = out.attr( 'path' ); out.remove(); return { path: ret, dx: dx, dy: dy, }; } set.translate( dx, dy ); return out; }; function getAnchors( p1x, p1y, p2x, p2y, p3x, p3y ) { var l1 = ( p2x - p1x ) / 2, l2 = ( p3x - p2x ) / 2, a = Math.atan( ( p2x - p1x ) / Math.abs( p2y - p1y ) ), b = Math.atan( ( p3x - p2x ) / Math.abs( p2y - p3y ) ); a = p1y < p2y ? Math.PI - a : a; b = p3y < p2y ? Math.PI - b : b; var alpha = Math.PI / 2 - ( ( a + b ) % ( Math.PI * 2 ) ) / 2, dx1 = l1 * Math.sin( alpha + a ), dy1 = l1 * Math.cos( alpha + a ), dx2 = l2 * Math.sin( alpha + b ), dy2 = l2 * Math.cos( alpha + b ); return { x1: p2x - dx1, y1: p2y + dy1, x2: p2x + dx2, y2: p2y + dy2, }; }