4

I am trying to create a typical bode plot with d3js. A typical bode plot created with Matlab looks as follows:

Matlab bode plot

Now I already came pretty far see: http://plnkr.co/edit/BpWis5uhC8KM2tRbXMk3 or the snippet embedded in this post.

The problem I have is that I want, as in the matlab bode plot, have major and minor ticks. Major ticks are solid while minor ticks are dotted. Unfortunately, I do not seem able to get this right. I already tried various solutions from:

Major and minor ticks with different style, whole page covered D3? Major and minor ticks with V3 of D3? d3.js alternative to axis.tickSubdivide? http://bl.ocks.org/vjpgo/4689130 http://bl.ocks.org/mbostock/4349486

<!DOCTYPE html>
<html lang="en">
<head>
<title>LOG</title>
<meta charset="utf-8">
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//mathjs.org/js/lib/math.js"></script>
<style type="text/css">
svg {
    font: 10px sans-serif;
    shape-rendering: crispEdges;
}
  
rect {
    fill: transparent;
}
  
.axis path,
.axis line {
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
}
  
.line {
    fill: none;
    stroke: steelblue;
    stroke-width: 1.5px;
    clip-path: url(#clip);
}
  
.grid .tick {
    stroke: lightgrey;
    opacity: 0.7;
}
  
.grid path {
    stroke-width: 0;
}
</style>
<script type="text/javascript">
 function linspace(a,b,n) {
  var every = (b-a)/(n-1),
   range = [];
  for (i = a; i < b; i += every)
   range.push(i);
  return range.length == n ? range : range.concat(b);
 }
 
 function logspace(a,b,n) {
  return linspace(a,b,n).map(function(x) { return Math.pow(10,x); });
 }
 
 function isInteger(value) {
  return typeof value === "number" && 
      isFinite(value) && 
      Math.floor(value) === value;
 };
 
 function leadlag(f) {
  w = 2*math.pi*f;
  s = math.complex(0,w);

  K  = 1;
  fz = 20;
  fp = 40;

  wz = 2*math.pi*fz;
  wp = 2*math.pi*fp;

  return math.multiply(K,math.multiply(math.divide(wp,wz),math.divide(math.add(s,wz),(math.add(s,wp)))));
 };
 
 function angle(f) {
  return math.atan2(f.im,f.re);
 };
 
 function deg2rad(deg) {
    return deg * math.pi / 180;
 };
 
 function rad2deg(rad) {
    return rad * 180 / math.pi;
 };
 
 function mag2db(mag) {
  return 20 * Math.log10(mag);
 }
 
 function db2mag(db) {
  return math.pow(10,db / 20);
 }
</script>
</head>
<body>
<div id="plotmagnitude"></div>
<div id="plotphase"></div>
<script type="text/javascript">
 var margin = {
        top: 20,
        right: 20,
        bottom: 35,
        left: 50
    };
 
 var width  = 450 - margin.left - margin.right;
 var height = 250 - margin.top - margin.bottom;
 
 var range = logspace(0,3,1000);

    var x = d3.scale.log()
  .domain([1, range[range.length-1].toFixed()])
  .range([0, width]);

    var y = d3.scale.linear()
  .domain([-10, 2])
  .range([height, 0]);

    var xAxis1 = d3.svg.axis()
  .scale(x)
  .orient("bottom")
  .ticks(1,"0.1s")
  .innerTickSize(-6)
  .outerTickSize(0)
  .tickPadding(7);

    var yAxis1 = d3.svg.axis()
  .scale(y)
  .orient("left")
  .ticks(5)
  .innerTickSize(-6)
  .outerTickSize(0)
  .tickPadding(7);

    var xAxis2 = d3.svg.axis()
  .scale(x)
  .orient("top")
  .ticks(5)
  .innerTickSize(-6)
  .tickPadding(-20)
  .outerTickSize(0)
  .tickFormat("");

    var yAxis2 = d3.svg.axis()
  .scale(y)
  .orient("left")
  .ticks(5)
  .innerTickSize(6)
  .tickPadding(-20)
  .outerTickSize(0)
  .tickFormat("");

    var xGrid = d3.svg.axis()
  .scale(x)
  .orient("bottom")
  .ticks(5)
  .tickSize(-height, -height, 0)
  .tickFormat("");

    var yGrid = d3.svg.axis()
  .scale(y)
  .orient("left")
  .ticks(5)
  .tickSize(-width, -width, 0)
  .tickFormat("");
 
    var line = d3.svg.line()
  .x(function(d) {
   return x(d.x);
  })
  .y(function(d) {
   return y(d.y);
  })
  .interpolate("linear");
   
    var zoom = d3.behavior.zoom()
  .x(x)
  .y(y)
  .scaleExtent([1, 1])
  .on("zoom",redraw);
      
    var plotMagnitude = d3.select("#plotmagnitude").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 + ")")
  .call(zoom);
   
    // Add x grid
    plotMagnitude.append("g")
  .attr("class","x grid")
  .attr("transform","translate(0," + height + ")")
  .call(xGrid);
  

    // Add y grid
    plotMagnitude.append("g")
  .attr("class","y grid")
  .call(yGrid);

    plotMagnitude.append("g")
  .attr("class","x1 axis")
  .attr("transform","translate(0," + height + ")")
  .call(xAxis1);

    plotMagnitude.append("g")
  .attr("class","y1 axis")
  .call(yAxis1);

    /* append additional X axis */
    plotMagnitude.append("g")
  .attr("class","x2 axis")
  .attr("transform","translate(" + [0, 0] + ")")
  .call(xAxis2);

    /* append additional y axis */
    plotMagnitude.append("g")
  .attr("class","y2 axis")
  .attr("transform","translate(" + [width, 0] + ")")
  .call(yAxis2);

    // Add x axis label  
    plotMagnitude.append("text")
        .attr("transform","translate(" + (width / 2) + "," + (height + margin.bottom) + ")")
        .style("font-size","15")
        .style("text-anchor","middle")
        .text("x axis");

    // Add y axis label
    plotMagnitude.append("text")
        .attr("transform", "rotate(-90)")
        .attr("y",0 - margin.left)
        .attr("x",0 - (height / 2))
        .attr("dy", "1em")
        .style("font-size","15")
        .style("text-anchor", "middle")
        .text("y axis");

    plotMagnitude.append("defs").append("clipPath")
  .attr("id", "clip")
  .append("rect")
  .attr("width", width)
  .attr("height", height);
      
    plotMagnitude.append("rect")
  .attr("width", width)
  .attr("height", height);

    function redraw() {
  plotMagnitude.select(".x1.axis").call(xAxis1);
  plotMagnitude.select(".y1.axis").call(yAxis1);
  plotMagnitude.select(".x2.axis").call(xAxis2);
  plotMagnitude.select(".y2.axis").call(yAxis2);
  plotMagnitude.select(".x.grid").call(xGrid);
  plotMagnitude.select(".y.grid").call(yGrid);
  
  var series = [];
  var data1 = [];
  var data2 = [];
  var data3 = [];

  for (var i = 0; i < range.length; i++) {
   data1.push({
    x: range[i],
    y: leadlag(range[i])
   });

   data2.push({
    x: range[i],
    y: mag2db(math.abs(leadlag(range[i])))
   });
   
   data3.push({
    x: range[i],
    y: rad2deg(angle(leadlag(range[i])))
   });
  }

  series.push({data: data2, width: 1, color: 'blue', stroke: "0,0", legend: "MAG"  });

  var series = plotMagnitude.selectAll(".line").data(series);

  series.enter().append('path');

  series.attr("class","line")
   .attr("d",function(d) { return line(d.data); })
   .attr("stroke-width", function(d) { return d.width; })
   .style("stroke", function(d) { return d.color; })
   .style("stroke-dasharray", function(d) { return d.stroke; });
 }

 $(function() {
  redraw();
 });
