1

I am visualizing web audio data with d3. I continuously run the function that appends the data to d3 by using requestanimationframe. When I console log the frequency data in the recursive function call, I get the result as expected. However, when I bind the data to an svg and attempt to create elements with d3, the loop only runs twice.

I tried passing different data sets to the data binding, but each outcome only calls the function twice. For example:

import createBoomBox from './boombox';

const frequencyData = new Uint8Array(200);

const createFrequency = (analyser) => {
  requestAnimationFrame(() => createFrequency(analyser));
  analyser.getByteFrequencyData(frequencyData);
  createBoomBox(frequencyData);
  };

const createAudio = (audioId) => {
  console.log('in audio');
  const audioCtx = new (window.AudioContext || 
  window.webkitAudioContext)();

  const audioElement = document.getElementById(audioId);

   const audioSrc = audioCtx.createMediaElementSource(audioElement) 
    || window.audiosrc;
   window.audioSrc = audioSrc;
   const analyser = audioCtx.createAnalyser();

   audioSrc.connect(analyser);

   audioSrc.connect(audioCtx.destination);

   createFrequency(analyser);
   };

   export default createAudio;

Is where I create the audio context and pass the frequency data into createBoomBox. createBoomBox looks as follows:

const svgHeight = 200;
const svgWidth = 200;

const boomBox = d3.selectAll('.boom-box');

const svg = boomBox.append('svg')
  .attr('height', svgHeight)
  .attr('width', svgWidth);

const createBoomBox = (frequencyData) => {
  const rangeScale = d3.scaleLinear()
    .domain([0, d3.max(frequencyData)])
    .range([0, svgHeight]);

  const hslScale = d3.scaleLinear()
    .domain([0, d3.max(frequencyData)])
    .range([0, 360]);

  console.log(frequencyData);
  svg.selectAll('circle')
    .data(frequencyData)
    .enter()
    .append('circle')
    .attr('r', d => rangeScale(d))
    .attr('cx', svgWidth / 2)
    .attr('cy', svgHeight / 2)
    .attr('fill', 'none')
    .attr('stroke-width', 4)
    .attr('stroke-opacity', 1)
    .attr('stroke', d => d3.hsl(hslScale(d), 1, 0.5))
    .exit()
    .remove();
};

export default createBoomBox;

The problem occurs in the createBoomBox function, at

 svg.selectAll

createBoomBox is repeatedly called with requestAnimationFrame. When console log the frequency data right above the svg.selectAll line, I get the result as expected. Namely, an array in the form of Uint8Array with frequency data.

However, when I console log the data inside the

svg.selectAll('circle')
        .data(frequencyData)
        .enter()
        .append('circle')
        .attr('r', d => rangeScale(d))
        .attr('cx', svgWidth / 2)
        .attr('cy', svgHeight / 2)
        .attr('fill', 'none')
        .attr('stroke-width', 4)
        .attr('stroke-opacity', 1)
        .attr('stroke', d => d3.hsl(hslScale(d), 1, 0.5))
        .exit()
        .remove();

The iteration in the above code block only occurs twice. So even though the entire function is being repeatedly called through requestAnimationFrame, the svg.selectAll block only gets run twice. Any idea as to what is causing the problem?

1
  • Hello, can you add code snippet please Commented Jan 12, 2019 at 8:20

1 Answer 1

1

Your exit selection is always empty.

You need to apply the Enter-Update-Exit Pattern.

var circles = svg.selectAll('circle')
    .data(frequencyData);
circles.exit().remove();
circles.enter()
  .append('circle')
    .attr('cx', svgWidth / 2)
    .attr('cy', svgHeight / 2)
    .attr('fill', 'none')
    .attr('stroke-width', 4)
    .attr('stroke-opacity', 1)
  .merge(circles)
    .attr('r', d => rangeScale(d))
    .attr('stroke', d => d3.hsl(hslScale(d), 1, 0.5));
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! It worked! Do you also happen to know how to remove created media element sources? For example, each audio file is trigged by a keypress. When I repeat the keypress, I get the following error: Failed to execute 'createMediaElementSource' on 'AudioContext': HTMLMediaElement already connected previously to a different MediaElementSourceNode

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.