blazs
blazs

Reputation: 4845

d3: linearly interpolate only close-enough points

Suppose I have an array of values with corresponding dates, [{date: d1, value: v1}, ..., {date: dn, value: vn}], that I'd like to visualize using d3.js. As long as subsequent measurements are within a certain time range, for example not more than a week apart, I am happy with d3 interpolating between the measurements.

However, when subsequent records are farther apart, I don't want d3 to connect them. What would be the easiest way to achieve this?

Upvotes: 3

Views: 856

Answers (2)

Gerardo Furtado
Gerardo Furtado

Reputation: 102188

Your question is not exactly clear: by "interpolate", I believe you mean "connecting the dots".

If you use a time scale for your x axis, D3 will automatically connect the dots for you and create a line (an SVG path element), regardless the time separation in the data points. But there is a way for making "gaps" in that line: using line.defined(). According to the API:

If defined is specified, sets the defined accessor to the specified function or boolean and returns this line generator.

The problem is, for this approach to work, you'll have to set a given value (let's say, null) in your dataset, between the dates in which you don't want to draw the line (that is, the dates that are not close enough, as you say in your question). You can do this manually or using a function.

This is a working demo: in my dataset, my data jumps from 7-Oct to 17-Oct (more than 1 week). So, I just created a null value in any date between these two (in my demo, 16-Oct). Then, in the line generator, the line jumps this null value, using defined:

d3.line().defined(function(d){return d.value != null;})

The result is that the line jumps from 7-Oct to 17-Oct:

var data = [{date: "1-Oct-16",value: 14},
{date: "2-Oct-16",value: 33},
{date: "3-Oct-16",value: 12},
{date: "4-Oct-16",value: 43},
{date: "5-Oct-16",value: 54},
{date: "6-Oct-16",value: 71},
{date: "7-Oct-16",value: 32},
{date: "16-Oct-16",value: null},
{date: "17-Oct-16",value: 54},
{date: "18-Oct-16",value: 14},
{date: "19-Oct-16",value: 34},
{date: "20-Oct-16",value: 32},
{date: "21-Oct-16",value: 56},
{date: "22-Oct-16",value: 24},
{date: "23-Oct-16",value: 42},
{date: "24-Oct-16",value: 52},
{date: "25-Oct-16",value: 66},
{date: "26-Oct-16",value: 34},
{date: "27-Oct-16",value: 62},
{date: "28-Oct-16",value: 48},
{date: "29-Oct-16",value: 51},
{date: "30-Oct-16",value: 42}];

var parseDate = d3.timeParse("%d-%b-%y");

data.forEach(function (d) {
    d.date = parseDate(d.date);
});

var svg = d3.select("body")
    .append("svg")
    .attr("width", 500)
    .attr("height", 200);
		
var xScale = d3.scaleTime().range([20, 480]);
var yScale = d3.scaleLinear().range([180, 10]);

xScale.domain(d3.extent(data, function (d) {
    return d.date;
    }));
yScale.domain([0, d3.max(data, function (d) {
    return d.value;
    })]);

var xAxis = d3.axisBottom(xScale).tickFormat(d3.timeFormat("%d"));

var yAxis = d3.axisLeft(yScale);

var baseline = d3.line()
	  .defined(function(d){return d.value != null;})
    .x(function (d) {
      return xScale(d.date);
    })
    .y(function (d) {
      return yScale(d.value);
    });
		
svg.append("path") // Add the valueline path.
.attr("d", baseline(data))
.attr("fill", "none")
.attr("stroke", "teal");

svg.append("g")
    .attr("transform", "translate(0,180)")
    .call(xAxis);

svg.append("g")
.attr("transform", "translate(20,0)")
.attr("class", "y axis")
    .call(yAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>

Upvotes: 3

Joel
Joel

Reputation: 6243

If you're trying to draw a broken line chart, then I don't think interpolation is what you're looking for. Building a custom SVG path is probably the way to go. See an example here:

https://bl.ocks.org/joeldouglass/1a0b2e855d2bedc24c63e396b04c8e36

Upvotes: 0

Related Questions