d3.js突出显示堆积条并获取选定值

时间:2015-08-17 19:05:33

标签: javascript d3.js svg

第一个问题

对于单条形图,当我们" mouseover"一个栏,我们将获得我们选择的栏的值i。例如,以下代码检测我们选择的x轴的哪个元素,并将其分配给名为selectedYear的变量:

.on("mouseover", function (d, i) {
    selectedYear = i;
    update();
    barChart.selectAll("text")
            .attr("fill", "black");
    d3.select(this).attr("fill", "orange");
})

然而,当它是下面的堆积条时,我怎么能给出#34; 2006"到selectedYear和" SmartPhone"鼠标悬停功能中的seletedCategoryenter image description here

第二个问题:

当鼠标悬停在栏上时,如何在x轴上突出显示相应的文字? 在上图中,我如何制作文字" 2006"当2006栏的任何区块被鼠标悬停时突出显示?

以下是我的全部代码:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>

<script src="d3.min.js" charset="UTF-8"></script>
<script>


    var width  = 700;
    var height = 500;
    var dataSet;
    var svg = d3.select("body")
            .append("svg")
            .attr("width", width)
            .attr("height", height);




    var dataSet1 = [
        { name: "PC" ,
            sales: [    { year:2005, profit: 3000 },
                { year:2006, profit: 1300 },
                { year:2007, profit: 3700 },
                { year:2008, profit: 4900 },
                { year:2009, profit: 700 }] },
        { name: "SmartPhone" ,
            sales: [    { year:2005, profit: 2000 },
                { year:2006, profit: 4000 },
                { year:2007, profit: 1810 },
                { year:2008, profit: 6540 },
                { year:2009, profit: 2820 }] },
        { name: "Software" ,
            sales: [    { year:2005, profit: 1100 },
                { year:2006, profit: 1700 },
                { year:2007, profit: 1680 },
                { year:2008, profit: 4000 },
                { year:2009, profit: 4900 }] }
    ];




    var stack = d3.layout.stack()
            .values(function(d){ return d.sales; })
            .x(function(d){ return d.year; })
            .y(function(d){ return d.profit; });
    var data = stack(dataSet1);




    var padding = { left:50, right:100, top:30, bottom:30 };


    var xRangeWidth = width - padding.left - padding.right;

    var xScale = d3.scale.ordinal()
            .domain(data[0].sales.map(function(d){ return d.year; }))
            .rangeBands([0, xRangeWidth],0.3);

    var maxProfit = d3.max(data[data.length-1].sales, function(d){
        return d.y0 + d.y;
    });


    var yRangeWidth = height - padding.top - padding.bottom;

    var yScale = d3.scale.linear()
            .domain([0, maxProfit])     
            .range([0, yRangeWidth]);   



    var color = d3.scale.category10();

    var groups = svg.selectAll("g")
            .data(data)
            .enter()
            .append("g")
            .style("fill",function(d,i){ return color(i); });


    var rects = groups.selectAll("rect")
            .data(function(d){ return d.sales; })
            .enter()
            .append("rect")
            .attr("x",function(d){ return xScale(d.year); })
            .attr("y",function(d){ return yRangeWidth - yScale( d.y0 + d.y ); })
            .attr("width",60)
            .attr("height",function(d){ return yScale(d.y); })
            .attr("transform","translate(" + padding.left + "," + padding.top + ")")
            .attr("stroke","white")
            .on("mouseover", function (d, i) {
                 groups.selectAll("rect")
                    .attr("stroke","white")
                d3.select(this)
                        .attr("stroke", "black")
                        .attr("stroke-width", 3);
                selectedYear = i;
            })
            .on("mouseout", function (d) {
//                d3.select(this).attr("stroke", "white").attr("stroke-width", 0);
            });




    var xAxis = d3.svg.axis()
            .scale(xScale)
            .orient("bottom");

    yScale.range([yRangeWidth, 0]);

    var yAxis = d3.svg.axis()
            .scale(yScale)
            .orient("left");

    svg.append("g")
            .attr("class","axis")
            .attr("transform","translate(" + padding.left + "," + (height - padding.bottom) +  ")")
            .call(xAxis);

    svg.append("g")
            .attr("class","axis")
            .attr("transform","translate(" + padding.left + "," + (height - padding.bottom - yRangeWidth) +  ")")
            .call(yAxis);


    var labHeight = 50;
    var labRadius = 10;

    var labelCircle = groups.append("circle")
            .attr("cx",width - padding.right*0.98)
            .attr("cy",function(d,i){ return padding.top * 2 + labHeight * i; })
            .attr("r",labRadius);

    var labelText = groups.append("text")
            .attr("x",width - padding.right*0.8)
            .attr("y",function(d,i){ return padding.top * 2 + labHeight * i; })
            .attr("dy",labRadius/2)
            .text(function(d){ return d.name; });





</script>
</body>
</html>

1 个答案:

答案 0 :(得分:1)