</script>
</body>
</html>
Community
  • 1
  • 1
WG-
  • 1,046
  • 2
  • 14
  • 27

1 Answers1

1

It seems that your "major" ticks are defined as those with labels, so the difficult part becomes finding them and then the corresponding grid line.

How about:

d3.selectAll('.x1.axis>.tick')  // find all the x axis ticks and loop
  .each(function(d,i){
    if (d3.select(this).select('text').text() === ""){ // if they have no label
      d3.select('.x.grid>.tick:nth-child(' + (i + 1) + ')') // get the corresponding grid line
        .style("stroke-dasharray", "3,3"); // and make it dashed
      }
  });

Full code:

<!DOCTYPE html>
<html lang="en">
<head>
<title>LOG</title>
<meta charset="utf-8">
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//mathjs.org/js/lib/math.js"></script>
<style type="text/css">
svg {
    font: 10px sans-serif;
    shape-rendering: crispEdges;
}
  
rect {
    fill: transparent;
}
  
.axis path,
.axis line {
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
}
  
.line {
    fill: none;
    stroke: steelblue;
    stroke-width: 1.5px;
    clip-path: url(#clip);
}
  
.grid .tick {
    stroke: lightgrey;
    opacity: 0.7;
}
  
.grid path {
    stroke-width: 0;
}
</style>
<script type="text/javascript">
 function linspace(a,b,n) {
  var every = (b-a)/(n-1),
   range = [];
  for (i = a; i < b; i += every)
   range.push(i);
  return range.length == n ? range : range.concat(b);
 }
 
 function logspace(a,b,n) {
  return linspace(a,b,n).map(function(x) { return Math.pow(10,x); });
 }
 
 function isInteger(value) {
  return typeof value === "number" && 
      isFinite(value) && 
      Math.floor(value) === value;
 };
 
 function leadlag(f) {
  w = 2*math.pi*f;
  s = math.complex(0,w);

  K  = 1;
  fz = 20;
  fp = 40;

  wz = 2*math.pi*fz;
  wp = 2*math.pi*fp;

  return math.multiply(K,math.multiply(math.divide(wp,wz),math.divide(math.add(s,wz),(math.add(s,wp)))));
 };
 
 function angle(f) {
  return math.atan2(f.im,f.re);
 };
 
 function deg2rad(deg) {
    return deg * math.pi / 180;
 };
 
 function rad2deg(rad) {
    return rad * 180 / math.pi;
 };
 
 function mag2db(mag) {
  return 20 * Math.log10(mag);
 }
 
 function db2mag(db) {
  return math.pow(10,db / 20);
 }
</script>
</head>
<body>
<div id="plotmagnitude"></div>
<div id="plotphase"></div>
<script type="text/javascript">
 var margin = {
        top: 20,
        right: 20,
        bottom: 35,
        left: 50
    };
 
 var width  = 450 - margin.left - margin.right;
 var height = 250 - margin.top - margin.bottom;
 
 var range = logspace(0,3,1000);

    var x = d3.scale.log()
  .domain([1, range[range.length-1].toFixed()])
  .range([0, width]);

    var y = d3.scale.linear()
  .domain([-10, 2])
  .range([height, 0]);

    var xAxis1 = d3.svg.axis()
  .scale(x)
  .orient("bottom")
  .ticks(1,"0.1s")
  .innerTickSize(-6)
  .outerTickSize(0)
  .tickPadding(7);

    var yAxis1 = d3.svg.axis()
  .scale(y)
  .orient("left")
  .ticks(5)
  .innerTickSize(-6)
  .outerTickSize(0)
  .tickPadding(7);

    var xAxis2 = d3.svg.axis()
  .scale(x)
  .orient("top")
  .ticks(5)
  .innerTickSize(-6)
  .tickPadding(-20)
  .outerTickSize(0)
  .tickFormat("");

    var yAxis2 = d3.svg.axis()
  .scale(y)
  .orient("left")
  .ticks(5)
  .innerTickSize(6)
  .tickPadding(-20)
  .outerTickSize(0)
  .tickFormat("");

    var xGrid = d3.svg.axis()
  .scale(x)
  .orient("bottom")
  .ticks(5)
  .tickSize(-height, -height, 0)
  .tickFormat("");

    var yGrid = d3.svg.axis()
  .scale(y)
  .orient("left")
  .ticks(5)
  .tickSize(-width, -width, 0)
  .tickFormat("");
 
    var line = d3.svg.line()
  .x(function(d) {
   return x(d.x);
  })
  .y(function(d) {
   return y(d.y);
  })
  .interpolate("linear");
   
    var zoom = d3.behavior.zoom()
  .x(x)
  .y(y)
  .scaleExtent([1, 1])
  .on("zoom",redraw);
      
    var plotMagnitude = d3.select("#plotmagnitude").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 + ")")
  .call(zoom);
   
    // Add x grid
    plotMagnitude.append("g")
  .attr("class","x grid")
  .attr("transform","translate(0," + height + ")")
  .call(xGrid);

    // Add y grid
    plotMagnitude.append("g")
  .attr("class","y grid")
  .call(yGrid);

    plotMagnitude.append("g")
  .attr("class","x1 axis")
  .attr("transform","translate(0," + height + ")")
  .call(xAxis1);

    plotMagnitude.append("g")
  .attr("class","y1 axis")
  .call(yAxis1);

    /* append additional X axis */
    plotMagnitude.append("g")
  .attr("class","x2 axis")
  .attr("transform","translate(" + [0, 0] + ")")
  .call(xAxis2);

    /* append additional y axis */
    plotMagnitude.append("g")
  .attr("class","y2 axis")
  .attr("transform","translate(" + [width, 0] + ")")
  .call(yAxis2);

    // Add x axis label  
    plotMagnitude.append("text")
        .attr("transform","translate(" + (width / 2) + "," + (height + margin.bottom) + ")")
        .style("font-size","15")
        .style("text-anchor","middle")
        .text("x axis");

    // Add y axis label
    plotMagnitude.append("text")
        .attr("transform", "rotate(-90)")
        .attr("y",0 - margin.left)
        .attr("x",0 - (height / 2))
        .attr("dy", "1em")
        .style("font-size","15")
        .style("text-anchor", "middle")
        .text("y axis");

    plotMagnitude.append("defs").append("clipPath")
  .attr("id", "clip")
  .append("rect")
  .attr("width", width)
  .attr("height", height);
      
    plotMagnitude.append("rect")
  .attr("width", width)
  .attr("height", height);

    function redraw() {
  plotMagnitude.select(".x1.axis").call(xAxis1);
  plotMagnitude.select(".y1.axis").call(yAxis1);
  plotMagnitude.select(".x2.axis").call(xAxis2);
  plotMagnitude.select(".y2.axis").call(yAxis2);
  plotMagnitude.select(".x.grid").call(xGrid);
  plotMagnitude.select(".y.grid").call(yGrid);
  
  var series = [];
  var data1 = [];
  var data2 = [];
  var data3 = [];

  for (var i = 0; i < range.length; i++) {
   data1.push({
    x: range[i],
    y: leadlag(range[i])
   });

   data2.push({
    x: range[i],
    y: mag2db(math.abs(leadlag(range[i])))
   });
   
   data3.push({
    x: range[i],
    y: rad2deg(angle(leadlag(range[i])))
   });
  }

  series.push({data: data2, width: 1, color: 'blue', stroke: "0,0", legend: "MAG"  });

  var series = plotMagnitude.selectAll(".line").data(series);

  series.enter().append('path');

  series.attr("class","line")
   .attr("d",function(d) { return line(d.data); })
   .attr("stroke-width", function(d) { return d.width; })
   .style("stroke", function(d) { return d.color; })
   .style("stroke-dasharray", function(d) { return d.stroke; });
   
  d3.selectAll('.x1.axis>.tick')
    .each(function(d,i){
      if (d3.select(this).select('text').text() === ""){
        d3.select('.x.grid>.tick:nth-child(' + (i + 1) + ')')
          .style("stroke-dasharray", "3,3");
      }
    });
  
 }

 $(function() {
  redraw();
 });
</script>
</body>
</html>
Mark
  • 106,305
  • 20
  • 172
  • 230
  • Thank you for your reply! At first I was really pleased with this answer. However, I am creating a bode plot. This includes two plots which are underneath each other. The magnitude plot, the upper one, normally does not have an x-axis label to save space and because it is repetitive w.r.t to the plot below, the phase plot. The next step I would do after this solutions is creating two plots, one without the x labels and one below with the x-labels. I am open to suggestions to improve the code as well because I have the feeling that it is a little bloated? – WG- Dec 23 '15 at 23:08
  • @WG-, if the xaxis on both plots match, this solution still works. You loop the axis with labels and select the corresponding grid lines from **both** plots. – Mark Dec 23 '15 at 23:12
  • I just tried your solution when I have both plots working; http://plnkr.co/edit/HE2f7JLZOjXV0JLf7iGY?p=preview, but it does not seem to work? It does not select the other x axis ticks? – WG- Dec 27 '15 at 18:18
  • Fixed it by using `d3.selectAll('.x.grid>.tick:nth-child(' + (i + 1) + ')').style("stroke-dasharray", "3,3");` – WG- Dec 28 '15 at 09:35