D3-具有正值和负值的图表 [英] D3 - Chart with positive and negative values

查看:61
本文介绍了D3-具有正值和负值的图表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用正数和负数值构建d3图表,如下所示

I am trying to build the d3 chart with the positive and negative number values as below

,我找到了 this

and I found some examples of this and this. I am facing difficulties in customizing it because I have no prior experience in d3 and I think it would need some time for learning. I tried that as well. Created some simple chart examples but could not achieve the above. So I thought of reaching for help. Maybe someone can help with this if they have already done a similar chart or some guidance would be greatly appreciated. Thanks in advance.

推荐答案

第一步是确定如何简化此图表.删除功能,直到剩下最基本的内容为止.然后,构建它并逐渐添加功能,直到它与您想要的相似为止.

The first step would be to identify how this chart can be simplified. Removing features until the most basic thing remains. Then, build that and gradually add features until it resembles what you want.

就您而言,那将是一个水平条形图.然后,添加一些负值和居中的零线.最后,减小条形的高度,使它们成为节点,并添加文本.

In your case, that'd be a horizontal bar chart. Then, add some negative values and a centred zero-line. Finally, make the height of the bars less so they become nodes, and add the text.

在这些步骤中,我将尝试添加类似这样的内容,而不包含布局和所有内容,但希望您能够看到我的逻辑.

I'll try to add something like this, in these steps, without the layout and everything, but hopefully you'll be able to see my logic.

基本垂直条形图

// Some fake data
const data = ['SaaS', 'Sales', 'Fruits & Veggies', 'IT'].map((v, i) => ({
  name: v,
  value: 3 * i + 2
}));

const width = 600,
  height = 300
margin = {
  top: 20,
  left: 100,
  right: 40,
  bottom: 40
};

// Process it to find the x and y axis domains
// scaleLinear because it considers numbers
const x = d3.scaleLinear()
  .domain([0, d3.max(data.map(d => d.value))]) // the possible values
  .range([0, width]); // the available screen space

// scaleBand because it's just categorical data
const y = d3.scaleBand()
  .domain(data.map(d => d.name)) // all possible values
  .range([height, 0]) // little weird, y-axis is always backwards, because (0,0) is the top left
  .padding(0.1);

