Amit kumar Sah
Amit kumar Sah

Reputation: 189

How to add filter to create single path from one bar to another bar (not in a loop)

I am have a requirement where the bar chart will containing max 4 bars and I am trying to add a filter on a bar chart where I can pick a particular bar from 4 bars and add a path to display the data.

Example

Different Paths:

Bar1 --> Bar3

Bar2 --> Bar4

Bar3 --> Bar2

Bar4 --> Bar1

Please help me to understand how can I pick any specific bar and set a path to another bar like above example? I have created different path code for the same but not able to get how I can get the target bar to create the proper path.

Below is my code:

        var barData = [{
                "Time": "Bar1",
                "Value": 5388
                },
                {
                "Time": "Bar2",
                "Value": 6453
                },
                {
                "Time": "Bar3",
                "Value": 3345
                },
                {
                "Time": "Bar4",
                "Value": 5345
                }];

            const container = d3.select('#graph');
          const divWidth = parseInt(container.style('width'));
            const divHeight = parseInt(container.style('height'));

        // Consider this width and Height are dynamic for div "graphID" because I am trying to responsive design
            const margin = {top: 30, right: 50, bottom: 50, left: 50};
          const width = divWidth - margin.left - margin.right;
          const height = divHeight - margin.top - margin.bottom;

                //To add svg in the visualization node i.e Dome node                    
         const svg = container.append("svg")
           .attr("width", divWidth)
           .attr("height", divHeight);
              
         const svgG = svg.append("g")
           .attr("transform", `translate(${margin.left},${margin.top})`);
           
           //To add tooltip for bar
           var tooltip = d3.select("body").append("div").attr("class", "toolTip");
          
          const defs = svg.append("defs");
          
          const marker = defs.append("marker")
          .attr("id","arrowhead")
          .attr("markerWidth","10")
           .attr("markerHeight","7")
           .attr("refX","0")
           .attr("refY","3.5")
           .attr("orient","auto")
           
           const polygon = marker.append("polygon")
           .attr("fill","gray")
           .attr("points","0 0, 10 3.5, 0 7")
                
        const xScale = d3.scaleBand()
          .domain(barData.map(d => d.Time))
          .range([0, width+margin.right]);

        const xAxis = d3.axisBottom(xScale);
                
        //Adding g attribute to svg for x axis
        svgG.append('g')
            .attr("transform", `translate(0,${height})`) 
            .call(xAxis);
                
        const yAxisMax = barData.reduce((max, item) => Math.max(max, item.Value), 0) * 1.5;
                
        const yScale = d3.scaleLinear()
            .domain([0, yAxisMax])
            .range([height, 0]);

        const yAxis = d3.axisLeft(yScale).ticks(4);
                
        svgG.append('g')
            .call(yAxis);

        const bars = svgG.selectAll('g.bar')
            .data(barData)
            .enter()
            .append('g')
          .classed('bar', true)
          .attr('transform', d => `translate(${xScale(d.Time) + xScale.bandwidth() / 2}, 0)`);
        /*  
        const staticColor =   "steelblue",
        highlightColor = "orange";

        var sheet = document.createElement('style')
        sheet.innerHTML = ".bar {fill: "+staticColor+"} .highlight {fill:"+highlightColor+"}";
        document.body.appendChild(sheet);
        */
        bars.append('rect')
            .attr('x', -20)
            .attr('width', 40)
            .attr('y', d =>  yScale(d.Value))
            .attr('height', d => height - yScale(d.Value) )
            .attr('fill', 'blue')
            //.attr("class", "bar")
            .on("mousemove", onMouseOver)
                    .on("mouseout", onMouseOut);
                
        function onMouseOver(d,i)
        {
                    tooltip
                      .style("left", d3.event.pageX - 50 + "px")
                      .style("top", d3.event.pageY - 70 + "px")
                      .style("display", "inline-block")
                      .html("Year: " + (d.Time) + "<br>" + "Value: " + (d.Value));
                      d3.select(this).attr('fill', "#eec42d");
                      //d3.select(this).attr('class', 'highlight');
                      //this.setState({ fillColour: 'green' });


        }

        function onMouseOut(d,i)
        {
         tooltip.style("display", "none");
         d3.select(this).attr('fill', "blue");
         //d3.select(this).attr('class', 'bar');
         //this.setState({ fillColour: 'blue' });
        }
              
        bars.append('text')
            .text(d => d.Value)
            .attr('text-anchor', 'middle')
            .attr('y', d => yScale(d.Value))
          .attr('dy', -5)
         ;

