D3js - 椭圆语音泡沫

时间:2014-11-28 13:58:59

标签: math d3.js ellipsis

我最近一直在阅读省略号和贝塞尔曲线,以便能够画出一个"语音泡沫"与d3js: 这个气泡必须包含在矩形或方形容器内,如下所示:

http://edn.embarcadero.com/article/images/10277/D15PX16.jpg

但当然它会在左下角包含一个小箭头来表示#34;语音泡沫"影响。 由于此容器可以调整大小,因此我需要在发生时重新计算这些点。

鉴于这些条件,在方形容器的情况下,气泡可以是椭圆形或几乎完美的圆形。 我猜我不能使用省略号,因为在那种情况下不可能画出语音泡泡的箭头。

在做研究的过程中,我偶然发现了http://spencermortensen.com/articles/bezier-circle/,我认为这是一个很好的起点,但我无法真正掌握如何开始将其转化为代码,最重要的是如何使用d3js来帮助我渲染它,特别是因为我很难在椭圆的左下角画一个箭头。

如果有人可以向我解释如何计算绘制该气泡需要的点数,以及D3js如何绘制它,我真的很感激。

谢谢!

PS:抱歉,我无法更具体地添加图片,但我目前的声誉并不允许我这样做。

2 个答案:

答案 0 :(得分:0)

DEMO:http://jsbin.com/firacaredi/2/

你可以只使用标记而不是用两个向量重建椭圆的交点来产生精确正确的贝塞尔曲线路径:

var styles = {
  board: {width: 500, height: 400},

  bubble: {id: "bubble", refX: 0, refY: 0, markerWidth: 200, markerHeight: 200, viewBox: "-4 -4 8 4"},
  bubble_ellipse: {fill: "snow", stroke: "none", cx: 0, cy: 0, rx:4, ry: 2},

  handle: {id: "handle", refX: 0, refY: 2, "markerWidth": 100, "markerHeight": 20, orient: "auto", viewBox: "0 0 4 4" },
  handle_path: {"d": "M 0,0 V 4 L10,1", fill: "snow"},

  text: {fill: "black", x: 190, y: 110},
  callout: {fill: "snow", stroke: "snow", "stroke-width": 2, "d": "M200,100L130,160", "marker-end": "url(#handle)", "marker-start": "url(#bubble)",}
};

var board = d3.select("body").append("svg:svg").attr(styles.board);
var defs = board.append("svg:defs");
defs.append("svg:marker").attr(styles.handle).append("svg:path").attr(styles.handle_path);
defs.append("svg:marker").attr(styles.bubble).append("svg:ellipse").attr(styles.bubble_ellipse);

board.append("svg:path").attr(styles.callout);
board.append("svg:text").attr(styles.text).text("HI!");

然后,如果你需要一个边框,你可以放下一个像素阴影或放置一个黑暗,两个像素更大的标注形状在灯光下。

答案 1 :(得分:0)

function callout(parameters) {
    var w = parameters.width || 200,
            h = parameters.height || 100,
            a = w / 2,
            b = h / 2,
            o_x = parameters.x0 || 100,
            o_y = parameters.y0 || 100,
            m_r = parameters.l || 300,
            m_w = 10,
            m_q = parameters.angle * Math.PI / 180 || 50 * Math.PI / 180,
            m_q_delta = Math.atan(m_w / (2 * Math.min(w, h)));

    var d = "M", x, y, 
            d_q = Math.PI / 30; // 1/30 -- precision of drawing

    // now, we are drawing the path step by step
    for (var alpha = 0; alpha < 2 * Math.PI; alpha += d_q) {

        if (alpha > m_q - m_q_delta && alpha < m_q + m_q_delta) { //edge
            x = o_x + m_r * Math.cos(m_q);
            y = o_y + m_r * Math.sin(m_q);
            d += "L" + x + "," + y;
            alpha = m_q + m_q_delta;
        } else { // ellipse
            x = a * Math.cos(alpha) + o_x;
            y = b * Math.sin(alpha) + o_y;
            d += "L" + x + "," + y + " ";
        }
    }
    d += "Z";
    return(d.replace(/^ML/, "M").replace(/ Z$/, "Z"));
}

var styles = {
    board: {width: 1000, height: 1000},
    callout: {stroke: "black", "stroke-width": 1, fill: "snow"}
};

var callout_params = {
    width: 300,
    height: 100,
    angle: 20,
    l: 250,
    x0: 200,
    y0: 200
};

var board = d3.select("body").append("svg:svg").attr(styles.board);
var defs = board.append("svg:defs");
var callout = board.append("svg:path").attr(styles.callout).attr("d", callout(callout_params));

样本: http://jsbin.com/kalijumepe/1/