Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

Revision 4fb2a8f6af5b5910b5d4d3bbc9172ddf57810df2 authored by lauragarrison on 17 March 2021, 07:10:20 UTC, committed by lauragarrison on 17 March 2021, 07:10:20 UTC
updates to readme, app.py, and requirements list for testing
1 parent 42366d9
  • Files
  • Changes
  • f115cd0
  • /
  • templates
  • /
  • libraries
  • /
  • parcoords
  • /
  • d3.parcoords.js
Raw File Download

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • revision
  • directory
  • content
revision badge
swh:1:rev:4fb2a8f6af5b5910b5d4d3bbc9172ddf57810df2
directory badge
swh:1:dir:fb73ddb7b1aa6bc0b7ecf2f5ebd4c769feec5376
content badge
swh:1:cnt:c6f9f1e7dc1b24e208869e394fed9f8c3c5af889

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • revision
  • directory
  • content
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
d3.parcoords.js
d3version3.parcoords = function(config) {
    var __ = {
        data: [],
        highlighted: [],
        dimensions: {},
        dimensionTitleRotation: 0,
        brushed: false,
        brushedColor: null,
        alphaOnBrushed: 0.0,
        mode: "default",
        rate: 20,
        width: 600,
        height: 300,
        margin: { top: 24, right: 0, bottom: 12, left: 0 },
        nullValueSeparator: "undefined", // set to "top" or "bottom"
        nullValueSeparatorPadding: { top: 8, right: 0, bottom: 8, left: 0 },
        color: "#069",
        composite: "source-over",
        alpha: 0.7,
        bundlingStrength: 0.5,
        bundleDimension: null,
        smoothness: 0.0,
        showControlPoints: false,
        hideAxis : [],
        flipAxes: [],
        animationTime: 1100, // How long it takes to flip the axis when you double click
        rotateLabels: false
    };

    extend(__, config);

    if (config && config.dimensionTitles) {
        console.warn("dimensionTitles passed in config is deprecated. Add title to dimension object.");
        d3version3.entries(config.dimensionTitles).forEach(function(d) {
            if (__.dimensions[d.key]) {
                __.dimensions[d.key].title = __.dimensions[d.key].title ? __.dimensions[d.key].title : d.value;
            } else {
                __.dimensions[d.key] = {
                    title: d.value
                };
            }
        });
    }
    var pc = function(selection) {
        selection = pc.selection = d3version3.select(selection);

        __.width = selection[0][0].clientWidth;
        __.height = selection[0][0].clientHeight;

        // canvas data layers
        ["marks", "foreground", "brushed", "highlight"].forEach(function(layer) {
            canvas[layer] = selection
                .append("canvas")
                .attr("class", layer)[0][0];
            ctx[layer] = canvas[layer].getContext("2d");
        });

        // svg tick and brush layers
        pc.svg = selection
            .append("svg")
            .attr("width", __.width)
            .attr("height", __.height)
            .style("font", "14px sans-serif")
            .style("position", "absolute")

            .append("svg:g")
            .attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");

        return pc;
    };
    var events = d3version3.dispatch.apply(this,["render", "resize", "highlight", "brush", "brushend", "brushstart", "axesreorder"].concat(d3version3.keys(__))),
        w = function() { return __.width - __.margin.right - __.margin.left; },
        h = function() { return __.height - __.margin.top - __.margin.bottom; },
        flags = {
            brushable: false,
            reorderable: false,
            axes: false,
            interactive: false,
            debug: false
        },
        xscale = d3version3.scale.ordinal(),
        dragging = {},
        line = d3version3.svg.line(),
        axis = d3version3.svg.axis().orient("left").ticks(5),
        g, // groups for axes, brushes
        ctx = {},
        canvas = {},
        clusterCentroids = [];

// side effects for setters
    var side_effects = d3version3.dispatch.apply(this,d3version3.keys(__))
        .on("composite", function(d) {
            ctx.foreground.globalCompositeOperation = d.value;
            ctx.brushed.globalCompositeOperation = d.value;
        })
        .on("alpha", function(d) {
            ctx.foreground.globalAlpha = d.value;
            ctx.brushed.globalAlpha = d.value;
        })
        .on("brushedColor", function (d) {
            ctx.brushed.strokeStyle = d.value;
        })
        .on("width", function(d) { pc.resize(); })
        .on("height", function(d) { pc.resize(); })
        .on("margin", function(d) { pc.resize(); })
        .on("rate", function(d) {
            brushedQueue.rate(d.value);
            foregroundQueue.rate(d.value);
        })
        .on("dimensions", function(d) {
            __.dimensions = pc.applyDimensionDefaults(d3version3.keys(d.value));
            xscale.domain(pc.getOrderedDimensionKeys());
            pc.sortDimensions();
            if (flags.interactive){pc.render().updateAxes();}
        })
        .on("bundleDimension", function(d) {
            if (!d3version3.keys(__.dimensions).length) pc.detectDimensions();
            pc.autoscale();
            if (typeof d.value === "number") {
                if (d.value < d3version3.keys(__.dimensions).length) {
                    __.bundleDimension = __.dimensions[d.value];
                } else if (d.value < __.hideAxis.length) {
                    __.bundleDimension = __.hideAxis[d.value];
                }
            } else {
                __.bundleDimension = d.value;
            }

            __.clusterCentroids = compute_cluster_centroids(__.bundleDimension);
            if (flags.interactive){pc.render();}
        })
        .on("hideAxis", function(d) {
            pc.dimensions(pc.applyDimensionDefaults());
            pc.dimensions(without(__.dimensions, d.value));
        })
        .on("flipAxes", function(d) {
            if (d.value && d.value.length) {
                d.value.forEach(function(axis) {
                    flipAxisAndUpdatePCP(axis);
                });
                pc.updateAxes(0);
            }
        });

// expose the state of the chart
    pc.state = __;
    pc.flags = flags;

// create getter/setters
    getset(pc, __, events);

// expose events
    d3version3.rebind(pc, events, "on");

// getter/setter with event firing
    function getset(obj,state,events)  {
        d3version3.keys(state).forEach(function(key) {
            obj[key] = function(x) {
                if (!arguments.length) {
                    return state[key];
                }
                if (key === 'dimensions' && Object.prototype.toString.call(x) === '[object Array]') {
                    console.warn("pc.dimensions([]) is deprecated, use pc.dimensions({})");
                    x = pc.applyDimensionDefaults(x);
                }
                var old = state[key];
                state[key] = x;
                side_effects[key].call(pc,{"value": x, "previous": old});
                events[key].call(pc,{"value": x, "previous": old});
                return obj;
            };
        });
    };

    function extend(target, source) {
        for (var key in source) {
            target[key] = source[key];
        }
        return target;
    };

    function without(arr, items) {
        items.forEach(function (el) {
            delete arr[el];
        });
        return arr;
    };
    /** adjusts an axis' default range [h()+1, 1] if a NullValueSeparator is set */
    function getRange() {
        if (__.nullValueSeparator=="bottom") {
            return [h()+1-__.nullValueSeparatorPadding.bottom-__.nullValueSeparatorPadding.top, 1];
        } else if (__.nullValueSeparator=="top") {
            return [h()+1, 1+__.nullValueSeparatorPadding.bottom+__.nullValueSeparatorPadding.top];
        }
        return [h()+1, 1];
    };

    pc.autoscale = function() {
        // yscale
        var defaultScales = {
            "date": function(k) {
                var extent = d3version3.extent(__.data, function(d) {
                    return d[k] ? d[k].getTime() : null;
                });

                // special case if single value
                if (extent[0] === extent[1]) {
                    return d3version3.scale.ordinal()
                        .domain([extent[0]])
                        .rangePoints(getRange());
                }

                return d3version3.time.scale()
                    .domain(extent)
                    .range(getRange());
            },
            "number": function(k) {
                var extent = d3version3.extent(__.data, function(d) { return +d[k]; });

                // special case if single value
                if (extent[0] === extent[1]) {
                    return d3version3.scale.ordinal()
                        .domain([extent[0]])
                        .rangePoints(getRange());
                }

                return d3version3.scale.linear()
                    .domain(extent)
                    .range(getRange());
            },
            "string": function(k) {
                var counts = {},
                    domain = [];

                // Let's get the count for each value so that we can sort the domain based
                // on the number of items for each value.
                __.data.map(function(p) {
                    if (p[k] === undefined && __.nullValueSeparator!== "undefined"){
                        return; // null values will be drawn beyond the horizontal null value separator!
                    }
                    if (counts[p[k]] === undefined) {
                        counts[p[k]] = 1;
                    } else {
                        counts[p[k]] = counts[p[k]] + 1;
                    }
                });

                domain = Object.getOwnPropertyNames(counts).sort(function(a, b) {
                    return counts[a] - counts[b];
                });

                return d3version3.scale.ordinal()
                    .domain(domain)
                    .rangePoints(getRange());
            }
        };

        d3version3.keys(__.dimensions).forEach(function(k) {
            if (!__.dimensions[k].yscale){
                __.dimensions[k].yscale = defaultScales[__.dimensions[k].type](k);
            }
        });

        // xscale
        xscale.rangePoints([0, w()], 1);

        // Retina display, etc.
        var devicePixelRatio = window.devicePixelRatio || 1;

        // canvas sizes
        pc.selection.selectAll("canvas")
            .style("margin-top", __.margin.top + "px")
            .style("margin-left", __.margin.left + "px")
            .style("width", (w()+2) + "px")
            .style("height", (h()+2) + "px")
            .attr("width", (w()+2) * devicePixelRatio)
            .attr("height", (h()+2) * devicePixelRatio);

        // default styles, needs to be set when canvas width changes
        ctx.foreground.strokeStyle = __.color;
        ctx.foreground.lineWidth = 1.4;
        ctx.foreground.globalCompositeOperation = __.composite;
        ctx.foreground.globalAlpha = __.alpha;
        ctx.foreground.scale(devicePixelRatio, devicePixelRatio);
        ctx.brushed.strokeStyle = __.brushedColor;
        ctx.brushed.lineWidth = 1.4;
        ctx.brushed.globalCompositeOperation = __.composite;
        ctx.brushed.globalAlpha = __.alpha;
        ctx.brushed.scale(devicePixelRatio, devicePixelRatio);
        ctx.highlight.lineWidth = 3;
        ctx.highlight.scale(devicePixelRatio, devicePixelRatio);

        return this;
    };

    pc.scale = function(d, domain) {
        __.dimensions[d].yscale.domain(domain);

        return this;
    };

    pc.flip = function(d) {
        //__.dimensions[d].yscale.domain().reverse();                               // does not work
        __.dimensions[d].yscale.domain(__.dimensions[d].yscale.domain().reverse()); // works

        return this;
    };

    pc.commonScale = function(global, type) {
        var t = type || "number";
        if (typeof global === 'undefined') {
            global = true;
        }

        // try to autodetect dimensions and create scales
        if (!d3version3.keys(__.dimensions).length) {
            pc.detectDimensions()
        }
        pc.autoscale();

        // scales of the same type
        var scales = d3version3.keys(__.dimensions).filter(function(p) {
            return __.dimensions[p].type == t;
        });

        if (global) {
            var extent = d3version3.extent(scales.map(function(d,i) {
                return __.dimensions[d].yscale.domain();
            }).reduce(function(a,b) {
                return a.concat(b);
            }));

            scales.forEach(function(d) {
                __.dimensions[d].yscale.domain(extent);
            });

        } else {
            scales.forEach(function(d) {
                __.dimensions[d].yscale.domain(d3version3.extent(__.data, function(d) { return +d[k]; }));
            });
        }

        // update centroids
        if (__.bundleDimension !== null) {
            pc.bundleDimension(__.bundleDimension);
        }

        return this;
    };
    pc.detectDimensions = function() {
        pc.dimensions(pc.applyDimensionDefaults());
        return this;
    };

    pc.applyDimensionDefaults = function(dims) {
        var types = pc.detectDimensionTypes(__.data);
        dims = dims ? dims : d3version3.keys(types);
        var newDims = {};
        var currIndex = 0;
        dims.forEach(function(k) {
            newDims[k] = __.dimensions[k] ? __.dimensions[k] : {};
            //Set up defaults
            newDims[k].orient= newDims[k].orient ? newDims[k].orient : 'left';
            newDims[k].ticks= newDims[k].ticks != null ? newDims[k].ticks : 5;
            newDims[k].innerTickSize= newDims[k].innerTickSize != null ? newDims[k].innerTickSize : 6;
            newDims[k].outerTickSize= newDims[k].outerTickSize != null ? newDims[k].outerTickSize : 0;
            newDims[k].tickPadding= newDims[k].tickPadding != null ? newDims[k].tickPadding : 3;
            newDims[k].type= newDims[k].type ? newDims[k].type : types[k];

            newDims[k].index = newDims[k].index != null ? newDims[k].index : currIndex;
            currIndex++;
        });
        return newDims;
    };

    pc.getOrderedDimensionKeys = function(){
        return d3version3.keys(__.dimensions).sort(function(x, y){
            return d3version3.ascending(__.dimensions[x].index, __.dimensions[y].index);
        });
    };

// a better "typeof" from this post: http://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable
    pc.toType = function(v) {
        return ({}).toString.call(v).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
    };

// try to coerce to number before returning type
    pc.toTypeCoerceNumbers = function(v) {
        if ((parseFloat(v) == v) && (v != null)) {
            return "number";
        }
        return pc.toType(v);
    };

// attempt to determine types of each dimension based on first row of data
    pc.detectDimensionTypes = function(data) {
        var types = {};
        d3version3.keys(data[0])
            .forEach(function(col) {
                types[isNaN(Number(col)) ? col : parseInt(col)] = pc.toTypeCoerceNumbers(data[0][col]);
            });
        return types;
    };
    pc.render = function() {
        // try to autodetect dimensions and create scales
        if (!d3version3.keys(__.dimensions).length) {
            pc.detectDimensions()
        }
        pc.autoscale();

        pc.render[__.mode]();

        events.render.call(this);
        return this;
    };

    pc.renderBrushed = function() {
        if (!d3version3.keys(__.dimensions).length) pc.detectDimensions();

        pc.renderBrushed[__.mode]();

        events.render.call(this);
        return this;
    };

    function isBrushed() {
        if (__.brushed && __.brushed.length !== __.data.length)
            return true;

        var object = brush.currentMode().brushState();

        for (var key in object) {
            if (object.hasOwnProperty(key)) {
                return true;
            }
        }
        return false;
    };

    pc.render.default = function() {
        pc.clear('foreground');
        pc.clear('highlight');

        pc.renderBrushed.default();

        __.data.forEach(path_foreground);
    };

    var foregroundQueue = d3version3.renderQueue(path_foreground)
        .rate(50)
        .clear(function() {
            pc.clear('foreground');
            pc.clear('highlight');
        });

    pc.render.queue = function() {
        pc.renderBrushed.queue();

        foregroundQueue(__.data);
    };

    pc.renderBrushed.default = function() {
        pc.clear('brushed');

        if (isBrushed()) {
            __.brushed.forEach(path_brushed);
        }
    };

    var brushedQueue = d3version3.renderQueue(path_brushed)
        .rate(50)
        .clear(function() {
            pc.clear('brushed');
        });

    pc.renderBrushed.queue = function() {
        if (isBrushed()) {
            brushedQueue(__.brushed);
        } else {
            brushedQueue([]); // This is needed to clear the currently brushed items
        }
    };
    function compute_cluster_centroids(d) {

        var clusterCentroids = d3version3.map();
        var clusterCounts = d3version3.map();
        // determine clusterCounts
        __.data.forEach(function(row) {
            var scaled = __.dimensions[d].yscale(row[d]);
            if (!clusterCounts.has(scaled)) {
                clusterCounts.set(scaled, 0);
            }
            var count = clusterCounts.get(scaled);
            clusterCounts.set(scaled, count + 1);
        });

        __.data.forEach(function(row) {
            d3version3.keys(__.dimensions).map(function(p, i) {
                var scaled = __.dimensions[d].yscale(row[d]);
                if (!clusterCentroids.has(scaled)) {
                    var map = d3version3.map();
                    clusterCentroids.set(scaled, map);
                }
                if (!clusterCentroids.get(scaled).has(p)) {
                    clusterCentroids.get(scaled).set(p, 0);
                }
                var value = clusterCentroids.get(scaled).get(p);
                value += __.dimensions[p].yscale(row[p]) / clusterCounts.get(scaled);
                clusterCentroids.get(scaled).set(p, value);
            });
        });

        return clusterCentroids;

    }

    function compute_centroids(row) {
        var centroids = [];

        var p = d3version3.keys(__.dimensions);
        var cols = p.length;
        var a = 0.5;			// center between axes
        for (var i = 0; i < cols; ++i) {
            // centroids on 'real' axes
            var x = position(p[i]);
            var y = __.dimensions[p[i]].yscale(row[p[i]]);
            centroids.push($V([x, y]));

            // centroids on 'virtual' axes
            if (i < cols - 1) {
                var cx = x + a * (position(p[i+1]) - x);
                var cy = y + a * (__.dimensions[p[i+1]].yscale(row[p[i+1]]) - y);
                if (__.bundleDimension !== null) {
                    var leftCentroid = __.clusterCentroids.get(__.dimensions[__.bundleDimension].yscale(row[__.bundleDimension])).get(p[i]);
                    var rightCentroid = __.clusterCentroids.get(__.dimensions[__.bundleDimension].yscale(row[__.bundleDimension])).get(p[i+1]);
                    var centroid = 0.5 * (leftCentroid + rightCentroid);
                    cy = centroid + (1 - __.bundlingStrength) * (cy - centroid);
                }
                centroids.push($V([cx, cy]));
            }
        }

        return centroids;
    }

    pc.compute_real_centroids = function(row) {
        var realCentroids = [];

        var p = d3version3.keys(__.dimensions);
        var cols = p.length;
        var a = 0.5;

        for (var i = 0; i < cols; ++i) {
            var x = position(p[i]);
            var y = __.dimensions[p[i]].yscale(row[p[i]]);
            realCentroids.push([x, y]);
        }

        return realCentroids;
    }

    function compute_control_points(centroids) {

        var cols = centroids.length;
        var a = __.smoothness;
        var cps = [];

        cps.push(centroids[0]);
        cps.push($V([centroids[0].e(1) + a*2*(centroids[1].e(1)-centroids[0].e(1)), centroids[0].e(2)]));
        for (var col = 1; col < cols - 1; ++col) {
            var mid = centroids[col];
            var left = centroids[col - 1];
            var right = centroids[col + 1];

            var diff = left.subtract(right);
            cps.push(mid.add(diff.x(a)));
            cps.push(mid);
            cps.push(mid.subtract(diff.x(a)));
        }
        cps.push($V([centroids[cols-1].e(1) + a*2*(centroids[cols-2].e(1)-centroids[cols-1].e(1)), centroids[cols-1].e(2)]));
        cps.push(centroids[cols - 1]);

        return cps;

    };pc.shadows = function() {
        flags.shadows = true;
        pc.alphaOnBrushed(0.1);
        pc.render();
        return this;
    };

// draw dots with radius r on the axis line where data intersects
    pc.axisDots = function(r) {
        var r = r || 0.1;
        var ctx = pc.ctx.marks;
        var startAngle = 0;
        var endAngle = 2 * Math.PI;
        ctx.globalAlpha = d3version3.min([ 1 / Math.pow(__.data.length, 1 / 2), 1 ]);
        __.data.forEach(function(d) {
            d3version3.entries(__.dimensions).forEach(function(p, i) {
                ctx.beginPath();
                ctx.arc(position(p), __.dimensions[p.key].yscale(d[p]), r, startAngle, endAngle);
                ctx.stroke();
                ctx.fill();
            });
        });
        return this;
    };

// draw single cubic bezier curve
    function single_curve(d, ctx) {

        var centroids = compute_centroids(d);
        var cps = compute_control_points(centroids);

        ctx.moveTo(cps[0].e(1), cps[0].e(2));
        for (var i = 1; i < cps.length; i += 3) {
            if (__.showControlPoints) {
                for (var j = 0; j < 3; j++) {
                    ctx.fillRect(cps[i+j].e(1), cps[i+j].e(2), 2, 2);
                }
            }
            ctx.bezierCurveTo(cps[i].e(1), cps[i].e(2), cps[i+1].e(1), cps[i+1].e(2), cps[i+2].e(1), cps[i+2].e(2));
        }
    };

// draw single polyline
    function color_path(d, ctx) {
        ctx.beginPath();
        if ((__.bundleDimension !== null && __.bundlingStrength > 0) || __.smoothness > 0) {
            single_curve(d, ctx);
        } else {
            single_path(d, ctx);
        }
        ctx.stroke();
    };

// draw many polylines of the same color
    function paths(data, ctx) {
        ctx.clearRect(-1, -1, w() + 2, h() + 2);
        ctx.beginPath();
        data.forEach(function(d) {
            if ((__.bundleDimension !== null && __.bundlingStrength > 0) || __.smoothness > 0) {
                single_curve(d, ctx);
            } else {
                single_path(d, ctx);
            }
        });
        ctx.stroke();
    };

// returns the y-position just beyond the separating null value line
    function getNullPosition() {
        if (__.nullValueSeparator=="bottom") {
            return h()+1;
        } else if (__.nullValueSeparator=="top") {
            return 1;
        } else {
            console.log("A value is NULL, but nullValueSeparator is not set; set it to 'bottom' or 'top'.");
        }
        return h()+1;
    };

    function single_path(d, ctx) {
        d3version3.entries(__.dimensions).forEach(function(p, i) {  //p isn't really p
            if (i == 0) {
                ctx.moveTo(position(p.key), typeof d[p.key] =='undefined' ? getNullPosition() : __.dimensions[p.key].yscale(d[p.key]));
            } else {
                ctx.lineTo(position(p.key), typeof d[p.key] =='undefined' ? getNullPosition() : __.dimensions[p.key].yscale(d[p.key]));
            }
        });
    };

    function path_brushed(d, i) {
        if (__.brushedColor !== null) {
            ctx.brushed.strokeStyle = d3version3.functor(__.brushedColor)(d, i);
        } else {
            ctx.brushed.strokeStyle = d3version3.functor(__.color)(d, i);
        }
        return color_path(d, ctx.brushed)
    };

    function path_foreground(d, i) {
        ctx.foreground.strokeStyle = d3version3.functor(__.color)(d, i);
        return color_path(d, ctx.foreground);
    };

    function path_highlight(d, i) {
        ctx.highlight.strokeStyle = d3version3.functor(__.color)(d, i);
        return color_path(d, ctx.highlight);
    };
    pc.clear = function(layer) {
        ctx[layer].clearRect(0, 0, w() + 2, h() + 2);

        // This will make sure that the foreground items are transparent
        // without the need for changing the opacity style of the foreground canvas
        // as this would stop the css styling from working
        if(layer === "brushed" && isBrushed()) {
            ctx.brushed.fillStyle = pc.selection.style("background-color");
            ctx.brushed.globalAlpha = 1 - __.alphaOnBrushed;
            ctx.brushed.fillRect(0, 0, w() + 2, h() + 2);
            ctx.brushed.globalAlpha = __.alpha;
        }
        return this;
    };
    d3version3.rebind(pc, axis, "ticks", "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat");

    function flipAxisAndUpdatePCP(dimension) {
        var g = pc.svg.selectAll(".dimension");

        pc.flip(dimension);

        d3version3.select(this.parentElement)
            .transition()
            .duration(__.animationTime)
            .call(axis.scale(__.dimensions[dimension].yscale))
            .call(axis.orient(__.dimensions[dimension].orient))
            .call(axis.ticks(__.dimensions[dimension].ticks))
            .call(axis.innerTickSize(__.dimensions[dimension].innerTickSize))
            .call(axis.outerTickSize(__.dimensions[dimension].outerTickSize))
            .call(axis.tickPadding(__.dimensions[dimension].tickPadding))
            .call(axis.tickFormat(__.dimensions[dimension].tickFormat));

        pc.render();
    }

    function rotateLabels() {
        if (!__.rotateLabels) return;

        var delta = d3version3.event.deltaY;
        delta = delta < 0 ? -5 : delta;
        delta = delta > 0 ? 5 : delta;

        __.dimensionTitleRotation += delta;
        pc.svg.selectAll("text.label")
            .attr("transform", "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")");
        d3version3.event.preventDefault();
    }

    function dimensionLabels(d) {
        return __.dimensions[d].title ? __.dimensions[d].title : d;  // dimension display names
    }

    pc.createAxes = function() {
        if (g) pc.removeAxes();

        // Add a group element for each dimension.
        g = pc.svg.selectAll(".dimension")
            .data(pc.getOrderedDimensionKeys(), function(d) {
                return d;
            })
            .enter().append("svg:g")
            .attr("class", id_class_dimension)
            .attr("transform", function(d) {
                return "translate(" + xscale(d) + ")";
            });

        // Add an axis and title.
        g.append("svg:g")
            .attr("class", "axis")
            .attr("transform", "translate(0,0)")
            .each(function(d) {
                var axisElement = d3version3.select(this).call( pc.applyAxisConfig(axis, __.dimensions[d]) );

                axisElement.selectAll("path")
                    .style("fill", "none")
                    .style("stroke", "#222")
                    .style("shape-rendering", "crispEdges");

                axisElement.selectAll("line")
                    .style("fill", "none")
                    .style("stroke", "#222")
                    .style("shape-rendering", "crispEdges");
            })
            .append("svg:text")
            .attr({
                "text-anchor": "middle",
                "y": 0,
                "transform": "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")",
                "x": 0,
                "class": "label"
            })
            .text(dimensionLabels)
            .on("dblclick", flipAxisAndUpdatePCP)
            .on("wheel", rotateLabels);

        if (__.nullValueSeparator=="top") {
            pc.svg.append("line")
                .attr("x1", 0)
                .attr("y1", 1+__.nullValueSeparatorPadding.top)
                .attr("x2", w())
                .attr("y2", 1+__.nullValueSeparatorPadding.top)
                .attr("stroke-width", 1)
                .attr("stroke", "#777")
                .attr("fill", "none")
                .attr("shape-rendering", "crispEdges");
        } else if (__.nullValueSeparator=="bottom") {
            pc.svg.append("line")
                .attr("x1", 0)
                .attr("y1", h()+1-__.nullValueSeparatorPadding.bottom)
                .attr("x2", w())
                .attr("y2", h()+1-__.nullValueSeparatorPadding.bottom)
                .attr("stroke-width", 1)
                .attr("stroke", "#777")
                .attr("fill", "none")
                .attr("shape-rendering", "crispEdges");
        }

        flags.axes= true;
        return this;
    };

    pc.removeAxes = function() {
        g.remove();
        g = undefined;
        return this;
    };

    pc.updateAxes = function(animationTime) {
        if (typeof animationTime === 'undefined') {
            animationTime = __.animationTime;
        }

        var g_data = pc.svg.selectAll(".dimension").data(pc.getOrderedDimensionKeys());

        // Enter
        g_data.enter().append("svg:g")
            .attr("class", id_class_dimension)
            .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
            .style("opacity", 0)
            .append("svg:g")
            .attr("class", "axis")
            .attr("transform", "translate(0,0)")
            .each(function(d) {
                var axisElement = d3version3.select(this).call( pc.applyAxisConfig(axis, __.dimensions[d]) );

                axisElement.selectAll("path")
                    .style("fill", "none")
                    .style("stroke", "#222")
                    .style("shape-rendering", "crispEdges");

                axisElement.selectAll("line")
                    .style("fill", "none")
                    .style("stroke", "#222")
                    .style("shape-rendering", "crispEdges");
            })
            .append("svg:text")
            .attr({
                "text-anchor": "middle",
                "y": 0,
                "transform": "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")",
                "x": 0,
                "class": "label"
            })
            .text(dimensionLabels)
            .on("dblclick", flipAxisAndUpdatePCP)
            .on("wheel", rotateLabels);

        // Update
        g_data.attr("opacity", 0);
        g_data.select(".axis")
            .transition()
            .duration(animationTime)
            .each(function(d) { d3version3.select(this).call( pc.applyAxisConfig(axis, __.dimensions[d]) )
            });
        g_data.select(".label")
            .transition()
            .duration(animationTime)
            .text(dimensionLabels)
            .attr("transform", "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")");

        // Exit
        g_data.exit().remove();

        g = pc.svg.selectAll(".dimension");
        g.transition().duration(animationTime)
            .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
            .style("opacity", 1);

        pc.svg.selectAll(".axis")
            .transition()
            .duration(animationTime)
            .each(function(d) { d3version3.select(this).call( pc.applyAxisConfig(axis, __.dimensions[d]) );
            });

        if (flags.brushable) pc.brushable();
        if (flags.reorderable) pc.reorderable();
        if (pc.brushMode() !== "None") {
            var mode = pc.brushMode();
            pc.brushMode("None");
            pc.brushMode(mode);
        }
        return this;
    };

    pc.applyAxisConfig = function(axis, dimension) {
        return axis.scale(dimension.yscale)
            .orient(dimension.orient)
            .ticks(dimension.ticks)
            .tickValues(dimension.tickValues)
            .innerTickSize(dimension.innerTickSize)
            .outerTickSize(dimension.outerTickSize)
            .tickPadding(dimension.tickPadding)
            .tickFormat(dimension.tickFormat)
    };

