首页 > 解决方案 > 如何根据下拉菜单中的选择呈现条形图?

问题描述

我正在尝试在 d3.js 中创建交互式条形图。此数据是一个 .csv 文件,其中存储每个 BIB(即运动员)的所有跑步。我已经使用d3.group()按 BIB 分组,但我不知道如何在下拉菜单中选择 BIB,以便显示该特定运动员的跑步。我是 d3.js 的新手,所以我感谢所有输入和帮助。现在,该图如下所示:

在此处输入图像描述

如您所见,我可以在控制台中记录特定的 BIB,但我不明白如何从这里为选定的 BIB 呈现仅显示其运行的图表。我想我很接近

这是存储我所有代码的 index.html:

const data = d3.range(20).map(i => ({
  BIB: Math.floor(i / 5) + 1,
  RATIO: -1 + Math.random() * 2,
  RUN: [1, 2, 3, 4, 5][i % 5],
  NAME: ['RASTAD', 'SANDENGEN', 'NYBORG', 'ANDERSEN'][Math.floor(i / 5)]
}));

// 1. This is the DropDownMenu 
const dropdown = (selection, props) => {
  const {
    options,
    onOptionClicked
  } = props

  let select = selection.selectAll("select").data([null]);
  select.enter().append("select").merge(select)
    .on("change", function() {
      console.log(this.value)
    })

  let option = select.selectAll("option").data(options);
  option.enter().append("option")
    .merge(option)
    .attr("value", d => d)
    .text(d => d)
}

// 2. This is the SVG element
const height = 300
const width = 700
const margin = {
  top: 20,
  right: 20,
  bottom: 20,
  left: 50
}
// 3. THis is where I define the margin convention
const innerWidth = width - margin.left - margin.right
const innerHeight = height - margin.top - margin.bottom

const svg = d3.select("body").append("svg")
  .attr("width", width)
  .attr("height", height)

let selectBib;
let onSelectBibClicked = BIB => {
  selectBib = BIB;
  render();
}

