Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3.JS: Zig zag line on y-axis between zero and start value

Tags:

d3.js

I've modified the y.domain of my D3 bar chart so it starts at a value above zero. However, I want to add a little "zig zag line" to indicate this, as in the picture below.

Chart with zig zag line

How could I do this in D3? Many thanks!

like image 721
TheLeonKing Avatar asked Jan 29 '26 01:01

TheLeonKing


2 Answers

I'd just hack this on by adding another path to the y axis:

// define how much space you'd like to create the axis "break" in
var axisBreakSpace = 50;

// Add the X Axis, with the space
svg.append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0," + (height + axisBreakSpace) + ")")
  .call(xAxis);

// Add the Y Axis, normally
var yG = svg.append("g")
  .attr("class", "y axis")
  .call(yAxis);

// add the zigzags path
yG.append("path")
  .attr("d", function(){
    var numZags = 10, // number of zigzags
        zagDist = (axisBreakSpace - 5) / numZags; // y distance on each zig or zag, -5 is a bit of space to finish it off

    // build the path at
    var curZig = height,
        d = "M0," + curZig;
    for (var i = 0; i < numZags; i++){
      curZig += zagDist;
      d += (i % 2 === 0) ? " L10," + curZig : " L-10," + curZig;
    }

    // finish it off to the x-axis
    d += " L0," + (height + axisBreakSpace);
    return d;
  });

Full working code sample:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  /* set the CSS */
  
  body {
    font: 12px Arial;
  }
  
  path {
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
  }
  
  .axis path,
  .axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
  }
</style>

<body>

  <!-- load the d3.js library -->
  <script src="http://d3js.org/d3.v3.min.js"></script>

  <script>
    // Set the dimensions of the canvas / graph
    var margin = {
        top: 30,
        right: 20,
        bottom: 100,
        left: 50
      },
      width = 600 - margin.left - margin.right,
      height = 270 - margin.top - margin.bottom,
      axisBreakSpace = 50;

    // Set the ranges
    var x = d3.scale.linear().range([0, width])
              .domain([0, 10]);
    var y = d3.scale.linear().range([height, 0])
              .domain([200,1000]);

    // Define the axes
    var xAxis = d3.svg.axis().scale(x)
      .orient("bottom")

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

    // Define the line
    var line = d3.svg.line()
      .x(function(d) {
        return x(d.x);
      })
      .y(function(d) {
        return y(d.y);
      });

    // Adds the svg canvas
    var svg = d3.select("body")
      .append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform",
        "translate(" + margin.left + "," + margin.top + ")");

    var data = d3.range(10).map(function(d){
      return {
        x: d,
        y: (Math.random() * 800) + 200
      }
    });

    // Add the valueline path.
    svg.append("path")
      .attr("class", "line")
      .attr("d", line(data));

    // Add the X Axis
    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + (height + axisBreakSpace) + ")")
      .call(xAxis);
      
    // Add the Y Axis
    var yG = svg.append("g")
      .attr("class", "y axis")
      .call(yAxis);
    
    yG.append("path")
      .attr("d", function(){
        var numZags = 10,
            zagDist = (axisBreakSpace - 5) / numZags;

        var curZig = height,
            d = "M0," + curZig;
        for (var i = 0; i < numZags; i++){
          curZig += zagDist;
          d += (i % 2 === 0) ? " L10," + curZig : " L-10," + curZig;
        }
        d += " L0," + (height + axisBreakSpace);
        return d;
      });

    
  </script>
</body>
like image 147
Mark Avatar answered Jan 31 '26 07:01

Mark


I would create some data and pass it to the D3 library. Something similar to this :

var data = [{
x1: xAxisSTARTPOINTX, //start
y1; xAxisSTARTPOINTY,
x2: firstXPointOnZigZag, 
y2; firstYPointOnZigZag},{
.
.
. },{
x1: lastXPointOnZigZag, //end
y1; lastYPointOnZigZag,
x2: yAxisSTARTPOINTX, 
y2; yAxisSTARTPOINTY}

}]

The values you put between will be the points on the zig zag which you can make up/generate.

Then pass this to this :

d3.select(container).data(data).enter().append('path')
.attr('x1', function(d){ return d.x1})
.attr('y1', function(d){ return d.y1})
.attr('x2', function(d){ return d.x2})
.attr('y2', function(d){ return d.y2})
.style('stroke','black');

You could generate the points yourself so you can change how many 'zigzags' you want by changing 'i' in the for loop.

A function to create points, something similar to this :

    function createPoints(xAxisStartPoint, yAxisStartPoint){ //pass two arrays

        var xAxisStartX = xAxisStartPoint[0], //xAxisStartPointX
        xAxisStartY = xAxisStartPoint[1], //xAxisStartPointY
        yAxisStartX = yAxisStartPoint[0], //xAxisStartPointX
        yAxisStartY = yAxisStartPoint[1]; //yAxisStartPointY

    var difference = xAxisStartY-yAxisStartY; //gets the difference between xAxis and yAxis to make sure the points are equal distance apart.

        var allPoints = []; //array to populate with points
        var numberOfPoints = 4; //number of zigzags
        var movement = 20; //movement left and right

        for(var i=0;i<=numberOfPoints;i++){
           var thisPoint = [];
              if(i===0){ //push xAxisStartPoint
                 thisPoint.push({
                    x:xAxisStartX,
                    y:xAxisStartY
                 })
              } else if(i===4){ //push yAxisStartPoint
                 thisPoint.push({
                   x:yAxisStartX,
                   y:yAxisStartY
              })
              } else {
                  thisCalcPointX;

                  if(i%2 > 0){ //if i is odd move left
                      thisCalcPointX = xAxisStartX-movement; //move point to the left
                  } else { //if it's even move right
                      thisCalcPointX = xAxisStartX+movement; //move point to the right
                  }
                  thisCalcPointY = xAxisStartY + difference/i; //move point up from xAxis start point at equal distance between xAxis and yAxis
                  thisPoint.push({
                   x: xAxisStartX,
                   y: thisCalcPointY
              })


        }
    allPoints.push(thisPoint); //push this point to array of points


        }


        return allPoints; //return the points
        }

//then pass this to create the path 

     var xAxisStart = [ xAxisStartX, xAxisStartY];
     var yAxisStart= [ yAxisStartX, yAxisStartY];
     var dataPoints = createPoints([xAxisStart, yAxisStart])

     d3.select(container).data(dataPoints).enter().append('path')
         .attr('x1', function(d){ return d.x1})
         .attr('y1', function(d){ return d.y1})
         .attr('x2', function(d){ return d.x2})
         .attr('y2', function(d){ return d.y2})
         .style('stroke','black');

Above code is not tested and just done on the fly, may need playing with, but the logic should work to create random points either side between both axis.

like image 29
thatOneGuy Avatar answered Jan 31 '26 06:01

thatOneGuy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!