D3.js:缩放时,在2D图形中沿着直线移动圆

D3.js: Moving circles along with line in a 2D graph when zoomed

本文关键字:移动 图形 2D js 缩放 D3      更新时间:2024-05-02

我目前正在尝试用d3.js绘制时间序列数据。我已经绘制了一条线,并为每个数据点绘制了圆圈(将来圆圈将用于注释特定的数据点)。我正在尝试使用d3.js中的"缩放"行为来缩放所有组件。但我无法沿直线拖动和缩放圆。

我怎样才能沿着直线移动圆圈。以下是代码的jsfiddle:

https://jsfiddle.net/adityap16/4sts8nfs/2/

代码:

var data = [{
  "mytime": "2015-12-01T23:10:00.000Z",
  "value": 64
}, {
  "mytime": "2015-12-01T23:15:00.000Z",
  "value": 67
}, {
  "mytime": "2015-12-01T23:20:00.000Z",
  "value": 70
}, {
  "mytime": "2015-12-01T23:25:00.000Z",
  "value": 64
}, {
  "mytime": "2015-12-01T23:30:00.000Z",
  "value": 72
}, {
  "mytime": "2015-12-01T23:35:00.000Z",
  "value": 75
}, {
  "mytime": "2015-12-01T23:40:00.000Z",
  "value": 71
}, {
  "mytime": "2015-12-01T23:45:00.000Z",
  "value": 80
}, {
  "mytime": "2015-12-01T23:50:00.000Z",
  "value": 83
}, {
  "mytime": "2015-12-01T23:55:00.000Z",
  "value": 86
}, {
  "mytime": "2015-12-02T00:00:00.000Z",
  "value": 80
}, {
  "mytime": "2015-12-02T00:05:00.000Z",
  "value": 85
}];
var parseDate = d3.time.format("%Y-%m-%dT%H:%M:%S.%LZ").parse;
data.forEach(function(d) {
  d.mytime = parseDate(d.mytime);
});
//var margin = { top: 30, right: 30, bottom: 40, left:50 },
var margin = {
    top: 30,
    right: 30,
    bottom: 40,
    left: 50
  },
  height = 200,
  width = 900;
var color = "green";
var xaxis_param = "mytime";
var yaxis_param = "value";
var params1 = {
  margin: margin,
  height: height,
  width: width,
  color: color,
  xaxis_param: xaxis_param,
  yaxis_param: yaxis_param
};
draw_graph(data, params1);
function draw_graph(data, params) {
  var make_x_axis = function() {
    return d3.svg.axis()
      .scale(x_scale)
      .orient("bottom")
      .ticks(5);
  };
  var make_y_axis = function() {
    return d3.svg.axis()
      .scale(y_scale)
      .orient("left")
      .ticks(5);
  };
  //Get the margin
  var xaxis_param = params.xaxis_param;
  var yaxis_param = params.yaxis_param;
  var color_code = params.color;
  var margin = params.margin;
  var height = params.height - margin.top - margin.bottom,
    width = params.width - margin.left - margin.right;
  var x_extent = d3.extent(data, function(d) {
    return d[xaxis_param]
  });
  var y_extent = d3.extent(data, function(d) {
    return d[yaxis_param]
  });
  var x_scale = d3.time.scale()
    .domain(x_extent)
    .range([0, width]);
  var y_scale = d3.scale.linear()
    .domain([0, y_extent[1]])
    .range([height, 0]);
  var zoom = d3.behavior.zoom()
    .x(x_scale)
    .y(y_scale)
    .on("zoom", zoomed);

  //Line
  var line = d3.svg.line()
    .defined(function(d) {
      return d[yaxis_param];
    })
    .x(function(d) {
      return x_scale(d[xaxis_param]);
    })
    .y(function(d) {
      return y_scale(d[yaxis_param]);
    });
  var lineRef = d3.svg.line()
    .x(function(d) {
      return x_scale(d[xaxis_param]);
    })
    .y(function(d) {
      return y_scale(20);
    });
  var myChart = d3.select('body').append('svg')
    .attr('id', 'graph')
    .style('background', '#E7E0CB')
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
    .append('g')
    .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')')
    .call(zoom);
   myChart.append("svg:rect")
    .attr("width", width)
    .attr("height", height)
    .attr("class", "plot");

  var legend = myChart.append("g")
    .attr("class", "legend")
    .attr("transform", "translate(" + 5 + "," + (height - 25) + ")")
  legend.append("rect")
    .style("fill", color_code)
    .attr("width", 20)
    .attr("height", 20);
  legend.append("text")
    .text(yaxis_param)
    .attr("x", 25)
    .attr("y", 12);

  var vAxis = d3.svg.axis()
    .scale(y_scale)
    .orient('left')
    .ticks(5)

  var hAxis = d3.svg.axis()
    .scale(x_scale)
    .orient('bottom')
    .ticks(5);
  var majorAxis = d3.svg.axis()
    .scale(x_scale)
    .orient('bottom')
    .ticks(d3.time.day, 1)
    .tickSize(-height)
    .outerTickSize(0);
  myChart.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(hAxis);
  myChart.append("g")
    .attr("class", "x axis major")
    .attr("transform", "translate(0," + height + ")")
    .call(majorAxis);
  myChart.append("g")
    .attr("class", "y axis")
    .call(vAxis);
  var circlePoint = myChart.selectAll('circle')
                         .data(data)
                         .enter()
                         .append("circle");
    var circleAttributes = circlePoint
                           .attr("cx", function (d) { return x_scale(d[xaxis_param]); })
                           .attr("cy", function (d) { return y_scale(d[yaxis_param]); })
                           .attr("r", 3)
                           .style("fill", "none")
                           .style("stroke", "red");
    var clip = myChart.append("svg:clipPath")
    .attr("id", "clip")
    .append("svg:rect")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", width)
    .attr("height", height);
    var chartBody = myChart.append("g")
    .attr("clip-path", "url(#clip)");

