javascript - D3 - 刷图表时重置缩放?

标签 javascript svg d3.js

我正在制作 D3 时间尺度/价格尺度财务图表。图表 SVG 本身使用 Zoom() 来几何平移和缩放数据并重新绘制轴。图表下方是一个 SVG 画笔 Pane ,它以较高的级别显示整个数据集并允许自行平移。我面临的问题与此 fiddle (不是我的代码)中所示的行为相同:http://jsfiddle.net/p29qC/8/ 。刷牙后缩放会导致跳跃行为,因为 Zoom() 从未从 Brush() 中获取更改。

缩放和画笔独立工作得很好,但我很难让它们一起工作。当图表被刷过时,我希望缩放能够检测到这一点,因此下次图表缩放时,它会从刷子离开的地方开始。反之亦然。

我已经设法设置同步功能,以便在启动缩放时使画笔正确更新,但我无法实现相反的功能 - 当导航器中发生画笔时更新图表缩放。我已经搜索了几个小时但没有结果。有没有补丁可以解决这个问题?我对长代码块表示歉意,但我希望它对设置上下文有所帮助!

设置代码(为简洁起见,省略了一些基本变量):

// Create svg
var svg = d3.select('#chart')
  .append('svg')
  .attr({
    class: 'fcChartArea',
    width: width+margin.left+margin.right,
    height: height+margin.bottom,
  })
  .style({'margin-top': margin.top});

// Create group for the chart
var chart = svg.append('g');

// Clipping path
chart.append('defs').append('clipPath')
  .attr('id', 'plotAreaClip')
  .append('rect')
  .attr({
    width: width,
    height: height
  });

// Create plot area, using the clipping path
var plotArea = chart.append('g')
  .attr({
    class: 'plotArea',
    'clip-path': 'url(#plotAreaClip)'
  });

// Compute mins and maxes
var minX = d3.min(data, function (d) {
  return new Date(d.startTime*1000);
});
var maxX = d3.max(data, function (d) {
  return new Date(d.startTime*1000);
});
var minY = d3.min(data, function (d) {
  return d.low;
});
var maxY = d3.max(data, function (d) {
  return d.high;
});

// Compute scales & axes
var dateScale = d3.time.scale()
  .domain([minX, maxX])
  .range([0, width]);
var dateAxis = d3.svg.axis()
  .scale(dateScale)
  .orient('bottom');
var priceScale = d3.scale.linear()
  .domain([minY, maxY])
  .nice()
  .range([height, 0]);
var priceAxis = d3.svg.axis()
  .scale(priceScale)
  .orient('right');

// Store initial scales
var initialXScale = dateScale.copy();
var initialYScale = priceScale.copy();

// Add axes to the chart
chart.append('g')
  .attr('class', 'axis date')
  .attr('transform', 'translate(0,' + height + ')')
  .call(dateAxis);
chart.append('g')
  .attr('class', 'axis price')
  .attr('transform', 'translate(' + width + ',0)')
  .call(priceAxis);

// Compute and append the OHLC series
var series = fc.series.ohlc('path')
  .xScale(dateScale)
  .yScale(priceScale);
var dataSeries = plotArea.append('g')
  .attr('class', 'series')
  .datum(data)
  .call(series);

// Create the SVG navigator
var navChart = d3.select('#chart')
  .classed('chart', true)
  .append('svg')
    .classed('navigator', true)
    .attr('width', navWidth + margin.left + margin.right)
    .attr('height', navHeight+margin.top+margin.bottom)
    .style({'margin-bottom': margin.bottom})
    .append('g');
// Compute scales & axes
var navXScale = d3.time.scale()
  .domain([minX, maxX])
  .range([0, navWidth]);
var navXAxis = d3.svg.axis()
  .scale(navXScale)
  .orient('bottom');
var navYScale = d3.scale.linear()
  .domain([minY, maxY])
  .range([navHeight, 0]);
// Add x-axis to the chart
navChart.append('g')
  .attr('class', 'axis date')
  .attr('transform', 'translate(0,' + navHeight + ')')
  .call(navXAxis);
// Add data to the navigator
var navData = d3.svg.area()
  .x(function (d) {
    return navXScale(new Date(d.startTime*1000));
  })
  .y0(navHeight)
  .y1(function (d) {
    return navYScale(d.close);
  });
var navLine = d3.svg.line()
  .x(function (d) {
    return navXScale(new Date(d.startTime*1000));
  })
  .y(function (d) {
    return navYScale(d.close);
  });
navChart.append('path')
  .attr('class', 'data')
  .attr('d', navData(data));
navChart.append('path')
  .attr('class', 'line')
  .attr('d', navLine(data));

// create brush viewport
var viewport = d3.svg.brush()
  .x(navXScale)
  .on("brush", brush);

// add brush viewport to the SVG navigator
navChart.append("g")
  .attr("class", "viewport")
  .call(viewport)
  .selectAll("rect")
  .attr("height", navHeight);

// set zoom behavior
var zoom = d3.behavior.zoom()
  .x(dateScale)
  .scaleExtent([1, 12.99])
  .on('zoom', zoom);

// Create zoom pane
plotArea.append('rect')
  .attr('class', 'zoom-overlay')
  .attr('width', width)
  .attr('height', height)
  .call(zoom);

