为什么一些网格线在我的响应式 D3 图表上随机消失?
Why are some of the grid lines randomly disappearing on my responsive D3 chart?
我创建了一个精简的 JSFiddle 我的 D3 图表。我使用以下解决方案使其响应(使用 viewbox 和 preserveaspectratio):
responsive D3 chart
当我调整 window 的大小并使其变小时,一些网格线似乎在消失并重新出现。我认为这在小分辨率下看起来会很糟糕(例如 320x480 移动 phone)。当 window 变小时,有没有办法保留我的网格线?
HTML代码:
<!--//d3 chart//-->
<div class="centre-div"></div>
CSS代码:
.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代码:
//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().
//
//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");
//}
这是一种混叠效果,因为线条的渲染方式会受到多种因素的影响。其中主要的三个是笔划宽度、位置和渲染模式。对于使用 shape-rendering: crispEdges
,SVG 规范指出:
To achieve crisp edges, the user agent might turn off anti-aliasing for all lines...
根据线条的缩放和平移,可以计算出出现在 两个 screen 像素之间,而缩放笔画宽度不够宽,无法为任何相邻的屏幕像素着色。这样,线条似乎随机消失并再次出现。
可以在 "Why is SVG stroke-width : 1 making lines transparent?" or in my to "Drew a straight line, but it is crooked d3" 中找到进一步的解释。
对于您的代码,您可以在绘制网格线时使用 shape-rendering: geometricPrecision
而不是 crispEdges
来更改渲染行为。查看更新后的 JSFiddle 以获取工作示例。
在我的例子中,我只是通过将 "showMaxMin" 设置为 false 来解决这个问题。
lineChart.xAxis.showMaxMin(false).tickValues(xAxisTickValues).tickFormat(function (d) {
if (typeof d === 'string') {
d = parseFloat(d);
}
return d3.time.format("%d %b")(new Date(d));
});
我创建了一个精简的 JSFiddle 我的 D3 图表。我使用以下解决方案使其响应(使用 viewbox 和 preserveaspectratio): responsive D3 chart
当我调整 window 的大小并使其变小时,一些网格线似乎在消失并重新出现。我认为这在小分辨率下看起来会很糟糕(例如 320x480 移动 phone)。当 window 变小时,有没有办法保留我的网格线?
HTML代码:
<!--//d3 chart//-->
<div class="centre-div"></div>
CSS代码:
.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代码:
//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().
//
//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");
//}
这是一种混叠效果,因为线条的渲染方式会受到多种因素的影响。其中主要的三个是笔划宽度、位置和渲染模式。对于使用 shape-rendering: crispEdges
,SVG 规范指出:
To achieve crisp edges, the user agent might turn off anti-aliasing for all lines...
根据线条的缩放和平移,可以计算出出现在 两个 screen 像素之间,而缩放笔画宽度不够宽,无法为任何相邻的屏幕像素着色。这样,线条似乎随机消失并再次出现。
可以在 "Why is SVG stroke-width : 1 making lines transparent?" or in my
对于您的代码,您可以在绘制网格线时使用 shape-rendering: geometricPrecision
而不是 crispEdges
来更改渲染行为。查看更新后的 JSFiddle 以获取工作示例。
在我的例子中,我只是通过将 "showMaxMin" 设置为 false 来解决这个问题。
lineChart.xAxis.showMaxMin(false).tickValues(xAxisTickValues).tickFormat(function (d) {
if (typeof d === 'string') {
d = parseFloat(d);
}
return d3.time.format("%d %b")(new Date(d));
});