// Jason Davies, http://bl.ocks.org/1341281
    pc.reorderable = function() {
        if (!g) pc.createAxes();

        g.style("cursor", "move")
            .call(d3version3.behavior.drag()
                .on("dragstart", function(d) {
                    dragging[d] = this.__origin__ = xscale(d);
                })
                .on("drag", function(d) {
                    dragging[d] = Math.min(w(), Math.max(0, this.__origin__ += d3version3.event.dx));
                    pc.sortDimensions();
                    xscale.domain(pc.getOrderedDimensionKeys());
                    pc.render();
                    g.attr("transform", function(d) {
                        return "translate(" + position(d) + ")";
                    });
                })
                .on("dragend", function(d) {
                    // Let's see if the order has changed and send out an event if so.
                    var i = 0,
                        j = __.dimensions[d].index,
                        elem = this,
                        parent = this.parentElement;

                    while((elem = elem.previousElementSibling) != null) ++i;
                    if (i !== j) {
                        events.axesreorder.call(pc, pc.getOrderedDimensionKeys());
                        // We now also want to reorder the actual dom elements that represent
                        // the axes. That is, the g.dimension elements. If we don't do this,
                        // we get a weird and confusing transition when updateAxes is called.
                        // This is due to the fact that, initially the nth g.dimension element
                        // represents the nth axis. However, after a manual reordering,
                        // without reordering the dom elements, the nth dom elements no longer
                        // necessarily represents the nth axis.
                        //
                        // i is the original index of the dom element
                        // j is the new index of the dom element
                        if (i > j) { // Element moved left
                            parent.insertBefore(this, parent.children[j - 1]);
                        } else {     // Element moved right
                            if ((j + 1) < parent.children.length) {
                                parent.insertBefore(this, parent.children[j + 1]);
                            } else {
                                parent.appendChild(this);
                            }
                        }
                    }

                    delete this.__origin__;
                    delete dragging[d];
                    d3version3.select(this).transition().attr("transform", "translate(" + xscale(d) + ")");
                    pc.render();
                }));
        flags.reorderable = true;
        return this;
    };

