具有动态轴缩放的固定中心

Fixed center with dynamic axis scaling

编辑: 我已经编辑了我的代码。现在可以直接运行Whosebug里面的代码了

昨天,我开始使用 D3,在修复动态轴时遇到了一些问题。我是 D3 的新手:)

我正在尝试构建以下内容。

我正在尝试使用 D3 绘制一些二维流数据。数据通过网络套接字传入。因此,随着数据的到来,我绘制了新的点。我在这方面取得了一些成功,但我的轴没有按预期运行。

随着新数据的到来,我重新调整了我的 x 轴和 y 轴。例如,如果 (20,100) 出现在下图中,那么我将缩放我的 y 轴以适应 100。但是如果你仔细观察 x 轴和 y 轴相交的点不在 0 上。最初它在 (0,0) 但重新调整后,它就在下方。如何在动态重新缩放轴的同时固定中心 a (0,0)?

你能帮帮我吗?这是我的代码。

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.line {
  fill: none;
  stroke: #000;
  stroke-width: 1.5px;
}

div.bar {
    display: inline-block;
    width: 20px;
    height: 75px;   /* We'll override this later */
    background-color: teal;
}


 /* Format X and Y Axis */
 .axis path,
 .axis line {
     fill: none;
     stroke: black;
     shape-rendering: crispEdges;
 }

 .axis text {
     font-family: sans-serif;
     font-size: 11px;
 }

</style>
<svg width="960" height="500"></svg>
<br/>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
 
 function getRandomInt(min, max) {
   return Math.floor(Math.random() * (max - min + 1)) + min;
 }
 
 var dataset = [];  // Initialize empty array
 var numDataPoints = 15;  // Number of dummy data points
 
 for(var i=0; i<numDataPoints; i++) {
 
     dataset.push([getRandomInt(-80,80), getRandomInt(-60,60)]);  // Add new number to array
 }
 
var width = 1020;
var height = 840;

var xScale = d3.scale.linear().domain([d3.min(dataset, function(d) { return d[0]; } ), d3.max(dataset, function(d) { return d[0]; })])
      .range([20, width - 20 * 2])
      .nice();
var yScale = d3.scale.linear().domain([d3.min(dataset, function(d) { return d[1]; } ), d3.max(dataset, function(d) { return d[1]; })])
      .range([height - 20, 20])
      .nice();

var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yAxis = d3.svg.axis().scale(yScale).orient("left");  

var svg = d3.select("svg")
   .attr("height",height)
   .attr("width", width)
   .append("g")
      .attr("transform","translate(20,20)")
         .attr("width", width- 40)
      .attr("height", height- 40);
    
    
    
    // x-axis
 svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + yScale(0) + ")")
    //.attr("transform", "translate(0," + (height - 20) + ")")
    //.style("position", "fixed")
    .call(xAxis);
    
    //y-axis
 svg.append("g")
    .attr("class", "y axis")
    .attr("transform", "translate(" + xScale(0) + ",0)")
    //.attr("transform", "translate(" + 20 + ",0)")
    //.style("position", "fixed")
    .call(yAxis);

  svg.selectAll("circle")
  .data(dataset)
  .enter()
  .append("circle")  // Add circle svg
  .attr("cx", function(d) {
      return xScale(d[0]);  // Circle's X
    })
   .attr("cy", function(d) {  // Circle's Y
      return yScale(d[1]);
  })
  .attr("r", 2)
   .call(updatePlotWithSocketData);
   
   function updatePlotWithSocketData() {
    console.log(dataset.length);
    
    for(var i=0; i<numDataPoints; i++) {
         dataset.push([getRandomInt(-200,200), getRandomInt(-100,100)]);  // Add new number to array
     }
        
        // Update scale domains
        xScale.domain([d3.min(dataset, function(d) { return d[0]; } ), d3.max(dataset, function(d) { return d[0]; })]);
        yScale.domain([d3.min(dataset, function(d) { return d[1]; } ), d3.max(dataset, function(d) { return d[1]; })]);
        xScale.range([20, width - 20 * 2]).nice();
        yScale.range([height - 20, 20]).nice();
        
        // Update old points to the new scale
        svg.selectAll("circle")
      .transition()
      .duration(1000)
      .attr("cx", function(d) {
             return xScale(d[0]);  // Circle's X
         })
         .attr("cy", function(d) {
             return yScale(d[1]);  // Circle's Y
         });
        
     // Update circles
        svg.selectAll("circle")
            .data(dataset)  // Update with new data
            .enter()
            .append("circle")  
            .transition()  // Transition from old to new
            .duration(1000)  // Length of animation
            .each("start", function() {  // Start animation
                d3.select(this)  // 'this' means the current element
                    .attr("fill", "red")  // Change color
                    .attr("r", 5);  // Change size
            })
            .delay(function(d, i) {
                return i / dataset.length * 500;  // Dynamic delay (i.e. each item delays a little longer)
            })
            
            .attr("cx", function(d) {
                return xScale(d[0]);  // Circle's X
            })
            .attr("cy", function(d) {
                return yScale(d[1]);  // Circle's Y
            })
            .each("end", function() {  // End animation
                d3.select(this)  // 'this' means the current element
                    .transition()
                    //.duration(500)
                    .attr("fill", "black")  // Change color
                    .attr("r", 2);  // Change radius
            });
            
     
     
      /* force.on("tick", function() {
       nodes[0].x = width / 2;
       nodes[1].y = height / 2;
      }); */
     
      /* var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
      var yAxis = d3.svg.axis().scale(yScale).orient("left"); */
     
      
        svg.selectAll(".x.axis")
         .transition()
         .duration(1000)
         .call(xAxis);

     // Update Y Axis
     svg.selectAll(".y.axis")
         .transition()
         .duration(1000)
         .call(yAxis);
     
   setTimeout(updatePlotWithSocketData, 3000);
 
   }
   
   
   // Socker code
   /*
   var socket;
    var registered = false;
    
    function startDataReceptionFromSocket() {
      console.log("opening socket");
      //on http server use document.domain instead od "localhost"
      //Start the websocket client
      socket = new WebSocket("ws://localhost:8887/");
      
      //When the connection is opened, login.
      socket.onopen = function() {
        console.log("Opened socket.");
        //register the user
        
      };
      
      socket.onmessage = function(a) {
          //process the message here
          console.log("received message socker => : " + a.data);
          var message = JSON.parse(a.data);
          console.log("message =>" + message.message);
          var numbers = message.message.split(" ")
          dataset.push([numbers[0],numbers[1]]);
          updatePlotWithSocketData();
        }
      
      socket.onclose = function() { console.log("Closed socket."); };
      socket.onerror = function() { console.log("Error during transfer."); };
    }
    
    // On document load, open the socket connection
    (function() {
     startDataReceptionFromSocket();
    })();*/
    
    
 
