有只小跳蛙
第一步是确定如何简化此图表。删除功能,直到保留最基本的东西。然后,构建它并逐渐添加功能,直到它类似于您想要的。在您的情况下,那将是一个水平条形图。然后,添加一些负值和居中的零线。最后,降低条形的高度,使它们成为节点,然后添加文本。我将尝试在这些步骤中添加类似这样的内容,没有布局和所有内容,但希望您能够看到我的逻辑。基本垂直条形图// Some fake dataconst data = ['SaaS', 'Sales', 'Fruits & Veggies', 'IT'].map((v, i) => ({ name: v, value: 3 * i + 2}));const width = 600, height = 300margin = { top: 20, left: 100, right: 40, bottom: 40};// Process it to find the x and y axis domains// scaleLinear because it considers numbersconst 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 dataconst 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 addconst bars = g.append('g') .selectAll('rect') .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() .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 axesg.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 negativeconst 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.extentconst 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 widthconst 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 herebars.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 barsconst 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>我希望你能遵循这个。如果对本教程有任何不清楚的地方,请告诉我。