为了账号安全,请及时绑定邮箱和手机立即绑定

D3 - 具有正值和负值的图表

D3 - 具有正值和负值的图表

慕桂英546537 2023-05-11 16:52:35
我正在尝试使用正负数值构建 d3 图表,如下所示我发现了一些this和this的例子。我在定制它时遇到困难,因为我以前没有 d3 的经验,我认为它需要一些时间来学习。我也试过了。创建了一些简单的图表示例,但无法实现上述目标。于是我想到了寻求帮助。如果有人已经做过类似的图表,也许有人可以帮助解决这个问题,或者一些指导将不胜感激。提前致谢。
查看完整描述

1 回答

?
有只小跳蛙

TA贡献1824条经验 获得超8个赞

第一步是确定如何简化此图表。删除功能,直到保留最基本的东西。然后,构建它并逐渐添加功能,直到它类似于您想要的。


在您的情况下,那将是一个水平条形图。然后,添加一些负值和居中的零线。最后,降低条形的高度,使它们成为节点,然后添加文本。


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


基本垂直条形图


// 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, 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>

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


带节点的水平图表


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>

我希望你能遵循这个。如果对本教程有任何不清楚的地方,请告诉我。



查看完整回答
反对 回复 2023-05-11
  • 1 回答
  • 0 关注
  • 190 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信