d3 悬停文本到剪贴路径

d3 hover text to clippath

我正在尝试将悬停时的工具提示添加到剪辑路径[这是我第一次使用剪辑路径...这是否正确?]...我认为我真的很接近[应该跟随线的圆圈现在显示在 (0.0)] 但我似乎遗漏了一些东西。我想知道有没有人有时间看一下?我正在尝试改编来自 https://blockbuilder.org/bendoesdata/2c8b315d103bbaf98264efda92d313ab

的代码

var data3 = [
  { group: 1, ser1: "2020-01-01", ser2: 3 },
  { group: 1, ser1: "2020-01-02", ser2: 5 },
  { group: 1, ser1: "2020-01-03", ser2: 9 },
  { group: 1, ser1: "2020-01-04", ser2: 3 },
  { group: 1, ser1: "2020-01-05", ser2: 5 },
  { group: 1, ser1: "2020-01-06", ser2: 9 },
  { group: 2, ser1: "2020-01-07", ser2: 10 },
  { group: 2, ser1: "2020-01-08", ser2: 9 },
  { group: 2, ser1: "2020-01-09", ser2: 10 },
  { group: 2, ser1: "2020-01-10", ser2: 20 },
  { group: 2, ser1: "2020-01-11", ser2: 10 },
  { group: 2, ser1: "2020-01-12", ser2: 12 },
  { group: 3, ser1: "2020-01-13", ser2: 20 },
  { group: 3, ser1: "2020-01-14", ser2: 12 },
  { group: 3, ser1: "2020-01-15", ser2: 4 },
  { group: 3, ser1: "2020-01-16", ser2: 22 },
  { group: 3, ser1: "2020-01-17", ser2: 2 },
  { group: 3, ser1: "2020-01-18", ser2: 4 },
]

var line = d3.line()
  .x(function (d) { return x(formatDate(d.ser1)); })
  .y(function (d) { return y(d.ser2); })

// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 30, left: 50 },
  width = 1000 - margin.left - margin.right,
  height = 400 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
  .append("svg")
  .attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`)
  .append("g")
  .attr("transform",
    "translate(" + margin.left + "," + margin.top + ")");

svg.append("defs")
  .append("clipPath")
  .attr("id", "chart-path")
  .append("rect")
  .attr("width", width - 100)
  .attr("height", height)

var formatDate = d3.timeParse("%Y-%m-%d");

var bisectDate = d3.bisector(function (d) {
  return d.str1;
}).left;

// Initialize a X axis:
var x = d3.scaleTime().range([0, width - 100]);
var xNum = d3.scaleLinear().range([0, width - 100])
var xAxis = d3.axisBottom().scale(x);
svg.append("g")
  .attr("transform", "translate(0," + height + ")")
  .attr("class", "myXaxis")

// Initialize an Y axis
var y = d3.scaleLinear().range([height, 0]);
var yAxis = d3.axisLeft().scale(y);
svg.append("g")
  .attr("class", "myYaxis")

// create the Y axis
y.domain([0, d3.max(data3, function (d) { return d.ser2 })]);
svg.selectAll(".myYaxis")
  .transition()
  .duration(1000)
  .call(yAxis);

// Create a update selection: bind to the new data
var u = svg.selectAll(".lineTest")
  .data([data3])
  .enter()
  .append("path")
  .attr("class", "lineTest")
  .attr("fill", "none")
  .attr("stroke", "#0b6dbd")
  .attr("stroke-width", 2.5)
  .attr("clip-path", "url(#chart-path)")


// Create a function that takes a dataset as input and update the plot:
function update(data) {
  // Create the X axis:
  x.domain(d3.extent(data, function (d) { return formatDate(d.ser1) }));
  svg.selectAll(".myXaxis")
    .transition()
    .duration(1000)
    .call(xAxis);
  u.transition()
    .duration(1000)
    .attr("d", line);
}

// At the beginning, I run the update function on the first dataset:
update(data3)


// this is where I try to implement a tooltip based on
// https://blockbuilder.org/bendoesdata/2c8b315d103bbaf98264efda92d313ab
function drawFocus() {

  // Create focus object
  let focus = svg.append('g')
    .attr('class', 'focus')

  // append circle on the line path
  focus.append('circle')
    .attr('r', 7.5)

  // add background rectangle behind the text tooltip
  focus.append('rect')
    .attr('x', -30)
    .attr('y', '-2em')
    .attr('width', 70)
    .attr('height', 20)
    .style("fill", "white");

  // add text annotation for tooltip
  focus.append('text')
    .attr('x', -30)
    .attr('dy', '-1em')
    .style("fill", "black")
    .style("font-family", "SuisseIntl");

  focus.append('div')
    .attr('x', 10)
    .attr('dy', '.35em')
    .attr("class", "tooltip")
    .style("opacity", 1)

  // create an overlay rectangle to draw the above objects on top of
  svg.append('rect')
    .attr('class', 'overlay')
    .attr('width', width - 100)
    .attr('height', height)
    .on('mouseover', () => focus.style('display', null))
    .on('mouseout', () => focus.style('display', 'none'))
    .on('mousemove', tipMove);

  // make the overlay rectangle transparent,
  // so it only serves the purpose of detecting mouse events
  d3.select('.overlay')
    .style('fill', 'none')
    .style('pointer-events', 'all');

  // select focus objects and set opacity
  d3.selectAll('.focus')
    .style('opacity', 0.9);

  // select the circle and style it
  d3.selectAll('.focus circle')
    .style("fill", '#FDD511')
    .style("opacity", 0)


  // function that adds tooltip on hover
  function tipMove() {
    // below code finds the date by bisecting and
    // stores the x and y coordinate as variables
    let x0 = x.invert(d3.mouse(this)[0]);
    let i = bisectDate(data3, x0, 1);
    let d0 = data3[i - 1];
    let d1 = data3[i];
    let d = x0 - d0.str1 > d1.str1 - x0 ? d1 : d0;

    // place the focus objects on the same path as the line
    focus.attr('transform', `translate(${x(d.date)}, ${y(d.value)})`);

    // position the x line
    focus.select('line.x')
      .attr('x1', 0)
      .attr('x2', x(formatDate(d.str1)))
      .attr('y1', 0)
      .attr('y2', 0);

    // position the y line
    focus.select('line.y')
      .attr('x1', 0)
      .attr('x2', 0)
      .attr('y1', 0)
      .attr('y2', height - y(d.str2));

    // position the text
    focus.select('text').text(d.str2).transition() // slowly fade in the tooltip
      .duration(100)
      .style("opacity", 1);

    // show the circle on the path
    focus.selectAll('.focus circle')
      .style("opacity", 1)

  };
}

drawFocus();
.tooltip {
    position: absolute;
    text-align: left;
    font-family: "Open Sans Condensed";
    font-size: 12px;
    width: 80px;
    height: 52px;
    padding: 8px;
    background: white;
    pointer-events: none;
    background-color: white;
    line-height: 0.05em;
    padding: 20px;
    /* border: solid;
        border-width: 1px; */
  }

  #my_dataviz {
    padding: 20px;
  }
<script src="https://d3js.org/d3.v5.js"></script>
<div id="my_dataviz"></div>

  1. 您混淆了您的命名。 ser1 你给 str1 打了很多次电话。这是一个明显的迹象,表明您的原始命名 ser1ser2 不够合乎逻辑!如果您现在不能遵循它,一年后您重新访问代码时如何?

  2. 在使用数据之前解析你的数据,而不是在你使用它的时候。如果您将日期作为字符串存储在原始对象中,请在 对其进行处理之前对其进行解析。否则,当您想要比较、绘图、格式化或只是 使用 时,您会 运行 遇到问题。

就是这样,我所做的大部分更改只是在需要时将 formatDate 替换为开头的 parseDate (这些是不同的东西),并将 str1/2 替换为 ser1/2 需要的地方。

var data3 = [
  { group: 1, ser1: "2020-01-01", ser2: 3 },
  { group: 1, ser1: "2020-01-02", ser2: 5 },
  { group: 1, ser1: "2020-01-03", ser2: 9 },
  { group: 1, ser1: "2020-01-04", ser2: 3 },
  { group: 1, ser1: "2020-01-05", ser2: 5 },
  { group: 1, ser1: "2020-01-06", ser2: 9 },
  { group: 2, ser1: "2020-01-07", ser2: 10 },
  { group: 2, ser1: "2020-01-08", ser2: 9 },
  { group: 2, ser1: "2020-01-09", ser2: 10 },
  { group: 2, ser1: "2020-01-10", ser2: 20 },
  { group: 2, ser1: "2020-01-11", ser2: 10 },
  { group: 2, ser1: "2020-01-12", ser2: 12 },
  { group: 3, ser1: "2020-01-13", ser2: 20 },
  { group: 3, ser1: "2020-01-14", ser2: 12 },
  { group: 3, ser1: "2020-01-15", ser2: 4 },
  { group: 3, ser1: "2020-01-16", ser2: 22 },
  { group: 3, ser1: "2020-01-17", ser2: 2 },
  { group: 3, ser1: "2020-01-18", ser2: 4 },
];

var parseDate = d3.timeParse("%Y-%m-%d");
// Parse the date ASAP
data3 = data3.map(function(d) {
  return {
    group: d.group,
    ser1: parseDate(d.ser1),
    ser2: d.ser2
  };
});

var line = d3.line()
  .x(function (d) { return x(d.ser1); })
  .y(function (d) { return y(d.ser2); })

// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 30, left: 50 },
  width = 1000 - margin.left - margin.right,
  height = 400 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
  .append("svg")
  .attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`)
  .append("g")
  .attr("transform",
    "translate(" + margin.left + "," + margin.top + ")");

