Как я могу комбинировать объекты в javascript-библиотеке Рафаэля?

Извините за длинный вопрос, но начнем. Я пытаюсь изменить формы перетаскивания вокруг демонстрации здесь:

http://raphaeljs.com/graffle.html

Демо работает нормально. Я хочу поместить слова внутри фигур и перемещать фигуру и текст как единый составной объект.

Вот код для создания объектов:

window.onload = function () {
    var dragger = function () {
        this.ox = this.type == "rect" ? this.attr("x") : this.attr("cx");
        this.oy = this.type == "rect" ? this.attr("y") : this.attr("cy");
        this.animate({"fill-opacity": .2}, 500);
    },
        move = function (dx, dy) {
            var att = this.type == "rect" ? {x: this.ox + dx, y: this.oy + dy} : {cx: this.ox + dx, cy: this.oy + dy};
            this.attr(att);
            for (var i = connections.length; i--;) {
                r.connection(connections[i]);
            }
            r.safari();
        },
        up = function () {
            this.animate({"fill-opacity": 0}, 500);
        },
        r = Raphael("holder", 640, 480),
        connections = [],
        shapes = [  r.ellipse(190, 100, 30, 20),
                    r.rect(290, 80, 60, 40, 10),
                    r.rect(290, 180, 60, 40, 2),
                    r.ellipse(450, 100, 20, 20)
                ];
    for (var i = 0, ii = shapes.length; i < ii; i++) {
        var color = Raphael.getColor();
        shapes[i].attr({fill: color, stroke: color, "fill-opacity": 0, "stroke-width": 2, cursor: "move"});
        shapes[i].drag(move, dragger, up);
    }
    connections.push(r.connection(shapes[0], shapes[1], "#fff"));
    connections.push(r.connection(shapes[1], shapes[2], "#fff", "#fff|5"));
    connections.push(r.connection(shapes[1], shapes[3], "#000", "#fff"));
};

Я пробовал что-то вроде этого:

 myWords = [ r.text(190, 100,  "Hello"),
      r.text(480,100, "Good Bye")
    ];

и внес изменения в другом месте, чтобы он работал, но затем он просто перемещает текст и фигуры, но форма и текст никогда не отображаются как единое целое. Я могу переместить текст отдельно от фигуры и наоборот. Мне нужно, чтобы они были одним объектом. поэтому они двигаются вместе. Как я могу это сделать? Спасибо за любую помощь.

РЕДАКТИРОВАТЬ:

Я пробовал это:

  st.push(r.text (190, 100, "node1"), r.ellipse(190, 100, 30, 20)),
  st.push(r.text (290, 80, "Center"), r.rect(290, 80, 60, 40, 10)),
  st.push(r.text (290, 180, "node2"), r.rect(290, 180, 60, 40, 2)),
  st.push(r.text (450, 100, "node3"), r.ellipse(450, 100, 20, 20))

Но текст и фигура не остались вместе, когда я переместил фигуру. Текст просто не двигался.

РЕДАКТИРОВАТЬ: я не могу получить стандартную демонстрацию на http://raphaeljs.com/graffle.html для работы с Хром. IE работает.


person johnny    schedule 09.09.2010    source источник
comment
Вы не двигаете фигуру. Вы перемещаете набор. Рассматривайте набор как одну фигуру, которая содержит текст и прямоугольник.   -  person slebetman    schedule 15.09.2010
comment
@slebetman - Как бы ты это сделал? Невозможно щелкнуть набор, чтобы выбрать его для перемещения, поскольку с наборами нет связанных элементов DOM. - @johnny - Вместо сложного метода с использованием наборов вы можете просто объединить элементы, используя настраиваемое свойство. - Смотрите мой ответ.   -  person Peter Ajtai    schedule 07.10.2010


Ответы (4)


Внесены серьезные изменения, чтобы связать элементы более элегантно.


Наборы хороши для группировки объектов Raphael, но наборы не создают свои собственные элементы, поэтому вы не может перетаскивать набор, поскольку при щелчке по холсту вы либо выбираете фигуру, либо текст, но никогда не выбираете набор (поскольку нет элемента набора).

Вот простой jsFiddle, показывающий свойства набора. Обратите внимание, что набор не имеет свойств x или y .

Из документации Raphael:

[Набор c] создает объект, подобный массиву, для одновременного хранения и управления парой элементов. Предупреждение: он не создает никаких элементов на странице для себя.


Простое решение - сделать отдельно перетаскиваемый текст и фигуру. Затем переместите связанный текст вместе с фигурой ... и связанную фигуру вместе с текстом.