// Reorder dimensions, such that the highest value (visually) is on the left and
// the lowest on the right. Visual values are determined by the data values in
// the given row.
    pc.reorder = function(rowdata) {
        var firstDim = pc.getOrderedDimensionKeys()[0];

        pc.sortDimensionsByRowData(rowdata);
        // NOTE: this is relatively cheap given that:
        // number of dimensions < number of data items
        // Thus we check equality of order to prevent rerendering when this is the case.
        var reordered = false;
        reordered = firstDim !== pc.getOrderedDimensionKeys()[0];

        if (reordered) {
            xscale.domain(pc.getOrderedDimensionKeys());
            var highlighted = __.highlighted.slice(0);
            pc.unhighlight();

            g.transition()
                .duration(1500)
                .attr("transform", function(d) {
                    return "translate(" + xscale(d) + ")";
                });
            pc.render();

            // pc.highlight() does not check whether highlighted is length zero, so we do that here.
            if (highlighted.length !== 0) {
                pc.highlight(highlighted);
            }
        }
    }

    pc.sortDimensionsByRowData = function(rowdata) {
        var copy = __.dimensions;
        var positionSortedKeys = d3version3.keys(__.dimensions).sort(function(a, b) {
            var pixelDifference = __.dimensions[a].yscale(rowdata[a]) - __.dimensions[b].yscale(rowdata[b]);

            // Array.sort is not necessarily stable, this means that if pixelDifference is zero
            // the ordering of dimensions might change unexpectedly. This is solved by sorting on
            // variable name in that case.
            if (pixelDifference === 0) {
                return a.localeCompare(b);
            } // else
            return pixelDifference;
        });
        __.dimensions = {};
        positionSortedKeys.forEach(function(p, i){
            __.dimensions[p] = copy[p];
            __.dimensions[p].index = i;
        });
    }

    pc.sortDimensions = function() {
        var copy = __.dimensions;
        var positionSortedKeys = d3version3.keys(__.dimensions).sort(function(a, b) {
            return position(a) - position(b);
        });
        __.dimensions = {};
        positionSortedKeys.forEach(function(p, i){
            __.dimensions[p] = copy[p];
            __.dimensions[p].index = i;
        })
    };

