score:1

You could create a custom curve generator. This could take a number of different forms. I'll recycle a previous example by tweaking one of the existing d3 curves and using its point method to create a custom curve.

Normally a custom curve applies the same curve between all points, to allow different types of lines to connect points, I'll keep track of the current point's index in the snippet below.

The custom curve in the snippet below is returned by a parent function that takes an index value. This index value indicates which data point should use a different curve between it and the next data point. The two types of curves are hand crafted - some types curves will present more challenges than others.

This produces a result such as: ``````function generator(i,context) {
var index = -1;
return function(context) {
var custom = d3.curveLinear(context);
custom._context = context;
custom.point = function(x,y) {
x = +x, y = +y;
index++;
switch (this._point) {
case 0: this._point = 1;
this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
this.x0 = x; this.y0 = y;
break;
case 1: this._point = 2;
default:
// curvy mountains between values if index isn't specified:
if(index != i+1) {
var x1 = this.x0 * 0.5 + x * 0.5;
var y1 = this.y0 * 0.5 + y * 0.5;
var m = 1/(y1 - y)/(x1 - x);
var r = -100; // offset of mid point.
var k = r / Math.sqrt(1 + (m*m) );
if (m == Infinity) {
y1 += r;
}
else {
y1 += k;
x1 += m*k;
}
// always update x and y values for next segment:
this.x0 = x; this.y0 = y;
break;
}
// straight lines if index matches:
else {
// the simplest line possible:
this._context.lineTo(x,y);
this.x0 = x; this.y0 = y;
break;
}
}
}
return custom;
}
}

var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 300);

var data = d3.range(10).map(function(d) {
var x = d*40+40;
var y = Math.random() * 200 + 50;

return { x:x, y:y }

})

var line = d3.line()
.curve(generator(3))  // striaght line between index 3 and 4.
.x(d=>d.x)
.y(d=>d.y)

svg.append("path")
.datum(data)
.attr("d",line)
.style("fill","none")
.style("stroke-width",3)
.style("stroke","#aaa")

svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx",d=>d.x)
.attr("cy",d=>d.y)
.attr("r", 2)``````
``<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>``

This line will work with canvas as well if you specify a context as the second argument of `generator()`. There are all sorts of refinements that could be made here - the basic principle should be fairly adaptable however.

score:0

Just change the curve type. The curveBasis approximates a curve between points but it doesn't cross them. So use either the usually used "curveCardinal" type or maybe even "curveCatmullRom" that are curves passing the data points.

``````// Fake data
const data = [
{
year: 2000,
bData: 300
},
{
year: 2001,
bData: 50
},
{
year: 2002,
bData: 100
},
{
year: 2003,
bData: 50
},
{
year: 2004,
bData: 80
},
{
year: 2005,
bData: 10
},
{
year: 2006,
bData: 200
}
];
const color = ["lightgreen", "lightblue"];
// Create SVG and padding for the chart
const svg = d3
.select("#chart")
.append("svg")
.attr("height", 300)
.attr("width", 600);

const strokeWidth = 1.5;
const margin = { top: 0, bottom: 20, left: 30, right: 20 };
const chart = svg.append("g").attr("transform", `translate(\${margin.left},0)`);

const width = +svg.attr("width") - margin.left - margin.right - strokeWidth * 2;
const height = +svg.attr("height") - margin.top - margin.bottom;
const grp = chart
.append("g")
.attr("transform", `translate(-\${margin.left - strokeWidth},-\${margin.top})`);

// Create stack
const stack = d3.stack().keys(["aData", "bData"]);
const stackedValues = stack(data);
const stackedData = [];
// Copy the stack offsets back into the data.
stackedValues.forEach((layer, index) => {
const currentStack = [];
layer.forEach((d, i) => {
currentStack.push({
values: d,
year: data[i].year
});
});
stackedData.push(currentStack);
});

// Create scales
const yScale = d3
.scaleLinear()
.range([height, 0])
.domain([0, d3.max(stackedValues[stackedValues.length - 1], dp => dp)]);
const xScale = d3
.scaleLinear()
.range([0, width])
.domain(d3.extent(data, dataPoint => dataPoint.year));

const area = d3
.area()
.x(dataPoint => xScale(dataPoint.year))
.y0(dataPoint => yScale(dataPoint.values))
.y1(dataPoint => yScale(dataPoint.values))
//.curve(d3.curveBasis)
.curve(d3.curveCardinal)
//.curve(d3.curveCatmullRom.alpha(0.5))
;

const series = grp
.selectAll(".series")
.data(stackedData)
.enter()
.append("g")
.attr("class", "series");

series
.append("path")
.attr("transform", `translate(\${margin.left},0)`)
.style("fill", (d, i) => color[i])
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", strokeWidth)
.attr("d", d => area(d));

const dotsGreen = chart
.selectAll(".gdot")
.data(data)
.enter()
.append("circle")
.attr("class", "gdot")
.attr("cx", function(d) {
return xScale(d.year)
})
.attr("cy", d => yScale(d.aData))
.attr("r", 4)
.attr("fill", "green");

const dotsBlue = chart
.selectAll(".bdot")
.data(data)
.enter()
.append("circle")
.attr("class", "bdot")
.attr("cx", function(d) {
return xScale(d.year)
})
.attr("cy", d => yScale(d.aData+d.bData))
.attr("r", 4)
.attr("fill", "blue");

// Add the X Axis
chart
.append("g")
.attr("transform", `translate(0,\${height})`)
.call(d3.axisBottom(xScale).ticks(data.length));

// Add the Y Axis
chart
.append("g")
.attr("transform", `translate(0, 0)`)
.call(d3.axisLeft(yScale));``````
``````#chart {
text-align: center;
margin-top: 40px;
}``````
``````<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="chart"></div>``````

The working codePen forked from your code is also here:

changed CodePen pen

Note: I've added the code for two circle groups (not SVG G element) that you can simply remove. They just serve for a proof where curves are drawn near data points based on curve type scripted.

Your sketched chart looks like you want to have a curve only between two given points. For that you would have to change the curve call to use the running index (e.g. (d,i)) in a function that would return different curve type based on chosen index (or indeces).

ADDED: you can play with different D3.js curve types here:

D3 curve explorer

score:2

I simplied your path by removing precision in: https://yqnn.github.io/svg-path-editor/

You can use that editor to play with d-path, and learn where/how you want to change that d-path String.

Copy the d-path below and paste it in: https://yqnn.github.io/svg-path-editor/

``````<svg height="300" width="600"><g transform="translate(30,0)">
<g transform="translate(-28.5,-90)">
<g class="series">
<path stroke="steelblue" stroke-linejoin="round"
stroke-linecap="round" stroke-width="1.5"
d="M0 257 15 250C30 242 61 227 91 216C122 205 152 197 182 199C213 200 243 211 274 208
C304 205 334 188 365 169C395 151 425 129 456 116C486 102 517 96 532 93
L547 90 547 280 532 280C517 280 486 280 456 280C425 280 395 280 365 280
C334 280 304 280 273 280C243 280 213 280 182 280C152 280 122 280 91 280
C61 280 30 280 15 280L0 280Z"
style="fill: lightgreen;">
</path></g></g></g></svg>``````