【问题标题】:How to set attribute name conditionally如何有条件地设置属性名称
【发布时间】:2021-09-30 12:38:47
【问题描述】:

我正在尝试根据我的数据中的一列动态设置属性名称,但它不起作用。

根据数据中的isReverse 列将路径箭头属性名称设置为marker-endmarker-start。我创建了一个函数并在 .attr() 函数中调用了该函数,但它引发了脚本错误。

此要求基本上是根据isReverse 列值反转箭头方向。并且从一根柱到目标柱的差值计算值也会反转。

function chkArrowDir(d){
    return d.isReverse == "false" ? 'marker-end' : 'marker-start';
}

bars.filter(d => d.Target != null)
  .append('path')
  .attr('d', (d, i) => pathBetweenBars(d, i))
  .style('stroke', 'gray')
  .style('fill', 'none')
  //.attr('marker-end', 'url(#arrowhead)')
  .attr(d => chkArrowDir(d), 'url(#arrowhead)')

下面是完整的代码:

var barData = [{
                "Time": "Bar1",
                "Value": 5388,
                "Target": 1,
                "isReverse": "true"
                },
                {
                "Time": "Bar2",
                "Value": 6453,
                "Target": 3,
                "isReverse": "false"
                },
                {
                "Time": "Bar3",
                "Value": 3345,
                "Target": -1,
                "isReverse": "false"
                },
                {
                "Time": "Bar4",
                "Value": 5345,
                "Target": 0,
                "isReverse": "false"
                }];

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

            const margin = {top: 30, 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);
              
         const svgG = svg.append("g")
           .attr("transform", `translate(${margin.left},${margin.top})`);
           
           //To add tooltip for bar
           var 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
        svgG.append('g')
            .attr("transform", `translate(0,${height})`) 
            .call(xAxis);
                
        const yAxisMax = barData.reduce((max, item) => Math.max(max, item.Value), 0) * 1.5;
                
        const yScale = d3.scaleLinear()
            .domain([0, yAxisMax])
            .range([height, 0]);

        const yAxis = d3.axisLeft(yScale).ticks(4);
                
        svgG.append('g')
            .call(yAxis);

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

        var sheet = document.createElement('style')
        sheet.innerHTML = ".bar {fill: "+staticColor+"} .highlight {fill:"+highlightColor+"}";
        document.body.appendChild(sheet);
        */
        bars.append('rect')
            .attr('x', -20)
            .attr('width', 40)
            .attr('y', d =>  yScale(d.Value))
            .attr('height', d => height - yScale(d.Value) )
            .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");
                      //d3.select(this).attr('class', 'highlight');
                      //this.setState({ fillColour: 'green' });


        }

        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('y', d => yScale(d.Value))
          .attr('dy', -5)
         ;
         
const topPosition = i => yScale(9000) + i * 15;         
         
const pathBetweenBars = (d, i) => {
if(d.Target != -1)
{
    const delta = d.Target - i;
  const targetValue = barData[d.Target].Value;
  const targetX = delta * xScale.bandwidth() - 5;
  const sourceY = yScale(d.Value);
  const targetY = yScale(targetValue);
  const topY = topPosition(i);
  return `M 5,${sourceY - 20} V ${topY} H ${targetX} V ${targetY - 25}`;
}
else
return `M 5,${-100} V ${-100} H ${-1000} V ${-0}`;
};         

const LABEL_WIDTH = 50;

const midPosition = (d, i) => {
if(d.Target != -1)
{
    const delta = d.Target - i;
  return delta * xScale.bandwidth() / 2;
  }
  else
  {
   return -1000;
  }
}

function chkArrowDir(d)
{
    return d.isReverse == "false" ? 'marker-end' : 'marker-start';
}

bars.filter(d => d.Target != null)
  .append('path')
  .attr('d', (d, i) => pathBetweenBars(d, i))
  .style('stroke', 'gray')
  .style('fill', 'none')
  .attr(d => chkArrowDir(d), 'url(#arrowhead)')
    
bars.filter((d) => d.Target != null)
 .append('rect')
 .attr('x', (d, i) => midPosition(d, i) - LABEL_WIDTH / 2)
 .attr('y', (d, i) => topPosition(i) - 10)
 .attr('width', LABEL_WIDTH)
 .attr('height', 20)
 .attr('rx', 10)
 .style('fill', 'white')
 .style('stroke', 'gray');

bars.filter((d, i) => d.Target != null)
 .append('text')
 .text((d, i) => `${d.Target != -1 ? barData[d.Target].Value > d.Value ? '+' : '':""}${d.Target != -1 ? Math.round((barData[d.Target].Value / d.Value * 100) - 100):""}%`)
  .attr('x', (d, i) => midPosition(d, i))
  .attr('y', (d, i) => topPosition(i) + 3)
  .attr('text-anchor', 'middle')
  .style('fill', 'black');