// pairs of adjacent dimensions
    pc.adjacent_pairs = function(arr) {
        var ret = [];
        for (var i = 0; i < arr.length-1; i++) {
            ret.push([arr[i],arr[i+1]]);
        };
        return ret;
    };

    var brush = {
        modes: {
            "None": {
                install: function(pc) {},            // Nothing to be done.
                uninstall: function(pc) {},          // Nothing to be done.
                selected: function() { return []; }, // Nothing to return
                brushState: function() { return {}; }
            }
        },
        mode: "None",
        predicate: "AND",
        currentMode: function() {
            return this.modes[this.mode];
        }
    };

// This function can be used for 'live' updates of brushes. That is, during the
// specification of a brush, this method can be called to update the view.
//
// @param newSelection - The new set of data items that is currently contained
//                       by the brushes
    function brushUpdated(newSelection) {
        __.brushed = newSelection;
        events.brush.call(pc,__.brushed);
        pc.renderBrushed();
    }

    function brushPredicate(predicate) {
        if (!arguments.length) { return brush.predicate; }

        predicate = String(predicate).toUpperCase();
        if (predicate !== "AND" && predicate !== "OR") {
            throw "Invalid predicate " + predicate;
        }

        brush.predicate = predicate;
        __.brushed = brush.currentMode().selected();
        pc.renderBrushed();
        return pc;
    }

    pc.brushModes = function() {
        return Object.getOwnPropertyNames(brush.modes);
    };

    pc.brushMode = function(mode) {
        if (arguments.length === 0) {
            return brush.mode;
        }

        if (pc.brushModes().indexOf(mode) === -1) {
            throw "pc.brushmode: Unsupported brush mode: " + mode;
        }

        // Make sure that we don't trigger unnecessary events by checking if the mode
        // actually changes.
        if (mode !== brush.mode) {
            // When changing brush modes, the first thing we need to do is clearing any
            // brushes from the current mode, if any.
            if (brush.mode !== "None") {
                pc.brushReset();
            }

            // Next, we need to 'uninstall' the current brushMode.
            brush.modes[brush.mode].uninstall(pc);
            // Finally, we can install the requested one.
            brush.mode = mode;
            brush.modes[brush.mode].install();
            if (mode === "None") {
                delete pc.brushPredicate;
            } else {
                pc.brushPredicate = brushPredicate;
            }
        }

        return pc;
    };

