#javascript #svg #d3.js
#javascript #svg #d3.js
Вопрос:
Я новичок в D3, и уже несколько дней безуспешно пытаюсь устранить эту проблему. Я не уверен, что попробовать дальше.
У меня есть набор данных JSON с ежедневными данными, и я пытаюсь создать столбчатую диаграмму с одним столбиком в день. Все хорошо. Однако у меня возникли проблемы с x
-axis . Я бы хотел x
, чтобы у -axis были отметки и метки только в начале каждого месяца. Это как если d3.timeMonth
бы каждая точка данных считалась новым месяцем:
Я настроил x
-axis как scaleBand
, потому что каждый раз, когда я пытался настроить его как scaleTime
, столбцы отображались как огромные перекрывающиеся столбцы. Однако непосредственно перед настройкой x
-axis я напечатал свои данные в журнале консоли, и они выглядят правильно отформатированными как даты.
const data = [
{
date_facet: '2020-08-31',
published: 2,
not_published: 0,
},
{
date_facet: '2020-09-01',
published: 0,
not_published: 0,
},
{
date_facet: '2020-09-02',
published: 1,
not_published: 0,
},
{
date_facet: '2020-09-03',
published: 1,
not_published: 0,
},
{
date_facet: '2020-09-04',
published: 0,
not_published: 0,
},
{
date_facet: '2020-09-05',
published: 0,
not_published: 0,
},
];
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 80,
left: 40
},
width = 450 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#graph")
.append("svg")
.attr("viewBox", '0 0 450 350')
.append("g")
.attr("transform",
"translate(" margin.left "," margin.top ")");
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%d");
// format the data
data.forEach(function(d) {
d.date_facet = parseTime(d.date_facet);
d.published = d.published;
});
// order the data
data.sort(function(a, b) {
return a["date_facet"] - b["date_facet"];
})
// X axis
var x = d3.scaleBand()
.range([0, width])
.domain(data.map(function(d) {
return d.date_facet;
}))
.padding(0.2);
// Y axis
var y = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.published);
}) 4]);
// Add X axis, ticks and labels
svg.append("g")
.attr("class", "axis axis-minor")
.attr("transform", "translate(0," height ")")
.call(d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)");
svg.append("g")
.call(d3.axisLeft(y));
// Bars
svg.selectAll("mybar")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.date_facet);
})
.attr("width", x.bandwidth())
.attr("height", function(d) {
return height - y(d.published);
})
.attr("y", function(d) {
return y(d.published);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="graph"></div>
Ответ №1:
Поскольку вы используете scaleBand
, все значения считаются категориальными. Я имею в виду, что они похожи на метки, такие как «шар», «оранжевый», «круг». Просто термины, совершенно не связанные друг с другом. Это контрастирует со временем или числами, где вы можете сказать, что одно значение больше другого, или одно ближе к A, чем к B.
Измените значения на scaleTime
вместо:
const data = [
{
date_facet: '2020-08-31',
published: 2,
not_published: 0,
},
{
date_facet: '2020-09-01',
published: 0,
not_published: 0,
},
{
date_facet: '2020-09-02',
published: 1,
not_published: 0,
},
{
date_facet: '2020-09-03',
published: 1,
not_published: 0,
},
{
date_facet: '2020-09-04',
published: 0,
not_published: 0,
},
{
date_facet: '2020-09-05',
published: 0,
not_published: 0,
},
];
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 80,
left: 40
},
width = 450 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#graph")
.append("svg")
.attr("viewBox", '0 0 450 350')
.append("g")
.attr("transform",
"translate(" margin.left "," margin.top ")");
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%d");
// format the data
data.forEach(function(d) {
d.date_facet = parseTime(d.date_facet);
d.published = d.published;
});
// order the data
data.sort(function(a, b) {
return a["date_facet"] - b["date_facet"];
})
// Extend the domain by 12 hours on each side to account for the bar widths
var xDomain = d3.extent(data.map(function(d) {
return d.date_facet;
}));
// Deep copy the date objects to make sure you can make safe modifications
xDomain = [new Date(xDomain[0]), new Date(xDomain[1])];
xDomain[0].setHours(xDomain[0].getHours() - 12);
xDomain[1].setHours(xDomain[1].getHours() 12);
// X axis
var x = d3.scaleTime()
.range([0, width])
.domain(xDomain);
var xDomainInDays = (x.domain()[1] - x.domain()[0]) / (1000 * 60 * 60 * 24);
var xBarWidth = width / xDomainInDays;
var padding = 0.2;
// Y axis
var y = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.published);
}) 4]);
// Add X axis, ticks and labels
svg.append("g")
.attr("class", "axis axis-minor")
.attr("transform", "translate(0," height ")")
.call(d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)");
svg.append("g")
.call(d3.axisLeft(y));
// Bars
svg.selectAll("mybar")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
// Get the x coordinate
// Then shift by half of xBarWidth so the middle of the bar is at the tick
// Then apply half of the padding (other half at the other side)
return x(d.date_facet) - (xBarWidth / 2) (padding / 2) * xBarWidth;
})
// Make the bar "padding * xBarWidth" thinner so it applies the padding correctly
.attr("width", xBarWidth - padding * xBarWidth)
.attr("height", function(d) {
return height - y(d.published);
})
.attr("y", function(d) {
return y(d.published);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="graph"></div>
Это связано с некоторыми сложностями.
- Вам нужно самостоятельно рассчитать ширину полосы. Я сделал это, проверив размер домена и размер диапазона, поэтому я нашел ширину, доступную для каждой панели таким образом;
- Галочка будет у левого края каждой строки. Если вы хотите центрировать его (что я и сделал здесь), вам нужно поиграть с заполнением и центрировать полосу на галочке;
- Теперь полоса будет немного превышать ось. Вы можете увеличить домен на 12 часов в обоих направлениях, это решает проблему.