#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>

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

    在 D3 中,attr 方法...

    selection.attr(name[, value])

    ...不能接收函数作为第一个参数,这里称为name。如果您查看source code,您会发现它必须是一个字符串。实际上,即使您传递另一个原语,例如数字,它也会是converted to string via:

    var prefix = name += ""
    

    一个可能的解决方案是使用null 作为值。在内部,使用null 将调用element.removeAttribute(),因此实际上您只在值为非空时设置属性。

    因此,它将是:

    .attr("marker-end", d => d.isReverse === "false" ? "url(#arrowhead)" : null)
    .attr("marker-start", d => d.isReverse === "false" ? null : "url(#arrowhead)")
    

    这是您的更改代码:

    var barData = [{
                    "Time": "Bar1",
                    "Value": 5388,
                    "Target": 1,
                    "isReverse": "true"
                    },
                    {
                    "Time": "Bar2",
                    "Value": 6453,
                    "Target": 3,
                    "isReverse": "false"
                    },
                    {
                    "Time": "Bar3",
                    "Value": 3345,
                    "Target": -1,
                    "isReverse": "false"
                    },
                    {
                    "Time": "Bar4",
                    "Value": 5345,
                    "Target": 0,
                    "isReverse": "false"
                    }];
    
                const container = d3.select('#graph');
              const divWidth = parseInt(container.style('width'));
                const divHeight = parseInt(container.style('height'));
    
                const margin = {top: 30, 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);
                  
             const svgG = svg.append("g")
               .attr("transform", `translate(${margin.left},${margin.top})`);
               
               //To add tooltip for bar
               var 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
            svgG.append('g')
                .attr("transform", `translate(0,${height})`) 
                .call(xAxis);
                    
            const yAxisMax = barData.reduce((max, item) => Math.max(max, item.Value), 0) * 1.5;
                    
            const yScale = d3.scaleLinear()
                .domain([0, yAxisMax])
                .range([height, 0]);
    
            const yAxis = d3.axisLeft(yScale).ticks(4);
                    
            svgG.append('g')
                .call(yAxis);
    
            const bars = svgG.selectAll('g.bar')
                .data(barData)
                .enter()
                .append('g')
              .classed('bar', true)
              .attr('transform', d => `translate(${xScale(d.Time) + xScale.bandwidth() / 2}, 0)`);
            /*  
            const staticColor =   "steelblue",
            highlightColor = "orange";
    
            var sheet = document.createElement('style')
            sheet.innerHTML = ".bar {fill: "+staticColor+"} .highlight {fill:"+highlightColor+"}";
            document.body.appendChild(sheet);
            */
            bars.append('rect')
                .attr('x', -20)
                .attr('width', 40)
                .attr('y', d =>  yScale(d.Value))
                .attr('height', d => height - yScale(d.Value) )
                .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");
                          //d3.select(this).attr('class', 'highlight');
                          //this.setState({ fillColour: 'green' });
    
    
            }
    
            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('y', d => yScale(d.Value))
              .attr('dy', -5)
             ;
             
    const topPosition = i => yScale(9000) + i * 15;         
             
    const pathBetweenBars = (d, i) => {
    if(d.Target != -1)
    {
        const delta = d.Target - i;
      const targetValue = barData[d.Target].Value;
      const targetX = delta * xScale.bandwidth() - 5;
      const sourceY = yScale(d.Value);
      const targetY = yScale(targetValue);
      const topY = topPosition(i);
      return `M 5,${sourceY - 20} V ${topY} H ${targetX} V ${targetY - 25}`;
    }
    else
    return `M 5,${-100} V ${-100} H ${-1000} V ${-0}`;
    };         
    
    const LABEL_WIDTH = 50;
    
    const midPosition = (d, i) => {
    if(d.Target != -1)
    {
        const delta = d.Target - i;
      return delta * xScale.bandwidth() / 2;
      }
      else
      {
       return -1000;
      }
    }
    
    function chkArrowDir(d)
    {
        return d.isReverse == "false" ? 'marker-end' : 'marker-start';
    }
    
    bars.filter(d => d.Target != null)
      .append('path')
      .attr('d', (d, i) => pathBetweenBars(d, i))
      .style('stroke', 'gray')
      .style('fill', 'none')
      .attr("marker-end", d => d.isReverse === "false" ? "url(#arrowhead)" : null)
    .attr("marker-start", d => d.isReverse === "false" ? null : "url(#arrowhead)")
        
    bars.filter((d) => d.Target != null)
     .append('rect')
     .attr('x', (d, i) => midPosition(d, i) - LABEL_WIDTH / 2)
     .attr('y', (d, i) => topPosition(i) - 10)
     .attr('width', LABEL_WIDTH)
     .attr('height', 20)
     .attr('rx', 10)
     .style('fill', 'white')
     .style('stroke', 'gray');
    
    bars.filter((d, i) => d.Target != null)
     .append('text')
     .text((d, i) => `${d.Target != -1 ? barData[d.Target].Value > d.Value ? '+' : '':""}${d.Target != -1 ? Math.round((barData[d.Target].Value / d.Value * 100) - 100):""}%`)
      .attr('x', (d, i) => midPosition(d, i))
      .attr('y', (d, i) => topPosition(i) + 3)
      .attr('text-anchor', 'middle')
      .style('fill', 'black');
    #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>

    【讨论】:

    • 您可以将其设置为"none",而不是删除该属性,这是表示不应绘制标记的默认值。
    • @altocumulus 在这种特殊情况下,是的,我可以。但是这里的总体思路是动态设置属性,因此尽管有效,但以"none" 为值的一组属性与如果 D3 接受函数作为第一个参数(尽管这就是 OP他们可以做到)。
    猜你喜欢
    • 2017-05-28
    • 1970-01-01
    • 1970-01-01
    • 2022-11-18
    • 2019-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-01
    相关资源
    最近更新 更多