Связать такие объекты просто ... создать свойство. В этом случае каждая фигура и каждый текст имеют свойство с именем .pair, которое является ссылкой на связанный элемент.

Вот как это делается:

var i, ii, tempS, tempT
     shapes = [  ... ],
     texts = [  ... ];
for (i = 0, ii = shapes.length; i < ii; i++) {
    tempS = shapes[i].attr( ... );
    tempT = texts[i].attr( ...);

      // Make all the shapes and texts dragable
    shapes[i].drag(move, dragger, up);
    texts[i].drag(move, dragger, up);

      // Associate the elements
    tempS.pair = tempT;
    tempT.pair = tempS;
}

А затем в коде перетаскивания, который представляет собой функции move(), dragger() и up(), вы должны убедиться, что имеете дело как с элементом, по которому щелкнули, так и с связанным с ним элементом.

Например, вот соответствующая часть функции move(). Обратите внимание, что с text можно обращаться так же, как с rectangle (путем изменения атрибутов x и y), поэтому условие false в каждом из условных операторов Javascript ниже обрабатывает как случай для rectangle, так и для text

move = function (dx, dy) {

      // Move main element
    var att = this.type == "ellipse" ? 
                           {cx: this.ox + dx, cy: this.oy + dy} : 
                           {x: this.ox + dx, y: this.oy + dy};
    this.attr(att);

      // Move paired element
    att = this.pair.type == "ellipse" ? 
                            {cx: this.pair.ox + dx, cy: this.pair.oy + dy} : 
                            {x: this.pair.ox + dx, y: this.pair.oy + dy};
    this.pair.attr(att);
    ...
}


А ниже полный рабочий код:

Рабочий пример jsFiddle перетаскиваемого текста и фигур

Raphael.fn.connection = function (obj1, obj2, line, bg) {
    if (obj1.line && obj1.from && obj1.to) {
        line = obj1;
        obj1 = line.from;
        obj2 = line.to;
    }
    var bb1 = obj1.getBBox(),
        bb2 = obj2.getBBox(),
        p = [{x: bb1.x + bb1.width / 2, y: bb1.y - 1},
        {x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + 1},
        {x: bb1.x - 1, y: bb1.y + bb1.height / 2},
        {x: bb1.x + bb1.width + 1, y: bb1.y + bb1.height / 2},
        {x: bb2.x + bb2.width / 2, y: bb2.y - 1},
        {x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + 1},
        {x: bb2.x - 1, y: bb2.y + bb2.height / 2},
        {x: bb2.x + bb2.width + 1, y: bb2.y + bb2.height / 2}],
        d = {}, dis = [];
    for (var i = 0; i < 4; i++) {
        for (var j = 4; j < 8; j++) {
            var dx = Math.abs(p[i].x - p[j].x),
                dy = Math.abs(p[i].y - p[j].y);
            if ((i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x) && ((i != 2 && j != 7) || p[i].x > p[j].x) && ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y))) {
                dis.push(dx + dy);
                d[dis[dis.length - 1]] = [i, j];
            }
        }
    }
    if (dis.length == 0) {
        var res = [0, 4];
    } else {
        res = d[Math.min.apply(Math, dis)];
    }
    var x1 = p[res[0]].x,
        y1 = p[res[0]].y,
        x4 = p[res[1]].x,
        y4 = p[res[1]].y;
    dx = Math.max(Math.abs(x1 - x4) / 2, 10);
    dy = Math.max(Math.abs(y1 - y4) / 2, 10);
    var x2 = [x1, x1, x1 - dx, x1 + dx][res[0]].toFixed(3),
        y2 = [y1 - dy, y1 + dy, y1, y1][res[0]].toFixed(3),
        x3 = [0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx][res[1]].toFixed(3),
        y3 = [0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4][res[1]].toFixed(3);
    var path = ["M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3)].join(",");
    if (line && line.line) {
        line.bg && line.bg.attr({path: path});
        line.line.attr({path: path});
    } else {
        var color = typeof line == "string" ? line : "#000";
        return {
            bg: bg && bg.split && this.path(path).attr({stroke: bg.split("|")[0], fill: "none", "stroke-width": bg.split("|")[1] || 3}),
            line: this.path(path).attr({stroke: color, fill: "none"}),
            from: obj1,
            to: obj2
        };
    }
};