一些想法......

  • 选择轴上的年份(为什么,我不知道......)
  • 从节点数据中选择的类别
  • 显示类别标签旁边的所选利润
  • 改进了所选矩形的突出显示
  • 保护x轴不被白色笔划覆盖

工作代码

var width  = 600,
    height = 200,
    padding = { left:50, right:200, top:20, bottom:30},
    xRangeWidth = width - padding.left - padding.right,
    yRangeWidth = height - padding.top - padding.bottom;

var dataSet;
var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height)
  .append("g")
  .attr("transform", "translate(" + [padding.left, padding.top] + ")");

var dataSet1 = [
      { name: "PC" ,
        sales: [    { year:2005, profit: 3000 },
                    { year:2006, profit: 1300 },
                    { year:2007, profit: 3700 },
                    { year:2008, profit: 4900 },
                    { year:2009, profit: 700 }] },
      { name: "SmartPhone" ,
        sales: [    { year:2005, profit: 2000 },
                    { year:2006, profit: 4000 },
                    { year:2007, profit: 1810 },
                    { year:2008, profit: 6540 },
                    { year:2009, profit: 2820 }] },
      { name: "Software" ,
        sales: [    { year:2005, profit: 1100 },
                    { year:2006, profit: 1700 },
                    { year:2007, profit: 1680 },
                    { year:2008, profit: 4000 },
                    { year:2009, profit: 4900 }] }
    ],
    // experiment to demonstrate unsupported data structure //////////////////////////////
    dataset2 = dataSet1.map(function(d){
      return {
        name: d.name,
        sales: d.sales.reduce(function(s, o) {
          return (s[o.year] = o.profit, s)
        },{})
      }
    }),
    _stack = d3.layout.stack()
      .values(layer)
      .x(function(d){ return d.year; })
      .y(function(d){ return d.profit; }),
    dataRaw = _stack(dataset2).map(function(d){
      return {
        name: d.name,
        sales: layer(d)
      }
    });

function layer(d){
  return Object.keys(d.sales).map(function(y){
    return {profit: d.sales[y],year:y}
  });
}
// end experiment /////////////////////////////////////////////////////////////////////

var offsetSelect = d3.ui.select({
      before: "svg",
      style: {position: "absolute", left: width - padding.right + 15 + "px", top: yRangeWidth + "px"},
      onUpdate: function() {
        update(dataSet1)
      },
      data    : ["wiggle", "zero", "expand", "silhouette"]
    }),
    orderSelect = d3.ui.select({
      before: "svg",
      style: {position: "absolute", left: width - padding.right + 15 + "px", top: yRangeWidth - 20 + "px"},
      onUpdate: function() {
        update(dataSet1)
      },
      data    : ["inside-out", "default", "reverse"]
    }),
    stack = d3.layout.stack()
      .values(function(d){ return d.sales; })
      .x(function(d){ return d.year; })
      .y(function(d){ return d.profit; })
      .out(function out(d, y0, y) {
        d.p0 = y0;
        d.y = y;
      }
    );

// apply a transform to map screen space to cartesian space
// this removes all confusion and mess when plotting data!
var plotArea = svg.append("g")
  .attr(transplot(yRangeWidth))
  .attr("class", "plotArea");
// x Axis
var xPadding = {inner: 0.1, outer: 0.3},
  xScale = d3.scale.ordinal()
      .rangeBands([0, xRangeWidth], xPadding.inner, xPadding.outer),
    xAxis = d3Axis()
      .scale(xScale)
      .orient("bottom"),
    gX    = svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + yRangeWidth  + ")");
// y Axis
var yAxisScale = d3.scale.linear()
      .range([yRangeWidth, 0]),
    yAxis = d3Axis()
      .scale(yAxisScale)
      .orient("left"),
    gY    = svg.append("g")
      .attr("class", "y axis")
      .style("pointer-events", "none");

var yScale = d3.scale.linear()
  .range([0, yRangeWidth]);

var color = d3.scale.category10();

