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>
${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.
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`;