// brush mode: 1D-Axes

    (function() {
        var brushes = {};

        function is_brushed(p) {
            return !brushes[p].empty();
        }

        // data within extents
        function selected() {
            var actives = d3version3.keys(__.dimensions).filter(is_brushed),
                extents = actives.map(function(p) { return brushes[p].extent(); });

            // We don't want to return the full data set when there are no axes brushed.
            // Actually, when there are no axes brushed, by definition, no items are
            // selected. So, let's avoid the filtering and just return false.
            //if (actives.length === 0) return false;

            // Resolves broken examples for now. They expect to get the full dataset back from empty brushes
            if (actives.length === 0) return __.data;

            // test if within range
            var within = {
                "date": function(d,p,dimension) {
                    if (typeof __.dimensions[p].yscale.rangePoints === "function") { // if it is ordinal
                        return extents[dimension][0] <= __.dimensions[p].yscale(d[p]) && __.dimensions[p].yscale(d[p]) <= extents[dimension][1]
                    } else {
                        return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
                    }
                },
                "number": function(d,p,dimension) {
                    if (typeof __.dimensions[p].yscale.rangePoints === "function") { // if it is ordinal
                        return extents[dimension][0] <= __.dimensions[p].yscale(d[p]) && __.dimensions[p].yscale(d[p]) <= extents[dimension][1]
                    } else {
                        return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
                    }
                },
                "string": function(d,p,dimension) {
                    return extents[dimension][0] <= __.dimensions[p].yscale(d[p]) && __.dimensions[p].yscale(d[p]) <= extents[dimension][1]
                }
            };

            return __.data
                .filter(function(d) {
                    switch(brush.predicate) {
                        case "AND":
                            return actives.every(function(p, dimension) {
                                return within[__.dimensions[p].type](d,p,dimension);
                            });
                        case "OR":
                            return actives.some(function(p, dimension) {
                                return within[__.dimensions[p].type](d,p,dimension);
                            });
                        default:
                            throw "Unknown brush predicate " + __.brushPredicate;
                    }
                });
        };

        function brushExtents(extents) {
            if(typeof(extents) === 'undefined')
            {
                var extents = {};
                d3version3.keys(__.dimensions).forEach(function(d) {
                    var brush = brushes[d];
                    if (brush !== undefined && !brush.empty()) {
                        var extent = brush.extent();
                        extent.sort(d3version3.ascending);
                        extents[d] = extent;
                    }
                });
                return extents;
            }
            else
            {
                //first get all the brush selections
                var brushSelections = {};
                g.selectAll('.brush')
                    .each(function(d) {
                        brushSelections[d] = d3version3.select(this);

                    });

                // loop over each dimension and update appropriately (if it was passed in through extents)
                d3version3.keys(__.dimensions).forEach(function(d) {
                    if (extents[d] === undefined){
                        return;
                    }

                    var brush = brushes[d];
                    if (brush !== undefined) {
                        //update the extent
                        brush.extent(extents[d]);

                        //redraw the brush
                        brushSelections[d]
                            .transition()
                            .duration(0)
                            .call(brush);

                        //fire some events
                        brush.event(brushSelections[d]);
                    }
                });

                //redraw the chart
                pc.renderBrushed();

                return pc;
            }
        }

        function brushFor(axis) {
            var brush = d3version3.svg.brush();

            brush
                .y(__.dimensions[axis].yscale)
                .on("brushstart", function() {
                    if(d3version3.event.sourceEvent !== null) {
                        events.brushstart.call(pc, __.brushed);
                        d3version3.event.sourceEvent.stopPropagation();
                    }
                })
                .on("brush", function() {
                    brushUpdated(selected());
                })
                .on("brushend", function() {
                    events.brushend.call(pc, __.brushed);
                });

            brushes[axis] = brush;
            return brush;
        };

        function brushReset(dimension) {
            if (dimension===undefined) {
                __.brushed = false;
                if (g) {
                    g.selectAll('.brush')
                        .each(function(d) {
                            d3version3.select(this)
                                .transition()
                                .duration(0)
                                .call(brushes[d].clear());
                        });
                    pc.renderBrushed();
                }
            }
            else {
                if (g) {
                    g.selectAll('.brush')
                        .each(function(d) {
                            if (d!=dimension) return;
                            d3version3.select(this)
                                .transition()
                                .duration(0)
                                .call(brushes[d].clear());
                            brushes[d].event(d3version3.select(this));
                        });
                    pc.renderBrushed();
                }
            }
            return this;
        };

        function install() {
            if (!g) pc.createAxes();

            // Add and store a brush for each axis.
            var brush = g.append("svg:g")
                .attr("class", "brush")
                .each(function(d) {
                    d3version3.select(this).call(brushFor(d));
                });

            brush.selectAll("rect")
                .style("visibility", null)
                .attr("x", -15)
                .attr("width", 30);

            brush.selectAll("rect.background")
                .style("fill", "transparent");

            brush.selectAll("rect.extent")
                .style("fill", "rgba(255,255,255,0.25)")
                .style("stroke", "rgba(0,0,0,0.6)");

            brush.selectAll(".resize rect")
                .style("fill", "rgba(0,0,0,0.1)");

            pc.brushExtents = brushExtents;
            pc.brushReset = brushReset;
            return pc;
        };

        brush.modes["1D-axes"] = {
            install: install,
            uninstall: function() {
                g.selectAll(".brush").remove();
                brushes = {};
                delete pc.brushExtents;
                delete pc.brushReset;
            },
            selected: selected,
            brushState: brushExtents
        }
    })();
// brush mode: 2D-strums
// bl.ocks.org/syntagmatic/5441022

    (function() {
        var strums = {},
            strumRect;

        function drawStrum(strum, activePoint) {
            var svg = pc.selection.select("svg").select("g#strums"),
                id = strum.dims.i,
                points = [strum.p1, strum.p2],
                line = svg.selectAll("line#strum-" + id).data([strum]),
                circles = svg.selectAll("circle#strum-" + id).data(points),
                drag = d3version3.behavior.drag();

            line.enter()
                .append("line")
                .attr("id", "strum-" + id)
                .attr("class", "strum");

            line
                .attr("x1", function(d) {
                    return d.p1[0]; })
                .attr("y1", function(d) {
                    return d.p1[1]; })
                .attr("x2", function(d) {
                    return d.p2[0]; })
                .attr("y2", function(d) {
                    return d.p2[1]; })
                .attr("stroke", "black")
                .attr("stroke-width", 2);

            drag
                .on("drag", function(d, i) {
                    var ev = d3version3.event;
                    i = i + 1;
                    strum["p" + i][0] = Math.min(Math.max(strum.minX + 1, ev.x), strum.maxX);
                    strum["p" + i][1] = Math.min(Math.max(strum.minY, ev.y), strum.maxY);
                    drawStrum(strum, i - 1);
                })
                .on("dragend", onDragEnd());

            circles.enter()
                .append("circle")
                .attr("id", "strum-" + id)
                .attr("class", "strum");

            circles
                .attr("cx", function(d) { return d[0]; })
                .attr("cy", function(d) { return d[1]; })
                .attr("r", 5)
                .style("opacity", function(d, i) {
                    return (activePoint !== undefined && i === activePoint) ? 0.8 : 0;
                })
                .on("mouseover", function() {
                    d3version3.select(this).style("opacity", 0.8);
                })
                .on("mouseout", function() {
                    d3version3.select(this).style("opacity", 0);
                })
                .call(drag);
        }

        function dimensionsForPoint(p) {
            var dims = { i: -1, left: undefined, right: undefined };
            d3version3.keys(__.dimensions).some(function(dim, i) {
                if (xscale(dim) < p[0]) {
                    var next = d3version3.keys(__.dimensions)[pc.getOrderedDimensionKeys().indexOf(dim)+1];
                    dims.i = i;
                    dims.left = dim;
                    dims.right = next;
                    return false;
                }
                return true;
            });

            if (dims.left === undefined) {
                // Event on the left side of the first axis.
                dims.i = 0;
                dims.left = pc.getOrderedDimensionKeys()[0];
                dims.right = pc.getOrderedDimensionKeys()[1];
            } else if (dims.right === undefined) {
                // Event on the right side of the last axis
                dims.i = d3version3.keys(__.dimensions).length - 1;
                dims.right = dims.left;
                dims.left = pc.getOrderedDimensionKeys()[d3version3.keys(__.dimensions).length - 2];
            }

            return dims;
        }

        function onDragStart() {
            // First we need to determine between which two axes the sturm was started.
            // This will determine the freedom of movement, because a strum can
            // logically only happen between two axes, so no movement outside these axes
            // should be allowed.
            return function() {
                var p = d3version3.mouse(strumRect[0][0]),
                    dims,
                    strum;

                p[0] = p[0] - __.margin.left;
                p[1] = p[1] - __.margin.top;

                dims = dimensionsForPoint(p),
                    strum = {
                        p1: p,
                        dims: dims,
                        minX: xscale(dims.left),
                        maxX: xscale(dims.right),
                        minY: 0,
                        maxY: h()
                    };

                strums[dims.i] = strum;
                strums.active = dims.i;

                // Make sure that the point is within the bounds
                strum.p1[0] = Math.min(Math.max(strum.minX, p[0]), strum.maxX);
                strum.p2 = strum.p1.slice();
            };
        }

        function onDrag() {
            return function() {
                var ev = d3version3.event,
                    strum = strums[strums.active];

                // Make sure that the point is within the bounds
                strum.p2[0] = Math.min(Math.max(strum.minX + 1, ev.x - __.margin.left), strum.maxX);
                strum.p2[1] = Math.min(Math.max(strum.minY, ev.y - __.margin.top), strum.maxY);
                drawStrum(strum, 1);
            };
        }

        function containmentTest(strum, width) {
            var p1 = [strum.p1[0] - strum.minX, strum.p1[1] - strum.minX],
                p2 = [strum.p2[0] - strum.minX, strum.p2[1] - strum.minX],
                m1 = 1 - width / p1[0],
                b1 = p1[1] * (1 - m1),
                m2 = 1 - width / p2[0],
                b2 = p2[1] * (1 - m2);

            // test if point falls between lines
            return function(p) {
                var x = p[0],
                    y = p[1],
                    y1 = m1 * x + b1,
                    y2 = m2 * x + b2;

                if (y > Math.min(y1, y2) && y < Math.max(y1, y2)) {
                    return true;
                }

                return false;
            };
        }

        function selected() {
            var ids = Object.getOwnPropertyNames(strums),
                brushed = __.data;

            // Get the ids of the currently active strums.
            ids = ids.filter(function(d) {
                return !isNaN(d);
            });

            function crossesStrum(d, id) {
                var strum = strums[id],
                    test = containmentTest(strum, strums.width(id)),
                    d1 = strum.dims.left,
                    d2 = strum.dims.right,
                    y1 = __.dimensions[d1].yscale,
                    y2 = __.dimensions[d2].yscale,
                    point = [y1(d[d1]) - strum.minX, y2(d[d2]) - strum.minX];
                return test(point);
            }

            if (ids.length === 0) { return brushed; }

            return brushed.filter(function(d) {
                switch(brush.predicate) {
                    case "AND":
                        return ids.every(function(id) { return crossesStrum(d, id); });
                    case "OR":
                        return ids.some(function(id) { return crossesStrum(d, id); });
                    default:
                        throw "Unknown brush predicate " + __.brushPredicate;
                }
            });
        }

        function removeStrum() {
            var strum = strums[strums.active],
                svg = pc.selection.select("svg").select("g#strums");

            delete strums[strums.active];
            strums.active = undefined;
            svg.selectAll("line#strum-" + strum.dims.i).remove();
            svg.selectAll("circle#strum-" + strum.dims.i).remove();
        }

        function onDragEnd() {
            return function() {
                var brushed = __.data,
                    strum = strums[strums.active];

                // Okay, somewhat unexpected, but not totally unsurprising, a mousclick is
                // considered a drag without move. So we have to deal with that case
                if (strum && strum.p1[0] === strum.p2[0] && strum.p1[1] === strum.p2[1]) {
                    removeStrum(strums);
                }

                brushed = selected(strums);
                strums.active = undefined;
                __.brushed = brushed;
                pc.renderBrushed();
                events.brushend.call(pc, __.brushed);
            };
        }

        function brushReset(strums) {
            return function() {
                var ids = Object.getOwnPropertyNames(strums).filter(function(d) {
                    return !isNaN(d);
                });

                ids.forEach(function(d) {
                    strums.active = d;
                    removeStrum(strums);
                });
                onDragEnd(strums)();
            };
        }

        function install() {
            var drag = d3version3.behavior.drag();

            // Map of current strums. Strums are stored per segment of the PC. A segment,
            // being the area between two axes. The left most area is indexed at 0.
            strums.active = undefined;
            // Returns the width of the PC segment where currently a strum is being
            // placed. NOTE: even though they are evenly spaced in our current
            // implementation, we keep for when non-even spaced segments are supported as
            // well.
            strums.width = function(id) {
                var strum = strums[id];

                if (strum === undefined) {
                    return undefined;
                }

                return strum.maxX - strum.minX;
            };

            pc.on("axesreorder.strums", function() {
                var ids = Object.getOwnPropertyNames(strums).filter(function(d) {
                    return !isNaN(d);
                });

                // Checks if the first dimension is directly left of the second dimension.
                function consecutive(first, second) {
                    var length = d3version3.keys(__.dimensions).length;
                    return d3version3.keys(__.dimensions).some(function(d, i) {
                        return (d === first)
                            ? i + i < length && __.dimensions[i + 1] === second
                            : false;
                    });
                }

                if (ids.length > 0) { // We have some strums, which might need to be removed.
                    ids.forEach(function(d) {
                        var dims = strums[d].dims;
                        strums.active = d;
                        // If the two dimensions of the current strum are not next to each other
                        // any more, than we'll need to remove the strum. Otherwise we keep it.
                        if (!consecutive(dims.left, dims.right)) {
                            removeStrum(strums);
                        }
                    });
                    onDragEnd(strums)();
                }
            });

            // Add a new svg group in which we draw the strums.
            pc.selection.select("svg").append("g")
                .attr("id", "strums")
                .attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");

            // Install the required brushReset function
            pc.brushReset = brushReset(strums);

            drag
                .on("dragstart", onDragStart(strums))
                .on("drag", onDrag(strums))
                .on("dragend", onDragEnd(strums));

            // NOTE: The styling needs to be done here and not in the css. This is because
            //       for 1D brushing, the canvas layers should not listen to
            //       pointer-events.
            strumRect = pc.selection.select("svg").insert("rect", "g#strums")
                .attr("id", "strum-events")
                .attr("x", __.margin.left)
                .attr("y", __.margin.top)
                .attr("width", w())
                .attr("height", h() + 2)
                .style("opacity", 0)
                .call(drag);
        }

        brush.modes["2D-strums"] = {
            install: install,
            uninstall: function() {
                pc.selection.select("svg").select("g#strums").remove();
                pc.selection.select("svg").select("rect#strum-events").remove();
                pc.on("axesreorder.strums", undefined);
                delete pc.brushReset;

                strumRect = undefined;
            },
            selected: selected,
            brushState: function () { return strums; }
        };

    }());