//path from Bar1 to Bar3
bars.filter((d, i) => i == 0 )
        .append('path')
           .attr('d', (d, i) => `M 5,${yScale(d.Value) - 20} V ${Math.min(yScale(d.Value), yScale(barData[i+2].Value)) - 60} H ${xScale.bandwidth() - 5} V ${yScale(barData[i+2].Value) - 25}`)
        .style('stroke', 'gray')
        .style('fill', 'none')
        .attr('marker-end', 'url(#arrowhead)')
    
    
 //path from Bar2 to Bar4
  
        bars.filter((d, i) => i == 1 )
        .append('path')
           .attr('d', (d, i) => `M 5,${yScale(d.Value) - 20} V ${Math.min(yScale(d.Value), yScale(barData[i+2].Value)) - 60} H ${xScale.bandwidth() - 5} V ${yScale(barData[i+2].Value) - 25}`)
        .style('stroke', 'gray')
        .style('fill', 'none')
        .attr('marker-end', 'url(#arrowhead)')
    
  //path from Bar3 to Bar2
  
        bars.filter((d, i) => i == 2 )
        .append('path')
           .attr('d', (d, i) => `M 5,${yScale(d.Value) - 20} V ${Math.min(yScale(d.Value), yScale(barData[i-1].Value)) - 60} H ${xScale.bandwidth() - 5} V ${yScale(barData[i-1].Value) - 25}`)
        .style('stroke', 'gray')
        .style('fill', 'none')
        .attr('marker-end', 'url(#arrowhead)')
    
    
  //path from Bar3 to Bar2
  
        bars.filter((d, i) => i == 3 )
        .append('path')
           .attr('d', (d, i) => `M 5,${yScale(d.Value) - 20} V ${Math.min(yScale(d.Value), yScale(barData[0].Value)) - 60} H ${xScale.bandwidth() - 5} V ${yScale(barData[0].Value) - 25}`)
        .style('stroke', 'gray')
        .style('fill', 'none')
        .attr('marker-end', 'url(#arrowhead)')
 
/*
        bars.filter((d, i) => i < barData.length - 1)
          .append('rect')
          .attr('x', 15)
          .attr('y', (d, i) => Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 70)
          .attr('width', xScale.bandwidth() - 30)
          .attr('height', 20)
          .attr('rx', 10)
          .style('fill', 'white')
          .style('stroke', 'gray');

        bars.filter((d, i) => i < barData.length - 1)
          .append('text')
          .text((d, i) => `${barData[i + 1].Value > d.Value ? '+' : ''}${Math.round((barData[i + 1].Value / d.Value * 100) - 100)}%`)
          .attr('x', xScale.bandwidth() / 2)
          .attr('y', (d, i) => Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 56)
          .attr('text-anchor', 'middle')
          .style('fill', 'black');
      
      */
#graph {
  width: 600px;
  height: 400px;
}

text {
  font-size: 12px;
  font-family: "Ubuntu";
}

