【问题标题】:d3.js brush fill color histogramd3.js 画笔填充颜色直方图
【发布时间】:2016-12-11 15:16:47
【问题描述】:

我用d3.js 创建了一些直方图。 我设法根据brush 的位置更改rect 的填充颜色。 但我想更改rect 内的颜色。例如,如果brush startrect 的中间,我想让我的rect 有两种颜色。

目前这就是我所拥有的:

这就是我想要的:

我见过一些例子,比如Here。我是 d3 的新手,我尝试理解代码。
我看到他们使用clip-path,当他们没有画笔时肯定会隐藏背景栏,并根据画笔的范围显示它们。

这是JS Bin

更新

我已详细阅读了link 中提供的代码。我发现他们没有创建<rect> 元素来制作图表,但barPath 喜欢关注:

function barPath(groups) {
        var path = [],
            i = -1,
            n = groups.length,
            d;
        while (++i < n) {
          d = groups[i];
          path.push("M", x(d.key), ",", height, "V", y(d.value), "h9V", height);
        }
        return path.join("");
      }

但我不明白这个函数中发生了什么,以及如果他们没有其他方法可以做到这一点,如何以这种方式添加它。

【问题讨论】:

    标签: javascript d3.js brush clip-path


    【解决方案1】:

    我不会尝试绘制部分条形(正如您的编辑似乎暗示的那样),而是将条形附加两次,一次是底部的灰色,然后是顶部的钢蓝。然后,您可以将剪辑路径应用于蓝色条,当它们被剪辑时,您会看到下面的灰色。

    完整代码:

    <!DOCTYPE html>
    <html>
    
    <head>
      <script data-require="d3@3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
      <style>
        .charts {
          padding: 10px 0;
        }
        
        .chart {
          padding-left: 20px;
          padding-top: 10px;
        }
        
        .axis text {
          font: 10px sans-serif;
          fill: black;
        }
        
        .chart text {
          font: 10px sans-serif;
          fill: black;
        }
        
        .axis path,
        .axis line {
          fill: none;
          stroke: #000;
          shape-rendering: crispEdges;
        }
        /*dont display yAxis for categorical variable*/
        
        #chart .y.axis g {
          display: none;
        }
        /*Labels in categorical chart */
        
        text#catTitle.catTitle {
          font: 10px sans-serif;
          fill: white;
        }
        /*Color for the brush */
        
        .brush rect.extent {
          fill: steelblue;
          fill-opacity: .125;
        }
        /*Color for the brush resize path*/
        
        .brush .resize path {
          fill: #eee;
          stroke: #666;
        }
        /*Color for the hidden object*/
        
        .hidden {
          fill: grey;
        }
        
        .bar {
          fill: steelblue;
        }
      </style>
    </head>
    
    <body>
    
      <svg class="chart" id="chart"></svg>
    
      <script>
        var data = [{
          key: 1,
          value: 37
        }, {
          key: 1.5,
          value: 13
        }, {
          key: 2.5,
          value: 1
        }, {
          key: 3,
          value: 4
        }, {
          key: 3.5,
          value: 14
        }, {
          key: 4,
          value: 18
        }, {
          key: 4.5,
          value: 21
        }, {
          key: 5,
          value: 17
        }, {
          key: 5.5,
          value: 16
        }, {
          key: 6,
          value: 5
        }, {
          key: 6.5,
          value: 4
        }];
    
        var margin = {
          top: 10,
          right: 41,
          bottom: 42,
          left: 10
        };
    
        var width = 400 - margin.left - margin.right,
          height = 250 - margin.top - margin.bottom;
    
        var y = d3.scale.linear()
          .domain([0, d3.max(data, function(d) {
            return d.value
          })])
          .range([height, 0]);
    
        var x = d3.scale.linear()
          .domain([0, d3.max(data, function(d) {
            return d.key;
          }) + 1])
          .rangeRound([0, width]);
    
        var xAxis = d3.svg.axis()
          .scale(x)
          .orient("bottom");
    
        var yAxis = d3.svg.axis()
          .scale(y)
          .orient("left");
    
        var chart = d3.select(".chart#chart")
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
          .style("margin-left", 15 + "px");
    
        chart.append("defs")
          .append("clipPath")
          .attr("id", "clip")
          .append("rect")
          .attr("x", 0)
          .attr("y", 0)
          .attr("width", width)
          .attr("height", height);
    
        var brush = d3.svg.brush()
          .x(x)
          .on("brush", brushed)
          .on("brushend", brushend);
    
        function brushend() {
          if (brush.empty()){
            chart.select("#clip>rect")
              .attr("x", 0)
              .attr("width", width);
          }
        }
    
        function brushed() {
          var e = brush.extent();
          chart.select("#clip>rect")
            .attr("x", x(e[0]))
            .attr("width", x(e[1]) - x(e[0]));
        }
    
        chart.selectAll(".hidden")
          .data(data)
          .enter().append("rect")
          .attr("class", "hidden")
          .attr("x", function(d) {
            return x(d.key);
          })
          .attr("y", function(d) {
            return y(d.value);
          })
          .attr("height", function(d) {
            return height - y(d.value);
          })
          .attr("width", x(0.5))
          .style("stroke", "white")
          .append("title")
          .text(function(d) {
            return d.key;
          });
    
        chart.selectAll(".bar")
          .data(data)
          .enter().append("rect")
          .attr("clip-path", "url(#clip)")
          .attr("class", "bar")
          .attr("x", function(d) {
            return x(d.key);
          })
          .attr("y", function(d) {
            return y(d.value);
          })
          .attr("height", function(d) {
            return height - y(d.value);
          })
          .attr("width", x(0.5))
          .style("stroke", "white")
          .append("title")
          .text(function(d) {
            return d.key;
          });
    
        chart.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + height + ")")
          .call(xAxis);
    
        chart.append("text") //Add chart title
          .attr("transform", "translate(" + (width / 2) + " ," + (height + margin.bottom) + ")")
          .style("text-anchor", "middle")
          .text("Petal Length");
    
        chart.append("g")
          .attr("class", "y axis")
          .call(yAxis);
    
        chart.append("g")
          .attr("class", "x brush")
          .call(brush) //call the brush function, causing it to create the rectangles
          .selectAll("rect") //select all the just-created rectangles
          .attr("y", -6)
          .attr("height", (height + margin.top)) //set their height
    
        function resizePath(d) {
          var e = +(d == "e"),
            x = e ? 1 : -1,
            y = height / 3;
          return "M" + (.5 * x) + "," + y + "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6) + "V" + (2 * y - 6) + "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y) + "Z" + "M" + (2.5 * x) + "," + (y + 8) + "V" + (2 * y - 8) + "M" + (4.5 * x) + "," + (y + 8) + "V" + (2 * y - 8);
        }
    
        chart.selectAll(".resize").append("path").attr("d", resizePath);
      </script>
    </body>
    
    </html>

    【讨论】:

      【解决方案2】:

      对于希望将@Mark 的答案带到 v6 的任何人:

      const data = [{
        key: 1,
        value: 37
      }, {
        key: 1.5,
        value: 13
      }, {
        key: 2.5,
        value: 1
      }, {
        key: 3,
        value: 4
      }, {
        key: 3.5,
        value: 14
      }, {
        key: 4,
        value: 18
      }, {
        key: 4.5,
        value: 21
      }, {
        key: 5,
        value: 17
      }, {
        key: 5.5,
        value: 16
      }, {
        key: 6,
        value: 5
      }, {
        key: 6.5,
        value: 4
      }];
      
      // svg sizes
      const width = 400,
            height = 200;
      
      const m = 50;
      const margin = {
        top: m,
        right: m,
        bottom: m,
        left: m,
      };
      
      const y = d3.scaleLinear()
        .domain(d3.extent(data, d => d.value))
        .range([height - margin.bottom, margin.top]);
      
      const x = d3.scaleLinear()
        .domain(d3.extent(data, d => d.key).map((v, i) => i==0 ? v - 1 : v + 1))
        .rangeRound([margin.left, width - margin.right]);
      
      const svg = d3.select('svg')
        .attr('width', width)
        .attr('height', height)
        .attr('viewBox', `0 0 ${width} ${height}`)
      
      const rects = svg.append('g').attr('class', 'rects');
      const clips = svg.append('g').attr('class', 'clips');
      
      svg.append('g')
        .attr('class', 'x-axis')
        .attr('transform', `translate(0,${height - margin.bottom})`)
        .call(d3.axisBottom(x));
      
      svg.append('g')
        .attr('class', 'y-axis')
        .style('display', 'none')
        .attr('transform', `translate(${margin.left},0)`)
        .call(d3.axisLeft(y));
      
      svg.append('defs')
        .append('clipPath')
        .attr('id', 'clip')
          .append('rect')
          .attr('x', margin.left)
          .attr('y', margin.top)
          .attr('width', width - margin.right)
          .attr('height', height - margin.bottom);
      
      const brush = d3.brushX()
        .extent([
          [x.range()[0], margin.top],
          [x.range()[1], height - margin.bottom]
        ])
        .on('brush', brushed)
        .on('start', brushed)
        .on('end', brushend);
      
      function brushend(e) {
        if (!e.selection || !e.selection.length) {
          svg.select('#clip>rect')
            .attr('x', margin.left)
            .attr('width', width - margin.right);
        }
      }
      
      function brushed(e) {
        svg.select('#clip>rect')
          .attr('x', e.selection[0])
          .attr('width', e.selection[1] - e.selection[0]);
      
        const selected = {
          x0: x.invert(e.selection[0]),
          x1: x.invert(e.selection[1]),
        }
      }
      
      rects.selectAll('rect')
        .data(data)
        .enter().append('rect')
        .attr('x', d => x(d.key))
        .attr('y', d => y(d.value))
        .attr('height', d => height - y(d.value) - margin.bottom)
        .attr('width', 20)
        .style('stroke', 'white')
        .style('fill', 'gray')
        .append('title')
        .text(d => d.key);
      
      clips.selectAll('rect')
        .data(data)
        .enter().append('rect')
        .attr('clip-path', 'url(#clip)')
        .attr('x', d => x(d.key))
        .attr('y', d => y(d.value))
        .attr('height', d => height - y(d.value) - margin.bottom)
        .attr('width', 20)
        .style('stroke', 'white')
        .append('title')
        .text(d => d.key);
      
      svg.append('g')
        .attr('class', 'x brush')
        .call(brush) // initialize the brush
        .selectAll('rect')
        .attr('y', 0)
        .attr('height', height)
      <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
      <svg/>

      【讨论】: