Paint

Научившись работать с Raphaël и пользуясь познаниями из прошлой статьи плагин нужно соединить с colorpicker’ом и datagrid’ом. Долго не гугля я выбрал два кажущиеся мне нормальные плагина и похоже не ошибся. Интеграция прошла быстро и безболезненно :)

Задача: есть некая модель данных, которая должна отображаться в двух видах: в табличке в виде данных и на картинке ввиде линии. Линии можно выделять при этом реагирует табличка подсвечивая выбранный ряд, соответственно и наоборот если выбрать ряд в табличке надо подсветить линию на рисунке, линии можно удалять, удаляя данные и из таблички, и добавлять добавляя пустой ряд в табличку. Так вроде все описал, на всякий случай еще приделал к этому цветовую палитру, еще наверное надо будет толщину линии выбирать разрешить, но это просто.

Модель данных взята от фонаря три шага налево…


var model = [
{id : 0, amount : 100, units : "meters", value : 13, color : "#00f", path : "M 250 73 L 250 73"},
{id : 1, amount : 200, units : "meters", value : 22, color : "#0f0", path : "M 327 67 L 327 67"},
{id : 2, amount : 300, units : "meters", value : 36, color : "#f00", path : "M 185 172 L 185"},
{id : 3, amount : 400, units : "meters", value : 18, color : "#00f", path : "M 216 231 L 216"},
{id : 4, amount : 150, units : "meters", value : 23, color : "#f00", path : "M 274 171 L 274 171"},
{id : 5, amount : 360, units : "liters", value : 36, color : "#00f", path : "M 308 176 L 308 176"},
{id : 6, amount : 170, units : "liters", value : 12, color : "#0f0", path : "M 358 113 L 358 113"},
{id : 7, amount : 180, units : "liters", value : 24, color : "#f00", path : "M 201 109 L 201 109"},
{id : 8, amount : 292, units : "liters", value : 37, color : "#00f", path : "M 228 114 L 228 114"},
{id : 9, amount : 182, units : "kgrams", value : 43, color : "#0f0", path : "M 357 245 L 357 245"},
{id : 10,amount : 273, units : "kgrams", value : 53, color : "#f00", path : "M 346 172 L 346 172"},
{id : 11,amount : 164, units : "kgrams", value : 61, color : "#00f", path : "M 386 179 L 386 179"},
{id : 12,amount : 254, units : "kgrams", value : 27, color : "#0f0", path : "M 218 178 L 218 178"}
];

Загадочная надпись M 250 73 L 250 73 это начало линии в формате SVG, дословно она означает Move To 250 px OX 73 px OY Line To 250 px OX 73 px OY, если в конце написать «z» то начало и конец линии замкнуться. В примере я укоротил запись в исходниках все по честному.

Теперь это все надо отрендерить, для этого я написал следующий код, который вы можете использовать как есть, но использовать пока не советую из-за крайней сырости, но за найденные баги буду очень благодарен