</script>

在我看来,解决方案是在更新轴时再次设置 translate

现在,在您的代码中,域(对于 x 和 y 尺度)正在改变,但轴是使用原始 xScale(0)yScale(0) 位置转换的。这个演示重现了这个问题:

var margin = 10;
var width = 250, height = 250;

var svg = d3.select("#mydiv")
 .append("svg")
 .attr("width", width)
 .attr("height", height);
 
var xScale = d3.scale.linear()
 .range([margin, width - margin])
 .domain([-10, 10]);
 
var yScale = d3.scale.linear()
 .range([margin, height - margin])
 .domain([-10, 10]);
 
var xAxis = d3.svg.axis()
 .scale(xScale)
 .orient("bottom");
 
var yAxis = d3.svg.axis()
 .scale(yScale)
 .orient("left");
 
svg.append("g").attr("class", "x axis")
 .attr("transform", "translate(0," + yScale(0) + ")")
 .call(xAxis);
 
svg.append("g").attr("class", "y axis")
 .attr("transform", "translate(" + xScale(0) + ",0)")
 .call(yAxis);
 
d3.select("#btn").on("click", randomize);

function randomize(){
 xScale.domain([Math.floor(Math.random()*50)*(-1),Math.floor(Math.random()*50)]);
 yScale.domain([Math.floor(Math.random()*50)*(-1),Math.floor(Math.random()*50)]);
 
  svg.selectAll(".x.axis")
         .transition()
         .call(xAxis);

     svg.selectAll(".y.axis")
         .transition()
         .call(yAxis);
 
}
.axis path, .axis line{
 fill: none;
 stroke: black;
    shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button id="btn">Randomize</button>
<div id="mydiv"></div>

现在是解决方案。下一个演示具有相同的代码,但更新位置如下:

svg.selectAll(".x.axis")
    .transition()
    .duration(1000)
    .attr("transform", "translate(0," + yScale(0) + ")")
    .call(xAxis);

svg.selectAll(".y.axis")
    .transition()
    .duration(1000)
    .attr("transform", "translate(" + xScale(0) + ",0)")
    .call(yAxis);

查看演示:

var margin = 10;

var width = 250, height = 250;

var svg = d3.select("#mydiv")
 .append("svg")
 .attr("width", width)
 .attr("height", height);
 
var xScale = d3.scale.linear()
 .range([margin, width - margin])
 .domain([-10, 10]);
 
var yScale = d3.scale.linear()
 .range([margin, height - margin])
 .domain([-10, 10]);
 
var xAxis = d3.svg.axis()
 .scale(xScale)
 .orient("bottom");
 
var yAxis = d3.svg.axis()
 .scale(yScale)
 .orient("left");
 
svg.append("g").attr("class", "x axis")
 .attr("transform", "translate(0," + yScale(0) + ")")
 .call(xAxis);
 
svg.append("g").attr("class", "y axis")
 .attr("transform", "translate(" + xScale(0) + ",0)")
 .call(yAxis);
 
d3.select("#btn").on("click", randomize);

function randomize(){
 xScale.domain([Math.floor(Math.random()*50)*(-1),Math.floor(Math.random()*50)]);
 yScale.domain([Math.floor(Math.random()*50)*(-1),Math.floor(Math.random()*50)]);
 
  svg.selectAll(".x.axis")
         .transition()
         .duration(1000)
     .attr("transform", "translate(0," + yScale(0) + ")")
         .call(xAxis);

     svg.selectAll(".y.axis")
         .transition()
         .duration(1000)
     .attr("transform", "translate(" + xScale(0) + ",0)")
         .call(yAxis);
 
}
.axis path, .axis line{
 fill: none;
 stroke: black;
    shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button id="btn">Randomize</button>
<div id="mydiv"></div>