Adjustable noise data based on a curve

I've been working on an Exoplanet Transit Simulator, and part of this simulation displays randomized data, with an adjustable randomization factor, connected to an arbitrary curve. When you click "Show simulated measurements", you can adjust the noise level of data points connected to the lightcurve. These data points are what an astronomer sees when looking for clues of a planet orbiting a distant star.

The theoretical lightcurve adjusts according to the noise level of data points.

We start off with the theoretical lightcurve. This is generated by some complicated calculations in Lightcurve.js. It's just a 2-dimensional array representing x-y values on the plot.

Here I'll go through the noise data function and describe how it's put together.

/**   
 * Generate noise around the lightcurve.
 */
getNoiseData(lightcurveData) {
    if (!this.props.showSimulatedMeasurements) {
        return [];
    }

First, find the extent of the lightcurve data - these X values go somewhere between 0 to 400.

    const xExtent = d3.extent(lightcurveData, d => d[0]);

xExtent is now [0, 400]. Now we'll make a bunch of random X values for this noise. The Y values are influenced by the actual lightcurve, but the X values are just completely random.

    const randomizedData = [];
    for (let i = 0; i < this.props.simMeasurementNumber; i++) {
        const randX = Math.random() * xExtent[1];

Now I have a random X value for this noise data. Great. But how do I define the Y value? It can't be completely random: it needs to be tied to the lightcurve, with an adjustable randomness. Well the first step is finding where the lightcurve is at this random X value. That's not so straightforward, because I don't have just a mathematical formula for the curve that I can plug randX into.

d3 has a family of "bisector" functions that return the index in an array where a given x value belongs, if it were to be inserted and maintain sorted order. So that's what I'm using here: this says, give me the index into lightcurveData where randX belongs. I don't actually modify lightcurveData, just use that index to find the closest Y value.

        // Find the closest lightcurve point to this random x
        // value.
        const idx = d3.bisector(function (d) {
            return d[0];
        }).left(lightcurveData, randX);

        // Generate noise based on the real y val.
        const y = lightcurveData[idx][1];

The rest is pretty straightforward: add some level of randomness to this Y value, depending on the adjustable noise value.

        const newY = y + (
            // Scale the randomization factor by the value
            // of the "noise" slider
            (Math.random() - 0.5) * this.props.noise);

        randomizedData.push([randX, newY]);
    }

    return randomizedData;
}
- LightcurveView.jsx