【问题标题】:d3.timeMonth on axis is showing every date, instead of every month轴上的 d3.timeMonth 显示每个日期,而不是每个月
【发布时间】:2020-10-04 11:51:52
【问题描述】:

我是 D3 的新手,几天来我一直在尝试解决这个问题,但没有运气。我不确定接下来要尝试什么。

我有一个包含每日数据的 JSON 数据集,我正在尝试制作一个每天一个条形的条形图。这都很好。但是,我遇到了x-axis 的问题。我希望 x-axis 仅在每个月的第一天有刻度和标签。就好像d3.timeMonth 认为每个数据点都是一个新的月份:

我已将x 轴设置为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>

【问题讨论】:

    标签: javascript svg d3.js


    【解决方案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 小时,这样可以解决问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-30
      • 1970-01-01
      • 1970-01-01
      • 2018-05-15
      • 2021-12-23
      • 1970-01-01
      • 1970-01-01
      • 2020-10-30
      相关资源
      最近更新 更多