Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are some of the grid lines randomly disappearing on my responsive D3 chart?

I have created a stripped down JSFiddle of my D3 chart. I have made it responsive (with viewbox and preserveaspectratio) using the solution at: responsive D3 chart

When I resize the window and make it smaller, some of the grid lines seem to be disappearing and reappearing. I presume this will look bad at small resolutions (eg. 320x480 mobile phone). Is there a way to preserve my gridlines when the window gets resized smaller?

HTML code:

<!--//d3 chart//-->
<div class="centre-div"></div>

CSS Code:

.centre-div {
  margin: 0 auto;
  max-width: 550px;
}

/* D3 chart css */
.axis path,
.axis line {
  fill: none;
  stroke: black;
  shape-rendering: crispEdges;
}

.axis text {
  font-family: sans-serif;
  font-size: 11px;
}

JS code:

//function createScatterplot() {
//Width and height
var margin = {
  top: 15,
  right: 2,
  bottom: 2,
  left: 2
};
//define width and height as the inner dimensions of the chart area.
var width = 550 - margin.left - margin.right;
var height = 550 - margin.top - margin.bottom;
var padding = 10;

//define svg as a G element that translates the origin to the top-left corner of the chart area.

//add <svg> to the last <div class="centre-div"> tag on the html page 
//this allows me to reuse the createScatterplot() function to draw multiple charts
var svg = d3.select(d3.selectAll(".centre-div")[0].pop()).append("svg")
  //.attr("width", width + margin.left + margin.right)
  //.attr("height", height + margin.top + margin.bottom)
  //make svg responsive
  .attr("width", "100%")
  .attr("height", "100%")
  .attr("viewBox", "0 0 550 550")
  .attr("preserveAspectRatio", "xMidYMid meet")
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//With this convention, all subsequent code can ignore margins.
//http://bl.ocks.org/mbostock/3019563

//Static dataset
var dataset = [
  [5, -2, "A"],
  [-4, -9, "B"],
  [2, 5, "C"],
  [1, -3, "D"],
  [-3, 5, "E"],
  [4, 1, "F"],
  [4, 4, "G"],
  [5, 7, "H"],
  [-5, -2, "I"],
  [0, 8, "J"],
  [-6, -5, "K"]
];

//Create scale functions
var xScale = d3.scale.linear()
  .domain([-10, 11])
  .range([padding, width - padding * 2]);

var yScale = d3.scale.linear()
  .domain([-10, 11])
  .range([height - padding, padding]);

//different scale for gridlines, so last tick has no line
var xScale2 = d3.scale.linear()
  .domain([-10, 10])
  .range([padding, width - padding * 2]);

var yScale2 = d3.scale.linear()
  .domain([-10, 10])
  .range([height - padding, padding]);
//add arrowheads
defs = svg.append("defs")
defs.append("marker")
  .attr({
    "id": "arrow",
    "viewBox": "-5 -5 10 10",
    "refX": 0,
    "refY": 0,
    "markerWidth": 7, //marker size
    "markerHeight": 7, //marker size
    "orient": "auto"
  })
  .append("path")
  .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
  .attr("fill", "#000");

//Define X axis
var xAxis = d3.svg.axis()
  .scale(xScale)
  .orient("bottom")
  .ticks(22)
  //Define Y axis
var yAxis = d3.svg.axis()
  .scale(yScale)
  .orient("left")
  .ticks(22)

//create scatterplot crosses
svg.selectAll("line.diag1")
  .data(dataset)
  .enter()
  .append("line")
  .attr({
    "class": "diag1",
    "x1": function(d) {
      return xScale(d[0]) - 4;
    },
    "y1": function(d) {
      return yScale(d[1]) - 4;
    },
    "x2": function(d) {
      return xScale(d[0]) + 4;
    },
    "y2": function(d) {
      return yScale(d[1]) + 4;
    },
    "stroke": "#006CCA",
    "opacity": "1",
    "stroke-width": "2px"
  });
svg.selectAll("line.diag2")
  .data(dataset)
  .enter()
  .append("line")
  .attr({
    "class": "diag2",
    "x1": function(d) {
      return xScale(d[0]) + 4;
    },
    "y1": function(d) {
      return yScale(d[1]) - 4;
    },
    "x2": function(d) {
      return xScale(d[0]) - 4;
    },
    "y2": function(d) {
      return yScale(d[1]) + 4;
    },
    "stroke": "#006CCA",
    "opacity": "1",
    "stroke-width": "2px"
  });