// brush mode: 1D-Axes with multiple extents
// requires d3version3.svg.multibrush

    (function() {
        if (typeof d3version3.svg.multibrush !== 'function') {
            return;
        }
        var brushes = {};

        function is_brushed(p) {
            return !brushes[p].empty();
        }

        // data within extents
        function selected() {
            var actives = d3version3.keys(__.dimensions).filter(is_brushed),
                extents = actives.map(function(p) { return brushes[p].extent(); });

            // We don't want to return the full data set when there are no axes brushed.
            // Actually, when there are no axes brushed, by definition, no items are
            // selected. So, let's avoid the filtering and just return false.
            //if (actives.length === 0) return false;

            // Resolves broken examples for now. They expect to get the full dataset back from empty brushes
            if (actives.length === 0) return __.data;

            // test if within range
            var within = {
                "date": function(d,p,dimension,b) {
                    if (typeof __.dimensions[p].yscale.rangePoints === "function") { // if it is ordinal
                        return b[0] <= __.dimensions[p].yscale(d[p]) && __.dimensions[p].yscale(d[p]) <= b[1]
                    } else {
                        return b[0] <= d[p] && d[p] <= b[1]
                    }
                },
                "number": function(d,p,dimension,b) {
                    if (typeof __.dimensions[p].yscale.rangePoints === "function") { // if it is ordinal
                        return b[0] <= __.dimensions[p].yscale(d[p]) && __.dimensions[p].yscale(d[p]) <= b[1]
                    } else {
                        return b[0] <= d[p] && d[p] <= b[1]
                    }
                },
                "string": function(d,p,dimension,b) {
                    return b[0] <= __.dimensions[p].yscale(d[p]) && __.dimensions[p].yscale(d[p]) <= b[1]
                }
            };

            return __.data
                .filter(function(d) {
                    switch(brush.predicate) {
                        case "AND":
                            return actives.every(function(p, dimension) {
                                return extents[dimension].some(function(b) {
                                    return within[__.dimensions[p].type](d,p,dimension,b);
                                });
                            });
                        case "OR":
                            return actives.some(function(p, dimension) {
                                return extents[dimension].some(function(b) {
                                    return within[__.dimensions[p].type](d,p,dimension,b);
                                });
                            });
                        default:
                            throw "Unknown brush predicate " + __.brushPredicate;
                    }
                });
        };

        function brushExtents(extents) {
            if (typeof(extents) === 'undefined') {
                extents = {};
                d3version3.keys(__.dimensions).forEach(function (d) {
                    var brush = brushes[d];
                    if (brush !== undefined && !brush.empty()) {
                        var extent = brush.extent();
                        extents[d] = extent;
                    }
                });
                return extents;
            }
            else {
                //first get all the brush selections
                var brushSelections = {};
                g.selectAll('.brush')
                    .each(function (d) {
                        brushSelections[d] = d3version3.select(this);
                    });

                // loop over each dimension and update appropriately (if it was passed in through extents)
                d3version3.keys(__.dimensions).forEach(function (d) {
                    if (extents[d] === undefined) {
                        return;
                    }

                    var brush = brushes[d];
                    if (brush !== undefined) {
                        //update the extent
                        brush.extent(extents[d]);

                        //redraw the brush
                        brushSelections[d]
                            .transition()
                            .duration(0)
                            .call(brush);

                        //fire some events
                        brush.event(brushSelections[d]);
                    }
                });

                //redraw the chart
                pc.renderBrushed();

                return pc;
            }
        }

        //function brushExtents() {
        //  var extents = {};
        //  d3version3.keys(__.dimensions).forEach(function(d) {
        //    var brush = brushes[d];
        //    if (brush !== undefined && !brush.empty()) {
        //      var extent = brush.extent();
        //      extents[d] = extent;
        //    }
        //  });
        //  return extents;
        //}

        function brushFor(axis) {
            var brush = d3version3.svg.multibrush();

            brush
                .y(__.dimensions[axis].yscale)
                .on("brushstart", function() {
                    if(d3version3.event.sourceEvent !== null) {
                        events.brushstart.call(pc, __.brushed);
                        d3version3.event.sourceEvent.stopPropagation();
                    }
                })
                .on("brush", function() {
                    brushUpdated(selected());
                })
                .on("brushend", function() {
                    // d3version3.svg.multibrush clears extents just before calling 'brushend'
                    // so we have to update here again.
                    // This fixes issue #103 for now, but should be changed in d3version3.svg.multibrush
                    // to avoid unnecessary computation.
                    brushUpdated(selected());
                    events.brushend.call(pc, __.brushed);
                })
                .extentAdaption(function(selection) {
                    selection
                        .style("visibility", null)
                        .attr("x", -15)
                        .attr("width", 30)
                        .style("fill", "rgba(255,255,255,0.25)")
                        .style("stroke", "rgba(0,0,0,0.6)");
                })
                .resizeAdaption(function(selection) {
                    selection
                        .selectAll("rect")
                        .attr("x", -15)
                        .attr("width", 30)
                        .style("visibility", null)
                        .style("fill", "rgba(0,0,0,0.1)");
                });

            brushes[axis] = brush;
            return brush;
        }

        function brushReset(dimension) {
            __.brushed = false;
            if (g) {
                g.selectAll('.brush')
                    .each(function(d) {
                        d3version3.select(this).call(
                            brushes[d].clear()
                        );
                    });
                pc.renderBrushed();
            }
            return this;
        };

        function install() {
            if (!g) pc.createAxes();

            // Add and store a brush for each axis.
            var brush = g.append("svg:g")
                .attr("class", "brush")
                .each(function(d) {
                    d3version3.select(this).call(brushFor(d));
                })

            brush.selectAll("rect")
                .style("visibility", null)
                .attr("x", -15)
                .attr("width", 30);

            brush.selectAll("rect.background")
                .style("fill", "transparent");

            brush.selectAll("rect.extent")
                .style("fill", "rgba(255,255,255,0.25)")
                .style("stroke", "rgba(0,0,0,0.6)");

            brush.selectAll(".resize rect")
                .style("fill", "rgba(0,0,0,0.1)");

            pc.brushExtents = brushExtents;
            pc.brushReset = brushReset;
            return pc;
        }

        brush.modes["1D-axes-multi"] = {
            install: install,
            uninstall: function() {
                g.selectAll(".brush").remove();
                brushes = {};
                delete pc.brushExtents;
                delete pc.brushReset;
            },
            selected: selected,
            brushState: brushExtents
        }
    })();
