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.