(function($) {

    /**
     * author: CTAPbIu_MABP
     * email: ctapbiumabp@gmail.com
     * site: http://mabp.kiev.ua/
     * last update: 08.12.2009
     * version: 0.2
     */

    var Paint = function (node, options) {
        $.extend(this, options);
        this.init(node);
    };


    Paint.prototype = {

        // private
        raphael : null,
        isStarted : false,
        node : null,
        offset : null,

        // public
        color : "#f00",
        defaultColor : "#000",

        // callbacks
        onInit : function() {
        },
        onLineClick : function() {
        },
        onLineDblClick : function() {
        },
        onLineDelete : function() {
        },
        onStart : function() {
        },
        onStop : function() {
        },
        onMove : function() {
        },

        init : function (raw_img) {

            var img = $(raw_img).hide(),
                    self = this,
                    width = img.width(),
                    height = img.height();

            this.node = img.wrap("<div/>").parent().addClass("plot").css({width:width,height:height});
            this.raphael = new Raphael(this.node.get(0), width, height);
            this.raphael.image(img.attr("src"), 0, 0, width, height);

            this.unselectable(this.node.get(0));
            this.node.mouseover(function() {
                //this.style.cursor = 'pointer';
                //$(this).css({'cursor':'url("pencil.cur")'});
            }).mousedown(function(e) {
                self.start(e);
            }).mouseup(function(e) {
                self.stop(e);
            }).mousemove(function(e) {
                self.move(e);
            });

            this.node.bind("drawLine setColor deleteLine", function(e, param) {
                self[e.type](param);
            });

            var model = this.model;
            this.model = [];

            for (var i = 0,j = model.length; i < j; i++) {
                this.setColor(model[i].color);
                this.drawLine(model[i].path);
            }

            this.onInit.apply(this, [raw_img]);
        },

        setColor : function(color) {
            this.color = /^#([a-f0-9]{3}){1,2}$/i.test(color) ? color : this.defaultColor;
        },

        start: function(e) {
            //console.log("start");
            e.preventDefault(); // prevent drag image
            if (this.isStarted)
                return;

            this.offset = this.node.offset();

            this.model.push({
                line : "M " + (e.pageX - this.offset.left) + " " + (e.pageY - this.offset.top),
                color : this.color,
                path : null
            });

            this.isStarted = true;
            this.onStart.apply(this, [e]);
        },

        move: function(e) {
            //console.log("move");
            if (!this.isStarted) return;

            var model = this.model.pop();
            model.path && model.path.remove();
            delete model.path,model;

            this.drawLine(model.line + " L " + (e.pageX - this.offset.left) + " " + (e.pageY - this.offset.top));
            this.onMove.apply(this, [e]);
        },

        stop: function(e) {
            //console.log("finish");
            this.isStarted = false;

            var model = this.model.pop();
            e.isClick = !model.path; // was it line or just click on image

            model.path && this.model.push(model) || delete model;

            this.onStop.apply(this, [e]);
        },

        drawLine : function(line) {
            var path = this.raphael.path(line)
                    .attr({stroke:this.color,'stroke-width':5,'stroke-linejoin':'round'});

            var self = this;

            $(path.node)
                .click((function(c) {return function() {self.onLineClick.apply(self, [c]);}})(self.model.length))
                .dblclick((function(c) {return function() {self.onLineDblClick.apply(self, [c]);}})(self.model.length))
                .mouseover(function() {this.style.cursor = 'crosshair';});

            this.model.push({line:line,path:path,color:this.color});
        },

        deleteLine : function(index) {
            var model = this.model[index];
            model.path && model.path.remove();
            delete model.path, model;

            this.onLineDelete.apply(this, [index])
        },

        unselectable: function(element) {
            // http://ajaxcookbook.org/disable-text-selection/
            element.onselectstart = function() {
                return false;
            };
            element.unselectable = "on";
            element.style.MozUserSelect = "none";
        }
    };


    $.fn.paint = function(options) {
        this.each(function() {
            new Paint(this, options);
        });
        return this;
    };

})(jQuery);

Простите, но комментариев к коду пока нет, будут в следующей версии, наверное, сильно не обольщайтесь. Да и скачать этот код можно отсюда.

Теперь пора показать как это все собрать в кучу, запускаю плагин.


$("#plot").paint({
    model : model,
    // текущий подсвеченный элемент
    hightlited : null, // it may be zero
    hightlitedColor : "#fff",

    // обработчик двойного клика на линию
    // удаляет подсветку
    // удаляет линию с рисунка
    // удаляет ряд из таблицы
    onLineDblClick : function(index){
        this.hightlited = null;
        this.deleteLine(index);
        $("#grid").jqGrid('delRowData',index+1); 
    },

    // обработчик клика на линию
    onLineClick : function (index) {
        // снимает прошлуб подсветку если была
        if (this.hightlited != null){
            var prev = this.model[this.hightlited];
            prev.path.attr({stroke:prev.color});
        }

        // добавляет новую
        this.model[index].path.attr({stroke:this.hightlitedColor});
        // подсвечивает ряд в таблице
        $("#grid").jqGrid('setSelection',index+1);
        this.hightlited = index;
    },

    // call-back на событие остановки рисования линии
    onStop : function(e){
        // удаляет старую подсветку если была
        if (this.hightlited != null){
            var prev = this.model[this.hightlited];
            prev.path.attr({stroke:prev.color});
        }

        // если это не случайный клик
        if (!e.isClick){
            // подсвечивает свежесозданную линию
            var index = this.model.length-1;
            this.model[index].path.attr({stroke:"#fff"});
            this.hightlited = index;
            // добавляет ряд в таблицу
            $("#grid").jqGrid('addRowData',index+1,this.model[index]);
        }
    },

    // пока не забыл надо в самом начале определить 
    // событие которое будет вызвано когда в таблице
    // поменяется выделенная строка
    onInit : function(){
        var self = this;
        this.node.bind("hightliteLine", function(e, index) {
            // убирает старую подсветку
            if (self.hightlited != null){
                var prev = self.model[self.hightlited];
                prev.path.attr({stroke:prev.color});
            }

            // подсвечивает новую линию
            self.model[index].path.attr({stroke:self.hightlitedColor});
            self.hightlited = index;
        });
    }
});

Теперь надо привязать выбор цвета, для этого создается небольшая функция инициализирующая colorpicker, когда colorpicker меняет цвет срабатывает событие у рисовалки, и она меняет цвет линии.


$('#colorpicker').ColorPicker({
    flat: true,
    // начальный цвет
    color: '#00ff00',
    // вся магия тут
    onChange: function(hsb, hex, rgb) {
        $("#plot").trigger("setColor","#"+hex);
    }
});

Еще немного магии для инициализации data grid’а :


$("#grid").jqGrid({
    datatype: "local",
    height: 250,
    colNames:['No','Amount','Units','Value'],
    colModel:[
        {name:'id',index:'id',width:100, sorttype:"int"},
        {name:'amount',index:'amount', width:100, sorttype:"int", align:"right"},
        {name:'units',index:'units', width:100, sorttype:"float"},
        {name:'value',index:'value', width:100, sorttype:"int", align:"right"}
    ],
    caption: "Lines Data",
    onSelectRow : function (id){
        $("#plot").trigger("hightliteLine", id-1);
    }
});

for(var i=0, j=model.length;i<=j;i++)
    $("#grid").jqGrid('addRowData',i+1,model[i]);

Тут, помимо общих настроек и установки модели, есть чудо метод который вызовет событие подсветки линии когда измениться текущий ряд в таблице.

face

Я думаю, я в ближайшее время не заброшу этот плагин так что ждите новую версию, возможно даже с документацией, пишите свои каменты, не забывайте читать мои бредни ;)

11 Комментарии “Paint

  1. Бажит немного при выходе за поле рисунка :) Но всё-равно супер :)
    3 интересных детективных статьи :)

  2. @deerua
    Это не баг, это — фича!!!

    Статьи немного порекомпоновал, в 2 часа ночи поток мысли не очень последовательный…

  3. Охрененно! Если понадобится — обязательно запарю тебя камментами :)

  4. В опере коряво рисует, водишь мышкой ничего не видно, прокручиваешь страницу, что бы картинка исчезла, снова возвращаешься к картинке, а там отображается, все что рисовалось.

  5. Не замечал:) А что оперу обежать? Понимаю ИЕ…))

  6. @kein
    да я поставил оперу на W7 посмотрел а оно не пашет
    а щас глянул с рабочего компа а оно вообще криво сделано
    например пути картинок для css неправльные
    думаю ночью поправлю

  7. Опера вообще запарила. IE 8 в некоторых случаях (связанных, опять же, с джаваскрипт и с библиотеками, с jquery, в частности), работает корректнее, чем она. Опера от версии к версии все глючнее и глючнее. Даже заядлые знакомые «оперисты» кочуют на огнелиса или хром. Печально. Я года 3 назад тоже был фанатом оперы…

Комментарии закрыты