chartBody.append("svg:path")
    .datum(data)
    .attr('class', 'line')
    .attr("d", line)
    .attr('stroke', color_code)
    .attr('stroke-width', 1)
    .attr('fill', 'none');
  chartBody
    .append('svg:path')
    .datum(data)
    .attr('class', 'line1')
    .attr("d", lineRef)
    .attr('stroke', 'blue')
    .attr('stroke-width', 1)
    .style("stroke-dasharray", ("3, 3"))
    .attr('fill', 'none');
  function zoomed() {
    myChart.select(".x.axis").call(hAxis);
    myChart.select(".y.axis").call(vAxis);
    myChart.select(".x.axis.major").call(majorAxis);
    myChart.select(".line")
        .attr("class", "line")
        .attr("d", line);
    myChart.select(".line1")
        .attr("class", "line1")
        .attr("d", lineRef);

  }
}

您只需要在zoomed()处理程序函数中更新圆的位置:

circlePoint
  .attr("cx", function (d) { return x_scale(d[xaxis_param]); })
  .attr("cy", function (d) { return y_scale(d[yaxis_param]); });

因为D3的缩放行为将负责更新缩放比例,所以它们可以很容易地用于计算新位置。

请看一下这个片段以获得完整的示例:

var data = [{
  "mytime": "2015-12-01T23:10:00.000Z",
  "value": 64
}, {
  "mytime": "2015-12-01T23:15:00.000Z",
  "value": 67
}, {
  "mytime": "2015-12-01T23:20:00.000Z",
  "value": 70
}, {
  "mytime": "2015-12-01T23:25:00.000Z",
  "value": 64
}, {
  "mytime": "2015-12-01T23:30:00.000Z",
  "value": 72
}, {
  "mytime": "2015-12-01T23:35:00.000Z",
  "value": 75
}, {
  "mytime": "2015-12-01T23:40:00.000Z",
  "value": 71
}, {
  "mytime": "2015-12-01T23:45:00.000Z",
  "value": 80
}, {
  "mytime": "2015-12-01T23:50:00.000Z",
  "value": 83
}, {
  "mytime": "2015-12-01T23:55:00.000Z",
  "value": 86
}, {
  "mytime": "2015-12-02T00:00:00.000Z",
  "value": 80
}, {
  "mytime": "2015-12-02T00:05:00.000Z",
  "value": 85
}];
var parseDate = d3.time.format("%Y-%m-%dT%H:%M:%S.%LZ").parse;
data.forEach(function(d) {
  d.mytime = parseDate(d.mytime);
});
//var margin = { top: 30, right: 30, bottom: 40, left:50 },
var margin = {
    top: 30,
    right: 30,
    bottom: 40,
    left: 50
  },
  height = 200,
  width = 900;
