首页 > 解决方案 > d3.js:图表外显示的网格线

问题描述

我在图表中添加了一些网格线。现在我的问题是网格线未与图表对齐。

此外,在右侧图表的末尾添加了最后一条网格线是否有可能阻止这种情况,并且只在 x 轴上的每个标签上添加一条线?

我试图以某种方式将网格线添加到剪辑路径,但它不起作用。

我的网格线是这样创建的:

var signalData = {
  signal1: {
    name: "signal1",
    data: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 1, 2, 3, 5, 4, 1, 4, 2, 9, 7, 5, 7, 4, 6],
  },
  signal2: {
    name: "signal2",
    data: [6, 4, 8, 5, 4, 8, 4, 3, 5, 4, 5, 8, 7, 2, 9, 5, 4, 1, 2, 6, 0, 5, 7, 1],
  },
  signal3: {
    name: "signal3",
    data: [9, 5, 12, 3, 8, 4, 8, 6, 3, 4, 7, 8, 5, 2, 1, 8, 6, 8, 5, 8, 4, 8, 5, 1],
  },
}


var margin = {
  top: 10,
  right: 50,
  bottom: 40,
  left: 50,
};
var width = window.innerWidth - margin.left - margin.right;
var height = 230 - margin.top - margin.bottom;
var minimapHeight = 150 - margin.top - margin.bottom;

var xScale = d3
  .scaleLinear()
  .domain([
    0,
    d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
  ]) // input
  .range([0, width]); // output

var brushXScale = d3
  .scaleLinear()
  .domain([
    0,
    d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
  ]) // input
  .range([0, width]); // output

var zoomBrush = d3
  .brushX()
  .extent([
    [0, 0],
    [width, minimapHeight],
  ])
  .on("brush", zoomBrushed)
  .on("end", function(event) {
    if (event.selection == null) {
      resetZoom();
    }
  });

var selectBrush = d3
  .brushX()
  .extent([
    [0, 0],
    [width, height],
  ])
  .on("brush", selectBrushed)
  .on("end", function(event) {
    if (event.selection == null) {
      resetSelection();
    }
  });

function resetZoom() {
  brushXScale.domain([
    0,
    d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
  ]); // input

  for (var signal in signalData) {
    updateChart(signalData[signal].data, signalData[signal].name);
  }
}

function zoomBrushed() {
  var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels

  // transform from pixels to x-values
  var selectionX = [
    xScale.invert(selectionPx[0]),
    xScale.invert(selectionPx[1]),
  ];

  // set x scale domain, then redraw the lines
  brushXScale.domain(selectionX);

  for (var signal in signalData) {
    updateChart(signalData[signal].data, signalData[signal].name);
  }
}

function resetSelection() {
  selectBrush.on("end", null);
  d3.selectAll("div[id^=signal] svg .brushcontainer").call(
    selectBrush.clear
  );
  selectBrush.on("end", function(event) {
    if (event.selection == null) {
      resetSelection();
    }
  });
}

function selectBrushed() {
  var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels

  selectBrush.on("brush", null);
  d3.selectAll("div[id^=signal] svg .brushcontainer").call(
    selectBrush.move,
    selectionPx
  );
  selectBrush.on("brush", selectBrushed);
}

//Generate the brush focus chart
generateMinimap(signalData.signal1.data);
//Generate charts dynamically as often as i have signals
for (var signal in this.signalData) {
  generateChart(signalData[signal].data, signalData[signal].name);
}

// This function is for the one time preparations
function generateChart(data, name) {
  var svg = d3
    .select("#" + name)
    .append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr(
      "transform",
      "translate(" + margin.left + "," + margin.top + ")"
    );

  //Gridlines
  function Xgridlines() {
    return d3.axisBottom(xScale).ticks(10);
  }

  svg
    .append("g")
    .attr("class", "grid")
    .attr("transform", "translate(0," + height + ")")
    .style("stroke-dasharray", "3,3")
    .call(Xgridlines().tickSize(-height).tickFormat(""));

  //clipPath to prevent path from overflow
  svg
    .append("defs")
    .append("clipPath")
    .attr("id", "clip")
    .append("rect")
    .attr("width", width)
    .attr("height", height);

  svg.append("g").attr("class", "brushcontainer").call(selectBrush);

  svg
    .append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")");

  svg.append("g").attr("class", "y axis");

  svg
    .append("path")
    .attr("clip-path", "url(#clip)")
    .attr("class", "line") // Assign a class for styling
    .attr("fill", "none")
    .attr("stroke", "blue");

  updateChart(data, name);
}