svg.append("defs")
  .append("clipPath")
  .attr("id", "chart-path")
  .append("rect")
  .attr("width", width - 100)
  .attr("height", height)

var formatDate = d3.timeParse("%Y-%m-%d");

var bisectDate = d3.bisector(function (d) {
  return d.ser1;
}).left;

// Initialize a X axis:
var x = d3.scaleTime()
  .range([0, width - 100])
  .domain(d3.extent(data3, function (d) { return d.ser1; }));
var xNum = d3.scaleLinear().range([0, width - 100])
var xAxis = d3.axisBottom().scale(x);
svg.append("g")
  .attr("transform", "translate(0," + height + ")")
  .attr("class", "myXaxis")

// Initialize an Y axis
var y = d3.scaleLinear().range([height, 0]);
var yAxis = d3.axisLeft().scale(y);
svg.append("g")
  .attr("class", "myYaxis")

// create the Y axis
y.domain([0, d3.max(data3, function (d) { return d.ser2 })]);
svg.selectAll(".myYaxis")
  .transition()
  .duration(1000)
  .call(yAxis);

// Create a update selection: bind to the new data
var u = svg.selectAll(".lineTest")
  .data([data3])
  .enter()
  .append("path")
  .attr("class", "lineTest")
  .attr("fill", "none")
  .attr("stroke", "#0b6dbd")
  .attr("stroke-width", 2.5)
  .attr("clip-path", "url(#chart-path)")

svg.selectAll(".myXaxis")
  .call(xAxis);
u.attr("d", line);

// this is where I try to implement a tooltip based on
// https://blockbuilder.org/bendoesdata/2c8b315d103bbaf98264efda92d313ab
function drawFocus() {
  // Create focus object
  let focus = svg.append('g')
    .attr('class', 'focus')

  // append circle on the line path
  focus.append('circle')
    .attr('r', 7.5)

  // add background rectangle behind the text tooltip
  focus.append('rect')
    .attr('x', -30)
    .attr('y', '-2em')
    .attr('width', 70)
    .attr('height', 20)
    .style("fill", "white");

  // add text annotation for tooltip
  focus.append('text')
    .attr('x', -30)
    .attr('dy', '-1em')
    .style("fill", "black")
    .style("font-family", "SuisseIntl");

  focus.append('div')
    .attr('x', 10)
    .attr('dy', '.35em')
    .attr("class", "tooltip")
    .style("opacity", 1)

  // create an overlay rectangle to draw the above objects on top of
  svg.append('rect')
    .attr('class', 'overlay')
    .attr('width', width - 100)
    .attr('height', height)
    .on('mouseover', () => focus.style('display', null))
    .on('mouseout', () => focus.style('display', 'none'))
    .on('mousemove', tipMove);

  // make the overlay rectangle transparent,
  // so it only serves the purpose of detecting mouse events
  d3.select('.overlay')
    .style('fill', 'none')
    .style('pointer-events', 'all');

  // select focus objects and set opacity
  d3.selectAll('.focus')
    .style('opacity', 0.9);

  // select the circle and style it
  d3.selectAll('.focus circle')
    .style("fill", '#FDD511')
    .style("opacity", 0)


  // function that adds tooltip on hover
  function tipMove() {
    // below code finds the date by bisecting and
    // stores the x and y coordinate as variables
    let x0 = x.invert(d3.mouse(this)[0]);
    let i = bisectDate(data3, x0, 1);
    let d0 = data3[i - 1];
    let d1 = data3[i];
    let d = x0 - d0.ser1 > d1.ser1 - x0 ? d1 : d0;

    // place the focus objects on the same path as the line
    focus.attr('transform', `translate(${x(d.ser1)}, ${y(d.ser2)})`);

    // position the x line
    focus.select('line.x')
      .attr('x1', 0)
      .attr('x2', x(d.ser1))
      .attr('y1', 0)
      .attr('y2', 0);

    // position the y line
    focus.select('line.y')
      .attr('x1', 0)
      .attr('x2', 0)
      .attr('y1', 0)
      .attr('y2', height - y(d.ser2));

    // position the text
    focus.select('text').text(d.ser2)
      .transition() // slowly fade in the tooltip
      .duration(100)
      .style("opacity", 1);

    // show the circle on the path
    focus.selectAll('.focus circle')
      .style("opacity", 1)

  };
}

drawFocus();
.tooltip {
    position: absolute;
    text-align: left;
    font-family: "Open Sans Condensed";
    font-size: 12px;
    width: 80px;
    height: 52px;
    padding: 8px;
    background: white;
    pointer-events: none;
    background-color: white;
    line-height: 0.05em;
    padding: 20px;
    /* border: solid;
        border-width: 1px; */
  }

  #my_dataviz {
    padding: 20px;
  }
<script src="https://d3js.org/d3.v5.js"></script>
<div id="my_dataviz"></div>