【问题标题】:D3js Updating Histogram elements not working (General Update Pattern)D3js 更新直方图元素不起作用(一般更新模式)
【发布时间】:2021-03-31 06:32:24
【问题描述】:

我正在尝试完成与此处类似的事情:https://www.opportunityatlas.org/。如果您继续访问此链接并单击 'Show Distribution' 以查看图表并选择 'On Screen',然后在地图上移动光标,您将看到矩形的大小发生变化,更新模式也有效,即如果矩形已经存在,它会水平移动到新值。

我也尝试过这样做,但无法实现更新部分。您能否指出我错过的地方。我附上了我的代码的一部分,其中有两个数据集 data1data2 有一些 id 常见但正如您所看到的,当您单击更新以更改数据集时,所有矩形都处于输入阶段,并且它们都没有改变现有矩形的位置(更新阶段没有)。如果有人可以指导我完成这个,那将会很有帮助。如果有其他方法可以实现我在链接中提供的相同图表,那么如果有其他方法也将有所帮助。 提前致谢!

let initialRender = true;
let selectedData1 = true;
const margin = {
    top: 10,
    right: 30,
    bottom: 30,
    left: 30
  },
  width = 550 - margin.left - margin.right,
  height = 150 - margin.top - margin.bottom;

function printChart(asd, data, dataGradients) {
  const svg = d3.select('#data-viz')
  // const isZoomed = map.getZoom() > zoomThreshold;

  // if (isZoomed !== changedZoom) {
  //   initialRender = true;
  //   changedZoom = isZoomed;
  // }

  // X axis and scale ------------->>>>>>>>>>>>>>>>>>>>
  const xScale = d3.scaleLinear()
    .domain(d3.extent(data.map(d => d.value)))
    .range([0, width])

  const xAxisCall = d3.axisBottom(xScale)
    .tickFormat(d3.format(".2s"))
    .ticks(5)
    .tickSizeOuter(0);

  let xAxis = null
  if (initialRender) {
    d3.select(".axis-x").remove()
    xAxis = svg.append("g")
      .attr("class", "axis-x")
      .attr("transform", "translate(0," + 115 + ")")
    initialRender = false
  } else {
    xAxis = d3.select(".axis-x")
  }

  xAxis.transition()
    .duration(2000)
    .ease(d3.easeSinInOut)
    .call(xAxisCall)
  // X axis and scale <<<<<<<<<<<<<<<<-----------------------------
  const binMin = 5;
  const binMax = 150;
  const tDuration = 3000;

  // Just to calculate max elements in each bin ---------->>>>>>>>>>>>>>>>>>
  let histogram = d3.histogram()
    .value(d => d.value)
    .domain(xScale.domain())
    .thresholds(xScale.ticks(10));

  let bins = histogram(data).filter(d => d.length > 0);
  console.log(bins);
  const max = d3.max(bins.map(bin => bin.length))
  const maxBinSize = max <= 10 ? 10 : max
  // Just to calculate max elements in each bin <<<<<<<<<<<<----------------

  // Decide parameters for histogram ------------>>>>>>>>>>>>>>>>>
  const dotSizeScale = d3.scaleLinear()
    .domain([binMin, binMax])
    .range([10, 4])
  const dotSize = dotSizeScale(maxBinSize);

  const dotSpacingScale = d3.scaleLinear()
    .domain([binMin, binMax])
    .range([12, 6])
  const dotSpacing = dotSpacingScale(maxBinSize);

  const thresholdScale = d3.scaleLinear()
    .domain([binMin, binMax])
    .range([10, 100])
  const threshold = thresholdScale(maxBinSize);

  const yTransformMarginScale = d3.scaleLinear()
    .domain([binMin, binMax])
    .range([100, 100])
  const yTransformMargin = yTransformMarginScale(maxBinSize);

  if (dotSize !== 10) {
    d3.selectAll('.gBin').remove()
    d3.selectAll('rect').remove()
  }

  histogram = d3.histogram()
    .value(d => d.value)
    .domain(xScale.domain())
    .thresholds(xScale.ticks(threshold));

  bins = histogram(data).filter(d => d.length > 0);
  // Decide parameters for histogram <<<<<<<<<<<<<<<<<<<<--------------------------

  // Y axis scale -------------------->>>>>>>>>>>>>>>>>>>>
  var yScale = d3.scaleLinear()
    .range([height, 0]);

  yScale.domain([0, d3.max(bins, (d) => d.length)]);
  svg.append("g")
    .attr("class", "axis-y")
    .call(d3.axisLeft(yScale));
  d3.select(".axis-y")
    .remove()
  // Y axis scale <<<<<<<<<<<<<<<<<<<<<<<-----------------

  const binGroup = svg.selectAll(".gBin")
    .data(bins,
      (d) => {
        console.log('id 1', d.x0)
        return d.x0
      }
    )

  binGroup
    .exit()
    .transition()
    .duration(2000)
    .style("opacity", 0)
    .remove()

  const binGroupEnter = binGroup
    .enter()
    .append("g")
    .merge(binGroup)
    .attr("class", "gBin")
    .attr("x", 1)
    .attr("transform", function(d) {
      return "translate(" + xScale(d.x0) + "," + yTransformMargin + ")";
    })
    .attr("width", 10)

  const elements = binGroupEnter.selectAll("rect")
    .data(d => d.map((p, i) => ({
        id: p.id,
        idx: i,
        value: p.value,
      })),
      function(d) {
        console.log('id 2', d)
        return d.id
      }
    )

  elements.exit()
    .transition()
    .duration(tDuration)
    .style("opacity", 0)
    .remove()

  elements
    .enter()
    .append("rect")
    .merge(elements)
    .attr("y", -(height + margin.top))
    // .on("mouseover", tooltipOn)
    // .on("mouseout", tooltipOff)
    .transition()
    .delay(function(d, i) {
      return 50 * i;
    })
    .duration(tDuration)
    .attr("id", d => d.value)
    .attr("y", (d, i) => -(i * dotSpacing))
    .attr("width", dotSize)
    .attr("height", dotSize)
    // .style("fill", (d) => getBinColor(d.value, dataGradients))
    .style("fill", 'red')
}