.toolTip {
  position: absolute;
  display: none;
  min-width: 80px;
  height: auto;
  background: none repeat scroll 0 0 #ffffff;
  border: 1px solid #6F257F;
  padding: 5px;
  text-align: left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<div id="graph">
</div>

Upvotes: 2

Views: 58

Answers (1)

Michael Rovinsky
Michael Rovinsky

Reputation: 7210

Here is an example with targets:

        var barData = [{
                "Time": "Bar1",
                "Value": 5388,
                "Target": 2
                },
                {
                "Time": "Bar2",
                "Value": 6453,
                "Target": 0,
                },
                {
                "Time": "Bar3",
                "Value": 3345,
                "Target": 3,
                },
                {
                "Time": "Bar4",
                "Value": 5345,
                "Target": 1,
                }];

            const container = d3.select('#graph');
          const divWidth = parseInt(container.style('width'));
            const divHeight = parseInt(container.style('height'));

            const margin = {top: 30, right: 50, bottom: 50, left: 50};
          const width = divWidth - margin.left - margin.right;
          const height = divHeight - margin.top - margin.bottom;

                //To add svg in the visualization node i.e Dome node                    
         const svg = container.append("svg")
           .attr("width", divWidth)
           .attr("height", divHeight);
              
         const svgG = svg.append("g")
           .attr("transform", `translate(${margin.left},${margin.top})`);
           
           //To add tooltip for bar
           var tooltip = d3.select("body").append("div").attr("class", "toolTip");
          
          const defs = svg.append("defs");
          
          const marker = defs.append("marker")
          .attr("id","arrowhead")
          .attr("markerWidth","10")
           .attr("markerHeight","7")
           .attr("refX","0")
           .attr("refY","3.5")
           .attr("orient","auto")
           
           const polygon = marker.append("polygon")
           .attr("fill","gray")
           .attr("points","0 0, 10 3.5, 0 7")
                
        const xScale = d3.scaleBand()
          .domain(barData.map(d => d.Time))
          .range([0, width+margin.right]);

        const xAxis = d3.axisBottom(xScale);
                
        //Adding g attribute to svg for x axis
        svgG.append('g')
            .attr("transform", `translate(0,${height})`) 
            .call(xAxis);
                
        const yAxisMax = barData.reduce((max, item) => Math.max(max, item.Value), 0) * 1.5;
                
        const yScale = d3.scaleLinear()
            .domain([0, yAxisMax])
            .range([height, 0]);

        const yAxis = d3.axisLeft(yScale).ticks(4);
                
        svgG.append('g')
            .call(yAxis);

        const bars = svgG.selectAll('g.bar')
            .data(barData)
            .enter()
            .append('g')
          .classed('bar', true)
          .attr('transform', d => `translate(${xScale(d.Time) + xScale.bandwidth() / 2}, 0)`);
        /*  
        const staticColor =   "steelblue",
        highlightColor = "orange";

        var sheet = document.createElement('style')
        sheet.innerHTML = ".bar {fill: "+staticColor+"} .highlight {fill:"+highlightColor+"}";
        document.body.appendChild(sheet);
        */
        bars.append('rect')
            .attr('x', -20)
            .attr('width', 40)
            .attr('y', d =>  yScale(d.Value))
            .attr('height', d => height - yScale(d.Value) )
            .attr('fill', 'blue')
            .on("mousemove", onMouseOver)
                    .on("mouseout", onMouseOut);
                
        function onMouseOver(d,i)
        {
                    tooltip
                      .style("left", d3.event.pageX - 50 + "px")
                      .style("top", d3.event.pageY - 70 + "px")
                      .style("display", "inline-block")
                      .html("Year: " + (d.Time) + "<br>" + "Value: " + (d.Value));
                      d3.select(this).attr('fill', "#eec42d");
                      //d3.select(this).attr('class', 'highlight');
                      //this.setState({ fillColour: 'green' });


        }

        function onMouseOut(d,i)
        {
         tooltip.style("display", "none");
         d3.select(this).attr('fill', "blue");
        }
              
        bars.append('text')
            .text(d => d.Value)
            .attr('text-anchor', 'middle')
            .attr('y', d => yScale(d.Value))
          .attr('dy', -5)
         ;
         
const topPosition = i => yScale(9000) + i * 15;         
         
const pathBetweenBars = (d, i) => {
    const delta = d.Target - i;
  const targetValue = barData[d.Target].Value;
  const targetX = delta * xScale.bandwidth() - 5;
  const sourceY = yScale(d.Value);
  const targetY = yScale(targetValue);
  const topY = topPosition(i);
  return `M 5,${sourceY - 20} V ${topY} H ${targetX} V ${targetY - 25}`;
};         

const LABEL_WIDTH = 50;

const midPosition = (d, i) => {
    const delta = d.Target - i;
  return delta * xScale.bandwidth() / 2;
}

bars.filter(d => d.Target != null)
  .append('path')
  .attr('d', (d, i) => pathBetweenBars(d, i))
  .style('stroke', 'gray')
  .style('fill', 'none')
  .attr('marker-end', 'url(#arrowhead)')
    
bars.filter((d) => d.Target != null)
 .append('rect')
 .attr('x', (d, i) => midPosition(d, i) - LABEL_WIDTH / 2)
 .attr('y', (d, i) => topPosition(i) - 10)
 .attr('width', LABEL_WIDTH)
 .attr('height', 20)
 .attr('rx', 10)
 .style('fill', 'white')
 .style('stroke', 'gray');

bars.filter((d, i) => d.Target != null)
 .append('text')
 .text((d, i) => `${barData[d.Target].Value > d.Value ? '+' : ''}${Math.round((barData[d.Target].Value / d.Value * 100) - 100)}%`)
  .attr('x', (d, i) => midPosition(d, i))
  .attr('y', (d, i) => topPosition(i) + 3)
  .attr('text-anchor', 'middle')
  .style('fill', 'black');
  
#graph {
  width: 600px;
  height: 400px;
}

text {
  font-size: 12px;
  font-family: "Ubuntu";
}

.toolTip {
  position: absolute;
  display: none;
  min-width: 80px;
  height: auto;
  background: none repeat scroll 0 0 #ffffff;
  border: 1px solid #6F257F;
  padding: 5px;
  text-align: left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<div id="graph">
</div>

Upvotes: 1

Related Questions