// brush mode: angular
// code based on 2D.strums.js

    (function() {
        var arcs = {},
            strumRect;

        function drawStrum(arc, activePoint) {
            var svg = pc.selection.select("svg").select("g#arcs"),
                id = arc.dims.i,
                points = [arc.p2, arc.p3],
                line = svg.selectAll("line#arc-" + id).data([{p1:arc.p1,p2:arc.p2},{p1:arc.p1,p2:arc.p3}]),
                circles = svg.selectAll("circle#arc-" + id).data(points),
                drag = d3version3.behavior.drag(),
                path = svg.selectAll("path#arc-" + id).data([arc]);

            path.enter()
                .append("path")
                .attr("id", "arc-" + id)
                .attr("class", "arc")
                .style("fill", "orange")
                .style("opacity", 0.5);

            path
                .attr("d", arc.arc)
                .attr("transform", "translate(" + arc.p1[0] + "," + arc.p1[1] + ")");

            line.enter()
                .append("line")
                .attr("id", "arc-" + id)
                .attr("class", "arc");

            line
                .attr("x1", function(d) { return d.p1[0]; })
                .attr("y1", function(d) { return d.p1[1]; })
                .attr("x2", function(d) { return d.p2[0]; })
                .attr("y2", function(d) { return d.p2[1]; })
                .attr("stroke", "black")
                .attr("stroke-width", 2);

            drag
                .on("drag", function(d, i) {
                    var ev = d3version3.event,
                        angle = 0;

                    i = i + 2;

                    arc["p" + i][0] = Math.min(Math.max(arc.minX + 1, ev.x), arc.maxX);
                    arc["p" + i][1] = Math.min(Math.max(arc.minY, ev.y), arc.maxY);

                    angle = i === 3 ? arcs.startAngle(id) : arcs.endAngle(id);

                    if ((arc.startAngle < Math.PI && arc.endAngle < Math.PI && angle < Math.PI) ||
                        (arc.startAngle >= Math.PI && arc.endAngle >= Math.PI && angle >= Math.PI)) {

                        if (i === 2) {
                            arc.endAngle = angle;
                            arc.arc.endAngle(angle);
                        } else if (i === 3) {
                            arc.startAngle = angle;
                            arc.arc.startAngle(angle);
                        }

                    }

                    drawStrum(arc, i - 2);
                })
                .on("dragend", onDragEnd());

            circles.enter()
                .append("circle")
                .attr("id", "arc-" + id)
                .attr("class", "arc");

            circles
                .attr("cx", function(d) { return d[0]; })
                .attr("cy", function(d) { return d[1]; })
                .attr("r", 5)
                .style("opacity", function(d, i) {
                    return (activePoint !== undefined && i === activePoint) ? 0.8 : 0;
                })
                .on("mouseover", function() {
                    d3version3.select(this).style("opacity", 0.8);
                })
                .on("mouseout", function() {
                    d3version3.select(this).style("opacity", 0);
                })
                .call(drag);
        }

        function dimensionsForPoint(p) {
            var dims = { i: -1, left: undefined, right: undefined };
            d3version3.keys(__.dimensions).some(function(dim, i) {
                if (xscale(dim) < p[0]) {
                    var next = d3version3.keys(__.dimensions)[pc.getOrderedDimensionKeys().indexOf(dim)+1];
                    dims.i = i;
                    dims.left = dim;
                    dims.right = next;
                    return false;
                }
                return true;
            });

            if (dims.left === undefined) {
                // Event on the left side of the first axis.
                dims.i = 0;
                dims.left = pc.getOrderedDimensionKeys()[0];
                dims.right = pc.getOrderedDimensionKeys()[1];
            } else if (dims.right === undefined) {
                // Event on the right side of the last axis
                dims.i = d3version3.keys(__.dimensions).length - 1;
                dims.right = dims.left;
                dims.left = pc.getOrderedDimensionKeys()[d3version3.keys(__.dimensions).length - 2];
            }

            return dims;
        }

        function onDragStart() {
            // First we need to determine between which two axes the arc was started.
            // This will determine the freedom of movement, because a arc can
            // logically only happen between two axes, so no movement outside these axes
            // should be allowed.
            return function() {
                var p = d3version3.mouse(strumRect[0][0]),
                    dims,
                    arc;

                p[0] = p[0] - __.margin.left;
                p[1] = p[1] - __.margin.top;

                dims = dimensionsForPoint(p),
                    arc = {
                        p1: p,
                        dims: dims,
                        minX: xscale(dims.left),
                        maxX: xscale(dims.right),
                        minY: 0,
                        maxY: h(),
                        startAngle: undefined,
                        endAngle: undefined,
                        arc: d3version3.svg.arc().innerRadius(0)
                    };

                arcs[dims.i] = arc;
                arcs.active = dims.i;

                // Make sure that the point is within the bounds
                arc.p1[0] = Math.min(Math.max(arc.minX, p[0]), arc.maxX);
                arc.p2 = arc.p1.slice();
                arc.p3 = arc.p1.slice();
            };
        }

        function onDrag() {
            return function() {
                var ev = d3version3.event,
                    arc = arcs[arcs.active];

                // Make sure that the point is within the bounds
                arc.p2[0] = Math.min(Math.max(arc.minX + 1, ev.x - __.margin.left), arc.maxX);
                arc.p2[1] = Math.min(Math.max(arc.minY, ev.y - __.margin.top), arc.maxY);
                arc.p3 = arc.p2.slice();
//      console.log(arcs.angle(arcs.active));
//      console.log(signedAngle(arcs.unsignedAngle(arcs.active)));
                drawStrum(arc, 1);
            };
        }

        // some helper functions
        function hypothenuse(a, b) {
            return Math.sqrt(a*a + b*b);
        }

        var rad = (function() {
            var c = Math.PI / 180;
            return function(angle) {
                return angle * c;
            };
        })();

        var deg = (function() {
            var c = 180 / Math.PI;
            return function(angle) {
                return angle * c;
            };
        })();

        // [0, 2*PI] -> [-PI/2, PI/2]
        var signedAngle = function(angle) {
            var ret = angle;
            if (angle > Math.PI) {
                ret = angle - 1.5 * Math.PI;
                ret = angle - 1.5 * Math.PI;
            } else {
                ret = angle - 0.5 * Math.PI;
                ret = angle - 0.5 * Math.PI;
            }
            return -ret;
        }

        /**
         * angles are stored in radians from in [0, 2*PI], where 0 in 12 o'clock.
         * However, one can only select lines from 0 to PI, so we compute the
         * 'signed' angle, where 0 is the horizontal line (3 o'clock), and +/- PI/2
         * are 12 and 6 o'clock respectively.
         */
        function containmentTest(arc) {
            var startAngle = signedAngle(arc.startAngle);
            var endAngle = signedAngle(arc.endAngle);

            if (startAngle > endAngle) {
                var tmp = startAngle;
                startAngle = endAngle;
                endAngle = tmp;
            }

            // test if segment angle is contained in angle interval
            return function(a) {

                if (a >= startAngle && a <= endAngle) {
                    return true;
                }

                return false;
            };
        }

        function selected() {
            var ids = Object.getOwnPropertyNames(arcs),
                brushed = __.data;

            // Get the ids of the currently active arcs.
            ids = ids.filter(function(d) {
                return !isNaN(d);
            });

            function crossesStrum(d, id) {
                var arc = arcs[id],
                    test = containmentTest(arc),
                    d1 = arc.dims.left,
                    d2 = arc.dims.right,
                    y1 = __.dimensions[d1].yscale,
                    y2 = __.dimensions[d2].yscale,
                    a = arcs.width(id),
                    b = y1(d[d1]) - y2(d[d2]),
                    c = hypothenuse(a, b),
                    angle = Math.asin(b/c);	// rad in [-PI/2, PI/2]
                return test(angle);
            }

            if (ids.length === 0) { return brushed; }

            return brushed.filter(function(d) {
                switch(brush.predicate) {
                    case "AND":
                        return ids.every(function(id) { return crossesStrum(d, id); });
                    case "OR":
                        return ids.some(function(id) { return crossesStrum(d, id); });
                    default:
                        throw "Unknown brush predicate " + __.brushPredicate;
                }
            });
        }

        function removeStrum() {
            var arc = arcs[arcs.active],
                svg = pc.selection.select("svg").select("g#arcs");

            delete arcs[arcs.active];
            arcs.active = undefined;
            svg.selectAll("line#arc-" + arc.dims.i).remove();
            svg.selectAll("circle#arc-" + arc.dims.i).remove();
            svg.selectAll("path#arc-" + arc.dims.i).remove();
        }

        function onDragEnd() {
            return function() {
                var brushed = __.data,
                    arc = arcs[arcs.active];

                // Okay, somewhat unexpected, but not totally unsurprising, a mousclick is
                // considered a drag without move. So we have to deal with that case
                if (arc && arc.p1[0] === arc.p2[0] && arc.p1[1] === arc.p2[1]) {
                    removeStrum(arcs);
                }

                if (arc) {
                    var angle = arcs.startAngle(arcs.active);

                    arc.startAngle = angle;
                    arc.endAngle = angle;
                    arc.arc
                        .outerRadius(arcs.length(arcs.active))
                        .startAngle(angle)
                        .endAngle(angle);
                }


                brushed = selected(arcs);
                arcs.active = undefined;
                __.brushed = brushed;
                pc.renderBrushed();
                events.brushend.call(pc, __.brushed);
            };
        }

        function brushReset(arcs) {
            return function() {
                var ids = Object.getOwnPropertyNames(arcs).filter(function(d) {
                    return !isNaN(d);
                });

                ids.forEach(function(d) {
                    arcs.active = d;
                    removeStrum(arcs);
                });
                onDragEnd(arcs)();
            };
        }

        function install() {
            var drag = d3version3.behavior.drag();

            // Map of current arcs. arcs are stored per segment of the PC. A segment,
            // being the area between two axes. The left most area is indexed at 0.
            arcs.active = undefined;
            // Returns the width of the PC segment where currently a arc is being
            // placed. NOTE: even though they are evenly spaced in our current
            // implementation, we keep for when non-even spaced segments are supported as
            // well.
            arcs.width = function(id) {
                var arc = arcs[id];

                if (arc === undefined) {
                    return undefined;
                }

                return arc.maxX - arc.minX;
            };

            // returns angles in [-PI/2, PI/2]
            angle = function(p1, p2) {
                var a = p1[0] - p2[0],
                    b = p1[1] - p2[1],
                    c = hypothenuse(a, b);

                return Math.asin(b/c);
            }

            // returns angles in [0, 2 * PI]
            arcs.endAngle = function(id) {
                var arc = arcs[id];
                if (arc === undefined) {
                    return undefined;
                }
                var sAngle = angle(arc.p1, arc.p2),
                    uAngle = -sAngle + Math.PI / 2;

                if (arc.p1[0] > arc.p2[0]) {
                    uAngle = 2 * Math.PI - uAngle;
                }

                return uAngle;
            }

            arcs.startAngle = function(id) {
                var arc = arcs[id];
                if (arc === undefined) {
                    return undefined;
                }

                var sAngle = angle(arc.p1, arc.p3),
                    uAngle = -sAngle + Math.PI / 2;

                if (arc.p1[0] > arc.p3[0]) {
                    uAngle = 2 * Math.PI - uAngle;
                }

                return uAngle;
            }

            arcs.length = function(id) {
                var arc = arcs[id];

                if (arc === undefined) {
                    return undefined;
                }

                var a = arc.p1[0] - arc.p2[0],
                    b = arc.p1[1] - arc.p2[1],
                    c = hypothenuse(a, b);

                return(c);
            }

            pc.on("axesreorder.arcs", function() {
                var ids = Object.getOwnPropertyNames(arcs).filter(function(d) {
                    return !isNaN(d);
                });

                // Checks if the first dimension is directly left of the second dimension.
                function consecutive(first, second) {
                    var length = d3version3.keys(__.dimensions).length;
                    return d3version3.keys(__.dimensions).some(function(d, i) {
                        return (d === first)
                            ? i + i < length && __.dimensions[i + 1] === second
                            : false;
                    });
                }

                if (ids.length > 0) { // We have some arcs, which might need to be removed.
                    ids.forEach(function(d) {
                        var dims = arcs[d].dims;
                        arcs.active = d;
                        // If the two dimensions of the current arc are not next to each other
                        // any more, than we'll need to remove the arc. Otherwise we keep it.
                        if (!consecutive(dims.left, dims.right)) {
                            removeStrum(arcs);
                        }
                    });
                    onDragEnd(arcs)();
                }
            });

            // Add a new svg group in which we draw the arcs.
            pc.selection.select("svg").append("g")
                .attr("id", "arcs")
                .attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");

            // Install the required brushReset function
            pc.brushReset = brushReset(arcs);

            drag
                .on("dragstart", onDragStart(arcs))
                .on("drag", onDrag(arcs))
                .on("dragend", onDragEnd(arcs));

            // NOTE: The styling needs to be done here and not in the css. This is because
            //       for 1D brushing, the canvas layers should not listen to
            //       pointer-events.
            strumRect = pc.selection.select("svg").insert("rect", "g#arcs")
                .attr("id", "arc-events")
                .attr("x", __.margin.left)
                .attr("y", __.margin.top)
                .attr("width", w())
                .attr("height", h() + 2)
                .style("opacity", 0)
                .call(drag);
        }

        brush.modes["angular"] = {
            install: install,
            uninstall: function() {
                pc.selection.select("svg").select("g#arcs").remove();
                pc.selection.select("svg").select("rect#arc-events").remove();
                pc.on("axesreorder.arcs", undefined);
                delete pc.brushReset;

                strumRect = undefined;
            },
            selected: selected,
            brushState: function () { return arcs; }
        };

    }());
    pc.interactive = function() {
        flags.interactive = true;
        return this;
    };

