获取分组条形图 y 域的 d3.max 值

Getting d3.max value for grouped bar chart y domain

提问人:MicrowavableFrenchfries 提问时间:11/15/2023 更新时间:11/17/2023 访问量:93

问:

我创建了一个分组条形图,并尝试将 y 域设置为最大条形值。这样做,而不是必须根据数据更改的静态数字。我对如何使用 CSV 文件中的多列感到困惑。我正在尝试使它,以便您不必使用特定的列名,因为在其他 CSV 文件中可能有所不同。现在,列名称为 cat1、cat2 和 cat3。我正在使用 D3 v7d3.max()

数据

name,cat1,cat2,cat3
Item 1,50,102,302
Item 2,79,140,330
Item 3,200,180,120
Item 4,104,80,83
Item 5,90,320,130
Item 6,85,114,130

我有点了解如何使用,但绝对不是在这种情况下。d3.max()

这是我目前使用它的方式。你会看到我在 y 域中有一个集合。我现在只是在尝试控制台日志最大值。我意识到我没有正确地做到这一点,但不确定该怎么做。350

第3天

const data = await d3.csv(src); 

  console.log(d3.max(data, d => {
    return d;
  }));
  // returns {name: 'Item 1', cat1: '50', cat2: '102', cat3: '302'}
  
  // Would like to use the max value
  // rather than 350
  y.domain([350, 0])
   .range([0, height]);

谢谢你的帮助。

CSV D3.js 分组条形图

评论


答:

0赞 Akash Ram 11/15/2023 #1

let data = [{
    name: 'Item 1',
    cat1: 50,
    cat2: 102,
    cat3: 302
},
{
    name: 'Item 2',
    cat1: 79,
    cat2: 140,
    cat3: 330
},
{
    name: 'Item 3',
    cat1: 200,
    cat2: 180,
    cat3: 120      
},
{
    name: 'Item 4',
    cat1: 104,
    cat2: 80,
    cat3: 83    
},
{
    name: 'Item 5',
    cat1: 90,
    cat2: 320,
    cat3: 130       
},
{
    name: 'Item 6',
    cat1: 85,
    cat2: 114,
    cat3: 130   
}
]

let max = d3.max(data, ((d)=> (d.cat1,d.cat2,d.cat3)));
let result = data.find((d) => d.cat1 == max || d.cat2 == max || d.cat3 == max);
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

评论

0赞 MicrowavableFrenchfries 11/16/2023
感谢您的回复。您是说 CSV 数据需要转换为对象数组,还是需要导入为 JSON?是否必须在 d3.max 和 data.find 中指定键名?
0赞 Jeremy Caney 11/16/2023
感谢您对 Stack Overflow 社区的贡献。这可能是一个正确的答案,但提供代码的额外解释,以便开发人员能够理解你的推理,这将是非常有用的。这对于不熟悉语法或难以理解概念的新开发人员特别有用。为了社区的利益,您能否编辑您的答案以包含其他详细信息?
0赞 Akash Ram 11/16/2023
在 D3.js 中使用 d3.csv 方法时,将自动分析生成的数据并将其转换为 JSON 格式。因此,一旦使用 d3.csv(),就可以像处理 JSON 格式一样处理数据。@MicrowavableFrenchfries
0赞 MicrowavableFrenchfries 11/16/2023
@AkashRam 感谢您澄清这一点。它仍然没有回答我的问题。有没有办法在不指定其列名的情况下找到这些键的最大值?例如,不同 csv 文件中的列名可能不相同。不仅如此,我怎样才能在 y 域中使用该最大值并使其适合 svg 的高度?我已经在我的代码笔中使用了你的代码片段,你会看到条形图延伸到svg高度之外。请参阅第 73-79 行 codepen.io/MicrowavableFrenchfries/pen/KKJyBmG/...
0赞 Akash Ram 11/17/2023
我根据您的要求提供新的解决方案。请检查it.@MicrowavableFrenchfries
0赞 Akash Ram 11/17/2023 #2

const margin = { top: 20, right: 50, bottom: 50, left: 70 };
const wrap = d3.select('#chart-wrap');
let wrapWidth = parseInt(wrap.style('width'));
let width = wrapWidth - margin.left - margin.right;
const wrapHeight = parseInt(wrap.style('height'));
const height = wrapHeight - margin.top - margin.bottom;
let subgroup;
const y = d3.scaleLinear();
const x0 = d3.scaleBand();
const x1 = d3.scaleBand();
const src = 'https://assets.codepen.io/1329727/data-multi-demo.csv';
const colors = d3.scaleOrdinal()
  .range(['#5626C4', '#E60576', '#2CCCC3', '#FACD3D', '#181818']);
let tooltipChart;

