【问题标题】:D3 - Chart with positive and negative valuesD3 - 具有正值和负值的图表
【发布时间】:2021-01-28 04:21:04
【问题描述】:

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

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

【问题讨论】:

    标签: javascript d3.js bar-chart


    【解决方案1】:

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

    在您的情况下,这将是一个水平条形图。然后,添加一些负值和居中的零线。最后,将条形的高度调小,使其成为节点,并添加文本。

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

    基本垂直条形图

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

    现在我将删除所有旧的 cmets 并解释我的不同之处。

    负水平条形图

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-25
      • 2019-11-04
      • 1970-01-01
      • 2013-11-24
      • 2012-07-03
      • 1970-01-01
      • 2019-03-23
      • 1970-01-01
      相关资源
      最近更新 更多