簡體   English   中英

工具提示 D3 折線圖問題

[英]Issue with tooltip D3 Line Chart

編輯:這是問題的一個小提琴。

https://jsfiddle.net/8tpz5s9w/1/

我在實現簡單的 D3 工具提示時遇到問題。

我有一個多折線圖,它適用於其中一條線,但僅適用於第二條線的第一個點。 所有行的 X 值都相同,只是 y 值不同。

提示? 部分相關代碼:

var div = d3.select("body").append("div") 
    .attr("class", "tooltip")       
    .style("opacity", 0);      

points.selectAll('.point')
        .data(function(d) {
          console.log('.point d is: ', d);
          d.values.forEach(function(kv) {
            console.log('kv is:', kv);
            return kv.name = d.name;
          })
          return d.values;
        })
        .enter()
        .append('circle')
        .attr('circleId', function(d, i) { 
          return 'circleId-'+(i+1);
        })
        .attr('cx', function(d) {
          return x(d.Period);
        })
        .attr('cy', function(d) {
          return y(d.Value);
        })
        .attr('r', function(d) { 
          return dotRadius()
        })
        .on("mouseover", function(d) { 
          console.log('mouseover d is :', d);   
          div.transition()    
            .duration(200)    
            .style("opacity", .9);    
          div.html(d.name + ': ' + d.Value)  
            .style("left", (d3.event.pageX - 24) + "px")   
            .style("top", (d3.event.pageY - 56) + "px");  
        })          
        .on("mouseout", function(d) {   
          div.transition()    
            .duration(500)    
            .style("opacity", 0); 
        });

整個代碼減去 CSS:

