Search code examples
javascriptd3.jsbrushclip-path

d3.js brush fill color histogram


i have created some histogram with d3.js. I managed to change fill color of rect depending on the position of the brush. But i would like to change the color inside a rect. For example if the brush start is in the middle of the rect i would like to have my rect with two color.

At the moment this is what i have :
enter image description here

And this is what i would like to have :
enter image description here

I have seen some examples like Here. I'm new to d3 and i try to understand the code.
I see that they use clip-path that certainly hide the background bar when their is no brush and show them when their is one depending on the range of the brush.

Here is a JS Bin

Update

I have read in details the code provided in the link. And i found that they dont create <rect> element to make chart but barPath like follow :

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("");
      }

But i didn't event understand what happend in this function and how to dot it in this way if their is no other way to do this.


Solution

  • For anyone looking to bring @Mark's answer to 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/>