seatmap.js 10.2 KB
/*

Seatmap javascript library for Moya

*/

function placemap(opts)
{
    if ( opts.element === undefined )
    {
        throw "element is mandatory argument";
    }

    if ( opts.moyaurl === undefined )
    {
        throw "moyaurl is mandatory argument";
    }

    if ( opts.map_id === undefined )
    {
        throw "map_id is mandatory argument";
    }

    if (opts.onclick === undefined )
    {
        throw "onclick is mandatory argument";
    }

    if (opts.editclick === undefined )
    {
    	opts.editclick = function(x, y) {return false;};
    }
    
    // px is returned object

    var px = {
        element: d3.select(opts.element),
        moyaurl: opts.moyaurl,
        map_id: opts.map_id,
        onclick: opts.onclick,
        clicked_place: undefined
    };


    var guide_layer = map_layer = px.element.append("g").attr("id", "guides");
    // Create background
    var background = px.element.append("rect")
        .attr("class", "background_2")
        .attr("width", px.element.attr('width'))
        .attr("height", px.element.attr('width'))
        .attr("opacity", 0);

    // Define layers to use
    var map_layer = px.element.append("g").attr("id", "places");

    var edit_layer = map_layer = px.element.append("g").attr("id", "edit");

    var clicked = false;

    px.place_colors = {
        "F": "#cccccc",
        "L": "#535353",
        "R": "#B40100",
        "P": "#2FA525",
        "T": "#4342C1"
    };

    px.snap_threshold = {x: 5, y: 5};

    var clickpos = {x: 0, y: 0};

    var tip = d3.tip()
      .attr('class', 'place-tooltip')
      .offset([-10, 0])
      .html(function(d) {
        return "<strong>" + d.name + "</strong>";
      });

    // TODO: Fix this tip layout
    var tip_updown = d3.tip().direction("s")
      .attr('class', 'place-tooltip-upside')
      .offset([10, 0])
      .html(function(d) {
        return "<strong>" + d.name + "</strong>";
      });

    function draw_guides(x1, y1, x2, y2, fade)
    {
        var guide = guide_layer.append('line')
                .attr('x1', x1)
                .attr('y1', y1)
                .attr('x2', x2)
                .attr('y2', y2)
                .attr("stroke-width", 1)
                .attr("stroke", "#666")
        if (fade == true)
        {
                guide.transition()
                .duration(500)
                .remove();
        }
    }


    function clear_guides()
    {
        guide_layer.selectAll('line').remove();
    }

    function snap_to_edges(x, y, fade)
    {
        // TODO: if there is node in range, snap coordinates next to it

        if (clicked == true) return;

        var xmin = x - px.snap_threshold.x;
        var xmax = x + px.snap_threshold.x;
        var ymin = y - px.snap_threshold.y;
        var ymax = y + px.snap_threshold.y;
        var xnode, ynode;
        var left, top, right, bottom;
        map_layer.selectAll('rect').each(function(d) {
            left = d.x;
            top = d.y;
            right = d.x + d.w;
            bottom = d.y + d.h;
            if (d.x > xmin && d.x < xmax)
            {
                if (xnode === undefined || Math.abs(x - d.x) < Math.abs(x - xnode.x))
                {
                    x = d.x;
                    xnode = d;
                }
            }
            if (right > xmin && right < xmax)
            {
                if (xnode === undefined || Math.abs(x - right) < Math.abs(x - xnode.x - xnode.w))
                {
                    x = right;
                    xnode = d;
                }
            }
            if (d.y > ymin && d.y < ymax)
            {
                if (ynode === undefined || Math.abs(y - top) < Math.abs(y - ynode.y)) {
                    y = d.y;
                    ynode = d;
                }
            }
            if (bottom > ymin && bottom < ymax)
            {
                if (ynode === undefined || Math.abs(y - bottom) < Math.abs(y - ynode.y - ynode.h)) {
                    y = bottom;
                    ynode = d;
                }
            }
        });

        clear_guides();

        if (ynode !== undefined)
        {
            if (ynode.y == y) draw_guides(ynode.x, ynode.y, x, y, fade);
            else draw_guides(ynode.x, ynode.y + ynode.h, x, y, fade);
        }

        if (xnode !== undefined)
        {
            if (xnode.x == x) draw_guides(xnode.x, xnode.y, x, y, fade);
            else draw_guides(xnode.x + xnode.w, xnode.y, x, y, fade);
        }

        return [x, y]
    }




    function show_tooltip(d, i) {
        var object = {
            x: d3.select(this).attr('x'),
            y: d3.select(this).attr('y'),
            width: d3.select(this).attr('width'),
            height: d3.select(this).attr('height')
        };
        if  (object.y >= 40)
        {
            return tip.show(d, i);
        }
        else {
             return tip_updown.show(d, i);
        }
    }

    function hide_tooltip(d, i)
    {
        tip.hide(d, i);
        tip_updown.hide(d, i);
    }

    function toggle_clicked(element)
    {
        // TODO: reserve place from rest first!

        data = element.data()[0];

        px.clicked_place = data.id;
        
        px.onclick(data);
        
        px.update_place(data.id);
        
        //if ( != true) return;

        /*if (data.state == "F")
        {
            element.transition().duration(100).style("fill", px.place_colors["T"]);
            data.state = "T";
        }
        else if (data.state == "T")
        {
            element.transition()
                .duration(100).style("fill", px.place_colors["F"]);
            data.state = "F";
        }*/
    }

    function place_color(d, i)
    {
        if (Object.keys(px.place_colors).indexOf(d.state) != -1) return px.place_colors[d.state];
        return "#888";
    }


    function draw_places(data)
    {
        // TODO: update functionality!
        var places = map_layer.selectAll("rect")
            .data(data, function(d) { return d.id; }).enter()
            .append("rect")
            .filter(function(d,i){ return d.state != "D"; })
            .attr("x", function(d, i) { return d.x; })
            .attr("y", function(d, i) { return d.y; })
            .attr("width", function(d, i) { return d.w;})
            .attr("height", function(d, i) { return d.h; })
            .attr("id", function(d) {return d.id; })
            .style("fill", place_color)
            .style('stroke', '#101010')
            .style('stroke-width', '1');
        places.append("title", function(d, i) { return d.name; });

        var old_places = map_layer.selectAll("rect")
        	.data(data, function(d) { return d.id; })
        	.filter(function(d,i){ return d.state != "D"; })
        	.attr("x", function(d, i) { return d.x; })
            .attr("y", function(d, i) { return d.y; })
            .attr("width", function(d, i) { return d.w;})
            .attr("height", function(d, i) { return d.h; })
            .attr("id", function(d) {return d.id; })
            .style("fill", place_color);
        
        if (px.edit_enabled == false)
        {
            places
            .on('mouseover', show_tooltip)
            .on('mouseout', hide_tooltip)
            .on('click', function (d, i) {
                toggle_clicked(d3.select(this));
            });
        }
    }

    function edit_clicked(d, i) {
        c = d3.mouse(this);
        var pos = {x: c[0], y: c[1]};

        // Begin click action
        if (clicked == false)
        {

            //coords = snap_to_edges(pos.x, pos.y, true);
            clicked = true;
            pos.x = coords[0];
            pos.y = coords[1];
            clickpos = pos;
            edit_layer.append("circle")
                .attr("cx", pos.x)
                .attr("cy", pos.y)
                .attr("r", 2)
                .attr("width", 10)
                .attr("height", 10)
                .attr("fill", "#111");
            	px.editclick(pos.x, pos.y);
        }
        else {
            edit_layer.selectAll("circle").remove();
            clicked = false;
            //tip_add_places.hide(d, i);
        }

    }
    px.edit_enabled = false;
    px.enable_edit = function()
    {
        map_layer.selectAll('rect')
            .on('click', null);
        background.on("click", edit_clicked);
        background.on("mousemove", function(){
            c = d3.mouse(this);
            var pos = {x: c[0], y: c[1]};
            coords = snap_to_edges(pos.x, pos.y, false);
        });
        px.edit_enabled = true;
    };

    function gen_row(x,y, width, height, direction, count)
    {
        var places = [];
        place = 1;
        for (var i = 0; i < count; i++)
        {
            places.push({x: x, y: y, w: width, h: height, name: "foo" + i + x + y, id: i*1000+y*100+x, state: "F"});
            place += 1;
            if (direction == "horizontal")
            {
                x += width;
            }
            else {
                y += height;
            }
        }
        return places;
    }

    px.update = function() {
        d3.json(px.moyaurl + "/rest/placemap/v1/" + px.map_id + "/places", function(data) {
            draw_places(data.places);
        });
    };

    px.update_place = function (place_id)
    {
    	if (place_id === undefined) place_id = px.clicked_place;
    	if (place_id !== undefined ) {
	    	d3.json(px.moyaurl + "/rest/placemap/v1/place/" + place_id, function (data) {
	    		draw_places(data.places);
	    	});
    	}
    };
    
    px.init = function() {
        d3.json(px.moyaurl + "/rest/placemap/v1/" + px.map_id + "/", function(data){
            px.element.attr("id", data.map.id).attr("name", data.map.name);
            px.update();
        });
    };

    px.init();

    px.element.call(tip);
    px.element.call(tip_updown);


    px.add_places = function (width, height, count, direction){
        var width = parseInt(document.getElementById("place_add_width").value);
        var height = parseInt(document.getElementById("place_add_height").value);
        var count = parseInt(document.getElementById("place_add_count").value);
        var direction = document.getElementById("place_add_direction").value;
        draw_places(gen_row(clickpos.x,clickpos.y, width, height, direction, count));
        edit_layer.selectAll("circle").remove();
        clicked = false;
        clear_guides();
    };

    return px;
}