d3.js 将网格线和块更改为均匀分布
d3.js Changing gridlines and blocks to be evenly spaced
我正在玩以下 d3 块 http://bl.ocks.org/lakenen/8529857,它是烛台图表的渲染。它的输出如下所示:
每个区块的数据是 1 天的金融股票数字:最高价、最低价、开盘价和收盘价。
但烛台图通常有所不同。通常,块是均匀分布的,每天有一条网格线,x 轴每天标记一次。这是关于 google 金融的一个例子:
下面是从上方呈现 d3 图表的代码:
var margin = 50;
var chart = d3.select("#chart")
.append("svg:svg")
.attr("class", "chart")
.attr("width", width)
.attr("height", height);
var y = d3.scale.linear()
.domain([d3.min(data.map(function(x) {return x["Low"];})), d3.max(data.map(function(x){return x["High"];}))])
.range([height-margin, margin]);
var x = d3.scale.linear()
.domain([d3.min(data.map(function(d){return d.timestamp;})), d3.max(data.map(function(d){ return d.timestamp;}))])
.range([margin,width-margin]);
chart.selectAll("line.x")
.data(x.ticks(10))
.enter().append("svg:line")
.attr("class", "x")
.attr("x1", x)
.attr("x2", x)
.attr("y1", margin)
.attr("y2", height - margin)
.attr("stroke", "#ccc");
chart.selectAll("line.y")
.data(y.ticks(10))
.enter().append("svg:line")
.attr("class", "y")
.attr("x1", margin)
.attr("x2", width - margin)
.attr("y1", y)
.attr("y2", y)
.attr("stroke", "#ccc");
chart.selectAll("text.xrule")
.data(x.ticks(10))
.enter().append("svg:text")
.attr("class", "xrule")
.attr("x", x)
.attr("y", height - margin)
.attr("dy", 20)
.attr("text-anchor", "middle")
.text(function(d){ var date = new Date(d * 1000); return (date.getMonth() + 1)+"/"+date.getDate(); });
chart.selectAll("text.yrule")
.data(y.ticks(10))
.enter().append("svg:text")
.attr("class", "yrule")
.attr("x", width - margin)
.attr("y", y)
.attr("dy", 0)
.attr("dx", 20)
.attr("text-anchor", "middle")
.text(String);
chart.selectAll("rect")
.data(data)
.enter().append("svg:rect")
.attr("x", function(d) { return x(d.timestamp); })
.attr("y", function(d) {return y(max(d.Open, d.Close));})
.attr("height", function(d) { return y(min(d.Open, d.Close))-y(max(d.Open, d.Close));})
.attr("width", function(d) { return 0.5 * (width - 2*margin)/data.length; })
.attr("fill",function(d) { return d.Open > d.Close ? "red" : "green" ;});
chart.selectAll("line.stem")
.data(data)
.enter().append("svg:line")
.attr("class", "stem")
.attr("x1", function(d) { return x(d.timestamp) + 0.25 * (width - 2 * margin)/ data.length;})
.attr("x2", function(d) { return x(d.timestamp) + 0.25 * (width - 2 * margin)/ data.length;})
.attr("y1", function(d) { return y(d.High);})
.attr("y2", function(d) { return y(d.Low); })
.attr("stroke", function(d){ return d.Open > d.Close ? "red" : "green"; })
}
我尝试修改 .data(x.ticks(10))
值,这会改变刻度数,但我不确定如何将其设置为等于数据点的值,而且我也不确定d3.scale.linear().domain(...)
在渲染开始之前到底是如何改变数据的。
那么,如何使块均匀分布,以便每个块制作一条网格线,每个块制作一个标签?
问题是您尝试模拟的图表没有基于时间的线性 x 轴(缺少天数)。您需要根据数据点的数量使用线性刻度,然后自定义设置标签值。
我没有真正测试过这段代码,所以可能存在错误。但是,这就是我处理问题的方式。
// Create a formatter that given an index, will print the day number for the
// data at that index in data
var dayFormatter = d3.time.format('%d');
var dayAxisFormatter = function(d) {
return dayFormatter(new Date(data[d].timestamp));
}
// Create a formatter that given an index, will print the short month name
// along with the day number for the data at that index in data
var dayWithMonthFormatter = d3.time.format('%b %d');
var dayWithMonthAxisFormatter = function(d) {
return dayWithMonthFormatter(new Date(data[d].timestamp));
}
// Custom formatter to handle printing just the day number except for the first
// instance of the month, there we will print the short month and the day
// helper to create the formatter function that d3 accepts
function timeFormat(formats) {
return function(date) {
var i = formats.length - 1, f = formats[i];
while (!f[1](date)) f = formats[--i];
return f[0](date);
};
}
var firstDone = {}; // track the months so first instance gets month label
var tickFormatter = timeFormat([
[dayAxisFormatter, function(d) { return true; }],
[dayWithMonthFormatter, function(d) {
var month = (new Date(data[d].timestamp)).getMonth();
var result = !firstDone['m' + month];
firstDone['m' + month] = true;
return result;
}],
]);
// Set up a regular linear scale. This would normally just count up from
// 0 to d.length, but we'll use a custom formatter to print out our day
// numbers instead.
var x = d3.scale.linear()
.domain([0, d.length]) // set the domain to be from 0 to # of points
.range([margin,width-margin]);
// Set up the axis to use our customer formatter
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(height)
.tickFormat(tickFormatter);
// Now when you go to draw your data, you need to remember that the
// underlying scale is based on the data index, not the data timestamp.
chart.selectAll("rect")
.data(data)
.enter().append("svg:rect")
.attr("x", function(d, i) { return x(i); })
...
我正在玩以下 d3 块 http://bl.ocks.org/lakenen/8529857,它是烛台图表的渲染。它的输出如下所示:
每个区块的数据是 1 天的金融股票数字:最高价、最低价、开盘价和收盘价。
但烛台图通常有所不同。通常,块是均匀分布的,每天有一条网格线,x 轴每天标记一次。这是关于 google 金融的一个例子:
下面是从上方呈现 d3 图表的代码:
var margin = 50;
var chart = d3.select("#chart")
.append("svg:svg")
.attr("class", "chart")
.attr("width", width)
.attr("height", height);
var y = d3.scale.linear()
.domain([d3.min(data.map(function(x) {return x["Low"];})), d3.max(data.map(function(x){return x["High"];}))])
.range([height-margin, margin]);
var x = d3.scale.linear()
.domain([d3.min(data.map(function(d){return d.timestamp;})), d3.max(data.map(function(d){ return d.timestamp;}))])
.range([margin,width-margin]);
chart.selectAll("line.x")
.data(x.ticks(10))
.enter().append("svg:line")
.attr("class", "x")
.attr("x1", x)
.attr("x2", x)
.attr("y1", margin)
.attr("y2", height - margin)
.attr("stroke", "#ccc");
chart.selectAll("line.y")
.data(y.ticks(10))
.enter().append("svg:line")
.attr("class", "y")
.attr("x1", margin)
.attr("x2", width - margin)
.attr("y1", y)
.attr("y2", y)
.attr("stroke", "#ccc");
chart.selectAll("text.xrule")
.data(x.ticks(10))
.enter().append("svg:text")
.attr("class", "xrule")
.attr("x", x)
.attr("y", height - margin)
.attr("dy", 20)
.attr("text-anchor", "middle")
.text(function(d){ var date = new Date(d * 1000); return (date.getMonth() + 1)+"/"+date.getDate(); });
chart.selectAll("text.yrule")
.data(y.ticks(10))
.enter().append("svg:text")
.attr("class", "yrule")
.attr("x", width - margin)
.attr("y", y)
.attr("dy", 0)
.attr("dx", 20)
.attr("text-anchor", "middle")
.text(String);
chart.selectAll("rect")
.data(data)
.enter().append("svg:rect")
.attr("x", function(d) { return x(d.timestamp); })
.attr("y", function(d) {return y(max(d.Open, d.Close));})
.attr("height", function(d) { return y(min(d.Open, d.Close))-y(max(d.Open, d.Close));})
.attr("width", function(d) { return 0.5 * (width - 2*margin)/data.length; })
.attr("fill",function(d) { return d.Open > d.Close ? "red" : "green" ;});
chart.selectAll("line.stem")
.data(data)
.enter().append("svg:line")
.attr("class", "stem")
.attr("x1", function(d) { return x(d.timestamp) + 0.25 * (width - 2 * margin)/ data.length;})
.attr("x2", function(d) { return x(d.timestamp) + 0.25 * (width - 2 * margin)/ data.length;})
.attr("y1", function(d) { return y(d.High);})
.attr("y2", function(d) { return y(d.Low); })
.attr("stroke", function(d){ return d.Open > d.Close ? "red" : "green"; })
}
我尝试修改 .data(x.ticks(10))
值,这会改变刻度数,但我不确定如何将其设置为等于数据点的值,而且我也不确定d3.scale.linear().domain(...)
在渲染开始之前到底是如何改变数据的。
那么,如何使块均匀分布,以便每个块制作一条网格线,每个块制作一个标签?
问题是您尝试模拟的图表没有基于时间的线性 x 轴(缺少天数)。您需要根据数据点的数量使用线性刻度,然后自定义设置标签值。
我没有真正测试过这段代码,所以可能存在错误。但是,这就是我处理问题的方式。
// Create a formatter that given an index, will print the day number for the
// data at that index in data
var dayFormatter = d3.time.format('%d');
var dayAxisFormatter = function(d) {
return dayFormatter(new Date(data[d].timestamp));
}
// Create a formatter that given an index, will print the short month name
// along with the day number for the data at that index in data
var dayWithMonthFormatter = d3.time.format('%b %d');
var dayWithMonthAxisFormatter = function(d) {
return dayWithMonthFormatter(new Date(data[d].timestamp));
}
// Custom formatter to handle printing just the day number except for the first
// instance of the month, there we will print the short month and the day
// helper to create the formatter function that d3 accepts
function timeFormat(formats) {
return function(date) {
var i = formats.length - 1, f = formats[i];
while (!f[1](date)) f = formats[--i];
return f[0](date);
};
}
var firstDone = {}; // track the months so first instance gets month label
var tickFormatter = timeFormat([
[dayAxisFormatter, function(d) { return true; }],
[dayWithMonthFormatter, function(d) {
var month = (new Date(data[d].timestamp)).getMonth();
var result = !firstDone['m' + month];
firstDone['m' + month] = true;
return result;
}],
]);
// Set up a regular linear scale. This would normally just count up from
// 0 to d.length, but we'll use a custom formatter to print out our day
// numbers instead.
var x = d3.scale.linear()
.domain([0, d.length]) // set the domain to be from 0 to # of points
.range([margin,width-margin]);
// Set up the axis to use our customer formatter
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(height)
.tickFormat(tickFormatter);
// Now when you go to draw your data, you need to remember that the
// underlying scale is based on the data index, not the data timestamp.
chart.selectAll("rect")
.data(data)
.enter().append("svg:rect")
.attr("x", function(d, i) { return x(i); })
...