jQuery(document).ready(function ($) {

  var margin = {top: 20, right: 30, bottom: 60, left: 45},
  width = 375 - margin.left - margin.right,
  height = 225 - margin.top - margin.bottom,
  dotRadius = function() { return 3 };  
  // dispatch = d3.dispatch("pointMouseover", "pointMouseout");

  var x = d3.time.scale()
    .range([0, width]);

  var y = d3.scale.linear()
    .range([height, 0]);

  var xAxis = d3.svg.axis()
    .scale(x)
    .orient('bottom')
    .tickFormat(d3.time.format('%b'));

  var yAxis = d3.svg.axis()
    .scale(y)
    .orient('left');

  var div = d3.select("body").append("div") 
    .attr("class", "tooltip")       
    .style("opacity", 0);

  // This is a function that determines the colours of the lines drawn, up to 10.
  var color = d3.scale.category10();

  // This is used to format the time for our data.
  var formatTime = d3.time.format("%Y-%m-%d");

  var line = d3.svg.line()
    .x(function(d) { return x(d.Period); })
    .y(function(d) { return y(d.Value); });


  var svg = d3.select("#pipeline-chart-render")
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
    .append('g')
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

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

  // This separates the data into the lines we want, although the data is stored
  // In the same original object.
  color.domain(d3.keys(data[0].values[0]).filter(function(key) { 
    if (key === 'Amount'
     || key === 'Quantity') {
        return key
    }
  }));


  // This returns the data into two separate objects which can be graphed.
  // In this case, Amount and Quantity.
  var datasets = color.domain().map(function(name) {
    return {
      name: name,
      values: data.map(function(d) {
        return {
          Period: formatTime.parse(d.values[0].Time),
          Value: +d.values[0][name]};
      })
    };
  });

  console.log('datasets is: ', datasets);

  // set the minYDomainValue to zero instead of letting it be a lingering magic number.
  var minDomainValue = 0

  var minDate = d3.min(datasets, function(d0){
    return d3.min(d0.values, function(d1){
      return d1.Period;
    })
  }),
  maxDate = d3.max(datasets, function(d0){
    return d3.max(d0.values, function(d1){
      return d1.Period;
    })
  });

  x.domain([minDate, maxDate]);
  y.domain([
    minDomainValue,
    d3.max(datasets, function(c) { return d3.max(c.values, function(v) { return v.Value; }); })
  ])

  // Append the x-axis class and move axis around.
  svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis)
    .append('text')
    .attr('x', 150)
    .attr('y', 36)
    .style('text-anchor', 'middle')
    .text('2015');

  // Append the y-axis class.
  svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
    .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", -30)
    .attr('x', -30)
    .style("text-anchor", "end")
    .text("Quantity/Amount");

  // The following is for defining the area BETWEEN graphs.  
  var areaAboveQuantity = d3.svg.area()
    .x(line.x())
    .y0(line.y())
    .y1(0);

  var areaBelowQuantity = d3.svg.area()
    .x(line.x())
    .y0(line.y())
    .y1(height);

  var areaAboveAmount = d3.svg.area()
    .x(line.x())
    .y0(line.y())
    .y1(0);
  var areaBelowAmount = d3.svg.area()
    .x(line.x())
    .y0(line.y())
    .y1(height);

  var pipeline = svg.selectAll('.pipeline')
    .data(datasets);

  pipeline.enter()
    .append('g')
    .attr('class', 'pipeline');


  pipeline.append('path')
    .attr('class', 'line')
    .attr('id', function(d, i) {
      return 'pipeline-'+(i+1);
    })
    .attr('d', function(d) { console.log('line d is: ', d); return line(d.values); })
    .attr("data-legend",function(d) { return d.name})
    .style("stroke", function(d) { return color(d.name); })

  pipeline.exit().remove()

  // Rendering the points on the graph.
  var points = svg.selectAll('.pipelinePoint')
    .data(datasets);

  points
    .enter()
    .append('g')
    .attr('class', 'pipelinePoint');

  points.selectAll('.point')
    .data(function(d) {
      console.log('.point d is: ', d);
      d.values.forEach(function(kv) {
        console.log('kv is:', kv);
        return kv.name = d.name;
      })
      return d.values;
    })
    .enter()
    .append('circle')
    .attr('circleId', function(d, i) { 
      return 'circleId-'+(i+1);
    })
    .attr('cx', function(d) {
      return x(d.Period);
    })
    .attr('cy', function(d) {
      return y(d.Value);
    })
    .attr('r', function(d) { 
      return dotRadius()
    })
    .on("mouseover", function(d) { 
      console.log('mouseover d is :', d);   
      div.transition()    
        .duration(200)    
        .style("opacity", .9);    
      div.html(d.name + ': ' + d.Value)  
        .style("left", (d3.event.pageX - 24) + "px")   
        .style("top", (d3.event.pageY - 56) + "px");  
    })          
    .on("mouseout", function(d) {   
      div.transition()    
        .duration(500)    
        .style("opacity", 0); 
    });



  var defs = svg.append('defs');

  defs.append('clipPath')
    .attr('id', 'clip-quantity')
    .append('path')
    .datum(datasets)
    .attr('d', function(d) {
       return areaAboveQuantity(d[1].values);
    });

  defs.append('clipPath')
    .attr('id', 'clip-amount')
    .append('path')
    .datum(datasets)
    .attr('d', function(d) {
      return areaAboveAmount(d[0].values);
    });


  svg.append('path')
    .datum(datasets)
    .attr('class', 'area')
    .attr('d', function(d) {
      return areaBelowQuantity(d[1].values)
    });

  // Quantity IS ABOVE Amount
  svg.append('path')
    .datum(datasets)
    .attr('d', function(d) {
      areaBelowQuantity(d[1].values);
    })
    .attr('clip-path', 'url(#clip-amount)')
    .style('fill', 'steelblue')
    .style('opacity', '0.2');


  // Amount IS ABOVE Quanity
  svg.append('path')
    .datum(datasets)
    .attr('d', function(d) {
      return areaBelowAmount(d[0].values);
    })
    .attr('clip-path', 'url(#clip-quantity)')
    .style('fill', 'steelblue')
    .style('opacity', '0.2')
    // .on("mouseover", function(d) { 
    //   console.log('mouseover d is :', d);   
    //   div.transition()    
    //     .duration(200)    
    //     .style("opacity", .9);    
    //   div.html('Hello')  
    //     .style("left", (d3.event.pageX) + "px")   
    //     .style("top", (d3.event.pageY - 28) + "px");  
    // })          
    // .on("mouseout", function(d) {   
    //   div.transition()    
    //     .duration(500)    
    //     .style("opacity", 0); 
    // });


  legend1 = svg.append("g")
    .attr("class","legend")
    .attr("transform","translate(-20,180)")
    .style("font-size","12px")
    .call(d3Legend)

})

