javascriptjquerycssjquery-uichart.js

how to adjust draggableElement to have correct x and y position


I have implemented chart js to plot a two curve indicated by red and blue line. I want to add a slider (black vertical line) so that it remains always within two curves and user can move it left and right of the plot. when users slides it left or right, it would adjust the height itself. So taking the x and y value of lower plot and setting height by subtracting y1 of first plot and y2 of second plot, it behaves as expected. Here is my code,

    // Initialize the draggable line
    $(function() {
      $("#draggable").draggable({
        axis: "x",
        containment: "#myChart",
        drag: function(event, ui) {
          const canvas = document.getElementById('myChart');
          const ctx = canvas.getContext('2d');
          const rect = canvas.getBoundingClientRect();
          const chartLeft = rect.left;
          
          const xPos = ui.position.left; // Position of the draggable line
          const xValue = myChart.scales.x.getValueForPixel(xPos); // X value on the chart

          // Find the nearest points on the datasets
          const pYValue = getYValueAtX(xValue, myChart.data.datasets[0].data);
          const sYValue = getYValueAtX(xValue, myChart.data.datasets[1].data);

          const difference = pYValue && sYValue ? (pYValue - sYValue) : null;

          // Update the tooltip with the current x, p, and s values
          const tooltip = document.getElementById('tooltip');
          tooltip.innerHTML = `X: ${xValue.toFixed(2)}<br>P: ${pYValue ? pYValue.toFixed(2) : 'N/A'}<br>S: ${sYValue ? sYValue.toFixed(2) : 'N/A'}<br>Difference: ${difference ? difference.toFixed(2) : 'N/A'}`;
          tooltip.style.display = 'block';
          tooltip.style.left = `${xPos + chartLeft + 10}px`;
          tooltip.style.top = `${rect.top + 10}px`;


    const xPixelPos = myChart.scales.x.getPixelForValue(xValue); // Get pixel for xValue
    const yPixelPos = myChart.scales.y.getPixelForValue(pYValue); // Get pixel for sYValue

    const y1PixelPos = myChart.scales.y.getPixelForValue(sYValue); // Get pixel for sYValue


    const height = Math.abs(yPixelPos - y1PixelPos);

    const blackLine = document.getElementById('draggable');
    blackLine.style.left = `${xPixelPos}px`; // Set the x position of the div
    blackLine.style.top = `${yPixelPos}px`;
    blackLine.style.height = `${height}px`; 

    console.log("xpixel:", xPixelPos, "ypixel:", yPixelPos, "y1pixel:", y1PixelPos, "height:", height);


draggableElement.style.height = `${newHeight}px`; // Set height



        }
      });
    });

    // Helper function to find Y value for a given X in the dataset
    function getYValueAtX(x, data) {
      // Find the nearest point in the data for the given x
      const point = data.find(p => p.x >= x);
      return point ? point.y : null;
    }


function interpolateData(data) {
  // Create arrays to store the new interpolated p and s values
  let interpolatedData = [];
  
  for (let i = 0; i < data.length; i++) {
    const currentPoint = data[i];
    const nextPoint = data[i + 1];

    // Check if "p" or "s" is missing and interpolate if necessary
    if (currentPoint.p === "" && nextPoint) {
      // Linear interpolation for 'p'
      const prevPoint = data[i - 1];
      if (prevPoint && nextPoint.p !== "") {
        currentPoint.p = prevPoint.p + ((nextPoint.x - prevPoint.x) * (nextPoint.p - prevPoint.p)) / (nextPoint.x - prevPoint.x);
      }
    }

    if (currentPoint.s === "" && nextPoint) {
      // Linear interpolation for 's'
      const prevPoint = data[i - 1];
      if (prevPoint && nextPoint.s !== "") {
        currentPoint.s = prevPoint.s + ((nextPoint.x - prevPoint.x) * (nextPoint.s - prevPoint.s)) / (nextPoint.x - prevPoint.x);
      }
    }

    // Push the currentPoint to the interpolatedData
    interpolatedData.push(currentPoint);
  }

  return interpolatedData;
}


    // AJAX function to fetch JSON data
    function fetchJSONFile(filePath, callback) {
      var xhr = new XMLHttpRequest();
      xhr.open('GET', filePath, true);
      xhr.responseType = 'json';
      xhr.onload = function() {
        if (xhr.status === 200) {
      const interpolatedData = interpolateData(xhr.response);
      callback(interpolatedData);
        } else {
          console.error('Failed to load JSON file.');
        }
      };
      xhr.send();
    }

    // Callback to process the data and plot the chart
    function plotChart(jsonData) {
      const pData = jsonData
        .filter(item => item.p !== "")
        .map(item => ({
          x: item.x,
          y: item.p
        }));
      const sData = jsonData
        .filter(item => item.s !== "")
        .map(item => ({
          x: item.x,
          y: item.s
        }));

      // Chart.js configuration
      const ctx = document.getElementById('myChart').getContext('2d');
      myChart = new Chart(ctx, {
        type: 'line',
        data: {
          datasets: [
            {
              label: 'p Values',
              data: pData,
              borderColor: 'blue',
              fill: false,
              tension: 0.1,
              pointRadius: 0,
              showLine: true
            },
            {
              label: 's Values',
              data: sData,
              borderColor: 'red',
              fill: false,
              tension: 0.1,
              pointRadius: 0,
              showLine: true
            }
          ]
        },
        options: {
          scales: {
            x: {
              type: 'linear',
              position: 'bottom',
              title: {
                display: true,
                text: 'X Axis'
              }
            },
            y: {
              title: {
                display: true,
                text: 'Y Axis'
              }
            }
          }
        }
      });
    }

    // Fetch and plot the chart using AJAX
    fetchJSONFile('https://www.sagarrawal.com.np/csvjson.json', plotChart);
    #chart-container {
      width: 50%;
      height: 90%;
      position: relative;
    }

    canvas {
      background-color: white;
    }

    /* Draggable vertical line */
    #draggable {
      position: absolute;
      width: 2px;
      height: 100%;
      background-color: black;
      z-index: 10;
      cursor: pointer;
    }

    /* Tooltip to show values */
    #tooltip {
      position: absolute;
      background-color: rgba(0, 0, 0, 0.75);
      color: white;
      padding: 5px;
      border-radius: 3px;
      font-size: 12px;
      display: none;
      z-index: 20;
    }
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

<div id="chart-container">
    <div id="draggable"></div>
    <canvas id="myChart"></canvas>
    <div id="tooltip"></div> <!-- Tooltip to display values -->
  </div>
It runs and achives its goals but with error cause by this line 'draggableElement.style.height = ${newHeight}px;' , which is understandable as it has not been defined anywhere in the code, so when i remove the line, the black line slider then appears on top of the plot outside the two plots. But Only keeping it , the plot behaves as expected and when dragged black line appears within the two plot. so though i get results the way i want but i'm not able to understand why removing the above line , my chart don't work as expected.


Solution

  • At first, you could replace that line causing error with something more meaningful like throw 'myError'. In any case, what happens is that an error that occurs at that point prevents the normal completion of the jquery-ui 'drag' event handler.

    Debugging your code into jquery-ui shows that the user handler (your drag function) is called at this line, draggable.js#L268:

    if ( this._trigger( "drag", event, ui ) === false ) {.....
    

    If an error happens there, it doesn't get to execute these lines, draggable.js#L275:

    this.helper[ 0 ].style.left = this.position.left + "px";
    this.helper[ 0 ].style.top = this.position.top + "px";
    

    the second line being the one that repositions your draggable element on top.

    And that provides the clean solution - replace the error-producing line(s) with:

    ui.position.top = yPixelPos;
    

    You could actually replace all the four lines involving const blackLine = document.getElementById('draggable') with:

    ui.position.top = yPixelPos;
    ui.position.left = xPixelPos;
    ui.helper[0].style.height = `${height}px`;