const data1 = [{
  id: 1,
  value: 14
}, {
  id: 13,
  value: 12
}, {
  id: 2,
  value: 50
}, {
  id: 32,
  value: 142
}]
const data2 = [{
  id: 1,
  value: 135
}, {
  id: 7,
  value: 2
}, {
  id: 2,
  value: 50
}, {
  id: 32,
  value: 50
}]
printChart(null, data1, null)

function changeData() {
  selectedData1 ?
    printChart(null, data2, null) :
    printChart(null, data1, null)
  selectedData1 = !selectedData1
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<button onclick="changeData()"> Update data </button>
<svg width="550" height="250" id="data-viz">
      <g transform="translate(30, 100)">
      </g>
    </svg>

【问题讨论】:

    标签: javascript d3.js data-science data-visualization data-analysis


    【解决方案1】:

    您的问题似乎是以下几行:

    if (dotSize !== 10) {
      d3.selectAll('.gBin').remove();
      d3.selectAll('rect').remove();
    }
    

    在计算任何选择之前删除所有元素,因此所有内容(您的 bin g 和元素 rect)都变为 enter

    另一个有趣的事情是您的垃圾箱的数据密钥。由于您使用的是 x0,因此您的 g 也将根据直方图函数计算 bin 的方式进入/退出。

    <html>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
      <button onclick="changeData()">Update data</button>
      <svg width="550" height="250" id="data-viz">
        <g transform="translate(30, 100)"></g>
      </svg>
      <script>
        let initialRender = true;
        let selectedData1 = true;
        const margin = {
            top: 10,
            right: 30,
            bottom: 30,
            left: 30,
          },
          width = 550 - margin.left - margin.right,
          height = 150 - margin.top - margin.bottom;
    
        function printChart(asd, data, dataGradients) {
        
          console.clear();
        
          const svg = d3.select('#data-viz');
          // const isZoomed = map.getZoom() > zoomThreshold;
    
          // if (isZoomed !== changedZoom) {
          //   initialRender = true;
          //   changedZoom = isZoomed;
          // }
    
          // X axis and scale ------------->>>>>>>>>>>>>>>>>>>>
          const xScale = d3
            .scaleLinear()
            .domain(d3.extent(data.map((d) => d.value)))
            .range([0, width]);
    
          const xAxisCall = d3
            .axisBottom(xScale)
            .tickFormat(d3.format('.2s'))
            .ticks(5)
            .tickSizeOuter(0);
    
          let xAxis = null;
          if (initialRender) {
            d3.select('.axis-x').remove();
            xAxis = svg
              .append('g')
              .attr('class', 'axis-x')
              .attr('transform', 'translate(0,' + 115 + ')');
            initialRender = false;
          } else {
            xAxis = d3.select('.axis-x');
          }
    
          xAxis.transition().duration(2000).ease(d3.easeSinInOut).call(xAxisCall);
          // X axis and scale <<<<<<<<<<<<<<<<-----------------------------
          const binMin = 5;
          const binMax = 150;
          const tDuration = 3000;
    
          // Just to calculate max elements in each bin ---------->>>>>>>>>>>>>>>>>>
          let histogram = d3
            .histogram()
            .value((d) => d.value)
            .domain(xScale.domain())
            .thresholds(xScale.ticks(10));
    
          let bins = histogram(data).filter((d) => d.length > 0);
          //console.log(bins);
          const max = d3.max(bins.map((bin) => bin.length));
          const maxBinSize = max <= 10 ? 10 : max;
          // Just to calculate max elements in each bin <<<<<<<<<<<<----------------
    
          // Decide parameters for histogram ------------>>>>>>>>>>>>>>>>>
          const dotSizeScale = d3
            .scaleLinear()
            .domain([binMin, binMax])
            .range([10, 4]);
          const dotSize = dotSizeScale(maxBinSize);
    
          const dotSpacingScale = d3
            .scaleLinear()
            .domain([binMin, binMax])
            .range([12, 6]);
          const dotSpacing = dotSpacingScale(maxBinSize);
    
          const thresholdScale = d3
            .scaleLinear()
            .domain([binMin, binMax])
            .range([10, 100]);
          const threshold = thresholdScale(maxBinSize);
    
          const yTransformMarginScale = d3
            .scaleLinear()
            .domain([binMin, binMax])
            .range([100, 100]);
          const yTransformMargin = yTransformMarginScale(maxBinSize);
    
          /*
          if (dotSize !== 10) {
            d3.selectAll('.gBin').remove()
            d3.selectAll('rect').remove()
          }
          */
    
          histogram = d3
            .histogram()
            .value((d) => d.value)
            .domain(xScale.domain())
            .thresholds(xScale.ticks(threshold));
    
          bins = histogram(data).filter((d) => d.length > 0);
          // Decide parameters for histogram <<<<<<<<<<<<<<<<<<<<--------------------------
    
          // Y axis scale -------------------->>>>>>>>>>>>>>>>>>>>
          var yScale = d3.scaleLinear().range([height, 0]);
    
          yScale.domain([0, d3.max(bins, (d) => d.length)]);
          svg.append('g').attr('class', 'axis-y').call(d3.axisLeft(yScale));
          d3.select('.axis-y').remove();
          // Y axis scale <<<<<<<<<<<<<<<<<<<<<<<-----------------
    
          const binGroup = svg.selectAll('.gBin').data(bins, (d) => {
            //console.log('id 1', d.x0)
            return d.x0;
          });
    
          binGroup.exit().transition().duration(2000).style('opacity', 0).remove();
    
          const binGroupEnter = binGroup
            .enter()
            .append('g')
            .merge(binGroup)
            .attr('class', 'gBin')
            .attr('x', 1)
            .attr('transform', function (d) {
              return 'translate(' + xScale(d.x0) + ',' + yTransformMargin + ')';
            })
            .attr('width', 10);
    
          const elements = binGroupEnter.selectAll('rect').data(
            (d) =>
              d.map((p, i) => ({
                id: p.id,
                idx: i,
                value: p.value,
              })),
            function (d) {
              //console.log('id 2', d)
              return d.id;
            }
          );
    
          let eex = elements
            .exit()
            .transition()
            .duration(tDuration)
            .style('opacity', 0)
            .remove();
    
          console.log("rects exiting", eex.nodes().map(e => "rect" + e.getAttribute('id')))
    
          let een = elements
            .enter()
            .append('rect')
            .attr('id', (d) => d.value);
    
          console.log("rects entering", een.nodes().map(e => "rect" + e.getAttribute('id')))
    
          let eem = een
            .merge(elements);
    
          console.log("rects merge", eem.nodes().map(e => "rect" + e.getAttribute('id')))
    
          eem
            .attr('y', -(height + margin.top))
            // .on("mouseover", tooltipOn)
            // .on("mouseout", tooltipOff)
            .transition()
            .delay(function (d, i) {
              return 50 * i;
            })
            .duration(tDuration)
            .attr('y', (d, i) => -(i * dotSpacing))
            .attr('width', dotSize)
            .attr('height', dotSize)
            // .style("fill", (d) => getBinColor(d.value, dataGradients))
            .style('fill', 'red');
        }
    
        const data1 = [
          {
            id: 1,
            value: 14,
          },
          {
            id: 13,
            value: 12,
          },
          {
            id: 2,
            value: 50,
          },
          {
            id: 32,
            value: 142,
          },
        ];
        const data2 = [
          {
            id: 1,
            value: 135,
          },
          {
            id: 7,
            value: 2,
          },
          {
            id: 2,
            value: 50,
          },
          {
            id: 32,
            value: 50,
          },
        ];
        printChart(null, data1, null);
    
        function changeData() {
          selectedData1
            ? printChart(null, data2, null)
            : printChart(null, data1, null);
          selectedData1 = !selectedData1;
        }
      </script>
    </html>

    【讨论】:

    • 我尝试删除这两行但仍然没有效果。元素仍然没有更新。你能帮我复制上面链接中显示的内容吗?如果您能做同样的事情并帮助我,那将非常有帮助。我会很高兴的。
    • @KishanVikani,我想我无法理解您认为应该计算哪些数据作为更新。现在,任何rect 都必须满足多个条件才能进行更新:1.) 与上次在同一个 bin 中 2.) 该 bin 必须经过计算才能与之前的数据具有相同的 .x0。哦,在您发送的链接中,我一定遗漏了一些东西,我只是看到了一张有趣的地图。我找不到任何直方图。
    • 如果你看到右上角,有一个部分。您可以在其中单击显示分发。然后你就可以看到图表了。在屏幕选项卡上选择,然后移动直方图将更改的地图。是的,我们需要跟踪两件事。 1) rect 改变 bin 和 2) 在同一个 bin 中改变位置。
    • 是的,但你看到我的代码没有做链接正在做的事情。当您在地图上移动或更改任何参数时,您会看到这种过渡。轴转换正在工作,但垃圾箱没有顺利移动到新位置。它们是从顶部进入的,而不是在我的代码中从旧位置移动到新位置。上面的链接很吸引人,而且做得很好。
    • @KishanVikani,你在寻找它的行为like this;个人 rectsid only 跟踪而不考虑 bin?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-23
    相关资源
    最近更新 更多