var el;
window.onload = function () {
    var color, i, ii, tempS, tempT,
        dragger = function () {
                // Original coords for main element
            this.ox = this.type == "ellipse" ? this.attr("cx") : this.attr("x");
            this.oy = this.type == "ellipse" ? this.attr("cy") : this.attr("y");
            if (this.type != "text") this.animate({"fill-opacity": .2}, 500);

                // Original coords for pair element
            this.pair.ox = this.pair.type == "ellipse" ? this.pair.attr("cx") : this.pair.attr("x");
            this.pair.oy = this.pair.type == "ellipse" ? this.pair.attr("cy") : this.pair.attr("y");
            if (this.pair.type != "text") this.pair.animate({"fill-opacity": .2}, 500);            
        },
        move = function (dx, dy) {
                // Move main element
            var att = this.type == "ellipse" ? {cx: this.ox + dx, cy: this.oy + dy} : 
                                               {x: this.ox + dx, y: this.oy + dy};
            this.attr(att);

                // Move paired element
            att = this.pair.type == "ellipse" ? {cx: this.pair.ox + dx, cy: this.pair.oy + dy} : 
                                               {x: this.pair.ox + dx, y: this.pair.oy + dy};
            this.pair.attr(att);            

                // Move connections
            for (i = connections.length; i--;) {
                r.connection(connections[i]);
            }
            r.safari();
        },
        up = function () {
                // Fade original element on mouse up
            if (this.type != "text") this.animate({"fill-opacity": 0}, 500);

                // Fade paired element on mouse up
            if (this.pair.type != "text") this.pair.animate({"fill-opacity": 0}, 500);            
        },
        r = Raphael("holder", 640, 480),
        connections = [],
        shapes = [  r.ellipse(190, 100, 30, 20),
                    r.rect(290, 80, 60, 40, 10),
                    r.rect(290, 180, 60, 40, 2),
                    r.ellipse(450, 100, 20, 20)
                ],
        texts = [   r.text(190, 100, "One"),
                    r.text(320, 100, "Two"),
                    r.text(320, 200, "Three"),
                    r.text(450, 100, "Four")
                ];
    for (i = 0, ii = shapes.length; i < ii; i++) {
        color = Raphael.getColor();
        tempS = shapes[i].attr({fill: color, stroke: color, "fill-opacity": 0, "stroke-width": 2, cursor: "move"});
        tempT = texts[i].attr({fill: color, stroke: "none", "font-size": 15, cursor: "move"});
        shapes[i].drag(move, dragger, up);
        texts[i].drag(move, dragger, up);

        // Associate the elements
        tempS.pair = tempT;
        tempT.pair = tempS;
    }
    connections.push(r.connection(shapes[0], shapes[1], "#fff"));
    connections.push(r.connection(shapes[1], shapes[2], "#fff", "#fff|5"));
    connections.push(r.connection(shapes[1], shapes[3], "#000", "#fff"));
};​

Для полноты здесь приведен код связанного с jsFiddle для отображения свойств набора:

window.onload = function () {
    var paper = Raphael("canvas", 320, 200),
        st = paper.set(), 
        propArr = [];

    st.push(
        paper.circle(10, 10, 5),
        paper.circle(30, 10, 5)
    );

    st.attr({fill: "red"});

    for(var prop in st) {
        if (st.hasOwnProperty(prop)) {
            // handle prop as required
            propArr.push(prop + " : " + st[prop]);
        }
    }
    alert(propArr.join("\n"));
};​

// Output:
// 0 : Raphael's object
// 1 : Raphael's object
// items : Raphael's object,Raphael's object
// length : 2
// type : set
person Peter Ajtai    schedule 06.10.2010
comment
Вот несколько более общее решение, для которого требуется исправление, которое я написал против Рафаэля, чтобы разрешить именованные наборы: stackoverflow.com/questions/6277129/ - вместо перекрестного вызова вы можете получить доступ другие члены набора из элемента в обратном вызове события. Спасибо за отличную работу, Питер; мое решение было напрямую вдохновлено этим ответом. - person David Eads; 04.08.2011
comment
Я не знаю, почему они больше не работают в Chrome, ну да ладно. По-прежнему хорошо работает в IE. - person johnny; 25.04.2013
comment
я думаю, было бы лучше просто вставить ссылку на пример на сайте raphael (raphaeljs.com/graffle.html), а затем получить весь код оттуда, чтобы вставить его сюда и jsfiddle - person Adam Moszczyński; 16.08.2013

Или попробуйте этот «групповой» плагин для Рафаэля, который позволяет вам создать правильный групповой элемент SVG.

https://github.com/rhyolight/Raphael-Plugins/blob/master/raphael.group.js

person Ben Bederson    schedule 25.05.2012

Да, объект set для этого:

var myWords = r.set();
myWords.push(
    r.text(190, 100, "Hello"),
    r.text(480,100, "Good Bye"
);

// now you can treat the set as a single object:
myWords.rotate(90);

Дополнительный ответ:

Хорошо, я вижу, что вы пробовали использовать набор, но используете его неправильно. Набор создает группу вещей. Точно так же, как если бы вы группировали фигуры и текст в Adobe Illustrator, Inkscape, Microsoft Word или Open Office. Если я правильно вас понял, вы хотите:

shapes = [  r.set(r.text (190, 100, "node1"), r.ellipse(190, 100, 30, 20)),
            r.set(r.text (290, 80, "Center"), r.rect(290, 80, 60, 40, 10)),
            r.set(r.text (290, 180, "node2"), r.rect(290, 180, 60, 40, 2)),
            r.set(r.text (450, 100, "node3"), r.ellipse(450, 100, 20, 20))
         ];

Вам также придется изменить функции перетаскивания и перемещения, поскольку фигуры больше не имеют тип 'rect', а имеют тип 'set':

var dragger = function () {
    this.ox = this.attr("x");
    this.oy = this.attr("y");
    this.animate({"fill-opacity": .2}, 500);
};
var move = function (dx, dy) {
    var att = {x: this.ox + dx, y: this.oy + dy};
    this.attr(att);
    for (var i = connections.length; i--;) {
        r.connection(connections[i]);
    }
    r.safari();
};

Все наборы имеют атрибуты x и y.

person slebetman    schedule 09.09.2010
comment
Спасибо. К сожалению, это не сработало. Я могу перемещать фигуры, но не текст. Я изменил все, что ты сказал. На данный момент все, что я знаю, это просто текст без форм. Я очень ценю вашу помощь. - person johnny; 15.09.2010
comment
Проблема с этим методом заключается в том, что наборы не создают никаких элементов на странице, поэтому, когда вы нажимаете для перетаскивания, вы не выбираете набор, а только элемент .... поэтому this в dragger относится к одному элемент, а не набор. - person Peter Ajtai; 06.10.2010
comment
Sets не имеют коллектива x и y. Действия с set применяются к каждому человеку в наборе. set не рассматривается как единое целое. ----------- myWords.rotate(90); не вращает set в целом. Он просто перебирает каждый объект в наборе и вращает эти == ›jsfiddle.net/UFxJZ (обратите внимание, что 2 слова начинались параллельно на 2 отдельных линиях .... после поворота они теперь параллельны, но на одной линии!) - person Peter Ajtai; 06.10.2010
comment
у нас есть два ответа, которые, кажется, противоречат другому. один говорит, что набор БУДЕТ позволять вам перемещать объекты в унисон, а другой говорит, что набор НЕ допускает эту функциональность. можем ли мы получить окончательный ответ относительно того, какой ответ правильный? они не могут оба быть правы, не так ли? Однако у них обоих есть голоса - person b_dubb; 16.03.2012

Разве не было бы проще просто изменить атрибуты парного объекта вместе с атрибутами, которые изменяются при перетаскивании основного объекта?

Что-то вроде этого:

window.onload = function () {
        var R = Raphael("holder"),
            circ = R.circle(100, 100, 50).attr({ "fill": "#d9d9d9", "stroke-width": 1 }),
        circ2 = R.circle(50, 50, 5),
            start = function () {
                this.ox = this.attr("cx"); //ox = original x value
                this.oy = this.attr("cy");
                this.animate({ "opacity": .5, "stroke-width": 15 }, 200);
            },
            move = function (dx, dy) {  //dx - delta x - diiference in movement between point a and b
                var cdx = circ2.attr("cx") - this.attr("cx"),
                    cdy = circ2.attr("cy") - this.attr("cy");
                this.attr({ "cx": this.ox + dx, "cy": this.oy + dy });
                group(this,circ2,cdx,cdy);
                R.safari();
            },
            up = function () {
                this.animate({ "opacity": 1, "stroke-width": 1 }, 200);
            },
            group = function (refObj,thisObj, dx, dy) {                    
                thisObj.attr({ "cx": refObj.attr("cx") + dx, "cy": refObj.attr("cy") + dy });
            };

            circ.drag(move, start, up);




    };
person Adam Moszczyński    schedule 20.07.2011
comment
Если вы вызываете перетаскивание только для одного из сгруппированных элементов, и они имеют перекрывающиеся границы, боковые элементы могут получить событие мыши и не позволить перетаскивать основной элемент. Использование toFront () не решило эту проблему для меня, но вызов перетаскивания для всех в группе работал хорошо. - person Anatortoise House; 23.07.2011