画笔和缩放功能:

// zoom - brush synchronizations
function updateBrushFromZoom() {
  if ((dateScale.domain()[0] <= minX) && (dateScale.domain()[1] >= maxX)) {
    viewport.clear();
  } else {
    viewport.extent(dateScale.domain());
  }
  navChart.select('.viewport').call(viewport);
}

function updateZoomFromBrush() {
  // help!!
}

function brush() {
  var g = d3.selectAll('svg').select('g');
  var newDomain = viewport.extent();
  if (newDomain[0].getTime() !== newDomain[1].getTime()) {
    dateScale.domain([newDomain[0], newDomain[1]]);
    var xTransform = fc.utilities.xScaleTransform(initialXScale, dateScale);

    // define new data set
    var range = moment().range(newDomain[0], newDomain[1]);
    var rangeData = [];
    for (var i = 0; i < data.length; i += 1) {
      if (range.contains(new Date(data[i].startTime*1000))) {
        rangeData.push(data[i]);
      }
    }
    // define new mins and maxes
    var newMinY = d3.min(rangeData, function (d) {
      return d.low;
    });
    var newMaxY = d3.max(rangeData, function (d) {
      return d.high;
    });

    // set new yScale
    priceScale.domain([newMinY, newMaxY]);
    var yTransform = fc.utilities.yScaleTransform(initialYScale, priceScale);

    // draw new axes on main chart
    g.select('.fcChartArea .date.axis')
      .call(dateAxis);
    g.select('.fcChartArea .price.axis')
      .call(priceAxis);

    // transform the data to fit new chart viewport
    g.select('.series')
      .attr('transform', 'translate(' + xTransform.translate + ',' + yTransform.translate+ ')' + ' scale(' + xTransform.scale + ',' + yTransform.scale + ')');
  }
  else {
    // remove transformation
    g.select('.series')
      .attr('transform', null);
  }
  updateZoomFromBrush();
}

// Zoom functions
function zoom() {
  var g = d3.selectAll('svg').select('g');
  // set new xScale
  var newDomain = dateScale.domain();
  var xTransformTranslate = d3.event.translate[0];
  var xTransformScale = d3.event.scale;

  // define new data set
  var range = moment().range(newDomain[0], newDomain[1]);
  var rangeData = [];
  for (var i = 0; i < data.length; i += 1) {
    if (range.contains(new Date(data[i].startTime*1000))) {
      rangeData.push(data[i]);
    }
  }

  // define new max and min
  var newMinY = d3.min(rangeData, function (d) {
    return d.low;
  });
  var newMaxY = d3.max(rangeData, function (d) {
    return d.high;
  });

  // set new yScale
  priceScale.domain([newMinY, newMaxY]);
  var yTransform = fc.utilities.yScaleTransform(initialYScale, priceScale);

  // draw new axes on main chart
  g.select('.fcChartArea .date.axis')
    .call(dateAxis);
  g.select('.fcChartArea .price.axis')
    .call(priceAxis);

  // transform the data to fit new chart viewport
  g.select('.series')
    .attr('transform', 'translate(' + xTransformTranslate + ',' + yTransform.translate+ ')' + ' scale(' + xTransformScale + ',' + yTransform.scale + ')');

  // update SVG navigator
  updateBrushFromZoom();
}

辅助函数:

fc.utilities.yScaleTransform = function(oldScale, newScale) {
  var oldDomain = oldScale.domain();
  var newDomain = newScale.domain();
  var scale = (oldDomain[1] - oldDomain[0]) / (newDomain[1] - newDomain[0]);
  var translate = scale * (oldScale.range()[1] - oldScale(newDomain[1]));
  return {
    translate: translate,
    scale: scale
  };
};

fc.utilities.xScaleTransform = function(oldScale, newScale) {
  var oldDomain = oldScale.domain();
  var newDomain = newScale.domain();
  var scale = (oldDomain[1] - oldDomain[0]) / (newDomain[1] - newDomain[0]);
  var translate = scale * (oldScale.range()[0] - oldScale(newDomain[0]));
  return {
    translate: translate,
    scale: scale
  };
};

最佳答案

updateZoomFromBrush() 中,使用 zoom.x(dateScale) 将比例重新绑定(bind)到缩放行为。

这是必需的,因为 d3.behavior.zoom() 对您传入的比例的副本进行操作,因此在不重新绑定(bind)比例的情况下,该行为不会对brush() 中的比例域。

参见此示例 http://bl.ocks.org/mbostock/3892928

关于javascript - D3 - 刷图表时重置缩放?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27204907/

相关文章:

javascript - Jquery:如何在其他元素中隐藏元素?

javascript - 闭包编译器 - 无法解析 @extends 标记中的类型

javascript - 使用 svg 作为使用 leaflet.js 的 map

d3.js - 在 d3js 条形图中,我想要固定条形宽度

javascript - 带有父变量的 Angular 单元测试观察者

javascript - 回溯 JavaScript 源代码中的表达式

javascript - 从浏览器中删除 SVG 元素的交互式方式

javascript - google charts timeline - 鼠标单击获取行上的文本

javascript - 在放置时确定 d3.js SVG 组中的不同元素

node.js - 谁能告诉我为什么我的 d3 条形图没有显示?