Source: D3Manager.js

/**
 * Manages displaying everything with d3 library
 * @param {number} update_rate - the update rate of the renderloop
 * @param {VelocityField} velocityField - the related VelocityField
 * @constructor
 */
var D3Manager = function (update_rate, velocityField) {
    var UPDATE_RATE = update_rate;
    var velocityField = velocityField;

    //get svg element to draw to, imagine it like a canvas -
    //it has NOTHING to do with the HTML5 canvas, though!
    var svgCanvas = d3.select("#canvas").append("svg")
        .attr("width", velocityField.widthOfField)
        .attr("height", velocityField.heightOfField)
        .style("border", "1px solid black");

    /**
     * sets the background image
     * @param {String} imgPath - path to image file
     * @param {number} width - final image width
     * @param {height} height - final image height
     */
    this.setBackgroundImg = function (imgPath, width, height) {
        svgCanvas.append("svg:image")
            .attr("xlink:href", imgPath)
            //.attr("x", "60")
            //.attr("y", "60")
            .attr("width", width)
            .attr("height", height);
    }

    /**
     * renders arrows and arrowHeads
     * d3 update pattern explained here: http://quintonlouisaiken.com/d3-general-update-pattern/
     * and here: https://bost.ocks.org/mike/join/
     * and here is an example with lines which helped me : http://bl.ocks.org/jonsadka/482005612916b3f5e408
     * @param {Array.<Arrow>} arrows - the arrows which should be rendered
     * @param {Array.<ArrowHead>} arrowHeads - coresponding Arrowheads
     */
    this.render = function (arrows, arrowHeads) {

        var lineFunction = d3.svg.area()
            .x(function (d) {return d.x;})
            .y(function (d) {return d.y;})
            .y0(function (d) {return (d.y + 5) ;})
            .y1(function (d) {return (d.y - 5) ;})
            .interpolate("basis");

        var circleFunction = d3.svg.area()
            .x(function (d) {return d.x + d.xOffset;}) //redo the x scale for a circle, would be to small with small velocity
            .y(function (d) {return d.y;})
            .y0(function (d) {return (d.cy + d.yOffset);}) //upper y-bound of the area
            .y1(function (d) {return (d.cy - d.yOffset);}) //lower y-bound of the area
            .interpolate("basis");

        var arrowData = arrowsToLineData(arrows);
        var d3arrows = svgCanvas.selectAll("path").data(arrowData);

        // transition from previous paths to new paths - update the existing ones
        d3arrows.transition().duration(UPDATE_RATE)
            .attr("d", function (d, i){
                if(arrowData[i][0].isCirc == 0) { return lineFunction(d)}
                else { return circleFunction(d,i)}
             ;}) 
            .attr("stroke", "blue")
            .attr("stroke-width",  function (d, i) {return arrowData[i][0].st;} )
            .attr("fill",  "yellow")
            .attr("opacity", function (d, i) {return arrowData[i][0].op;});


        // draw any new arrows if neccessary
        d3arrows.enter()
            .append("path")
            .attr("d", function (d, i){
                if(arrowData[i][0].isCirc == 0) {return lineFunction(d)}
                else {return circleFunction(d,i)}
             ;})
            .attr("stroke", "blue")
            .attr("stroke-width",  function (d, i) {return arrowData[i][0].st;})
            .attr("fill",  "yellow")
            .attr("opacity", function (d, i) {return arrowData[i][0].op;});

        d3arrows.exit().remove();

        //Draw the arrowheads
        var triangleData = arrowHeadsToTriangleData(arrowHeads); //TODO: rotate polygons according to the angles
        var d3arrowHeads = svgCanvas.selectAll("polygon").data(triangleData);

         d3arrowHeads.transition().duration(UPDATE_RATE)
            .attr("points", function (d, i) { 
                if(arrowData[i][0].isCirc == 0) { 
                    return '-16 -16 , -16 16 , 16 0'}
                    else { return '0 0, 0 0, 0 0' }
                ;})
            .attr("transform", function (d, i) { 
                //TODO: ROTATION SHOULD BE triangleData[i][0].rotation BUT IS NaN? WTF?
                //console.log("TODO: FIX ROTATION IN D3 MANAGER!")
                return "translate(" + (triangleData[i][0].mX) + "," + triangleData[i][0].mY + ") rotate(" + triangleData[i][0].angle + ")";   
            })
            .attr("stroke", "blue")
            .attr("stroke-width",  function (d, i) {return arrowData[i][0].st;})
            .attr("fill",  "yellow")
            .attr("opacity", function (d, i) {return arrowData[i][0].op;});


        // draw new arrowHeads if neccessary
        d3arrowHeads.enter()        
            .append("polygon")
            .attr("class", "aHeads")
            .attr("points", function (d, i) {
                if(arrowData[i][0].isCirc == 0) { 
                    return '-16 -16 , -16 16 , 16 0'}
                    else { return '0 0, 0 0, 0 0' }
                ;})
            .attr("transform", function (d, i) {
                //console.log("TODO: FIX ROTATION IN D3 MANAGER!")
                //console.log(triangleData[i][0].angle)
                return "translate(" + (triangleData[i][0].mX) + "," + triangleData[i][0].mY + ") rotate(" +  triangleData[i][0].angle + ")";   
            })
            .attr("stroke", "blue")
            .attr("stroke-width",  function (d, i) {return arrowData[i][0].st;})
            .attr("fill",  "yellow")
            .attr("opacity", function (d, i) {return arrowData[i][0].op;});

        d3arrowHeads.exit().remove();

        /*
        //DEBUG draw handle Points
        var pointData = arrowsToPointData(arrows);
        var d3arrowPoints = svgCanvas.selectAll("circle").data(pointData);

        d3arrowPoints.transition().duration(UPDATE_RATE)
            .attr("cx", function(d) {
                return d[0];
            })
            .attr("cy", function(d) {
                return d[1];
            })
            .attr("r", "5px")
            .attr("fill", "red");

        d3arrowPoints.enter().append("circle")
            .attr("cx", function(d) {
                return d[0];
            })
            .attr("cy", function(d) {
                return d[1];
            })
            .attr("r", "5px")
            .attr("fill", "red");

        d3arrowPoints.exit().remove();

        
        //DEBUG distanceMap lines
        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 100)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 100)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 200)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 200)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 300)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 300)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 400)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 400)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 500)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 500)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 600)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 600)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 700)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 700)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 800)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 800)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 900)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 900)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 1000)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 1000)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line

        //horizontal lines
        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 0)     // x position of the first end of the line
            .attr("y1", 0)      // y position of the first end of the line
            .attr("x2", 1000)     // x position of the second end of the line
            .attr("y2", 0);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 0)     // x position of the first end of the line
            .attr("y1", 100)      // y position of the first end of the line
            .attr("x2", 1000)     // x position of the second end of the line
            .attr("y2", 100);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 0)     // x position of the first end of the line
            .attr("y1", 200)      // y position of the first end of the line
            .attr("x2", 1000)     // x position of the second end of the line
            .attr("y2", 200);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 0)     // x position of the first end of the line
            .attr("y1", 300)      // y position of the first end of the line
            .attr("x2", 1000)     // x position of the second end of the line
            .attr("y2", 300);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 0)     // x position of the first end of the line
            .attr("y1", 400)      // y position of the first end of the line
            .attr("x2", 1000)     // x position of the second end of the line
            .attr("y2", 400);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 0)     // x position of the first end of the line
            .attr("y1", 500)      // y position of the first end of the line
            .attr("x2", 1000)     // x position of the second end of the line
            .attr("y2", 500);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 0)     // x position of the first end of the line
            .attr("y1", 600)      // y position of the first end of the line
            .attr("x2", 1000)     // x position of the second end of the line
            .attr("y2", 600);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 0)     // x position of the first end of the line
            .attr("y1", 700)      // y position of the first end of the line
            .attr("x2", 1000)     // x position of the second end of the line
            .attr("y2", 700);    // y position of the second end of the line

        svgCanvas.append("line")          // attach a line
            .style("stroke", "black")  // colour the line
            .attr("x1", 0)     // x position of the first end of the line
            .attr("y1", 800)      // y position of the first end of the line
            .attr("x2", 1000)     // x position of the second end of the line
            .attr("y2", 800);    // y position of the second end of the line
        */

    }

    /**
     * map all positional data from the arrows array to an array of coordinates for the SVG to render paths
     * @param {Array.<Arrow>} arrows - the arrows
     * @returns {Array} restructured arrows for d3
     */
    var arrowsToLineData = function(arrows) {
        var dataArrows = arrows.map(function(arrow){
            var rObj = 
            [   {"x":arrow.currp0x, "y":arrow.currp0y, "yOffset":arrow.oy1, "xOffset":arrow.ox1, "cy":arrow.yCirc, "isCirc":arrow.isCirc, "op":arrow.opacity, "st":arrow.strokeWidth}, //erstelle 2 verschiedene arrays, ja nachdem, was im arrow drin ist. 
                {"x":arrow.currp1x, "y":arrow.currp1y, "yOffset":arrow.oy2, "xOffset":arrow.ox2, "cy":arrow.yCirc},
                {"x":arrow.currX, "y":arrow.currY,     "yOffset":arrow.oy3, "xOffset":arrow.ox3, "cy":arrow.yCirc},
                {"x":arrow.currp2x, "y":arrow.currp2y, "yOffset":arrow.oy4, "xOffset":arrow.ox4, "cy":arrow.yCirc},
                {"x":arrow.currp3x, "y":arrow.currp3y, "yOffset":arrow.oy5, "xOffset":arrow.ox5, "cy":arrow.yCirc},
            ];
            return rObj;
        });

        return dataArrows;
    }

    /**
     * restructure ArrowHeads for d3
     * @param {Array.<ArrowHead>} arrowHeads - the ArrowHeads
     * @returns {Array} restructured Arrowsheads for d3
     */
    var arrowHeadsToTriangleData = function(arrowHeads) {
        var data = arrowHeads.map(function(arrowHead){
            var rObj =
            [
                {"mX":arrowHead.posX , "mY": arrowHead.posY, "angle": arrowHead.rotation}
            ];
            return rObj;
        });

        return data;
    }

    //
    var arrowsToPointData = function(arrows) {
        var points = [];
        arrows.forEach(function(arrow) {
            points.push([arrow.currp0x, arrow.currp0y]);
            points.push([arrow.currp0x, arrow.currp0y]);
            points.push([arrow.currp1x, arrow.currp1y]);
            points.push([arrow.currX, arrow.currY]);
            points.push([arrow.currp2x, arrow.currp2y]);
            points.push([arrow.currp3x, arrow.currp3y])
        })

        return points;
    }

}