// Tooltip
const tooltipMouseMove = (key, value, loc) => {

  tooltipChart
    .html(() => {
      return (
        `<div class="chart-tooltip-wrap">
          <p><strong>${key}</strong></p>
          <p>${value}</p>
         </div>
        `
       );
    })
    .style('visibility', 'visible')
    .style('left', `${d3.pointer(event)[0] + loc}px`)
    .style('top', `${d3.pointer(event)[1] + 20}px`)
}

const tooltipMouseOut = () => {
  tooltipChart
    .style('visibility', 'hidden');
}

const svg = wrap.append('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom);
  
// SVG aria tags
svg.append('title')
  .attr('id','chart-title')
  .html('Group, verical bar chart');

svg.append('desc')
  .attr('id','chart-desc')
  .html('Displays grouped bar charts for different items.');

svg.attr('aria-labelledby','chart-title chart-desc');

tooltipChart = wrap.append('div')
    .attr('class','chart-tooltip')
    .style('visibility', 'hidden');

const group = svg.append('g')
    .attr('class', 'group')
    .attr('transform', `translate(${margin.left}, ${margin.top})`);
      
async function createGroupBars() {
  const data = await d3.csv(src); 
  
  // Keys and group keys
  const keys = data.columns.slice(1);
  const groupKey = data.columns[0];
  
  const values = [];
  data.forEach((el)=> {
    for(let i = 0; i < keys.length; i++) {
        values.push(+el[keys[i]])
    }
  })
  
  // Bar width
  const barWidth = (width / groupKey.length);
  
  const isSame = (data,keys) => {
       let output = false;
       for(let i=0; i<keys.length;i++) {
         if(data[keys[i]] == max){
           output = true;
           break;
         }
       }
    return output;
  }
  
 // Getting the maximum value from the keys
 let max = d3.max(values);
 let result = data.find((d) => {
   return isSame(d,keys);
 });
  
  // Scales
  y.domain([max, 0])
    .range([0, height]);

  x0.domain(data.map(d => { return d[groupKey]; }))
    .range([0, width])
    .paddingInner(0.3);
  
  if (width < 400) {
    x0.paddingInner(0.15);
  }

   x1.domain(keys)
    .range([0, x0.bandwidth()]);
 
  // X Axis
  const xAxis = group.append('g')
    .attr('class', 'x-axis')
    .attr('transform', `translate(0, ${height})`)
    .call(
      d3.axisBottom(x0)
      .tickSize(7)
    );

  xAxis.call(g => g.select('.domain').remove());
  
  // X axis labels 
  xAxis.selectAll('text')
      .attr('transform', 'translate(-10, 0) rotate(-45)')
      .style('text-anchor', 'end');
  
  // Y Axis
  group.append('g')
    .attr('class', 'y-axis')
    .call(
      d3.axisLeft(y)
        .tickSizeOuter(0)
        .tickSize(-width)
     );
  
    d3.selectAll('.y-axis text')
      .attr('x', -10)
  
   // Subgroups of rectangles
  subgroup = group.selectAll('.subgroup')
    .data(data)
    .join('g')
    .attr('class', 'subgroup')
    .attr('transform', (d) => {
      return `translate(${x0(d[groupKey])}, 0)`;
    })
    .attr('aria-label', d => {
      return `Values for ${d.name}`
    })

  // Rectangles
  subgroup
    .selectAll('rect')
    .data(d => {
      return keys.map(key => {
        return { key: key, value: d[key] }
      });
    })
    .join('rect')
    .attr('class', 'rect')
    .attr('y', d => y(d.value))
    .attr('x', d => { return x1(d.key); })
    .attr('height', (d) => { return height - y(d.value); })
    .attr('width', x1.bandwidth())
    .attr('fill', (d, i) => { return colors(d.key); })
    .attr('aria-label', d => {
      return `${d.key} bar`;
    })
    .on('mousemove', function (event, d, i) {
    
      // Get parent's translate x value
      const parent = d3.select(this.parentNode).attr('transform').slice(10);
      const loc = parseFloat(parent);
    
      // call tooltip function
      tooltipMouseMove(d.key, d.value, loc);
    })
    .on('mouseout', () => {
      tooltipMouseOut();
    });

  // Rectangle labels
  subgroup.selectAll('.bar-labels')
    .data(d => {
      return keys.map(key => {
        return { key: key, value: d[key] }
      });
    })
    .join('text')
    .attr('class', 'bar-labels')
    .attr('y', d => { return y(d.value) - 3 })
    .attr('x', d => { return x1(d.key) + 12; })
    .attr('text-anchor', 'middle')
    .style('fill', '#181818')
    .text(d => { return d.value });
  
   // Legend
  const createLegend = (parent, cat) => {
      parent.append('div')
        .attr('class', 'legend')
        .selectAll('div')
        .data(data.columns.slice(1))
        .enter()
        .append('div')
        .attr('class', 'legend-group')
        .html((d, i) => {
          return(`
            <div class="legend-box" style="background-color: ${colors(cat[i])};"></div>
            <p class="legend-label">${cat[i]}</p>
          `);
        });
      }
    createLegend(wrap, Object.keys(data[0]).slice(1));

    // Resize
    const resize = () => {
      wrapWidth = parseInt(wrap.style('width'));
      width = wrapWidth - margin.left - margin.right;

      x0.range([0, width])
        .paddingInner(0.3);
      
      if (width < 400) {
        x0.paddingInner(0.15);
      }

      x1.range([0, x0.bandwidth()]);
      
      svg.attr('width', width + margin.left + margin.right);
      
      subgroup.selectAll('.rect')
        .attr('x', d => { return x1(d.key); })
        .attr('width', x1.bandwidth());
      
      subgroup.selectAll('.bar-labels')
        .attr('x', d => { return x1(d.key) + 12; })
      
      group.select('.x-axis')
        .attr('transform', `translate(0, ${height})`)
        .call(
          d3.axisBottom(x0)
        );
      
      group.select('.y-axis')
        .call(
          d3.axisLeft(y)
            .tickSizeOuter(0)
            .tickSize(-width)
         );
      
      subgroup.attr('transform', (d) => {
        return `translate(${x0(d[groupKey])}, 0)`;
      });
    }
    
    d3.select(window).on('resize', resize);
  
}
createGroupBars();
body {
  background-color: #f7f4e9;
  font-family: sans-serif;
}

.chart-section {
  margin: 2rem auto;
  padding: 1rem;
  width: calc(100% - 2rem);
  max-width: 700px;
}
.chart-section #chart-wrap {
  height: 400px;
  width: 100%;
  position: relative;
}
.chart-section #chart-wrap .chart-tooltip {
  margin-left: 10px;
  position: absolute;
  z-index: 10;
}
.chart-section #chart-wrap .chart-tooltip .chart-tooltip-wrap {
  background-color: #181818;
  border-radius: 10px;
  box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
  display: block;
  padding: 0.875rem;
}
.chart-section #chart-wrap .chart-tooltip .chart-tooltip-wrap p {
  color: #fff;
  font-size: 0.875rem;
  line-height: 1.75;
  margin: 0;
}
.chart-section #chart-wrap svg {
  margin: auto;
}
.chart-section #chart-wrap svg .y-axis .domain {
  display: none;
}
.chart-section #chart-wrap svg .y-axis .tick line {
  stroke: #aaa;
  stroke-dasharray: 3, 3;
}
.chart-section #chart-wrap svg .y-axis .tick:last-child line {
  stroke: #555;
  stroke-dasharray: 0;
}
.chart-section #chart-wrap svg .bar-labels {
  font-size: 0.875rem;
  display: block;
}
@media (max-width: 700px) {
  .chart-section #chart-wrap svg .bar-labels {
    display: none;
  }
}
.chart-section #chart-wrap svg .tick line {
  stroke: #555;
}
.chart-section #chart-wrap svg .tick text {
  font-size: 0.875rem;
}
.chart-section #chart-wrap .legend {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  margin: 2rem auto;
  width: 100%;
}
.chart-section #chart-wrap .legend .legend-group {
  align-items: center;
  display: flex;
  flex-basis: 100px;
  flex-direction: row;
  gap: 8px;
  justify-content: flex-start;
}
.chart-section #chart-wrap .legend .legend-group .legend-box {
  height: 20px;
  margin: 0;
  width: 20px;
}
.chart-section #chart-wrap .legend .legend-group .legend-label {
  margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