//Create X axis
svg.append("g")
  .attr("class", "axis")
  .style("stroke-width", 2)
  .attr("transform", "translate(0," + 11 * (height) / 21 + ")")
  .call(xAxis)
  //add x label
  .append("text")
  .attr("class", "label")
  .attr("x", width)
  .attr("y", 15)
  .attr("font-style", "italic")
  .attr("font-weight", "bold")
  .style("text-anchor", "end")
  .text("x");

//Create Y axis
svg.append("g")
  .attr("class", "axis")
  .style("stroke-width", 2)
  .attr("transform", "translate(" + 10 * (width - padding) / 21 + ",0)")
  .call(yAxis)
  //add y label
  .append("text")
  .attr("class", "label")
  .attr("x", -10)
  .attr("y", -5)
  .attr("font-style", "italic")
  .attr("font-weight", "bold")
  .style("text-anchor", "end")
  .text("y");

//add arrowheads to axis ends   
//add line on top of x-axis and arrowhead
svg.append("line")
  .attr({
    "x1": 0,
    "y1": 11 * height / 21,
    "x2": width - padding * 1.5,
    "y2": 11 * height / 21,
    "stroke": "black",
    "stroke-width": "2px",
    "marker-end": "url(#arrow)"
  });
//add line on top of y-axis and arrowhead
svg.append("line")
  .attr({
    "x1": 10 * (width - padding) / 21,
    "y1": height,
    "x2": 10 * (width - padding) / 21,
    "y2": 0.4 * padding,
    "stroke": "black",
    "stroke-width": "2px",
    "marker-end": "url(#arrow)"
  });

//Assuming that you have Mike Bostock's standard margins defined and you have defined a linear scale for the y-axis the following code will create horizontal gridlines without using tickSize().   
//https://stackoverflow.com/questions/15580300/proper-way-to-draw-gridlines

//create horizontal grid lines      
var gridwidth = 19 * width / 20;
var gridheight = 19 * height / 20;
svg.selectAll("line.horizontalGrid").data(yScale2.ticks(20)).enter()
  .append("line")
  .attr({
    "class": "horizontalGrid",
    "x1": 0,
    "x2": gridwidth,
    "y1": function(d) {
      return yScale(d);
    },
    "y2": function(d) {
      return yScale(d);
    },
    "fill": "none",
    "shape-rendering": "crispEdges",
    "stroke": "black",
    "stroke-width": "1px",
    "opacity": "0.3"
  });
//create vertical gridlines
svg.selectAll("line.verticalGrid").data(xScale2.ticks(20)).enter()
  .append("line")
  .attr({
    "class": "verticalGrid",
    "y1": height - gridheight,
    "y2": height,
    "x1": function(d) {
      return xScale(d);
    },
    "x2": function(d) {
      return xScale(d);
    },
    "fill": "none",
    "shape-rendering": "crispEdges",
    "stroke": "black",
    "stroke-width": "1px",
    "opacity": "0.3"
  });

//remove last ticks and zero ticks
svg.selectAll(".tick")
  .filter(function(d) {
    return d === 11;
  })
  .remove();
svg.selectAll(".tick")
  .filter(function(d) {
    return d === 0;
  })
  .remove();
//add a custom origin identifier
svg.append("text")
  .attr({
    "class": "origintext",
    "x": 455 * width / 1000,
    "y": 552 * height / 1000,
    "text-anchor": "end",
    "font-size": "65%"
  })
  .text("0");

//add labels to points plotted
svg.selectAll("textlabels")
  .data(dataset)
  .enter()
  .append("text")
  .text(function(d) {
    return d[2];
  })
  .attr("x", function(d) {
    return xScale(d[0]) + 5;
  })
  .attr("y", function(d) {
    return yScale(d[1]) - 5;
  })
  .attr("font-weight", "bold")
  .attr("font-size", "12px")
  .attr("fill", "black");

//}
like image 456
Kin Avatar asked Aug 11 '16 12:08

Kin


1 Answers

That is an aliasing effect which will occur because the way the lines will get rendered is influenced by various factors. The main three of them being stroke width, position and rendering mode. For using shape-rendering: crispEdges the SVG spec states:

To achieve crisp edges, the user agent might turn off anti-aliasing for all lines...

Depending on the scaling and the translation of the line it may be calculated to appear between two screen pixels while the scaled stroke width is not broad enough to color any of those adjacent screen pixels. That way the lines seem to randomly disappear and appear again.

Further explanations can be found in "Why is SVG stroke-width : 1 making lines transparent?" or in my answer to "Drew a straight line, but it is crooked d3".

For your code you can change the rendering behaviour by using shape-rendering: geometricPrecision instead of crispEdges when drawing the grid lines. Have a look at the updated JSFiddle for a working example.

like image 170
altocumulus Avatar answered Nov 15 '22 12:11

altocumulus