// 4. This is the render function that creates the bar chart
const render = () => {

  d3.select("body").call(dropdown, {
    options: groupBySkiers,
    onOptionClicked: onSelectBibClicked,
  });

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

  const xScale = d3.scaleBand()
    .domain(data.map(d => d.RUN))
    .range([0, innerWidth])
    .padding(0.1)


  const yAxis = d3.axisLeft(yScale)
  const xAxis = d3.axisTop(xScale)

  const g = svg.append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`)

  g.selectAll("rect")
    .data(data)
    .enter()
    .append("rect")
    .attr('v', d => d.RATIO)
    .attr("x", d => xScale(d.RUN))
    .attr("width", xScale.bandwidth())
    .attr("height", d => d.RATIO < 0 ? yScale(d.RATIO) - yScale(0) : yScale(0) - yScale(d.RATIO))
    .attr("y", d => d.RATIO < 0 ? yScale(0) : yScale(d.RATIO))
    .attr("fill", d => d.RATIO < 0 ? '#2ec1ac' : 'red')
    .append("title")
    .text(function(d) {
      return d.RATIO
    })

  yAxis(g.append("g").attr("class", "axis"))
  xAxis(g.append("g").attr("class", "axis").attr("transform", `translate(0,${yScale(-3)})`))

}

groupBySkiers = d3.group(data, d => d.BIB)
render()
label {
  font-size: larger;
  font-family: sans-serif;
}

#startnummer {
  font-family: sans-serif;
  font-size: larger;
}

.axis {
  font-size: larger;
  font-family: sans-serif;
}

rect:hover {
  opacity: 0.6;
}
<script src="https://d3js.org/d3.v6.min.js"></script>
<label for="bibnumber">Startnummer :</label>

<select name="STARTNUMMER" id="startnummer">
  <option value="volvo">1</option>
</select>

原始 .csv 文件可以从这里下载

标签: javascriptd3.js

解决方案


我对你的图表做了一些改进。首先,我给下拉列表提供了更好的标签,并将值更改为每个运动员的 BIB。

我还将代码拆分为一个render和一个update函数,因此一次性代码可以从常规代码中分离出来。

您不需要d3.group,因为您不需要值,只需要唯一的 BIB 编号。因此,您可以将其更改为使用 ES6 Set、对象或独特的函数,如下所示:

// This works because `indexOf` returns the first match.
// Any other matches have a different index `i` than the first match
const uniqueValues = arrayWithDuplicates.filter((d, i, arr) => arr.indexOf(d) === i));

selectBib最后,如果您只使用一次,则无需存储变量。仅当您想在不是由下拉更改触发的过程中查找值时才需要。

const data = d3.range(20).map(i => ({
  BIB: Math.floor(i / 5) + 1,
  RATIO: -1 + Math.random() * 2,
  RUN: [1, 2, 3, 4, 5][i % 5],
  NAME: ['RASTAD', 'SANDENGEN', 'NYBORG', 'ANDERSEN'][Math.floor(i / 5)]
}));

// 1. This is the DropDownMenu 
const dropdown = (options) => {
  let select = d3.select('select')
    .on("change", function() {
      const bib = Number(d3.select(this).property('value'));
      update(bib)
    })

  let option = select.selectAll("option").data(Object.entries(options));
  option.enter()
    .append("option")
    .merge(option)
    .attr("value", ([bib, name]) => bib)
    .text(([bib, name]) => `${bib} (${name})`)
}

// 2. This is the SVG element
const height = 300
const width = 700
const margin = {
  top: 20,
  right: 20,
  bottom: 20,
  left: 50
}
// 3. THis is where I define the margin convention
const innerWidth = width - margin.left - margin.right
const innerHeight = height - margin.top - margin.bottom

let g, xScale, yScale

// 4. This is the render function that creates the bar chart
const render = () => {
  const svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)

  dropdown(uniqueSkiers);

  yScale = d3.scaleLinear()
    .domain([-3, 3])
    .range([innerHeight, 0]);

  xScale = d3.scaleBand()
    .domain(data.map(d => d.RUN))
    .range([0, innerWidth])
    .padding(0.1)

  const yAxis = d3.axisLeft(yScale)
  const xAxis = d3.axisTop(xScale)

  g = svg.append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`)

  g.append("g")
    .attr("class", "axis")
    .call(yAxis)
  g.append("g")
    .attr("class", "axis")
    .attr("transform", `translate(0,${yScale(-3)})`)
    .call(xAxis)
  update(data[0].BIB)
}

const update = bib => {
  const filteredData = data.filter(d => d.BIB === bib);
  const rect = g.selectAll("rect")
    .data(filteredData, d => d.RUN);

  rect.exit().remove();
  rect
    .enter()
    .append("rect")
    .merge(rect)
    .attr('v', d => d.RATIO)
    .attr("x", d => xScale(d.RUN))
    .attr("width", xScale.bandwidth())
    .attr("height", d => d.RATIO < 0 ? yScale(d.RATIO) - yScale(0) : yScale(0) - yScale(d.RATIO))
    .attr("y", d => d.RATIO < 0 ? yScale(0) : yScale(d.RATIO))
    .attr("fill", d => d.RATIO < 0 ? '#2ec1ac' : 'red')
    .append("title")
    .text(function(d) {
      return d.RATIO
    })
}

const uniqueSkiers = data.reduce((obj, {
  BIB,
  NAME
}) => {
  obj[BIB] = NAME;
  return obj;
}, {});

render()
label {
  font-size: larger;
  font-family: sans-serif;
}

#startnummer {
  font-family: sans-serif;
  font-size: larger;
}

.axis {
  font-size: larger;
  font-family: sans-serif;
}

rect:hover {
  opacity: 0.6;
}
<script src="https://d3js.org/d3.v6.min.js"></script>
<label for="bibnumber">Startnummer :</label>

<select name="STARTNUMMER" id="startnummer">
  <option value="volvo">1</option>
</select>


推荐阅读