如何将缩放和刷子连接到可变比例域?

时间:2017-10-31 23:57:06

标签: javascript d3.js

我有2个简单的d3(v4)示例。我的第一个例子显示了使用缩放和画笔与线性图表。它使用了不可变线性标度x.domain([0, POINTS])

enter image description here

const POINTS = 100
const coords = []
const wrapper = d3.select("#chart")
const brushWrap = d3.select('#brush')

const time = 1e2
let interval = null

var width = 500;
var height = 200;

var margin = {
  left: 0, right: 0, top: 0, bottom: 0
};

// point to coordinate converter
var x = d3.scaleLinear()
  .domain([0, POINTS - 1])
  .range([0, width - margin.left - margin.right]);

var y = d3.scaleLinear()
  .domain([0, POINTS - 1])
  .range([height - margin.top - margin.bottom, 0]);

//create d3 path generation function
const line = d3.line()
  .x((d) => x(d.x))
  .y((d) => y(d.y))
  .curve(d3.curveBasis)
;

const zoom = d3.zoom()
  .scaleExtent([1, 10])
  .translateExtent([ [0, 0], [width, height] ])
  .extent([ [0, 0], [width, height] ])

const brush = d3.brushX().extent([ [0, 0], [width, 40] ])

function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

// reused chart func
function draw(selection) {  
  selection.each(function(data) {
    var container = d3.select(this)
    
    var svg = container.selectAll("svg").data([data]);
    var gEnter = svg.enter().append("svg").append("g"); // virtual g elem
    
    svg
      .attr("width", width)
      .attr('height', height)
        
    // append path layer
    gEnter
      .append("g")
        .attr("class", "path layer")
        .append("path")
          .attr("class", "line")
    
    gEnter
      .append('rect')
        .attr('class', 'zoom')
        .attr("width", width)
        .attr('height', height)
        .call(zoom)
    
    var g = svg.select("g")     // real g elem
      .attr('transform', `translate(${margin.left},${margin.top})`)

    g.select("path.line")
      .attr('d', line(data))
  })
}

function render() {
  wrapper
    .datum(coords.map((y, x) => ({x,y})))
    .call(draw)
}

function task() {
  if (coords.length > POINTS) {
    coords.shift()
  }
  
  coords.push(random(0, POINTS - 1))
  render()
}

function start() {
  if (interval !== null) {
    return
  }
  
  interval = d3.interval(task, time)
}

function stop() {
  if (interval === null) {
    return
  }
  
  interval.stop()
  interval = null
}

function zoomIn() {
  wrapper.select('rect.zoom').call(zoom.scaleBy, 2)
}

function zoomOut() {
  wrapper.select('rect.zoom').call(zoom.scaleBy, 0.5)
}

function onzoom() {
  if (d3.event.sourceEvent && d3.event.sourceEvent.type === 'brush') return;

  const transform = d3.event.transform;
  
  const scaleX = transform.rescaleX(x)
  line.x(d => scaleX(d.x))
  
  const points = scaleX.range().map(transform.invertX, transform)
  brushWrap.select('.brush').call(brush.move, points)
  
  render()
}

function onbrush() {
  if (d3.event.sourceEvent && d3.event.sourceEvent.type === 'zoom') return;
  
  const selection = d3.event.selection
  const transform = d3.zoomIdentity
    .scale(width / (selection[1] - selection[0]))
    .translate(-selection[0], 0);
  
  const scaleX = transform.rescaleX(x)
  line.x(d => scaleX(d.x))
  
  wrapper.select('rect.zoom').call(zoom.transform, transform)
  render()
}

brushWrap
  .append('svg')
  .attr('width', width)
  .attr('height', 40)
  .append('g')
  .attr('class', 'brush')
  .call(brush);

zoom.on('zoom', onzoom)
brush.on('brush', onbrush)

brushWrap.select('.brush').call(brush.move, x.range())

