首页 > 解决方案 > 在 D3.js V5 中,带箭头的路径向后方向未正确显示

问题描述

我试图实现如下图表,这个问题是使图表有效地呈现负值的延续:

在此处输入图像描述

我刚刚反转了差异绝对值和百分比文本计算的计算,并使标记开始而不是标记结束。但标记不像向下箭头那样正常。我面临的唯一问题与箭头方向有关。以下是相同的代码:

//negative value consideration code

        const barData = [{
        "Time": "2019",
        "Value": 538865
          },
          {
        "Time": "2020",
        "Value": 645375
          },
          {
        "Time": "2021",
        "Value": -434535
          },
          {
        "Time": "2022",
        "Value": -534595
          },
          {
        "Time": "2023",
        "Value": -834545
          },
          {
        "Time": "2024",
        "Value": -534555
          },
          {
        "Time": "2025",
        "Value": 634565
          },
          {
        "Time": "2026",
        "Value": 33455
          }
        ];

        const container = d3.select('#graph');
        const divWidth = parseInt(container.style('width'));
        const divHeight = parseInt(container.style('height'));

        // Consider this width and Height are dynamic for div "graphID" because I am trying to responsive design
        const margin = {
          top: 50,
          right: 50,
          bottom: 50,
          left: 50
        };
        const width = divWidth - margin.left - margin.right;
        const height = divHeight - margin.top - margin.bottom;

        //To add svg in the visualization node i.e Dome node                    
        const svg = container.append("svg")
          .attr("width", divWidth)
          .attr("height", divHeight)
          .append("g")
          .attr("transform", `translate(${margin.left},${margin.top})`);

        //To add tooltip for bar
        const tooltip = d3.select("body").append("div").attr("class", "toolTip");

        const defs = svg.append("defs");

        const marker = defs.append("marker")
          .attr("id", "arrowhead")
          .attr("markerWidth", "10")
          .attr("markerHeight", "7")
          .attr("refX", "0")
          .attr("refY", "3.5")
          .attr("orient", "auto")

        const polygon = marker.append("polygon")
          .attr("fill", "gray")
          .attr("points", "0 0, 10 3.5, 0 7")

        const xScale = d3.scaleBand()
          .domain(barData.map(d => d.Time))
          .range([0, width + margin.right]);

        const xAxis = d3.axisBottom(xScale)

        //Adding g attribute to svg for x axis
        const yAxisMax = barData.reduce((max, item) => Math.max(max, item.Value), 0) * 1.5;

        const yAxisMin = barData.reduce((min, item) => Math.min(min, item.Value), 0) * 1.5;

        const yAxisRange = Math.max(yAxisMax, Math.abs(yAxisMin));

        const yScale = d3.scaleLinear()
          .domain([-yAxisRange, yAxisRange])
          .range([height, 0]);

        const yAxis = d3.axisLeft(yScale).ticks(4);

        svg.append('g')
          .call(yAxis);

        const bars = svg.selectAll('g.bar')
          .data(barData)
          .enter()
          .append('g')
          .classed('bar', true)
          .attr('transform', d => `translate(${xScale(d.Time) + xScale.bandwidth() / 2}, 0)`);

        bars.append('rect')
          .attr('x', -20)
          .attr('width', 40)
          .attr('y', d => Math.min(yScale(d.Value), height/2))
          .attr('height', d => d.Value > 0 ?
        (height/2 - yScale(d.Value)) : 
        (yScale(d.Value) - height/2)
          )
          .attr('fill', 'blue')
          .on("mousemove", onMouseOver)
          .on("mouseout", onMouseOut);

        function onMouseOver(d, i) {
          tooltip
        .style("left", d3.event.pageX - 50 + "px")
        .style("top", d3.event.pageY - 70 + "px")
        .style("display", "inline-block")
        .html("Year: " + (d.Time) + "<br>" + "Value: " + (d.Value));
          d3.select(this).attr('fill', "#eec42d");
        }

        function onMouseOut(d, i) {
          tooltip.style("display", "none");
          d3.select(this).attr('fill', "blue");
        }

        bars.append('text')
          .text(d => d.Value)
          .attr('text-anchor', 'middle')
          .attr('alignment-baseline', d => d.Value > 0 ? 'baseline' : 'hanging')
          .attr('y', d => yScale(d.Value))
          .attr('dy', d => d.Value > 0 ? -5 : 5);

        bars.filter((d, i) => i < barData.length - 1)
          .append('path')
          .attr('d', (d, i) => `M 5,${d.Value < 0 && barData[i + 1].Value < 0? Math.max(yScale(d.Value) + 20, height/2): Math.min(yScale(d.Value) - 20, height/2)} V ${d.Value < 0 && barData[i + 1].Value < 0 ? Math.max(yScale(d.Value)  ,  yScale(barData[i + 1].Value) )+40 : Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 40} H ${xScale.bandwidth() - 5} V ${d.Value < 0 && barData[i + 1].Value < 0 ? Math.max(yScale(barData[i + 1].Value) + 25, height/2 + 10): Math.min(yScale(barData[i + 1].Value) - 25, height/2 - 10)}`)
          .style('stroke', 'gray')
          .style('fill', 'none')
          .attr('marker-start', 'url(#arrowhead)')

        bars.filter((d, i) => i < barData.length - 1)
          .append('rect')
          .attr('x', 13)
          .attr('y', (d, i) => d.Value < 0 && barData[i + 1].Value < 0 ? Math.max(yScale(d.Value), yScale(barData[i + 1].Value)) + 15 : Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 55)
          .attr('width', xScale.bandwidth() - 25)
          .attr('height', 40)
          .attr('rx', 20)
          .style('fill', "white")
          .style('stroke', 'gray');

        bars.filter((d, i) => i < barData.length - 1)
          .append('text')
          .html((d, i) => `${d.Value > barData[i + 1].Value  ? '+' : ''}${Math.round(((d.Value - barData[i + 1].Value) / Math.abs(barData[i + 1].Value))* 100)}%`)
          .attr('x', xScale.bandwidth() / 2)
          .attr('y', (d, i) => d.Value < 0 && barData[i + 1].Value < 0 ? Math.max(yScale(d.Value), yScale(barData[i + 1].Value)) + 44 : Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 36)
          .attr('text-anchor', 'middle')
          .style('fill', 'black');
      
      bars.filter((d, i) => i < barData.length - 1)
          .append('text')
          .html((d, i) => `${Math.round(d.Value - barData[i + 1].Value)}`)
          .attr('x', xScale.bandwidth() / 2)
          .attr('y', (d, i) => d.Value < 0 && barData[i + 1].Value < 0 ? Math.max(yScale(d.Value), yScale(barData[i + 1].Value)) + 34 : Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 26)
          .attr('text-anchor', 'middle')
          .style('fill', 'black');

        const xAxisG = svg.append('g')
          .attr("transform", `translate(0,${height/2})`)
          .call(xAxis);
          
        xAxisG.selectAll('.tick').each(function() {
          const tick = d3.select(this);
          const text = tick.select('text').text();
          const data = barData.find(d => d.Time === text);
          if (data.Value < 0) {
        tick.select('text').style('fill', 'white');
        tick.select('line').style('stroke', 'white');
          }
        })  
#graph
{
  width:600px;
  height:400px;
 
 }
 
 text {
   font-size: 12px;
   font-family: "Ubuntu";
  }
  
  .toolTip {
  position: absolute;
  display: none;
  min-width: 80px;
  height: auto;
  background: none repeat scroll 0 0 #ffffff;
  border: 1px solid #6F257F;
  padding: 5px;
  text-align: left;
}

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

标签: javascriptd3.js

解决方案


我得到了答案,即代替 orient = auto 使用“auto-start-reverse”。


推荐阅读