<section class="chart-section">
  <div id="chart-wrap"></div>
</section>

代码说明

我修改了容器中 SVG 的代码

// increase bottom value
const margin = { top: 20, right: 50, bottom: 20, left: 70 };
// change zero translate y value to ${margin.top}
const group = svg.append('g')
    .attr('class', 'group')
    .attr('transform', `translate(${margin.left}, ${margin.top})`)

并从 CSV 文件中获取动态最大值

const values = [];
  data.forEach((el)=> {
    for(let i = 0; i < keys.length; i++) {
        values.push(+el[keys[i]])
    }
  })
  
  // Bar width
  const barWidth = (width / groupKey.length);
  
  const isSame = (data,keys) => {
       let output = false;
       for(let i=0; i<keys.length;i++) {
         if(data[keys[i]] == max){
           output = true;
           break;
         }
       }
    return output;
  }
  
 // Getting the maximum value from the keys
 let max = d3.max(values);
 let result = data.find((d) => {
   return isSame(d,keys);
 });

注意:我不关注代码的时间复杂度;我只是想解决问题。

评论

0赞 MicrowavableFrenchfries 11/18/2023
这太完美了。正是我想要的。感谢您的帮助!
0赞 Akash Ram 11/18/2023
如果它能帮助你解决你的问题,请通过投票给答案来将帮助传播给其他人。@MicrowavableFrenchfries