const svg = d3.select('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom);

const g = svg
  // Append a container element. This will hold the chart
  .append('g')
  // Move it a little to account for the axes and labels
  .attr('transform', `translate(${margin.left} ${margin.right})`);

// Draw the bars
// First, assign the data to the bar objects, this will decide which to remove, update, and add
const bars = g.append('g')
  .selectAll('rect')
  .data(data);

// Good practice: always call remove before adding stuff
bars.exit().remove();

// Add the new bars and assign any attributes that do not depend on the data
// for example, font for texts
bars.enter()
  .append('rect')
  .attr('fill', 'steelblue')
  // Now merge it with the existing bars
  .merge(bars)
  // From now on we operate on both the old and the new bars
  // Bars are weird, first we position the top left corner of each bar
  .attr('x', 0)
  .attr('y', d => y(d.name))
  // Then we determine the width and height
  .attr('width', d => x(d.value))
  .attr('height', y.bandwidth())

// Draw the x and y axes
g.append('g')
  .classed('x-axis', true)
  .attr('transform', `translate(0, ${height})`)
  .call(d3.axisBottom(x))

g.append('g')
  .classed('y-axis', true)
  .call(d3.axisLeft(y))

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

现在,我将删除所有旧评论,并解释我的不同做法.

Now I'll remove all old comments and explain what I'm doing differently.

负水平条形图

// Now, the data can also be negative
const data = ['SaaS', 'Sales', 'Fruits & Veggies', 'IT'].map((v, i) => ({
  name: v,
  value: 3 * i - 5
}));

const width = 600,
  height = 300,
  margin = {
    top: 20,
    left: 100,
    right: 40,
    bottom: 40
  };

// Now, we don't use 0 as a minimum, but get it from the data using d3.extent
const x = d3.scaleLinear()
  .domain(d3.extent(data.map(d => d.value)))
  .range([0, width]);

const y = d3.scaleBand()
  .domain(data.map(d => d.name))
  .range([height, 0])
  .padding(0.1);

const svg = d3.select('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom);

const g = svg
  .append('g')
  .attr('transform', `translate(${margin.left} ${margin.right})`);

const bars = g.append('g')
  .selectAll('rect')
  .data(data);

bars.exit().remove();

bars.enter()
  .append('rect')
  .merge(bars)
  // All the same until here
  // Now, if a bar is positive it starts at x = 0, and has positive width
  // If a bar is negative it starts at x < 0 and ends at x = 0
  .attr('x', d => d.value > 0 ? x(0) : x(d.value))
  .attr('y', d => y(d.name))
  // If the bar is positive it ends at x = v, but that means it's x(v) - x(0) wide
  // If the bar is negative it ends at x = 0, but that means it's x(0) - x(v) wide
  .attr('width', d => d.value > 0 ? x(d.value) - x(0) : x(0) - x(d.value))
  .attr('height', y.bandwidth())
  // Let's color the bar based on whether the value is positive or negative
  .attr('fill', d => d.value > 0 ? 'darkgreen' : 'darkred')

g.append('g')
  .classed('x-axis', true)
  .attr('transform', `translate(0, ${height})`)
  .call(d3.axisBottom(x))

g.append('g')
  .classed('y-axis', true)
  .call(d3.axisLeft(y))

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

现在,我将把条形更改为示例代码中具有的节点.

And now, I'll change the bars to the nodes you have in your example code.

带有节点的水平图

const data = ['SaaS', 'Sales', 'Fruits & Veggies', 'IT'].map((v, i) => ({
  name: v,
  value: 3 * i - 5
}));

// We want to center each rect around the value it's supposed to have.
// That means that we need to have a node width
const nodeWidth = 60;

const width = 600,
  height = 300,
  margin = {
    top: 20,
    left: 100,
    right: 40,
    bottom: 40
  };

// We also need to make sure there is space for all nodes, even at the edges.
// One way to get this is by just extending the domain a little.
const domain = d3.extent(data.map(d => d.value));
const x = d3.scaleLinear()
  .domain([domain[0] - 1.5, domain[1] + 1.5])
  .range([0, width]);

const y = d3.scaleBand()
  .domain(data.map(d => d.name))
  .range([height, 0])
  .padding(0.1);

const svg = d3.select('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom);

const g = svg
  .append('g')
  .attr('transform', `translate(${margin.left} ${margin.right})`);

const bars = g.append('g')
  .selectAll('rect')
  .data(data);

bars.exit().remove();

// All the same until here
bars.enter()
  .append('rect')
  // width has become a constant
  .attr('width', nodeWidth)
  // Now, transform each node so it centers around the value it's supposed to have
  .attr('transform', `translate(${-nodeWidth / 2} 0)`)
  // Round the corners for aesthetics
  .attr('rx', 15)
  .merge(bars)
  // `x` denotes the placement directly again
  .attr('x', d => x(d.value))
  .attr('y', d => y(d.name))
  .attr('height', y.bandwidth())
  .attr('fill', d => d.value > 0 ? 'darkgreen' : 'darkred');

// Now one more thing, we want to add labels to each node.
// `<rect>` can't have children, we we add them to the plot seperately
// using the same `data` as for the bars
const labels = g.append('g')
  .selectAll('text')
  .data(data);

labels.exit().remove();

labels.enter()
  .append('text')
  .attr('fill', 'white')
  .attr('text-anchor', 'middle') // center-align the text
  .attr('dy', 5) // place it down a little so it middles nicely in the node.
  .merge(bars)
  // `x` denotes the placement directly
  .attr('x', d => x(d.value))
  // Add half a bar's height to target the center of each node 
  .attr('y', d => y(d.name) + y.bandwidth() / 2)
  // Actually fill in the text
  .text(d => d.value);

g.append('g')
  .classed('x-axis', true)
  .attr('transform', `translate(0, ${height})`)
  .call(d3.axisBottom(x))

g.append('g')
  .classed('y-axis', true)
  .call(d3.axisLeft(y))

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

我希望你能遵循这一点.让我知道关于本教程的任何不清楚之处.

I hope you can follow this. Let me know if anything about this tutorial is unclear.

这篇关于D3-具有正值和负值的图表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