猿问

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

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

我发现了一些thisthis的例子。我在定制它时遇到困难,因为我以前没有 d3 的经验,我认为它需要一些时间来学习。我也试过了。创建了一些简单的图表示例,但无法实现上述目标。于是我想到了寻求帮助。如果有人已经做过类似的图表,也许有人可以帮助解决这个问题,或者一些指导将不胜感激。提前致谢。



慕桂英546537
浏览 220回答 1
1回答

有只小跳蛙

第一步是确定如何简化此图表。删除功能,直到保留最基本的东西。然后,构建它并逐渐添加功能,直到它类似于您想要的。在您的情况下,那将是一个水平条形图。然后,添加一些负值和居中的零线。最后,降低条形的高度,使它们成为节点,然后添加文本。我将尝试在这些步骤中添加类似这样的内容,没有布局和所有内容,但希望您能够看到我的逻辑。基本垂直条形图// Some fake dataconst data = ['SaaS', 'Sales', 'Fruits & Veggies', 'IT'].map((v, i) => ({&nbsp; name: v,&nbsp; value: 3 * i + 2}));const width = 600,&nbsp; height = 300margin = {&nbsp; top: 20,&nbsp; left: 100,&nbsp; right: 40,&nbsp; bottom: 40};// Process it to find the x and y axis domains// scaleLinear because it considers numbersconst x = d3.scaleLinear()&nbsp; .domain([0, d3.max(data.map(d => d.value))]) // the possible values&nbsp; .range([0, width]); // the available screen space// scaleBand because it's just categorical dataconst y = d3.scaleBand()&nbsp; .domain(data.map(d => d.name)) // all possible values&nbsp; .range([height, 0]) // little weird, y-axis is always backwards, because (0,0) is the top left&nbsp; .padding(0.1);const svg = d3.select('svg')&nbsp; .attr('width', width + margin.left + margin.right)&nbsp; .attr('height', height + margin.top + margin.bottom);const g = svg&nbsp; // Append a container element. This will hold the chart&nbsp; .append('g')&nbsp; // Move it a little to account for the axes and labels&nbsp; .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 addconst bars = g.append('g')&nbsp; .selectAll('rect')&nbsp; .data(data);// Good practice: always call remove before adding stuffbars.exit().remove();// Add the new bars and assign any attributes that do not depend on the data// for example, font for textsbars.enter()&nbsp; .append('rect')&nbsp; .attr('fill', 'steelblue')&nbsp; // Now merge it with the existing bars&nbsp; .merge(bars)&nbsp; // From now on we operate on both the old and the new bars&nbsp; // Bars are weird, first we position the top left corner of each bar&nbsp; .attr('x', 0)&nbsp; .attr('y', d => y(d.name))&nbsp; // Then we determine the width and height&nbsp; .attr('width', d => x(d.value))&nbsp; .attr('height', y.bandwidth())// Draw the x and y axesg.append('g')&nbsp; .classed('x-axis', true)&nbsp; .attr('transform', `translate(0, ${height})`)&nbsp; .call(d3.axisBottom(x))g.append('g')&nbsp; .classed('y-axis', true)&nbsp; .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 negativeconst data = ['SaaS', 'Sales', 'Fruits & Veggies', 'IT'].map((v, i) => ({&nbsp; name: v,&nbsp; value: 3 * i - 5}));const width = 600,&nbsp; height = 300,&nbsp; margin = {&nbsp; &nbsp; top: 20,&nbsp; &nbsp; left: 100,&nbsp; &nbsp; right: 40,&nbsp; &nbsp; bottom: 40&nbsp; };// Now, we don't use 0 as a minimum, but get it from the data using d3.extentconst x = d3.scaleLinear()&nbsp; .domain(d3.extent(data.map(d => d.value)))&nbsp; .range([0, width]);const y = d3.scaleBand()&nbsp; .domain(data.map(d => d.name))&nbsp; .range([height, 0])&nbsp; .padding(0.1);const svg = d3.select('svg')&nbsp; .attr('width', width + margin.left + margin.right)&nbsp; .attr('height', height + margin.top + margin.bottom);const g = svg&nbsp; .append('g')&nbsp; .attr('transform', `translate(${margin.left} ${margin.right})`);const bars = g.append('g')&nbsp; .selectAll('rect')&nbsp; .data(data);bars.exit().remove();bars.enter()&nbsp; .append('rect')&nbsp; .merge(bars)&nbsp; // All the same until here&nbsp; // Now, if a bar is positive it starts at x = 0, and has positive width&nbsp; // If a bar is negative it starts at x < 0 and ends at x = 0&nbsp; .attr('x', d => d.value > 0 ? x(0) : x(d.value))&nbsp; .attr('y', d => y(d.name))&nbsp; // If the bar is positive it ends at x = v, but that means it's x(v) - x(0) wide&nbsp; // If the bar is negative it ends at x = 0, but that means it's x(0) - x(v) wide&nbsp; .attr('width', d => d.value > 0 ? x(d.value) - x(0) : x(0) - x(d.value))&nbsp; .attr('height', y.bandwidth())&nbsp; // Let's color the bar based on whether the value is positive or negative&nbsp; .attr('fill', d => d.value > 0 ? 'darkgreen' : 'darkred')g.append('g')&nbsp; .classed('x-axis', true)&nbsp; .attr('transform', `translate(0, ${height})`)&nbsp; .call(d3.axisBottom(x))g.append('g')&nbsp; .classed('y-axis', true)&nbsp; .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) => ({&nbsp; name: v,&nbsp; 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 widthconst nodeWidth = 60;const width = 600,&nbsp; height = 300,&nbsp; margin = {&nbsp; &nbsp; top: 20,&nbsp; &nbsp; left: 100,&nbsp; &nbsp; right: 40,&nbsp; &nbsp; bottom: 40&nbsp; };// 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()&nbsp; .domain([domain[0] - 1.5, domain[1] + 1.5])&nbsp; .range([0, width]);const y = d3.scaleBand()&nbsp; .domain(data.map(d => d.name))&nbsp; .range([height, 0])&nbsp; .padding(0.1);const svg = d3.select('svg')&nbsp; .attr('width', width + margin.left + margin.right)&nbsp; .attr('height', height + margin.top + margin.bottom);const g = svg&nbsp; .append('g')&nbsp; .attr('transform', `translate(${margin.left} ${margin.right})`);const bars = g.append('g')&nbsp; .selectAll('rect')&nbsp; .data(data);bars.exit().remove();// All the same until herebars.enter()&nbsp; .append('rect')&nbsp; // width has become a constant&nbsp; .attr('width', nodeWidth)&nbsp; // Now, transform each node so it centers around the value it's supposed to have&nbsp; .attr('transform', `translate(${-nodeWidth / 2} 0)`)&nbsp; // Round the corners for aesthetics&nbsp; .attr('rx', 15)&nbsp; .merge(bars)&nbsp; // `x` denotes the placement directly again&nbsp; .attr('x', d => x(d.value))&nbsp; .attr('y', d => y(d.name))&nbsp; .attr('height', y.bandwidth())&nbsp; .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 barsconst labels = g.append('g')&nbsp; .selectAll('text')&nbsp; .data(data);labels.exit().remove();labels.enter()&nbsp; .append('text')&nbsp; .attr('fill', 'white')&nbsp; .attr('text-anchor', 'middle') // center-align the text&nbsp; .attr('dy', 5) // place it down a little so it middles nicely in the node.&nbsp; .merge(bars)&nbsp; // `x` denotes the placement directly&nbsp; .attr('x', d => x(d.value))&nbsp; // Add half a bar's height to target the center of each node&nbsp;&nbsp; .attr('y', d => y(d.name) + y.bandwidth() / 2)&nbsp; // Actually fill in the text&nbsp; .text(d => d.value);g.append('g')&nbsp; .classed('x-axis', true)&nbsp; .attr('transform', `translate(0, ${height})`)&nbsp; .call(d3.axisBottom(x))g.append('g')&nbsp; .classed('y-axis', true)&nbsp; .call(d3.axisLeft(y))<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script><svg></svg>我希望你能遵循这个。如果对本教程有任何不清楚的地方,请告诉我。
随时随地看视频慕课网APP

相关分类

JavaScript
我要回答