d3.select('#start').on('click', start)
d3.select('#stop').on('click', stop)
d3.select('#zoomin').on('click', zoomIn)
d3.select('#zoomout').on('click', zoomOut)

start()
html, body {
  height: 100%;
}

body {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  -webkit-box-pack: center;
      -ms-flex-pack: center;
          justify-content: center;
}

#wrap {
  width: 500px;
  height: 250px;
}

#chart, #brush {
  margin-top: 10px;
  border: 1px solid lightgrey;
}

/* Chart Styles */
.line {
  fill: none;
  stroke: #FF5722;
  stroke-width: 1px;
}

.zoom {
  fill: none;
  cursor: move;
  pointer-events: all;
}

#brush {
  height: 40px;
  line-height: 1;
}
<div id="wrap">
  <div class="button-group">
    <button id="start">start</button>
    <button id="stop">stop</button>
    <button id="zoomin">+</button>
    <button id="zoomout">-</button>
  </div>
  <div id="chart"></div>
  <div id="brush"></div>
</div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js'></script>

我的第二个例子显示了可变域的使用(每次显示当前时间刻度 x.domain([now, now - 5e3])):

enter image description here

const coords = []
const POINTS = 100;
const MAX = 10;

const width = 500;
const height = 250;

const margin = {
  left: 0, right: 0, top: 0, bottom: 15
};

const viewHeight = height - margin.top - margin.bottom;

// point to coordinate converter
const x = d3.scaleTime()
  .range([0, width - margin.left - margin.right]);

const y = d3.scaleLinear()
  .domain([0, MAX])
  .range([viewHeight, 0]);

const xAxis = d3.axisBottom(x)
  .tickSizeInner(-viewHeight)
  .tickSizeOuter(0)
  .tickPadding(4)
  .tickFormat(d3.timeFormat(':%S.%L'));

//create d3 path generation function
const line = d3.line()
  .curve(d3.curveBasis)
  .defined(p => !!p)
  .x((d) => x(d.x))
  .y((d) => y(d.y));

function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

// reused chart func
function draw(selection) {
  // change domain every time
  const now = Date.now();
  x.domain([now, now - 5e3])
  
  selection.each(function(data) {
    const container = d3.select(this)
    
    const svg = container.selectAll("svg").data([data]);
    const gEnter = svg.enter().append("svg").append("g");
    
    svg
      .attr("width", width)
      .attr('height', height)
        
    // append path layer
    gEnter
      .append("g")
        .attr("class", "path layer")
        .append("path")
          .attr("class", "line")
    
    gEnter
      .append('g')
      .attr('transform', `translate(0, ${viewHeight})`)
      .attr('class', 'x axis')
      .call(xAxis);
    
    const g = svg.select("g")     // real g elem
      .attr('transform', `translate(${margin.left},${margin.top})`)

    g.select("path.line")
      .attr('d', line(data))
    
    g.select('.x.axis')
      .call(xAxis)
      .selectAll('line')
      .attr('stroke-dasharray', 5)
      .attr('y2', -viewHeight);
  })
}

const wrapper = d3.select("#wrap");

d3.interval(() => {  
  coords.unshift({
    y: random(0, MAX),
    x: new Date()
  })
  
  if (coords.length > POINTS) {
    coords.pop()
  }
  
  wrapper
    .datum(coords)
    .call(draw)
}, 100)
html, body {
  height: 100%;
}

body {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  -webkit-box-pack: center;
      -ms-flex-pack: center;
          justify-content: center;
}

#wrap {
  width: 500px;
  height: 250px;
  border: 1px solid lightgrey;
}

.axis {
  font: 12px  sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #B0BEC5;
  shape-rendering: crispEdges;
}

.line {
  fill: none;
  stroke: #FF5722;
  stroke-width: 1px;
}
<div id="wrap"></div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js'></script>

如何加入2这些例子?主要困难是可变域。

0 个答案:

没有答案