Skip to content

Instantly share code, notes, and snippets.

@Dmitra
Forked from mbostock/.block
Last active August 12, 2016 06:39
Show Gist options
  • Select an option

  • Save Dmitra/e5c4f50be12a71b3fa19 to your computer and use it in GitHub Desktop.

Select an option

Save Dmitra/e5c4f50be12a71b3fa19 to your computer and use it in GitHub Desktop.

Revisions

  1. Dmitra revised this gist Feb 18, 2015. 1 changed file with 2 additions and 3 deletions.
    5 changes: 2 additions & 3 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,2 @@
    D3's [axis component](http://bl.ocks.org/1166403) supports automatic interpolation; simply change the scale, create a transition, and call the axis. The axis component will automatically create a smooth transition.

    However, it's also possible to create a custom tween and redraw the axis synchronously for each frame of the transition. If you wanted to display intermediate ticks for a long-running transition, this might be preferred to the default behavior. This example shows a slow zoom from three years to one day.
    This fork of axis example use changed D3's axis component.
    The goal of rewrite is to incapsulate all module functionality in one object.
  2. Dmitra revised this gist Feb 18, 2015. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -100,6 +100,9 @@
    });
    }

    self._defaultOrient = "bottom",
    self._orients = {top: 1, right: 1, bottom: 1, left: 1};

    var scale = d3.scale.linear(),
    orient = self._defaultOrient,
    innerTickSize = 6,
    @@ -109,9 +112,6 @@
    tickValues = null,
    tickFormat_;

    self._defaultOrient = "bottom",
    self._orients = {top: 1, right: 1, bottom: 1, left: 1};

    self.scale = function(x) {
    if (!arguments.length) return scale;
    scale = x;
  3. Dmitra revised this gist Feb 18, 2015. 1 changed file with 168 additions and 0 deletions.
    168 changes: 168 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -17,6 +17,174 @@
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script>

    //Legacy code
    var ε = 1e-6;
    function d3_identity(d) {
    return d;
    }
    function d3_scaleExtent(domain) {
    var start = domain[0], stop = domain[domain.length - 1];
    return start < stop ? [start, stop] : [stop, start];
    }

    function d3_scaleRange(scale) {
    return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
    }

    d3.svg.axis = function (g) {
    var self = function (g) {
    g.each(function() {
    var g = d3.select(this);

    // Stash a snapshot of the new scale, and retrieve the old snapshot.
    var scale0 = this.__chart__ || scale,
    scale1 = this.__chart__ = scale.copy();

    // Ticks, or domain values for ordinal scales.
    var ticks = tickValues == null ? (scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain()) : tickValues,
    tickFormat = tickFormat_ == null ? (scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity) : tickFormat_,
    tick = g.selectAll(".tick").data(ticks, scale1),
    tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε),
    tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(),
    tickUpdate = d3.transition(tick.order()).style("opacity", 1),
    tickSpacing = Math.max(innerTickSize, 0) + tickPadding,
    tickTransform;

    // Domain.
    var range = d3_scaleRange(scale1),
    path = g.selectAll(".domain").data([0]),
    pathUpdate = (path.enter().append("path").attr("class", "domain"), d3.transition(path));

    tickEnter.append("line");
    tickEnter.append("text");

    var lineEnter = tickEnter.select("line"),
    lineUpdate = tickUpdate.select("line"),
    text = tick.select("text").text(tickFormat),
    textEnter = tickEnter.select("text"),
    textUpdate = tickUpdate.select("text"),
    sign = orient === "top" || orient === "left" ? -1 : 1,
    x1, x2, y1, y2;

    if (orient === "bottom" || orient === "top") {
    tickTransform = self._x, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2";
    text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
    pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);
    } else {
    tickTransform = self._y, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2";
    text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
    pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
    }

    lineEnter.attr(y2, sign * innerTickSize);
    textEnter.attr(y1, sign * tickSpacing);
    lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
    textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);

    // If either the new or old scale is ordinal,
    // entering ticks are undefined in the old scale,
    // and so can fade-in in the new scale’s position.
    // Exiting ticks are likewise undefined in the new scale,
    // and so can fade-out in the old scale’s position.
    if (scale1.rangeBand) {
    var x = scale1, dx = x.rangeBand() / 2;
    scale0 = scale1 = function(d) { return x(d) + dx; };
    } else if (scale0.rangeBand) {
    scale0 = scale1;
    } else {
    tickExit.call(tickTransform, scale1, scale0);
    }

    tickEnter.call(tickTransform, scale0, scale1);
    tickUpdate.call(tickTransform, scale1, scale1);
    });
    }

    var scale = d3.scale.linear(),
    orient = self._defaultOrient,
    innerTickSize = 6,
    outerTickSize = 6,
    tickPadding = 3,
    tickArguments_ = [10],
    tickValues = null,
    tickFormat_;

    self._defaultOrient = "bottom",
    self._orients = {top: 1, right: 1, bottom: 1, left: 1};

    self.scale = function(x) {
    if (!arguments.length) return scale;
    scale = x;
    return self;
    };

    self.orient = function(x) {
    if (!arguments.length) return orient;
    orient = x in self._orients ? x + "" : self._defaultOrient;
    return self;
    };

    self.ticks = function() {
    if (!arguments.length) return tickArguments_;
    tickArguments_ = arguments;
    return self;
    };

    self.tickValues = function(x) {
    if (!arguments.length) return tickValues;
    tickValues = x;
    return self;
    };

    self.tickFormat = function(x) {
    if (!arguments.length) return tickFormat_;
    tickFormat_ = x;
    return self;
    };

    self.tickSize = function(x) {
    var n = arguments.length;
    if (!n) return innerTickSize;
    innerTickSize = +x;
    outerTickSize = +arguments[n - 1];
    return self;
    };

    self.innerTickSize = function(x) {
    if (!arguments.length) return innerTickSize;
    innerTickSize = +x;
    return self;
    };

    self.outerTickSize = function(x) {
    if (!arguments.length) return outerTickSize;
    outerTickSize = +x;
    return self;
    };

    self.tickPadding = function(x) {
    if (!arguments.length) return tickPadding;
    tickPadding = +x;
    return self;
    };

    self.tickSubdivide = function() {
    return arguments.length && self;
    };

    self._x = function (selection, x0, x1) {
    selection.attr("transform", function(d) { var v0 = x0(d); return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)"; });
    }

    self._y = function (selection, y0, y1) {
    selection.attr("transform", function(d) { var v0 = y0(d); return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")"; });

    }
    return self;
    }
    </script>
    <script>

    var width = 960,
    height = 500;

  4. @mbostock mbostock revised this gist Mar 17, 2014. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -20,8 +20,8 @@
    var width = 960,
    height = 500;

    var domain0 = [new Date(2000, 0, 1), new Date(2003, 0, 1)],
    domain1 = [new Date(2000, 1, 1), new Date(2000, 1, 2)];
    var domain0 = [+new Date(2000, 0, 1), +new Date(2003, 0, 1)],
    domain1 = [+new Date(2000, 1, 1), +new Date(2000, 1, 2)];

    var x = d3.time.scale.utc()
    .domain(domain0)
    @@ -53,4 +53,4 @@
    });
    }

    </script>
    </script>
  5. @mbostock mbostock revised this gist Aug 22, 2013. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -6,14 +6,15 @@
    font: 10px sans-serif;
    }

    .axis path, .axis line {
    .axis path,
    .axis line {
    fill: none;
    stroke: #000;
    }

    </style>
    <body>
    <script src="http://d3js.org/d3.v2.min.js?2.9.4"></script>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script>

    var width = 960,
  6. @mbostock mbostock revised this gist Oct 12, 2012. 1 changed file with 0 additions and 0 deletions.
    Binary file added thumbnail.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  7. @mbostock mbostock revised this gist Jun 24, 2012. 2 changed files with 13 additions and 6 deletions.
    4 changes: 2 additions & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,3 @@
    This examples shows bug in ticks positioning if using step method for animation.
    D3's [axis component](http://bl.ocks.org/1166403) supports automatic interpolation; simply change the scale, create a transition, and call the axis. The axis component will automatically create a smooth transition.

    Click timeline to animate. On animation end ticks are incorrectly positioned.
    However, it's also possible to create a custom tween and redraw the axis synchronously for each frame of the transition. If you wanted to display intermediate ticks for a long-running transition, this might be preferred to the default behavior. This example shows a slow zoom from three years to one day.
    15 changes: 11 additions & 4 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,10 @@
    <meta charset="utf-8">
    <style>

    .axis text {
    font: 10px sans-serif;
    }

    .axis path, .axis line {
    fill: none;
    stroke: #000;
    @@ -16,7 +20,7 @@
    height = 500;

    var domain0 = [new Date(2000, 0, 1), new Date(2003, 0, 1)],
    domain1 = [new Date(2000, 1, 1), new Date(2001, 1, 1)];
    domain1 = [new Date(2000, 1, 1), new Date(2000, 1, 2)];

    var x = d3.time.scale.utc()
    .domain(domain0)
    @@ -35,14 +39,17 @@
    .attr("class", "x axis")
    .call(xAxis);

    d3.select(window).on("click", function() {
    gAxis.transition().duration(700).tween("step", function(d, i) {
    transition();
    setInterval(transition, 10000);

    function transition() {
    gAxis.transition().duration(8500).tween("axis", function(d, i) {
    var i = d3.interpolate(domain0, domain1);
    return function(t) {
    x.domain(i(t));
    gAxis.call(xAxis);
    }
    });
    });
    }

    </script>
  8. @mbostock mbostock revised this gist Jun 24, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -13,7 +13,7 @@
    <script>

    var width = 960,
    height = 100;
    height = 500;

    var domain0 = [new Date(2000, 0, 1), new Date(2003, 0, 1)],
    domain1 = [new Date(2000, 1, 1), new Date(2001, 1, 1)];
  9. @mbostock mbostock revised this gist Jun 24, 2012. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -27,7 +27,9 @@

    var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(0,200)");

    var gAxis = svg.append("g")
    .attr("class", "x axis")
  10. @mbostock mbostock revised this gist Jun 24, 2012. 2 changed files with 13 additions and 19 deletions.
    File renamed without changes.
    32 changes: 13 additions & 19 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -2,8 +2,9 @@
    <meta charset="utf-8">
    <style>

    rect {
    fill: #ffccdd;
    .axis path, .axis line {
    fill: none;
    stroke: #000;
    }

    </style>
    @@ -14,39 +15,32 @@
    var width = 960,
    height = 100;

    var domain0 = [new Date(2000, 0, 1), new Date(2003, 0, 1)],
    domain1 = [new Date(2000, 1, 1), new Date(2001, 1, 1)];

    var x = d3.time.scale.utc()
    .domain([new Date(2000, 0, 1), new Date(2003, 0, 1)])
    .domain(domain0)
    .range([0, width]);

    var xAxis = d3.svg.axis()
    .scale(x)
    .tickSize(-20);
    .scale(x);

    var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

    svg.append("rect")
    .attr("width", width)
    .attr("height", height);

    var gAxis = svg.append("g")
    .attr("class", "x axis")
    .call(xAxis);

    svg.on("click", function() {
    svg.select(".x.axis").transition()
    .duration(700)
    .tween("step", stepTween);

    function stepTween(d, i) {
    var i = d3.interpolate(x.domain(), [new Date(2000, 1, 1), new Date(2001, 1, 1)]);
    d3.select(window).on("click", function() {
    gAxis.transition().duration(700).tween("step", function(d, i) {
    var i = d3.interpolate(domain0, domain1);
    return function(t) {
    console.log(t);
    x.domain(i(t));
    gAxis.call(xAxis);
    }
    }
    });
    });

    </script>
    </script>
  11. @mbostock mbostock revised this gist Jun 24, 2012. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -42,6 +42,7 @@
    function stepTween(d, i) {
    var i = d3.interpolate(x.domain(), [new Date(2000, 1, 1), new Date(2001, 1, 1)]);
    return function(t) {
    console.log(t);
    x.domain(i(t));
    gAxis.call(xAxis);
    }
  12. @mbostock mbostock revised this gist Jun 24, 2012. 2 changed files with 49 additions and 32 deletions.
    3 changes: 3 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    This examples shows bug in ticks positioning if using step method for animation.

    Click timeline to animate. On animation end ticks are incorrectly positioned.
    78 changes: 46 additions & 32 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -1,37 +1,51 @@
    <!DOCTYPE html>
    <meta charset="utf-8">
    <script src="http://mbostock.github.com/d3/d3.v2.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    <body style="margin:0">
    <p>This examples shows bug in ticks positioning if using step method for animation.</br>
    Click timeline to animate. On animation end ticks are incorrectly positioned.</p>
    <div id="timeline"></div>
    <style>

    rect {
    fill: #ffccdd;
    }

    </style>
    <body>
    <script src="http://d3js.org/d3.v2.min.js?2.9.4"></script>
    <script>
    var w = $('#timeline').width(),
    startX, endX,
    svg = d3.select('#timeline').append('svg')
    .attr('class', 'timeline')
    .attr('width', w)
    .attr('height', 100),
    x = d3.time.scale.utc()
    .domain([new Date(2000, 0, 1), new Date(2003, 0, 1)])
    .range([0, w]),
    xAxis = d3.svg.axis().scale(x).tickSize(-20);
    svg.insert('rect').attr('width', w).attr('height', 100).attr('fill', '#ffccdd')
    var holder = svg.append('g').attr('class', 'axis');

    holder.call(xAxis);

    svg.on('click', function() {
    startX = x.copy();
    endX = x.copy().domain([new Date(2000, 1, 1), new Date(2001, 1, 1)]);
    holder.transition().duration(700).tween('step', stepRedraw);
    });

    function stepRedraw(d, i) {
    return function(t) {
    x.domain(d3.interpolateArray(startX.domain(), endX.domain())(t));
    holder.call(xAxis);
    var width = 960,
    height = 100;

    var x = d3.time.scale.utc()
    .domain([new Date(2000, 0, 1), new Date(2003, 0, 1)])
    .range([0, width]);

    var xAxis = d3.svg.axis()
    .scale(x)
    .tickSize(-20);

    var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

    svg.append("rect")
    .attr("width", width)
    .attr("height", height);

    var gAxis = svg.append("g")
    .attr("class", "x axis")
    .call(xAxis);

    svg.on("click", function() {
    svg.select(".x.axis").transition()
    .duration(700)
    .tween("step", stepTween);

    function stepTween(d, i) {
    var i = d3.interpolate(x.domain(), [new Date(2000, 1, 1), new Date(2001, 1, 1)]);
    return function(t) {
    x.domain(i(t));
    gAxis.call(xAxis);
    }
    }
    }
    </script>
    });

    </script>
  13. @tandu tandu revised this gist Jun 24, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    Click timeline to animate. On animation end ticks are incorrectly positioned.</p>
    <div id="timeline"></div>
    <script>
    var w = $(window).width(),
    var w = $('#timeline').width(),
    startX, endX,
    svg = d3.select('#timeline').append('svg')
    .attr('class', 'timeline')
  14. @tandu tandu revised this gist Jun 24, 2012. 1 changed file with 30 additions and 4 deletions.
    34 changes: 30 additions & 4 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -1,11 +1,37 @@
    <!DOCTYPE html>
    <meta charset="utf-8">
    <style>
    </style>
    <body>
    test
    <script src="http://mbostock.github.com/d3/d3.v2.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    <body style="margin:0">
    <p>This examples shows bug in ticks positioning if using step method for animation.</br>
    Click timeline to animate. On animation end ticks are incorrectly positioned.</p>
    <div id="timeline"></div>
    <script>
    var w = $(window).width(),
    startX, endX,
    svg = d3.select('#timeline').append('svg')
    .attr('class', 'timeline')
    .attr('width', w)
    .attr('height', 100),
    x = d3.time.scale.utc()
    .domain([new Date(2000, 0, 1), new Date(2003, 0, 1)])
    .range([0, w]),
    xAxis = d3.svg.axis().scale(x).tickSize(-20);
    svg.insert('rect').attr('width', w).attr('height', 100).attr('fill', '#ffccdd')
    var holder = svg.append('g').attr('class', 'axis');

    holder.call(xAxis);

    svg.on('click', function() {
    startX = x.copy();
    endX = x.copy().domain([new Date(2000, 1, 1), new Date(2001, 1, 1)]);
    holder.transition().duration(700).tween('step', stepRedraw);
    });

    function stepRedraw(d, i) {
    return function(t) {
    x.domain(d3.interpolateArray(startX.domain(), endX.domain())(t));
    holder.call(xAxis);
    }
    }
    </script>
  15. @tandu tandu revised this gist Jun 24, 2012. 1 changed file with 1 addition and 698 deletions.
    699 changes: 1 addition & 698 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -1,708 +1,11 @@
    <!DOCTYPE html>
    <meta charset="utf-8">
    <style>

    text {
    font: 12px sans-serif;
    }

    .axis path {
    fill: none;
    stroke: #000;
    stroke-opacity: .75;
    shape-rendering: crispEdges;
    }

    .x.axis path.domain,
    .y.axis path.domain {
    stroke-opacity: .75;
    }

    .axis line {
    fill: none;
    stroke: #000;
    stroke-opacity: .25;
    shape-rendering: crispEdges;
    }

    .axis line.zero {
    stroke-opacity: .75;
    }


    .lines path {
    fill: none;
    stroke-width: 1.5px;
    stroke-opacity: 1;
    }

    .point-paths path {
    stroke: none;
    }

    .point.hover {
    stroke: #000 !important;
    stroke-width: 15px;
    stroke-opacity: .2;
    }


    .legend .series {
    cursor: pointer;
    }

    .legend .disabled circle {
    fill-opacity: 0;
    }

    </style>
    <body>


    <svg id="test1"></svg>


    test
    <script src="http://mbostock.github.com/d3/d3.v2.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    <script>

    var nv = {models: {}};


    nv.models.legend = function() {
    var margin = {top: 5, right: 0, bottom: 5, left: 10},
    width = 400,
    height = 20,
    color = d3.scale.category20().range(),
    dispatch = d3.dispatch('toggle');
    //TODO: rethink communication between charts:
    // **Maybe everything should be through dispatch instead of linking using the same data
    // **Maybe not

    function chart(selection) {
    selection.each(function(data) {

    var wrap = d3.select(this).selectAll('g.legend').data([data]);
    var gEnter = wrap.enter().append('g').attr('class', 'legend').append('g');


    var g = wrap.select('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');


    var series = g.selectAll('.series')
    .data(function(d) { return d });
    series.exit().remove();
    var seriesEnter = series.enter().append('g').attr('class', 'series')
    .on('click', function(d, i) {
    d.disabled = !d.disabled;

    d3.select(this).classed('disabled', d.disabled);

    if (!data.filter(function(d) { return !d.disabled }).length) {
    data.map(function(d) {
    d.disabled = false;
    wrap.selectAll('.series').classed('disabled', false);
    return d;
    });
    }

    dispatch.toggle(d, i);
    });
    seriesEnter.append('circle')
    .style('fill', function(d, i){ return d.color || color[i * 2 % 20] })
    .style('stroke', function(d, i){ return d.color || color[i * 2 % 20] })
    .style('stroke-width', 2)
    .attr('r', 5);
    seriesEnter.append('text')
    .text(function(d) { return d.label })
    .attr('text-anchor', 'start')
    .attr('dy', '.32em')
    .attr('dx', '8');


    var ypos = 5,
    newxpos = 5,
    maxwidth = 0,
    xpos;
    series
    .attr('transform', function(d, i) {
    var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
    xpos = newxpos;

    //TODO: 1) Make sure dot + text of every series fits horizontally, or clip text to fix
    // 2) Consider making columns in line so dots line up
    // --all labels same width? or just all in the same column?
    // --optional, or forced always?
    if (width < margin.left + margin.right + xpos + length) {
    newxpos = xpos = 5;
    ypos += 20;
    }

    newxpos += length;
    if (newxpos > maxwidth) maxwidth = newxpos;

    return 'translate(' + xpos + ',' + ypos + ')';
    });

    //position legend as far right as possible within the total width
    g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');

    //update height value if calculated larger than current
    if (height < margin.top + margin.bottom + ypos + 15)
    height = margin.top + margin.bottom + ypos + 15;

    //TODO: Fix dimenstion calculations... now if height grows automatically, it doesn't shrink back
    // 1) Can have calc height vs set height and use largest
    // 2) Consider ONLY allowing a single dimension, height or width, and the other one always elastic

    });

    return chart;
    }

    chart.dispatch = dispatch;

    chart.margin = function(_) {
    if (!arguments.length) return margin;
    margin = _;
    return chart;
    };

    chart.width = function(_) {
    if (!arguments.length) return width;
    width = _;
    return chart;
    };

    chart.height = function(_) {
    if (!arguments.length) return height;
    height = _;
    return chart;
    };

    return chart;
    }










    nv.models.line = function() {
    var margin = {top: 0, right: 0, bottom: 0, left: 0},
    width = 960,
    height = 500,
    animate = 500,
    dotRadius = function() { return 2.5 },
    color = d3.scale.category10().range(),
    id = Math.floor(Math.random() * 10000), //Create semi-unique ID incase user doesn't select one
    x = d3.scale.linear(),
    y = d3.scale.linear();


    function chart(selection) {
    selection.each(function(data) {

    x .domain(d3.extent(d3.merge(data), function(d) { return d[0] } ))
    .range([0, width - margin.left - margin.right]);

    y .domain(d3.extent(d3.merge(data), function(d) { return d[1] } ))
    .range([height - margin.top - margin.bottom, 0]);


    var wrap = d3.select(this).selectAll('g.d3line').data([data]);
    var gEnter = wrap.enter().append('g').attr('class', 'd3line').append('g');

    gEnter.append('g').attr('class', 'lines');
    gEnter.append('g').attr('class', 'point-clips');
    gEnter.append('g').attr('class', 'points');
    gEnter.append('g').attr('class', 'point-paths');


    var g = wrap.select('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');


    var vertices = d3.merge(data.map(function(series, index) {
    return series.map(function(point) {
    return [x(point[0]), y(point[1]), index]; //adding series index to point because data is being flattened
    })
    })
    );


    //TODO: now that user can change ID, probably need to set ID, and anything that uses them, on update not just enter selection
    var voronoiClip = gEnter.append('g').attr('class', 'voronoi-clip')
    .append('clipPath')
    .attr('id', 'voronoi-clip-path-' + id)
    .append('rect');

    wrap.select('.voronoi-clip rect')
    .attr('x', -10) // TODO: reconsider this static -10, while it makes sense, maybe should calculate from margin
    .attr('y', -10)
    .attr('width', width - margin.left - margin.right + 20)
    .attr('height', height - margin.top - margin.bottom + 20);

    wrap.select('.point-paths')
    .attr('clip-path', 'url(#voronoi-clip-path)');


    //var pointClips = wrap.select('.point-clips').selectAll('clipPath') // **BROWSER BUG** can't reselect camel cased elements
    var pointClips = wrap.select('.point-clips').selectAll('.clip-path')
    .data(vertices);
    pointClips.enter().append('clipPath').attr('class', 'clip-path')
    .attr('id', function(d, i) { return 'clip-' + id + '-' + i })
    .append('circle')
    .attr('r', 20);
    pointClips.exit().remove();
    pointClips
    .attr('transform', function(d) { return d[3] !== 'none' ?
    'translate(' + d[0] + ',' + d[1] + ')' :
    'translate(-100,-100)' })


    //TODO: ****MUST REMOVE DUPLICATES**** ....figure out best equation for this
    var pointPaths = wrap.select('.point-paths').selectAll('path')
    .data(d3.geom.voronoi(vertices));

    pointPaths.enter().append('path')
    .attr('class', function(d,i) { return 'path-' + i; })
    .style('fill', d3.rgb(230, 230, 230,0))
    //.style('stroke', d3.rgb(230, 230, 230,0))
    .style('fill-opacity', 0);

    pointPaths
    .attr('clip-path', function(d,i) { return 'url(#clip-' + id + '-' + i +')'; })
    .attr('d', function(d) { return 'M' + d.join(',') + 'Z'; })
    .on('mouseover', function(d, i) {
    wrap.select('circle.point-' + i)
    .classed('hover', true)

    //TODO: Figure out what broke point interaction in Firefox

    //TODO: don't derive value, use ACTUAL... make sure to use axis tickFormat to format values in tooltip
    //log(Math.round(x.invert(wrap.select('circle.point-' + i).attr('cx'))*10000)/10000,
    //Math.round(y.invert(wrap.select('circle.point-' + i).attr('cy'))*10000)/10000);
    })
    .on('mouseout', function(d, i) {
    wrap.select('circle.point-' + i)
    .classed('hover', false)
    });


    //TODO: consider putting points ONTOP of voronoi paths, and adding hover toggle, this way there won't
    // be any funky or unreachable points, while random with the voronoi, some points near the edge
    // can be hard/impossible to get to (appears to be a very small cornor case)
    var points = wrap.select('.points').selectAll('circle.point')
    .data(vertices, function(d,i) { return d[0] + '-' + d[2] }) //TODO: FIX KEY FUNCTION!!
    points.enter().append('circle')
    .attr('class', function(d,i) { return 'point point-' + i + ' series' + d[2] }) //TODO: consider using unique ID
    .attr('cx', function(d) { return d[0] })
    .attr('cy', function(d) { return y.range()[0] });
    points.exit().remove();
    points
    .style('fill', function(d, i){ return color[d[2] % 20] })
    .attr('r', dotRadius())
    .transition().duration(animate)
    .attr('cx', function(d,i) { if (typeof d[0] == 'object') { console.log("Why? d[0]",d,i,vertices[i])} return vertices[i][0] })
    .attr('cy', function(d,i) { return vertices[i][1] });
    //TODO: Fix above workaround, likely causing points and paths to be mis align
    // ***think it only occurs when shrinking the vertical, appears to be caused by
    // the scroll bar being introduced, then removed, between the 2 calculations




    var lines = wrap.select('.lines').selectAll('.line')
    .data(function(d) { return d }, function(d,i) { return color[i] ? color[i].substr(1) : undefined }); //TODO: Fix key function!!!
    lines.enter().append('g').attr('class', function(d,i) { return 'line series' + i });
    lines.exit().remove();
    lines
    .style('fill', function(d,i){ return color[i % 20] })
    .style('stroke', function(d,i){ return color[i % 20] });


    var paths = lines.selectAll('path')
    .data(function(d) { return [d] });
    paths.enter().append('path')
    .attr('d', d3.svg.line()
    .x(function(d) { return x(d[0]) })
    .y(function(d) { return y.range()[0] })
    );
    paths.exit().remove();
    paths
    .transition().duration(animate)
    .attr('d', d3.svg.line()
    .x(function(d) { return x(d[0]) })
    .y(function(d) { return y(d[1]) })
    );


    /*
    var points = lines.selectAll('circle.point')
    .data(function(d) { return d.data });
    points.enter().append('circle').attr('class', 'point')
    .attr('cx', function(d) { return x(d.x) })
    .attr('cy', function(d) { return y(y.domain()[0]) });
    points.exit().remove();
    points
    .transition().duration(animate)
    .attr('cx', function(d) { return x(d.x) })
    .attr('cy', function(d) { return y(d.y) })
    .attr('r', dotRadius());
    */


    });

    return chart;
    }


    chart.margin = function(_) {
    if (!arguments.length) return margin;
    margin = _;
    return chart;
    };

    chart.width = function(_) {
    if (!arguments.length) return width;
    width = _;
    return chart;
    };

    chart.height = function(_) {
    if (!arguments.length) return height;
    height = _;
    return chart;
    };

    chart.dotRadius = function(_) {
    if (!arguments.length) return dotRadius;
    dotRadius = d3.functor(_);
    return chart;
    };

    chart.animate = function(_) {
    if (!arguments.length) return animate;
    animate = _;
    return chart;
    };

    chart.color = function(_) {
    if (!arguments.length) return color;
    color = _;
    return chart;
    };

    chart.id = function(_) {
    if (!arguments.length) return id;
    id = _;
    return chart;
    };


    return chart;
    }











    nv.models.lineWithLegend = function() {
    var margin = {top: 20, right: 20, bottom: 50, left: 60},
    width = 960,
    height = 500,
    animate = 500,
    dotRadius = function() { return 2.5 },
    xAxisRender = true,
    yAxisRender = true,
    xAxisLabelText = false,
    yAxisLabelText = false,
    color = d3.scale.category20().range();

    var x = d3.scale.linear(),
    y = d3.scale.linear(),
    xAxis = d3.svg.axis().scale(x).orient('bottom'),
    yAxis = d3.svg.axis().scale(y).orient('left'),
    legend = nv.models.legend().height(30),
    lines = nv.models.line();


    function chart(selection) {
    selection.each(function(data) {
    var series = data.filter(function(d) { return !d.disabled })
    .map(function(d) { return d.data });

    x .domain(d3.extent(d3.merge(series), function(d) { return d[0] } ))
    .range([0, width - margin.left - margin.right]);

    y .domain(d3.extent(d3.merge(series), function(d) { return d[1] } ))
    .range([height - margin.top - margin.bottom, 0]);

    lines
    .width(width - margin.left - margin.right)
    .height(height - margin.top - margin.bottom)
    .color(data.map(function(d,i) {
    return d.color || color[i * 2 % 20];
    }).filter(function(d,i) { return !data[i].disabled }))

    xAxis
    .ticks( width / 100 )
    .tickSize(-(height - margin.top - margin.bottom), 0);
    yAxis
    .ticks( height / 36 )
    .tickSize(-(width - margin.right - margin.left), 0);


    var wrap = d3.select(this).selectAll('g.wrap').data([data]);
    var gEnter = wrap.enter().append('g').attr('class', 'wrap d3lineWithLegend').append('g');

    gEnter.append('g').attr('class', 'legendWrap');
    gEnter.append('g').attr('class', 'x axis');
    gEnter.append('g').attr('class', 'y axis');
    gEnter.append('g').attr('class', 'linesWrap');


    legend.dispatch.on('toggle', function(d,i) { chart(selection) });

    legend.width(width/2 - margin.right);

    wrap.select('.legendWrap')
    .datum(data)
    .attr('transform', 'translate(' + (width/2 - margin.left) + ',' + (-legend.height()) +')')
    .call(legend);


    //TODO: margins should be adjusted based on what components are used: axes, axis labels, legend

    var g = wrap.select('g')
    .attr('transform', 'translate(' + margin.left + ',' + legend.height() + ')');



    wrap.select('.linesWrap')
    .datum(series)
    .call(lines);



    //TODO: Extract Axis component with Label for reuse
    var xAxisLabel = g.select('.x.axis').selectAll('text.axislabel')
    .data([xAxisLabelText || null]);
    xAxisLabel.enter().append('text').attr('class', 'axislabel')
    .attr('text-anchor', 'middle')
    .attr('x', x.range()[1] / 2)
    .attr('y', margin.bottom - 20);
    xAxisLabel.exit().remove();
    xAxisLabel.text(function(d) { return d });

    var yAxisLabel = g.select('.y.axis').selectAll('text.axislabel')
    .data([yAxisLabelText || null]);
    yAxisLabel.enter().append('text').attr('class', 'axislabel')
    .attr('transform', 'rotate(-90)')
    .attr('text-anchor', 'middle')
    .attr('y', 20 - margin.left);
    yAxisLabel.exit().remove();
    yAxisLabel
    .attr('x', -y.range()[0] / 2)
    .text(function(d) { return d });


    g.select('.x.axis')
    .attr('transform', 'translate(0,' + y.range()[0] + ')')
    .call(xAxis)
    .selectAll('line.tick')
    .filter(function(d) { return !d })
    .classed('zero', true);

    g.select('.y.axis')
    .call(yAxis)
    .selectAll('line.tick')
    .filter(function(d) { return !d })
    .classed('zero', true);
    });

    return chart;
    }


    chart.margin = function(_) {
    if (!arguments.length) return margin;
    margin = _;
    return chart;
    };

    chart.width = function(_) {
    if (!arguments.length) return width;
    width = _;
    return chart;
    };

    chart.height = function(_) {
    if (!arguments.length) return height;
    height = _;
    return chart;
    };

    chart.dotRadius = function(_) {
    if (!arguments.length) return dotRadius;
    dotRadius = d3.functor(_);
    lines.dotRadius = d3.functor(_);
    return chart;
    };

    chart.animate = function(_) {
    if (!arguments.length) return animate;
    animate = _;
    lines.animate(_);
    return chart;
    };

    //TODO: consider directly exposing both axes
    //chart.xAxis = xAxis;

    //Expose the x-axis' tickFormat method.
    chart.xAxis = {};
    d3.rebind(chart.xAxis, xAxis, 'tickFormat');

    chart.xAxis.label = function(_) {
    if (!arguments.length) return xAxisLabelText;
    xAxisLabelText = _;
    return chart;
    }

    // Expose the y-axis' tickFormat method.
    //chart.yAxis = yAxis;

    chart.yAxis = {};
    d3.rebind(chart.yAxis, yAxis, 'tickFormat');

    chart.yAxis.label = function(_) {
    if (!arguments.length) return yAxisLabelText;
    yAxisLabelText = _;
    return chart;
    }

    return chart;
    }











    $(document).ready(function() {
    var margin = {top: 20, right: 10, bottom: 50, left: 60},
    chart = nv.models.lineWithLegend()
    .xAxis.label('Time (ms)')
    .width(width(margin))
    .height(height(margin))
    .yAxis.label('Voltage (v)');


    //chart.xaxis.tickFormat(d3.format(".02f"))

    d3.select('#test1')
    .datum(sinAndCos())
    .attr('width', width(margin))
    .attr('height', height(margin))
    .call(chart);



    $(window).resize(function() {
    var margin = chart.margin(),
    animate = chart.animate();

    chart
    .animate(0)
    .width(width(margin))
    .height(height(margin));

    d3.select('#test1')
    .attr('width', width(margin))
    .attr('height', height(margin))
    .call(chart);

    chart
    .animate(animate);
    });




    function width(margin) {
    var w = $(window).width() - 40;

    return ( (w - margin.left - margin.right - 20) < 0 ) ? margin.left + margin.right + 2 : w;
    }

    function height(margin) {
    var h = $(window).height() - 40;

    return ( h - margin.top - margin.bottom - 20 < 0 ) ?
    margin.top + margin.bottom + 2 : h;
    }


    //data
    function sinAndCos() {
    var sin = [],
    cos = [];

    for (var i = 0; i < 100; i++) {
    sin.push([ i, Math.sin(i/10)]);
    cos.push([ i, .5 * Math.cos(i/10)]);
    }

    return [
    {
    data: sin,
    //color: "#ff7f0e",
    label: "Sine Wave"
    },
    {
    data: cos,
    //color: "#2ca02c",
    label: "Cosine Wave"
    }
    ];

    /*
    //WHY DOES DATA IN THIS ORDER RUIN TOGGLE AFTER FIRST CLICK IN CHROME?!?!?!?
    return [
    {
    data: cos,
    //color: "#2ca02c",
    label: "Cosine Wave"
    },
    {
    data: sin,
    //color: "#ff7f0e",
    label: "Sine Wave"
    }
    ];
    */
    }

    });


    </script>
  16. @tandu tandu created this gist Jun 24, 2012.
    708 changes: 708 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,708 @@
    <!DOCTYPE html>
    <meta charset="utf-8">
    <style>

    text {
    font: 12px sans-serif;
    }

    .axis path {
    fill: none;
    stroke: #000;
    stroke-opacity: .75;
    shape-rendering: crispEdges;
    }

    .x.axis path.domain,
    .y.axis path.domain {
    stroke-opacity: .75;
    }

    .axis line {
    fill: none;
    stroke: #000;
    stroke-opacity: .25;
    shape-rendering: crispEdges;
    }

    .axis line.zero {
    stroke-opacity: .75;
    }


    .lines path {
    fill: none;
    stroke-width: 1.5px;
    stroke-opacity: 1;
    }

    .point-paths path {
    stroke: none;
    }

    .point.hover {
    stroke: #000 !important;
    stroke-width: 15px;
    stroke-opacity: .2;
    }


    .legend .series {
    cursor: pointer;
    }

    .legend .disabled circle {
    fill-opacity: 0;
    }

    </style>
    <body>


    <svg id="test1"></svg>


    <script src="http://mbostock.github.com/d3/d3.v2.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    <script>

    var nv = {models: {}};


    nv.models.legend = function() {
    var margin = {top: 5, right: 0, bottom: 5, left: 10},
    width = 400,
    height = 20,
    color = d3.scale.category20().range(),
    dispatch = d3.dispatch('toggle');
    //TODO: rethink communication between charts:
    // **Maybe everything should be through dispatch instead of linking using the same data
    // **Maybe not

    function chart(selection) {
    selection.each(function(data) {

    var wrap = d3.select(this).selectAll('g.legend').data([data]);
    var gEnter = wrap.enter().append('g').attr('class', 'legend').append('g');


    var g = wrap.select('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');


    var series = g.selectAll('.series')
    .data(function(d) { return d });
    series.exit().remove();
    var seriesEnter = series.enter().append('g').attr('class', 'series')
    .on('click', function(d, i) {
    d.disabled = !d.disabled;

    d3.select(this).classed('disabled', d.disabled);

    if (!data.filter(function(d) { return !d.disabled }).length) {
    data.map(function(d) {
    d.disabled = false;
    wrap.selectAll('.series').classed('disabled', false);
    return d;
    });
    }

    dispatch.toggle(d, i);
    });
    seriesEnter.append('circle')
    .style('fill', function(d, i){ return d.color || color[i * 2 % 20] })
    .style('stroke', function(d, i){ return d.color || color[i * 2 % 20] })
    .style('stroke-width', 2)
    .attr('r', 5);
    seriesEnter.append('text')
    .text(function(d) { return d.label })
    .attr('text-anchor', 'start')
    .attr('dy', '.32em')
    .attr('dx', '8');


    var ypos = 5,
    newxpos = 5,
    maxwidth = 0,
    xpos;
    series
    .attr('transform', function(d, i) {
    var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
    xpos = newxpos;

    //TODO: 1) Make sure dot + text of every series fits horizontally, or clip text to fix
    // 2) Consider making columns in line so dots line up
    // --all labels same width? or just all in the same column?
    // --optional, or forced always?
    if (width < margin.left + margin.right + xpos + length) {
    newxpos = xpos = 5;
    ypos += 20;
    }

    newxpos += length;
    if (newxpos > maxwidth) maxwidth = newxpos;

    return 'translate(' + xpos + ',' + ypos + ')';
    });

    //position legend as far right as possible within the total width
    g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');

    //update height value if calculated larger than current
    if (height < margin.top + margin.bottom + ypos + 15)
    height = margin.top + margin.bottom + ypos + 15;

    //TODO: Fix dimenstion calculations... now if height grows automatically, it doesn't shrink back
    // 1) Can have calc height vs set height and use largest
    // 2) Consider ONLY allowing a single dimension, height or width, and the other one always elastic

    });

    return chart;
    }

    chart.dispatch = dispatch;

    chart.margin = function(_) {
    if (!arguments.length) return margin;
    margin = _;
    return chart;
    };

    chart.width = function(_) {
    if (!arguments.length) return width;
    width = _;
    return chart;
    };

    chart.height = function(_) {
    if (!arguments.length) return height;
    height = _;
    return chart;
    };

    return chart;
    }










    nv.models.line = function() {
    var margin = {top: 0, right: 0, bottom: 0, left: 0},
    width = 960,
    height = 500,
    animate = 500,
    dotRadius = function() { return 2.5 },
    color = d3.scale.category10().range(),
    id = Math.floor(Math.random() * 10000), //Create semi-unique ID incase user doesn't select one
    x = d3.scale.linear(),
    y = d3.scale.linear();


    function chart(selection) {
    selection.each(function(data) {

    x .domain(d3.extent(d3.merge(data), function(d) { return d[0] } ))
    .range([0, width - margin.left - margin.right]);

    y .domain(d3.extent(d3.merge(data), function(d) { return d[1] } ))
    .range([height - margin.top - margin.bottom, 0]);


    var wrap = d3.select(this).selectAll('g.d3line').data([data]);
    var gEnter = wrap.enter().append('g').attr('class', 'd3line').append('g');

    gEnter.append('g').attr('class', 'lines');
    gEnter.append('g').attr('class', 'point-clips');
    gEnter.append('g').attr('class', 'points');
    gEnter.append('g').attr('class', 'point-paths');


    var g = wrap.select('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');


    var vertices = d3.merge(data.map(function(series, index) {
    return series.map(function(point) {
    return [x(point[0]), y(point[1]), index]; //adding series index to point because data is being flattened
    })
    })
    );


    //TODO: now that user can change ID, probably need to set ID, and anything that uses them, on update not just enter selection
    var voronoiClip = gEnter.append('g').attr('class', 'voronoi-clip')
    .append('clipPath')
    .attr('id', 'voronoi-clip-path-' + id)
    .append('rect');

    wrap.select('.voronoi-clip rect')
    .attr('x', -10) // TODO: reconsider this static -10, while it makes sense, maybe should calculate from margin
    .attr('y', -10)
    .attr('width', width - margin.left - margin.right + 20)
    .attr('height', height - margin.top - margin.bottom + 20);

    wrap.select('.point-paths')
    .attr('clip-path', 'url(#voronoi-clip-path)');


    //var pointClips = wrap.select('.point-clips').selectAll('clipPath') // **BROWSER BUG** can't reselect camel cased elements
    var pointClips = wrap.select('.point-clips').selectAll('.clip-path')
    .data(vertices);
    pointClips.enter().append('clipPath').attr('class', 'clip-path')
    .attr('id', function(d, i) { return 'clip-' + id + '-' + i })
    .append('circle')
    .attr('r', 20);
    pointClips.exit().remove();
    pointClips
    .attr('transform', function(d) { return d[3] !== 'none' ?
    'translate(' + d[0] + ',' + d[1] + ')' :
    'translate(-100,-100)' })


    //TODO: ****MUST REMOVE DUPLICATES**** ....figure out best equation for this
    var pointPaths = wrap.select('.point-paths').selectAll('path')
    .data(d3.geom.voronoi(vertices));

    pointPaths.enter().append('path')
    .attr('class', function(d,i) { return 'path-' + i; })
    .style('fill', d3.rgb(230, 230, 230,0))
    //.style('stroke', d3.rgb(230, 230, 230,0))
    .style('fill-opacity', 0);

    pointPaths
    .attr('clip-path', function(d,i) { return 'url(#clip-' + id + '-' + i +')'; })
    .attr('d', function(d) { return 'M' + d.join(',') + 'Z'; })
    .on('mouseover', function(d, i) {
    wrap.select('circle.point-' + i)
    .classed('hover', true)

    //TODO: Figure out what broke point interaction in Firefox

    //TODO: don't derive value, use ACTUAL... make sure to use axis tickFormat to format values in tooltip
    //log(Math.round(x.invert(wrap.select('circle.point-' + i).attr('cx'))*10000)/10000,
    //Math.round(y.invert(wrap.select('circle.point-' + i).attr('cy'))*10000)/10000);
    })
    .on('mouseout', function(d, i) {
    wrap.select('circle.point-' + i)
    .classed('hover', false)
    });


    //TODO: consider putting points ONTOP of voronoi paths, and adding hover toggle, this way there won't
    // be any funky or unreachable points, while random with the voronoi, some points near the edge
    // can be hard/impossible to get to (appears to be a very small cornor case)
    var points = wrap.select('.points').selectAll('circle.point')
    .data(vertices, function(d,i) { return d[0] + '-' + d[2] }) //TODO: FIX KEY FUNCTION!!
    points.enter().append('circle')
    .attr('class', function(d,i) { return 'point point-' + i + ' series' + d[2] }) //TODO: consider using unique ID
    .attr('cx', function(d) { return d[0] })
    .attr('cy', function(d) { return y.range()[0] });
    points.exit().remove();
    points
    .style('fill', function(d, i){ return color[d[2] % 20] })
    .attr('r', dotRadius())
    .transition().duration(animate)
    .attr('cx', function(d,i) { if (typeof d[0] == 'object') { console.log("Why? d[0]",d,i,vertices[i])} return vertices[i][0] })
    .attr('cy', function(d,i) { return vertices[i][1] });
    //TODO: Fix above workaround, likely causing points and paths to be mis align
    // ***think it only occurs when shrinking the vertical, appears to be caused by
    // the scroll bar being introduced, then removed, between the 2 calculations




    var lines = wrap.select('.lines').selectAll('.line')
    .data(function(d) { return d }, function(d,i) { return color[i] ? color[i].substr(1) : undefined }); //TODO: Fix key function!!!
    lines.enter().append('g').attr('class', function(d,i) { return 'line series' + i });
    lines.exit().remove();
    lines
    .style('fill', function(d,i){ return color[i % 20] })
    .style('stroke', function(d,i){ return color[i % 20] });


    var paths = lines.selectAll('path')
    .data(function(d) { return [d] });
    paths.enter().append('path')
    .attr('d', d3.svg.line()
    .x(function(d) { return x(d[0]) })
    .y(function(d) { return y.range()[0] })
    );
    paths.exit().remove();
    paths
    .transition().duration(animate)
    .attr('d', d3.svg.line()
    .x(function(d) { return x(d[0]) })
    .y(function(d) { return y(d[1]) })
    );


    /*
    var points = lines.selectAll('circle.point')
    .data(function(d) { return d.data });
    points.enter().append('circle').attr('class', 'point')
    .attr('cx', function(d) { return x(d.x) })
    .attr('cy', function(d) { return y(y.domain()[0]) });
    points.exit().remove();
    points
    .transition().duration(animate)
    .attr('cx', function(d) { return x(d.x) })
    .attr('cy', function(d) { return y(d.y) })
    .attr('r', dotRadius());
    */


    });

    return chart;
    }


    chart.margin = function(_) {
    if (!arguments.length) return margin;
    margin = _;
    return chart;
    };

    chart.width = function(_) {
    if (!arguments.length) return width;
    width = _;
    return chart;
    };

    chart.height = function(_) {
    if (!arguments.length) return height;
    height = _;
    return chart;
    };

    chart.dotRadius = function(_) {
    if (!arguments.length) return dotRadius;
    dotRadius = d3.functor(_);
    return chart;
    };

    chart.animate = function(_) {
    if (!arguments.length) return animate;
    animate = _;
    return chart;
    };

    chart.color = function(_) {
    if (!arguments.length) return color;
    color = _;
    return chart;
    };

    chart.id = function(_) {
    if (!arguments.length) return id;
    id = _;
    return chart;
    };


    return chart;
    }











    nv.models.lineWithLegend = function() {
    var margin = {top: 20, right: 20, bottom: 50, left: 60},
    width = 960,
    height = 500,
    animate = 500,
    dotRadius = function() { return 2.5 },
    xAxisRender = true,
    yAxisRender = true,
    xAxisLabelText = false,
    yAxisLabelText = false,
    color = d3.scale.category20().range();

    var x = d3.scale.linear(),
    y = d3.scale.linear(),
    xAxis = d3.svg.axis().scale(x).orient('bottom'),
    yAxis = d3.svg.axis().scale(y).orient('left'),
    legend = nv.models.legend().height(30),
    lines = nv.models.line();


    function chart(selection) {
    selection.each(function(data) {
    var series = data.filter(function(d) { return !d.disabled })
    .map(function(d) { return d.data });

    x .domain(d3.extent(d3.merge(series), function(d) { return d[0] } ))
    .range([0, width - margin.left - margin.right]);

    y .domain(d3.extent(d3.merge(series), function(d) { return d[1] } ))
    .range([height - margin.top - margin.bottom, 0]);

    lines
    .width(width - margin.left - margin.right)
    .height(height - margin.top - margin.bottom)
    .color(data.map(function(d,i) {
    return d.color || color[i * 2 % 20];
    }).filter(function(d,i) { return !data[i].disabled }))

    xAxis
    .ticks( width / 100 )
    .tickSize(-(height - margin.top - margin.bottom), 0);
    yAxis
    .ticks( height / 36 )
    .tickSize(-(width - margin.right - margin.left), 0);


    var wrap = d3.select(this).selectAll('g.wrap').data([data]);
    var gEnter = wrap.enter().append('g').attr('class', 'wrap d3lineWithLegend').append('g');

    gEnter.append('g').attr('class', 'legendWrap');
    gEnter.append('g').attr('class', 'x axis');
    gEnter.append('g').attr('class', 'y axis');
    gEnter.append('g').attr('class', 'linesWrap');


    legend.dispatch.on('toggle', function(d,i) { chart(selection) });

    legend.width(width/2 - margin.right);

    wrap.select('.legendWrap')
    .datum(data)
    .attr('transform', 'translate(' + (width/2 - margin.left) + ',' + (-legend.height()) +')')
    .call(legend);


    //TODO: margins should be adjusted based on what components are used: axes, axis labels, legend

    var g = wrap.select('g')
    .attr('transform', 'translate(' + margin.left + ',' + legend.height() + ')');



    wrap.select('.linesWrap')
    .datum(series)
    .call(lines);



    //TODO: Extract Axis component with Label for reuse
    var xAxisLabel = g.select('.x.axis').selectAll('text.axislabel')
    .data([xAxisLabelText || null]);
    xAxisLabel.enter().append('text').attr('class', 'axislabel')
    .attr('text-anchor', 'middle')
    .attr('x', x.range()[1] / 2)
    .attr('y', margin.bottom - 20);
    xAxisLabel.exit().remove();
    xAxisLabel.text(function(d) { return d });

    var yAxisLabel = g.select('.y.axis').selectAll('text.axislabel')
    .data([yAxisLabelText || null]);
    yAxisLabel.enter().append('text').attr('class', 'axislabel')
    .attr('transform', 'rotate(-90)')
    .attr('text-anchor', 'middle')
    .attr('y', 20 - margin.left);
    yAxisLabel.exit().remove();
    yAxisLabel
    .attr('x', -y.range()[0] / 2)
    .text(function(d) { return d });


    g.select('.x.axis')
    .attr('transform', 'translate(0,' + y.range()[0] + ')')
    .call(xAxis)
    .selectAll('line.tick')
    .filter(function(d) { return !d })
    .classed('zero', true);

    g.select('.y.axis')
    .call(yAxis)
    .selectAll('line.tick')
    .filter(function(d) { return !d })
    .classed('zero', true);
    });

    return chart;
    }


    chart.margin = function(_) {
    if (!arguments.length) return margin;
    margin = _;
    return chart;
    };

    chart.width = function(_) {
    if (!arguments.length) return width;
    width = _;
    return chart;
    };

    chart.height = function(_) {
    if (!arguments.length) return height;
    height = _;
    return chart;
    };

    chart.dotRadius = function(_) {
    if (!arguments.length) return dotRadius;
    dotRadius = d3.functor(_);
    lines.dotRadius = d3.functor(_);
    return chart;
    };

    chart.animate = function(_) {
    if (!arguments.length) return animate;
    animate = _;
    lines.animate(_);
    return chart;
    };

    //TODO: consider directly exposing both axes
    //chart.xAxis = xAxis;

    //Expose the x-axis' tickFormat method.
    chart.xAxis = {};
    d3.rebind(chart.xAxis, xAxis, 'tickFormat');

    chart.xAxis.label = function(_) {
    if (!arguments.length) return xAxisLabelText;
    xAxisLabelText = _;
    return chart;
    }

    // Expose the y-axis' tickFormat method.
    //chart.yAxis = yAxis;

    chart.yAxis = {};
    d3.rebind(chart.yAxis, yAxis, 'tickFormat');

    chart.yAxis.label = function(_) {
    if (!arguments.length) return yAxisLabelText;
    yAxisLabelText = _;
    return chart;
    }

    return chart;
    }











    $(document).ready(function() {
    var margin = {top: 20, right: 10, bottom: 50, left: 60},
    chart = nv.models.lineWithLegend()
    .xAxis.label('Time (ms)')
    .width(width(margin))
    .height(height(margin))
    .yAxis.label('Voltage (v)');


    //chart.xaxis.tickFormat(d3.format(".02f"))

    d3.select('#test1')
    .datum(sinAndCos())
    .attr('width', width(margin))
    .attr('height', height(margin))
    .call(chart);



    $(window).resize(function() {
    var margin = chart.margin(),
    animate = chart.animate();

    chart
    .animate(0)
    .width(width(margin))
    .height(height(margin));

    d3.select('#test1')
    .attr('width', width(margin))
    .attr('height', height(margin))
    .call(chart);

    chart
    .animate(animate);
    });




    function width(margin) {
    var w = $(window).width() - 40;

    return ( (w - margin.left - margin.right - 20) < 0 ) ? margin.left + margin.right + 2 : w;
    }

    function height(margin) {
    var h = $(window).height() - 40;

    return ( h - margin.top - margin.bottom - 20 < 0 ) ?
    margin.top + margin.bottom + 2 : h;
    }


    //data
    function sinAndCos() {
    var sin = [],
    cos = [];

    for (var i = 0; i < 100; i++) {
    sin.push([ i, Math.sin(i/10)]);
    cos.push([ i, .5 * Math.cos(i/10)]);
    }

    return [
    {
    data: sin,
    //color: "#ff7f0e",
    label: "Sine Wave"
    },
    {
    data: cos,
    //color: "#2ca02c",
    label: "Cosine Wave"
    }
    ];

    /*
    //WHY DOES DATA IN THIS ORDER RUIN TOGGLE AFTER FIRST CLICK IN CHROME?!?!?!?
    return [
    {
    data: cos,
    //color: "#2ca02c",
    label: "Cosine Wave"
    },
    {
    data: sin,
    //color: "#ff7f0e",
    label: "Sine Wave"
    }
    ];
    */
    }

    });


    </script>