// expose a few objects
    pc.xscale = xscale;
    pc.ctx = ctx;
    pc.canvas = canvas;
    pc.g = function() { return g; };

// rescale for height, width and margins
// TODO currently assumes chart is brushable, and destroys old brushes
    pc.resize = function() {
        // selection size
        pc.selection.select("svg")
            .attr("width", __.width)
            .attr("height", __.height)
        pc.svg.attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");

        // FIXME: the current brush state should pass through
        if (flags.brushable) pc.brushReset();

        // scales
        pc.autoscale();

        // axes, destroys old brushes.
        if (g) pc.createAxes();
        if (flags.brushable) pc.brushable();
        if (flags.reorderable) pc.reorderable();

        events.resize.call(this, {width: __.width, height: __.height, margin: __.margin});
        return this;
    };

// highlight an array of data
    pc.highlight = function(data) {
        if (arguments.length === 0) {
            return __.highlighted;
        }

        __.highlighted = data;
        pc.clear("highlight");
        d3version3.selectAll([canvas.foreground, canvas.brushed]).classed("faded", true);
        data.forEach(path_highlight);
        events.highlight.call(this, data);
        return this;
    };

// clear highlighting
    pc.unhighlight = function() {
        __.highlighted = [];
        pc.clear("highlight");
        d3version3.selectAll([canvas.foreground, canvas.brushed]).classed("faded", false);
        return this;
    };

// calculate 2d intersection of line a->b with line c->d
// points are objects with x and y properties
    pc.intersection =  function(a, b, c, d) {
        return {
            x: ((a.x * b.y - a.y * b.x) * (c.x - d.x) - (a.x - b.x) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x)),
            y: ((a.x * b.y - a.y * b.x) * (c.y - d.y) - (a.y - b.y) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x))
        };
    };

    function position(d) {
        if (xscale.range().length === 0) {
            xscale.rangePoints([0, w()], 1);
        }
        var v = dragging[d];
        return v == null ? xscale(d) : v;
    }

// Merges the canvases and SVG elements into one canvas element which is then passed into the callback
// (so you can choose to save it to disk, etc.)
    pc.mergeParcoords = function(callback) {
        // Retina display, etc.
        var devicePixelRatio = window.devicePixelRatio || 1;

        // Create a canvas element to store the merged canvases
        var mergedCanvas = document.createElement("canvas");
        mergedCanvas.width = pc.canvas.foreground.clientWidth * devicePixelRatio
        mergedCanvas.height = (pc.canvas.foreground.clientHeight + 30) * devicePixelRatio;
        mergedCanvas.style.width = mergedCanvas.width / devicePixelRatio + "px";
        mergedCanvas.style.height = mergedCanvas.height / devicePixelRatio + "px";

        // Give the canvas a white background
        var context = mergedCanvas.getContext("2d");
        context.fillStyle = "#ffffff";
        context.fillRect(0, 0, mergedCanvas.width, mergedCanvas.height);

        // Merge all the canvases
        for (var key in pc.canvas) {
            context.drawImage(pc.canvas[key], 0, 24 * devicePixelRatio, mergedCanvas.width, mergedCanvas.height - 30 * devicePixelRatio);
        }

        // Add SVG elements to canvas
        var DOMURL = window.URL || window.webkitURL || window;
        var serializer = new XMLSerializer();
        var svgStr = serializer.serializeToString(pc.selection.select("svg")[0][0]);

        // Create a Data URI.
        var src = 'data:image/svg+xml;base64,' + window.btoa(svgStr);
        var img = new Image();
        img.onload = function () {
            context.drawImage(img, 0, 0, img.width * devicePixelRatio, img.height * devicePixelRatio);
            if (typeof callback === "function") {
                callback(mergedCanvas);
            }
        };
        img.src = src;
    }
    pc.version = "0.7.0";
    // this descriptive text should live with other introspective methods
    pc.toString = function() { return "Parallel Coordinates: " + d3version3.keys(__.dimensions).length + " dimensions (" + d3version3.keys(__.data[0]).length + " total) , " + __.data.length + " rows"; };

    return pc;
};

d3version3.renderQueue = (function(func) {
    var _queue = [],                  // data to be rendered
        _rate = 10,                   // number of calls per frame
        _clear = function() {},       // clearing function
        _i = 0;                       // current iteration

    var rq = function(data) {
        if (data) rq.data(data);
        rq.invalidate();
        _clear();
        rq.render();
    };

    rq.render = function() {
        _i = 0;
        var valid = true;
        rq.invalidate = function() { valid = false; };

        function doFrame() {
            if (!valid) return true;
            if (_i > _queue.length) return true;

            // Typical d3version3 behavior is to pass a data item *and* its index. As the
            // render queue splits the original data set, we'll have to be slightly
            // more carefull about passing the correct index with the data item.
            var end = Math.min(_i + _rate, _queue.length);
            for (var i = _i; i < end; i++) {
                func(_queue[i], i);
            }
            _i += _rate;
        }

        d3version3.timer(doFrame);
    };

    rq.data = function(data) {
        rq.invalidate();
        _queue = data.slice(0);
        return rq;
    };

    rq.rate = function(value) {
        if (!arguments.length) return _rate;
        _rate = value;
        return rq;
    };

    rq.remaining = function() {
        return _queue.length - _i;
    };

    // clear the canvas
    rq.clear = function(func) {
        if (!arguments.length) {
            _clear();
            return rq;
        }
        _clear = func;
        return rq;
    };

    rq.invalidate = function() {};

    return rq;
});
The diff you're trying to view is too large. Only the first 1000 changed files have been loaded.
Showing with 0 additions and 0 deletions (0 / 0 diffs computed)
swh spinner

Computing file changes ...

back to top

Software Heritage — Copyright (C) 2015–2026, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API