var color = "green";
var xaxis_param = "mytime";
var yaxis_param = "value";
var params1 = {
  margin: margin,
  height: height,
  width: width,
  color: color,
  xaxis_param: xaxis_param,
  yaxis_param: yaxis_param
};
draw_graph(data, params1);
function draw_graph(data, params) {
  var make_x_axis = function() {
    return d3.svg.axis()
      .scale(x_scale)
      .orient("bottom")
      .ticks(5);
  };
  var make_y_axis = function() {
    return d3.svg.axis()
      .scale(y_scale)
      .orient("left")
      .ticks(5);
  };
  //Get the margin
  var xaxis_param = params.xaxis_param;
  var yaxis_param = params.yaxis_param;
  var color_code = params.color;
  var margin = params.margin;
  var height = params.height - margin.top - margin.bottom,
    width = params.width - margin.left - margin.right;
  var x_extent = d3.extent(data, function(d) {
    return d[xaxis_param]
  });
  var y_extent = d3.extent(data, function(d) {
    return d[yaxis_param]
  });
  var x_scale = d3.time.scale()
    .domain(x_extent)
    .range([0, width]);
  var y_scale = d3.scale.linear()
    .domain([0, y_extent[1]])
    .range([height, 0]);
  var zoom = d3.behavior.zoom()
    .x(x_scale)
    .y(y_scale)
    .on("zoom", zoomed);
  //Line
  var line = d3.svg.line()
    .defined(function(d) {
      return d[yaxis_param];
    })
    .x(function(d) {
      return x_scale(d[xaxis_param]);
    })
    .y(function(d) {
      return y_scale(d[yaxis_param]);
    });
  var lineRef = d3.svg.line()
    .x(function(d) {
      return x_scale(d[xaxis_param]);
    })
    .y(function(d) {
      return y_scale(20);
    });
  var myChart = d3.select('body').append('svg')
    .attr('id', 'graph')
    .style('background', '#E7E0CB')
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
    .append('g')
    .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')')
    .call(zoom);
   myChart.append("svg:rect")
    .attr("width", width)
    .attr("height", height)
    .attr("class", "plot");
  var legend = myChart.append("g")
    .attr("class", "legend")
    .attr("transform", "translate(" + 5 + "," + (height - 25) + ")")
  legend.append("rect")
    .style("fill", color_code)
    .attr("width", 20)
    .attr("height", 20);
  legend.append("text")
    .text(yaxis_param)
    .attr("x", 25)
    .attr("y", 12);
  var vAxis = d3.svg.axis()
    .scale(y_scale)
    .orient('left')
    .ticks(5)
  var hAxis = d3.svg.axis()
    .scale(x_scale)
    .orient('bottom')
    .ticks(5);
  var majorAxis = d3.svg.axis()
    .scale(x_scale)
    .orient('bottom')
    .ticks(d3.time.day, 1)
    .tickSize(-height)
    .outerTickSize(0);
  myChart.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(hAxis);
  myChart.append("g")
    .attr("class", "x axis major")
    .attr("transform", "translate(0," + height + ")")
    .call(majorAxis);
  myChart.append("g")
    .attr("class", "y axis")
    .call(vAxis);
  var circlePoint = myChart.selectAll('circle')
                         .data(data)
                         .enter()
                         .append("circle");
    var circleAttributes = circlePoint
                           .attr("cx", function (d) { return x_scale(d[xaxis_param]); })
                           .attr("cy", function (d) { return y_scale(d[yaxis_param]); })
                           .attr("r", 3)
                           .style("fill", "none")
                           .style("stroke", "red");
    var clip = myChart.append("svg:clipPath")
    .attr("id", "clip")
    .append("svg:rect")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", width)
    .attr("height", height);
    var chartBody = myChart.append("g")
    .attr("clip-path", "url(#clip)");
chartBody.append("svg:path")
    .datum(data)
    .attr('class', 'line')
    .attr("d", line)
    .attr('stroke', color_code)
    .attr('stroke-width', 1)
    .attr('fill', 'none');
  chartBody
    .append('svg:path')
    .datum(data)
    .attr('class', 'line1')
    .attr("d", lineRef)
    .attr('stroke', 'blue')
    .attr('stroke-width', 1)
    .style("stroke-dasharray", ("3, 3"))
    .attr('fill', 'none');
  function zoomed() {
    myChart.select(".x.axis").call(hAxis);
    myChart.select(".y.axis").call(vAxis);
    myChart.select(".x.axis.major").call(majorAxis);
    myChart.select(".line")
        .attr("class", "line")
        .attr("d", line);
    myChart.select(".line1")
        .attr("class", "line1")
        .attr("d", lineRef);
    circlePoint
      .attr("cx", function (d) { return x_scale(d[xaxis_param]); })
      .attr("cy", function (d) { return y_scale(d[yaxis_param]); });
  }
}
svg {
  font: 10px sans-serif;
}  
.plot {
    fill: rgba(250, 250, 255, 0.6);
}
.axis path,
.axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}
.axis text {
    font-family: sans-serif;
    font-size: 11px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>