// This function needs to be called to update the already prepared chart
function updateChart(data, name) {
  var svg = d3.select("#" + name + " svg");

  var yScale = d3
    .scaleLinear()
    .domain([0, d3.max(data)]) // input
    .range([height, 0]); // output

  function Ygridlines() {
    return d3.axisLeft(yScale).ticks(5);
  }

  svg
    .append("g")
    .attr("class", "grid")
    .attr("transform", "translate(0," - height - ")")
    .style("stroke-dasharray", "3,3")
    .call(
      Ygridlines()
      .tickSize(-width - margin.left)
      .tickFormat("")
    );

  var line = d3
    .line()
    .x((d, i) => brushXScale(i))
    .y((d) => yScale(d));

  svg.select(".x.axis").call(d3.axisBottom(brushXScale));

  svg.select(".y.axis").call(d3.axisLeft(yScale));

  svg
    .select(".line")
    .datum(data) // 10. Binds data to the line
    .attr("d", line); // 11. Calls the line generator
}

function generateMinimap(data) {
  var yScale = d3
    .scaleLinear()
    .domain([0, d3.max(data)]) // input
    .range([minimapHeight, 0]); // output

  var line = d3
    .line()
    .x((d, i) => xScale(i))
    .y((d) => yScale(d));

  var svg = d3
    .select("#minimap")
    .append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", minimapHeight + margin.top + margin.bottom)
    .append("g")
    .attr(
      "transform",
      "translate(" + margin.left + "," + margin.top + ")"
    );

  svg.append("g").call(zoomBrush);

  svg
    .append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + minimapHeight + ")")
    .call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom

  svg
    .append("path")
    .datum(data) // 10. Binds data to the line
    .attr("class", "line") // Assign a class for styling
    .attr("d", line) // 11. Calls the line generator
    .attr("fill-opacity", "0.17")
    .attr("fill", "blue")
    .attr("stroke", "blue");
}
<div id="minimap"></div>
<!-- In the original project these divs are not 
    static and get generated with v-for as many times as 
    i have a signal in signalData -->
<div id="signal1"></div>
<div id="signal2"></div>
<div id="signal3"></div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>

标签: javascriptd3.js

解决方案


  1. 不要.append()updateChart. 您永远不会删除您绘制的轴,而只是在它们上面绘制新的轴...
  2. 只需将现有的轴变成带有网格线的轴,使用tickSizeInner()

var signalData = {
  signal1: {
    name: "signal1",
    data: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 1, 2, 3, 5, 4, 1, 4, 2, 9, 7, 5, 7, 4, 6],
  },
  signal2: {
    name: "signal2",
    data: [6, 4, 8, 5, 4, 8, 4, 3, 5, 4, 5, 8, 7, 2, 9, 5, 4, 1, 2, 6, 0, 5, 7, 1],
  },
  signal3: {
    name: "signal3",
    data: [9, 5, 12, 3, 8, 4, 8, 6, 3, 4, 7, 8, 5, 2, 1, 8, 6, 8, 5, 8, 4, 8, 5, 1],
  },
}


var margin = {
  top: 10,
  right: 50,
  bottom: 40,
  left: 50,
};
var width = window.innerWidth - margin.left - margin.right;
var height = 230 - margin.top - margin.bottom;
var minimapHeight = 150 - margin.top - margin.bottom;

var xScale = d3
  .scaleLinear()
  .domain([
    0,
    d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
  ]) // input
  .range([0, width]); // output

var brushXScale = d3
  .scaleLinear()
  .domain([
    0,
    d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
  ]) // input
  .range([0, width]); // output

var zoomBrush = d3
  .brushX()
  .extent([
    [0, 0],
    [width, minimapHeight],
  ])
  .on("brush", zoomBrushed)
  .on("end", function(event) {
    if (event.selection == null) {
      resetZoom();
    }
  });

var selectBrush = d3
  .brushX()
  .extent([
    [0, 0],
    [width, height],
  ])
  .on("brush", selectBrushed)
  .on("end", function(event) {
    if (event.selection == null) {
      resetSelection();
    }
  });