var d3Legend = function(g) {
  g.each(function() {
    var g= d3.select(this),
        items = {},
        svg = d3.select(g.property("nearestViewportElement")),
        legendPadding = g.attr("data-style-padding") || 5,
        lb = g.selectAll(".legend-box").data([true]),
        li = g.selectAll(".legend-items").data([true])

    lb.enter().append("rect").classed("legend-box",true)
    li.enter().append("g").classed("legend-items",true)

    svg.selectAll("[data-legend]").each(function() {
      var self = d3.select(this)
      items[self.attr("data-legend")] = {
        pos : self.attr("data-legend-pos") || this.getBBox().y,
        color : self.attr("data-legend-color") != undefined ? self.attr("data-legend-color") : self.style("fill") != 'none' ? self.style("fill") : self.style("stroke") 
      }
    })

    items = d3.entries(items).sort(function(a,b) { return a.value.pos-b.value.pos})

    li.selectAll("text")
        .data(items,function(d) { return d.key})
        .call(function(d) { d.enter().append("text")})
        .call(function(d) { d.exit().remove()})
        .attr("y",function(d,i) { return i+"em"})
        .attr("x","1em")
        .text(function(d) { ;return d.key})

    li.selectAll("circle")
        .data(items,function(d) { return d.key})
        .call(function(d) { d.enter().append("circle")})
        .call(function(d) { d.exit().remove()})
        .attr("cy",function(d,i) { return i-0.25+"em"})
        .attr("cx",0)
        .attr("r","0.4em")
        .style("fill",function(d) { return d.value.color})  

    // Reposition and resize the box
    var lbbox = li[0][0].getBBox()  
    lb.attr("x",(lbbox.x-legendPadding))
        .attr("y",(lbbox.y-legendPadding))
        .attr("height",(lbbox.height+2*legendPadding))
        .attr("width",(lbbox.width+2*legendPadding))
  })
  return g
};

var data = [
  {
    key: 1,
    values: [
      {
        Amount: 33,
        Quantity: 22,
        Time: '2015-01-01'
      }
    ]
  },
  {
    key: 2,
    values: [
      {
        Amount: 52,
        Quantity: 20,
        Time: '2015-02-01'
      }
    ]
  },
  {
    key: 3,
    values: [
      {
        Amount: 63,
        Quantity: 30,
        Time: '2015-03-01'
      }
    ]
  },
  {
    key: 4,
    values: [
      {
        Amount: 92,
        Quantity: 60,
        Time: '2015-04-01'
      }
    ]
  },
  {
    key: 5,
    values: [
      {
        Amount: 50,
        Quantity: 29,
        Time: '2015-05-01'
      }
    ]
  },
  {
    key: 6,
    values: [
      {
        Amount: 53,
        Quantity: 25,
        Time: '2015-06-01'
      }
    ]
  },
  {
    key: 7,
    values: [
      {
        Amount: 46,
        Quantity: 12,
        Time: '2015-07-01'
      }
    ]
  },
  {
    key: 8,
    values: [
      {
        Amount: 52,
        Quantity: 15,
        Time: '2015-08-01'
      }
    ]
  },
  {
    key: 9,
    values: [
      {
        Amount: 55,
        Quantity: 20,
        Time: '2015-09-01'
      }
    ]
  },
  {
    key: 10,
    values: [
      {
        Amount: 35,
        Quantity: 17,
        Time: '2015-10-01'
      }
    ]
  },
  {
    key: 11,
    values: [
      {
        Amount: 80,
        Quantity: 45,
        Time: '2015-11-01'
      }
    ]
  },
  {
    key: 12,
    values: [
      {
        Amount: 64,
        Quantity: 24,
        Time: '2015-12-01'
      }
    ]
  }
]

這是由於您的路徑妨礙了能夠懸停在節點上。 您需要像這樣將pointer-events設置為none

我創建了一個類 - noPointers :

.noPointers{
  pointer-events:none;
}

並將其應用於擋路的路徑(第 369 行):

 // Amount IS ABOVE Quanity
    svg.append('path')
      .datum(datasets).attr('class', 'noPointers')//added class here

更新小提琴: https : //jsfiddle.net/reko91/8tpz5s9w/3/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM