JSFiddle - React, Tailwind, and code Playground

JavaScript

var r = new Raphael(10,10, 500, 500);
var X = 0; //better readable
var Y = 1;

var points = [
  [25, 75],
  [25, 125],
  [75, 125],
  [125, 125],
  [175, 125],
  [125, 75],
  [75, 75]
];

var path_string = ""; 

//old path using catmull-rom
var point = points[0];
path_string += "M"+point[X]+","+point[Y]+"R";
for (var i=1; i<points.length; i++)
{
    point = points[i];
    path_string += " "+point[X]+","+point[Y];
}
path_string += "Z";
var path = r.path(path_string);
path.attr({
    "stroke": "red"
});

//new version using quadratic curves and midpoints:
points = [ //just shifting every point 200px downwards
  [25, 275],
  [25, 325],
  [75, 325],
  [125, 325],
  [175, 325],
  [125, 275],
  [75, 275]
]; 

function midPoint(Ax, Ay, Bx, By)
{
    var Zx = (Ax-Bx)/2 + Bx;
    var Zy = (Ay-By)/2 + By;
    return new Array(Zx, Zy);
}
var first, second, next, Z;
path_string = "";

point =  points[0];
path_string += "M"+point[X]+","+point[Y]+"M";
first = point;

for (var i=1; i<points.length; i++)
{
    next = points[i];
    
    Z = midPoint(point[X], point[Y], next[X], next[Y]);
    
    path_string += " "+Z[X]+","+Z[Y];
    path_string += "Q"+next[X]+","+next[Y];
    point = next;
}

second = points[1];
var Z = midPoint(first[X], first[Y], second[X], second[Y]);

path_string += "Q"+first[X]+","+first[Y];  //use first point as control to smooth last curve
path_string += " "+Z[X]+","+Z[Y];
path_string += "Z"; //close to first point (invisible because we started with Mx,yM...)

path = r.path(path_string);
path.attr({
    "stroke": "blue"
});