function resetZoom() {
  brushXScale.domain([
    0,
    d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
  ]); // input

  for (var signal in signalData) {
    updateChart(signalData[signal].data, signalData[signal].name);
  }
}

function zoomBrushed() {
  var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels

  // transform from pixels to x-values
  var selectionX = [
    xScale.invert(selectionPx[0]),
    xScale.invert(selectionPx[1]),
  ];

  // set x scale domain, then redraw the lines
  brushXScale.domain(selectionX);

  for (var signal in signalData) {
    updateChart(signalData[signal].data, signalData[signal].name);
  }
}

function resetSelection() {
  selectBrush.on("end", null);
  d3.selectAll("div[id^=signal] svg .brushcontainer").call(
    selectBrush.clear
  );
  selectBrush.on("end", function(event) {
    if (event.selection == null) {
      resetSelection();
    }
  });
}

function selectBrushed() {
  var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels

  selectBrush.on("brush", null);
  d3.selectAll("div[id^=signal] svg .brushcontainer").call(
    selectBrush.move,
    selectionPx
  );
  selectBrush.on("brush", selectBrushed);
}

//Generate the brush focus chart
generateMinimap(signalData.signal1.data);
//Generate charts dynamically as often as i have signals
for (var signal in this.signalData) {
  generateChart(signalData[signal].data, signalData[signal].name);
}

// This function is for the one time preparations
function generateChart(data, name) {
  var svg = d3
    .select("#" + name)
    .append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr(
      "transform",
      "translate(" + margin.left + "," + margin.top + ")"
    );

  //clipPath to prevent path from overflow
  svg
    .append("defs")
    .append("clipPath")
    .attr("id", "clip")
    .append("rect")
    .attr("width", width)
    .attr("height", height);

  svg.append("g").attr("class", "brushcontainer").call(selectBrush);

  svg
    .append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")");

  svg.append("g").attr("class", "y axis");

  svg
    .append("path")
    .attr("clip-path", "url(#clip)")
    .attr("class", "line") // Assign a class for styling
    .attr("fill", "none")
    .attr("stroke", "blue");

  updateChart(data, name);
}

// This function needs to be called to update the already prepared chart
function updateChart(data, name) {
  var svg = d3.select("#" + name + " svg");

  var yScale = d3
    .scaleLinear()
    .domain([0, d3.max(data)]) // input
    .range([height, 0]); // output

  var line = d3
    .line()
    .x((d, i) => brushXScale(i))
    .y((d) => yScale(d));

  svg.select(".x.axis").call(
    d3.axisBottom(brushXScale)
      .tickSizeOuter(0)
      .tickSizeInner(-height)
  );

  svg.select(".y.axis").call(
    d3.axisLeft(yScale)
      .tickSizeInner(-width)
      .tickSizeOuter(0)
      .ticks(5)
  );

  svg
    .select(".line")
    .datum(data) // 10. Binds data to the line
    .attr("d", line); // 11. Calls the line generator
}

function generateMinimap(data) {
  var yScale = d3
    .scaleLinear()
    .domain([0, d3.max(data)]) // input
    .range([minimapHeight, 0]); // output

  var line = d3
    .line()
    .x((d, i) => xScale(i))
    .y((d) => yScale(d));

  var svg = d3
    .select("#minimap")
    .append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", minimapHeight + margin.top + margin.bottom)
    .append("g")
    .attr(
      "transform",
      "translate(" + margin.left + "," + margin.top + ")"
    );

  svg.append("g").call(zoomBrush);

  svg
    .append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + minimapHeight + ")")
    .call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom

  svg
    .append("path")
    .datum(data) // 10. Binds data to the line
    .attr("class", "line") // Assign a class for styling
    .attr("d", line) // 11. Calls the line generator
    .attr("fill-opacity", "0.17")
    .attr("fill", "blue")
    .attr("stroke", "blue");
}
.tick line {
  stroke-dasharray: 3 3;
}
<div id="minimap"></div>
<!-- In the original project these divs are not 
    static and get generated with v-for as many times as 
    i have a signal in signalData -->
<div id="signal1"></div>
<div id="signal2"></div>
<div id="signal3"></div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>


推荐阅读