function update(dataSet) {

  var data = stack.offset(offsetSelect.value())
        .order(orderSelect.value())(dataSet),
      maxProfit = d3.max(data,function(d) {
        return d3.max(d.sales, function(s) {
          return s.profit + s.p0
        })
      });

  function yDomain(){return [0, offsetSelect.value() == "expand" ? 1 : maxProfit]}

  xScale.domain(data[0].sales.map(stack.x()));
  yAxisScale.domain(yDomain());
  yScale.domain(yAxisScale.domain());

  var series = plotArea.selectAll(".series")
    .data(data);
  series.enter()
    .append("g")
    .attr("class", "series");
  series.style("fill", function(d, i) {
    return color(i);
  });
  series.exit().remove();

  var s = xScale.rangeBand(),
      w = s - xPadding.inner,
      points    = series.selectAll("rect")
        .data(function(d) {
          return d.sales;
        }),
      newPoints = points.enter()
        .append("rect")
        .attr("width", w)
        .on("mouseover", function(d) {
          var rect = d3.select(this), selectedYear = d.year;
          // if the plot is not normalised, offset the axis to align with the selected group
          if(offsetSelect.value() != "expand") {
            var pMin = d3.min(data,function(d) {
              return d3.min(d.sales.filter(function(p) {
                return p.year == selectedYear
              }), function(s) {
                return s.p0
              })
            });
            yAxisScale.domain([-pMin, yDomain()[1] - pMin]);
            gY.transition().call(yAxis).attr("transform", "translate(" + rect.attr("x") + ",0)");
          }
          // manage the highlighting
          series.selectAll("rect")
            .transition()
            .attr({opacity: 0.5});
          rect
            .transition()
            .attr({opacity: 1});
          d3.selectAll(".x.axis .tick")
            .filter(function(d) {
              return d == selectedYear
            })
            .classed("highlight", true);
          // move the selected element to the front
          d3.select(this.parentNode)
            .moveToFront();
          gX.moveToFront();
          // add the value for the moused over item to the legend text and highlight it
          var g = d3.select(this.parentNode).selectAll(".label").select("text");
          g.classed("highlight", true);
          g.text(g.text() + ": " + d3.format(">8.0f")(d.profit));
          series
            .append("g")
            .attr("class", "tooltip")
            .attr("transform", "translate(" + [rect.attr("x"), rect.attr("y")] + ")")
            .append("text")
            .attr(transflip())
            .text(d3.format(">8.0f")(d.profit))
            .attr({x: "1em", y: -rect.attr("height")/2, dy: ".35em", opacity: 0})
            .transition().attr("opacity", 1)
            .style({fill: "black", "pointer-events": "none"})
        })
        .on("mouseout", function(d) {
          var year = d.year;
          d3.selectAll(".x.axis .tick")
            .filter(function(d) {
              return d == year
            })
            .classed("highlight", false);
          series.selectAll("rect")
            .transition()
            .attr({opacity: 1});
          var g = d3.select(this.parentNode).select("text");
          g.classed("highlight", false);
          g.text(g.text().split(":")[0])
          yAxisScale.domain(yDomain());
          yAxis.tickSize(6);
          gY.transition().call(yAxis).attr("transform", "translate(0,0)");
          series.selectAll(".tooltip")
            .transition()
            .attr({opacity: 0})
            .remove();
        });

  points.transition()
    .attr("x", function(d) {
      return xScale(d.year);
    })
    .attr("y", function(d) {
      return yScale(d.p0);
    })
    .attr("height", function(d) {
      return yScale(d.y);
    })
    .attr("stroke", "white");

  points.exit().remove;

  gX.transition().call(xAxis);
  gY.transition().call(yAxis);

  // Add the legend inside the series containers
  // The series legend is wrapped in another g so that the
  // plot transform can be reversed. Otherwise the text would be mirrored
  var labHeight = 40,
      labRadius = 10,
      label = series.selectAll(".label").data(function(d){return [d.name]}),
      newLabel = label.enter().append("g")
        .attr("class", "label")
        // reverse the transform (it is it's own inverse)
        .attr(transplot(yRangeWidth));
  label.exit().remove();

  // add the marker and the legend text to the normalised container
  // push the data (name) down to them
  var labelCircle = label.selectAll("circle").data(aID),
      // take a moment to get the series order delivered by stack
      orders = data.map(function(d) { // simplify the form
        return {name: d.name, base: d.sales[0].p0}
      }).map(function(d) {            // get a copy, sorted by p0
        return d
      }).sort(function(a, b){
        return a.base - b.base
      }).map(function(d) {            // convert to index permutations
        return data.map(function(p) {
          return p.name
        }).indexOf(d.name)
      }).reverse();                   // convert to screen y ordinate
  labelCircle.enter().append("circle");
  labelCircle.attr("cx", xRangeWidth + 20)
    .attr("cy", function(d, i, j) {
      return labHeight * orders[j];
    })
    .attr("r", labRadius);

  var labelText = label.selectAll("text").data(aID);
  labelText.enter().append("text");
  labelText.attr("x", xRangeWidth + 40)
    .attr("y", function(d, i, j) {
      return labHeight * orders[j];
    })
    .attr("dy", labRadius / 2)
    .text(function(d) {
      return d;
    });

  function aID(d){
    return [d];
  }
}

update(dataSet1);

d3.selection.prototype.moveToFront = function() {
  return this.each(function() {
    this.parentNode.appendChild(this);
  });
};
    body {
      position: relative;
    }
    .axis path,
    .axis line {
      fill: none;
      stroke: #000;
      shape-rendering: crispEdges;
    }
    .axis .tick line {
      stroke: #ccc;
      /*opacity: 0.5;*/
      pointer-events: none;
    }
    .highlight {
      font-weight: bold ;
    }
    svg {
      overflow: visible;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/inputs/select/select.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/plot-transform.js"></script>