Как соеденить точки на графике линиями?(html)

Всем привет. Сейчас делаю одно задание, в котором нужно сверстать несколько слайдеров, в которых ползунки будут соединяться кривой линией и, естественно, эта линия должна передвигать вместе с ползунками. У меня в макете есть svg элементы линий, но мне это, как-то, не особо помогает. Я, вроде как, понимаю, что нужно настраивать линию примерно как показано тут:

function makePath(x1, y1, x2, y2) {
    const weight = 0.3;
      const dx = (x2 - x1) * weight;
      const c1 = x1 + dx;
      const c2 = x2 - dx;
      return `<path d="M${x1},${y1} C${c1},${y1} ${c2},${y2} ${x2},${y2}" stroke-width="2" stroke="#DDD" fill="transparent"/>`;
}

Но, хоть я и, примерно, понял смысл происходящего и даже смог привязать линии к ползункам, все равно не получается сделать их хотя бы немного похожими(не обязательно прям точь в точь), как на макете. У меня получаются только какие-то странные некрасивые кривые) И, в общем, неужели это все(изменения координат в зависимости от x/y) и правда нужно настраивать руками, может есть какой-то более простой способ?

введите сюда описание изображения


Ответы (1 шт):

Автор решения: Leonid

Примерно так для второй переносимой точки. Ее можно двигать. Рандомизацию отклонений контрольных точек не делал, можно добавить.

const svg = document.querySelector('svg');
const circle = svg.querySelector('#movable');
const path = svg.querySelector('path');

const wave_wid = 50; // Width of the WAVE

const path_start = [20,40]; // Start-point of path
let path_end = [200,140];  // Initial end-point of path

drawPath(path_end); // Drawing path with initial end-point



function drawPath(coords){  // Calculate, build path string, and assign it to path
    path_end = coords;
    const len = getLen(path_start, path_end);
    
    const wave_num = Math.floor(len/wave_wid);
    const points = [];
    let isUp = true;
    for(let i = 0; i < wave_num; i++){
        points.push([i*wave_wid, 0]); // start-end points of curves
        if(isUp){
            points.push([i*wave_wid + wave_wid/2, -wave_wid/2]); // control points of curves upwards (may be randomized)
        } else {
            points.push([i*wave_wid + wave_wid/2, wave_wid/2]); // control points of curves downwards (may be randomized)
        }
        isUp = !isUp; // change direction of contron point tilt
    }
    points.push([len, 0]); // last point
    path.setAttribute('d', createPath(points));

    const angle = getAngle(path_start, path_end);

    path.setAttribute('transform', `translate(20,40), rotate(${angle})`); // move to start-point and rotate the path
}

function getLen(first_point, second_point){ // Calculate path length (straightly drawn line)
    return Math.hypot(Math.abs(first_point[1] - second_point[1]), Math.abs(first_point[0] - second_point[0]));
}

function getAngle(first_point, second_point){ // Calculate angle of path rotation
    return Math.atan2(first_point[1] - second_point[1], first_point[0] - second_point[0]) * 180 / Math.PI + 180;
}

function createPath(points){ // Build path string using Quadratic Bezier
    let result = `M 0 0 `;
    for(let i = 1; i < points.length; i+=2){
        result += `Q ${points[i][0]} ${points[i][1]} ${points[i+1][0]} ${points[i+1][1]} `;
    }
    return result;
}


// The circle manipulation part of code

let movable = false;

circle.addEventListener('mousedown', () => {movable = true});
document.body.addEventListener('mouseup', () => {movable = false});

svg.addEventListener('mousemove', (e) => {if(movable) moveCircle(e)});

function moveCircle(event){
    const x = event.clientX;

    circle.setAttribute('cx', x);
    
    drawPath([x,140]);
}
<svg width="600px" height="180px">
    <circle r="10" cx="20" cy="40"></circle>
    <circle r="10" cx="200" cy="140" id="movable"></circle>
    <path d="" stroke="black" fill="none"></path>
</svg>

Здесь после рефакторинга и добавил рандомизацию. Теперь функция drawPath() принимает два элемента SVG типа circle (имеющими атрибуты cx и сy). А также элемент path, атрибуту d которого будет назначена строка нового пути.

Так функция получается более универсальной, при изменении положения одного круга, можно вызывать ее два раза поочередно с соседними кругами, передавать существующие элементы пути, например, по id вроде c1-c2 при передаче кругов с id с1 и c2 соответственно.

При перемещении круга (второго) из-за рандомизации путь "играет", можно разкомментировать строку без добавления рандомных отклонений.

const svg = document.querySelector('svg');
const path = svg.querySelector('path');

const circle_movable = svg.querySelector('#movable');
const circle_static = svg.querySelector('#static');

const wave_wid = 50; // Width of the WAVE

drawPath(circle_static, circle_movable, path); // Drawing path with initial end-point



function drawPath(first_circle, second_circle, path){  // Calculate, build path string, and assign it to path

    const path_start = [+first_circle.getAttribute('cx'), +first_circle.getAttribute('cy')];
    const path_end = [+second_circle.getAttribute('cx'), +second_circle.getAttribute('cy')];

    const len = getLen(path_start, path_end);
    
    const wave_num = Math.floor(len/wave_wid);

    const points = []; // key points of curved path

    let isUp = true;

    for(let i = 0; i < wave_num; i++){
        points.push([i*wave_wid, 0]); // start-end points of curves
        let sign = 1;
        if(isUp){
            sign = -1;
        }

        points.push([i*wave_wid + wave_wid/2, sign * wave_wid/2 + randomPlusMinus(10)]); // control points of curves with randomizing

        // points.push([i*wave_wid + wave_wid/2, sign * wave_wid/2]); // Without randomizing

        isUp = !isUp; // change direction of contron point tilt
    }
    points.push([len, 0]); // last point
    path.setAttribute('d', createPath(points));

    const angle = getAngle(path_start, path_end);

    path.setAttribute('transform', `translate(20,40), rotate(${angle})`); // move to start-point and rotate the path
}

function getLen(first_point, second_point){ // Calculate path length (straightly drawn line)
    return Math.hypot(first_point[1] - second_point[1], first_point[0] - second_point[0]);
}

function getAngle(first_point, second_point){ // Calculate angle of path rotation
    return Math.atan2(second_point[1] - first_point[1], second_point[0] - first_point[0]) * 180 / Math.PI;
}

function randomPlusMinus(range){
    return Math.floor(Math.random() * ( range * 2 + 1) ) - range;
}

function createPath(points){ // Build path string using Quadratic Bezier
    let result = `M 0 0 `;
    for(let i = 1; i < points.length; i+=2){
        result += `Q ${points[i][0]} ${points[i][1]} ${points[i+1][0]} ${points[i+1][1]} `;
    }
    return result;
}


// The circle manipulation part of code

let movable = false;

circle_movable.addEventListener('mousedown', () => {movable = true});
document.addEventListener('mouseup', () => {movable = false});
document.addEventListener('mousemove', (e) => {if(movable) moveCircle(e)});

function moveCircle(event){
    const x = event.clientX;

    circle_movable.setAttribute('cx', x);

    drawPath(circle_static, circle_movable, path);
}
<svg width="600px" height="180px">
    <circle r="10" cx="20" cy="40" id="static"></circle>
    <circle r="10" cx="200" cy="140" id="movable"></circle>
    <path d="" stroke="black" fill="none"></path>
</svg>

→ Ссылка