title: D3 focus/context chart: synchronization of brushing and panning
body: <p>I have a d3 focus/context chart where I would like to be able to pan on the focus portion after brushing the context, and I would like the brushed section of the context area to move in sync with the panning of the focus area.  However, when I click on the focus portion after I select a region in the context chart, the focus scale changes and the points no longer show up at the correct coordinates.  The following code is available on <a href="http://jsfiddle.net/sNPKX/2" rel="nofollow">jsfiddle</a> as well:</p>
  
  <pre><code>&lt;html&gt;
     &lt;head&gt;
        &lt;meta http-equiv="content-type" content="text/html; charset=UTF-8"&gt;
        &lt;title&gt; - jsFiddle demo&lt;/title&gt;
        &lt;script type="text/javascript" src="http://d3js.org/d3.v3.min.js"&gt;&lt;/script&gt;
        &lt;link rel="stylesheet" type="text/css" href="/css/result-light.css"&gt;
        &lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
        &lt;style type="text/css"&gt;
           circle {
           -webkit-transition: fill-opacity 250ms linear;
           }
           .selecting circle {
           fill-opacity: .5;
           }
           .selecting circle.selected {
           stroke: #f00;
           }
           .brush .extent {
           stroke: #B8C6D0;
           fill-opacity: .125;
           shape-rendering: crispEdges;
           }
           #context .axis path.domain {
           stroke: lightsteelblue;
           stroke-width: 5px;
           }
           #context .tick {
           stroke:black;
           stroke-width: 1px;
           }
           #context .x .tick {
           stroke:black;
           stroke-width: 2px;
           }
           .axis path, .axis line {
           fill: none;
           stroke: #ddd;
           stroke-width: 1px;
           shape-rendering: crispEdges;
           }
           .axis path {
           stroke: #999;
           stroke-width: 2px;
           }
        &lt;/style&gt;
        &lt;script type="text/javascript"&gt;//&lt;![CDATA[ 
      var data = [{
          Id: "1",
          Year: 1950,
          Relevance: 55,
          Category: "Cat1",
          SpecFreq: 5,
          GenFreq: 10
      }, {
          Id: "2",
          Year: 1975,
          Relevance: 25,
          Category: "Cat1",
          SpecFreq: 2,
          GenFreq: 31
      }, {
          Id: "3",
          Year: 1990,
          Relevance: 75,
          Category: "Cat1",
          SpecFreq: 8,
          GenFreq: 23
      }, {
          Id: "4",
          Year: 1970,
          Relevance: 45,
          Category: "Cat1",
          SpecFreq: 17,
          GenFreq: 60
      }, {
          Id: "5",
          Year: 1985,
          Relevance: 90,
          Category: "Cat1",
          SpecFreq: 17,
          GenFreq: 25
      }];
  
      $(function () {
          //dimensions
          var margin = {
              top: 5.5,
              right: 19.5,
              bottom: 39.5,
              left: 39.5
          };
  
          //data domain extents
          var extentX = d3.extent(data, function (d) {
              return d.Year;
          });
          var extentY = d3.extent(data, function (d) {
              return d.Relevance;
          });
  
          var focusAxisOptions = {
              x: {
                  ticks: {
                      format: d3.format("d"),
                      size: -1* (500 - margin.top - margin.bottom),
                      ticks: 10
                  },
                  showLabel: true
              },
              y: {
                  ticks: {
                      format: d3.format(""),
                      size: -1 * (800 - margin.left - margin.right),
                      ticks: 10
                  },
                  showLabel: true
              }
          };
  
          var contextAxisOptions = {
              x: {
                  ticks: {
                      format: d3.format("d"),
                      size: -1 * (100 - margin.top - margin.bottom),
                      ticks: 10
                  },
                  showLabel: true
              },
              y: {
                  ticks: {
                      format: "",
                      size: 0,
                      ticks: 0
                  },
                  showLabel: false
              }
          };
  
          var focus = DrawChart(data, margin, 800 - margin.left - margin.right, 500 - margin.top - margin.bottom, extentX, extentY, focusAxisOptions);
          var context = DrawChart(data, margin, 800 - margin.left - margin.right, 100 - margin.top - margin.bottom, extentX, extentY, contextAxisOptions);
  
          MakeContextBrushable(context, focus);
  
          MakeFocusZoomable(focus);
      });
  
      function DrawChart(data, margin, width, height, extentX, extentY, axisOptions) {
  
          //pad extents to provide some extra "blank" areas around edge of graph
          var paddedExtentX = [extentX[0] - 5, extentX[1] +5];
          var paddedExtentY = [extentY[0] - 5, extentY[1] +5];
  
          //scales
          var x = d3.scale.linear().domain(paddedExtentX).range([0, width]);
          var y = d3.scale.linear().domain(paddedExtentY).range([height, 0]);
          var radiusMax = .025 * width;
          var radius = d3.scale.sqrt().domain([0, 100]).range([0, radiusMax]);
          var color = d3.scale.ordinal().domain(["Cat1", "Cat2", "Cat3"]).range(["#b7b8a0", "#898a72", "#878772"]);
  
          //axes
          var xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(axisOptions.x.ticks.format).tickSize(axisOptions.x.ticks.size).ticks(axisOptions.x.ticks.ticks);
          var yAxis = d3.svg.axis().scale(y).orient("left").tickFormat(axisOptions.y.ticks.format).tickSize(axisOptions.y.ticks.size).ticks(axisOptions.y.ticks.ticks);
  
          //create and size svg element
          var svg = d3.select("#chart").append("svg")
              .attr("width", width + margin.left + margin.right)
              .attr("height", height + margin.top + margin.bottom)
              .style("float", "left")
              .style("clear", "left");
  
          var g = svg
              .append("g")
              .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); ;
  
          // Add the x-axis.
          g.append("g")
              .attr("class", "x axis")
              .attr("transform", "translate(0," + height + ")")
              .call(xAxis)
              .selectAll("text")
              .style("font-size", "10px")
              .attr("dy", "1.5em");
  
          // Add the y-axis.
          g.append("g")
              .attr("class", "y axis")
              .call(yAxis)
              .selectAll("text")
              .style("font-size", "10px")
              .attr("dx", "-1em");
  
          // Add the x-axis label.
          if (axisOptions.x.showLabel) {
              g.append("text")
              .attr("class", "x label")
              .attr("text-anchor", "end")
              .attr("x", width / 2)
              .attr("y", height + 35)
              .text(" Year");
          }
  
          // Add the y-axis label.
          if (axisOptions.y.showLabel) {
              g.append("text")
              .attr("class", "y label")
              .attr("text-anchor", "end")
              .attr("x", -1 * height / 2)
              .attr("y", -40)
              .attr("dy", ".75em")
              .attr("transform", "rotate(-90)")
              .text("Relevance");
          }
  
          //plot genFreq
          var gGenerally = g.append("g").selectAll("circle")
              .data(data)
              .enter().append("circle")
              .style("fill", function (d) {
                  return color(d.Category);
              })
              .attr("cx", function (d) {
                  return x(d.Year);
              })
              .attr("cy", function (d) {
                  return y(d.Relevance);
              })
              .attr("r", function (d) {
                  return radius(d.GenFreq);
              })
              .append("title")
              .text(function (d) {
                  return "(" + d.Year + ", " + d.Relevance + ", " + d.GenFreq + ", " + d.SpecFreq + ")";
              });
  
          //plot specFreq
          var gWithin = g.append("g").selectAll("circle")
              .data(data)
              .enter().append("circle")
              .style("fill", function (d) {
                  return "#d6d487";
              })
              .attr("cx", function (d) {
                  return x(d.Year);
              })
              .attr("cy", function (d) {
                  return y(d.Relevance);
              })
              .attr("r", function (d) {
                  return radius(d.SpecFreq);
              })
              .append("title")
              .text(function (d) {
                  return "(" + d.Year + ", " + d.Relevance + ", " + d.GenFreq + ", " + d.SpecFreq + ")";
              });
  
          var chart = {
              svg: svg,
              g: g,
              x: x,
              y: y,
              xAxis: xAxis,
              yAxis: yAxis
          }
          return chart;
      }
  
      function MakeContextBrushable(context, focus) {
          var brush = d3.svg.brush().x(context.x).y(context.y)
          .on("brushstart", brushstart)
          .on("brush", brushmove)
          .on("brushend", brushend);
  
          context.g.append("g")
          .attr("class", "brush")
          .call(brush);
  
          function brushstart() {
              context.svg.classed("selecting", true);
          }
  
          function brushmove() {
              var e = d3.event.target.extent();
              var circle = context.svg.selectAll("circle");
              circle.classed("selected", function (d) {
                  return e[0][0] &lt;= d["DecisionYear"] &amp;&amp; d["DecisionYear"] &lt;= e[1][0]
              &amp;&amp; e[0][1] &lt;= d["Relevance"] &amp;&amp; d["Relevance"] &lt;= e[1][1];
              });
          }
  
          function brushend() {
              context.svg.classed("selecting", !d3.event.target.empty());
              if (!d3.event.target.empty()) {
                  var e = d3.event.target.extent();
                  focus.x.domain([e[0][0], e[1][0]]);
                  focus.y.domain([e[0][1], e[1][1]]);
  
                  focus.g.select(".x.axis").call(focus.xAxis)
                      .selectAll("text")
                      .style("font-size", "10px")
                      .attr("dx", "-1em");
                  focus.g.select(".y.axis").call(focus.yAxis)
                      .selectAll("text")
                      .style("font-size", "10px")
                      .attr("dx", "-1em");
                  var circle = focus.svg.selectAll("circle").attr("cx", function (d) { return focus.x(d.Year); })
                  .attr("cy", function (d) { return focus.y(d.Relevance); })
                  console.log("BrushEnd Domain: [" + focus.x.domain()[0] + ", " + focus.x.domain()[1] + "]");
              }
              else {
                  focus.x.domain(context.x.domain());
                  focus.y.domain(context.y.domain());
                  focus.g.select(".x.axis").call(focus.xAxis)
                      .selectAll("text")
                      .style("font-size", "10px")
                      .attr("dx", "-1em");
                  focus.g.select(".y.axis").call(focus.yAxis)
                      .selectAll("text")
                      .style("font-size", "10px")
                      .attr("dx", "-1em");
                  var circle = focus.g.selectAll("circle").attr("cx", function (d) { return focus.x(d.Year); })
                  .attr("cy", function (d) { return focus.y(d.Relevance); })
              }
          }
      }
  
      function MakeFocusZoomable(focus) {
          focus.svg.call(d3.behavior.zoom().x(focus.x).y(focus.y).on("zoom", Zoom));
  
          function Zoom() {
              focus.svg.select(".x.axis").call(focus.xAxis).selectAll("text")
                          .style("font-size", "10px")
                          .attr("dy", "1.5em");
  
              focus.svg.select(".y.axis").call(focus.yAxis).selectAll("text")
                          .style("font-size", "10px")
                          .attr("dx", "-1em");
  
              focus.svg.selectAll("circle").attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
          }
      }
        &lt;/script&gt;
     &lt;/head&gt;
     &lt;body&gt;
        &lt;div id="chart"&gt;
        &lt;/div&gt;
     &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>