How to make points in chartjs draggable? - javascript

Question
I'm looking to make 3 points on a graph draggable between 0 and 100. I'm looking to do this in React with ChartJS via react-chartjs-2.
Here is a fiddle with everything setup (js also below).
Extra Info
The fiddle uses some random version of react-chartjs-2 because I couldn't figure out how to import. On my local setup I use import { Line } from 'react-chartjs-2'; but I'm not sure how to do that in jsfiddle. Webdev is super new to me.
What have I tried so far?
I tried using a few chartjs plugins (like this one) that claimed
to do draggable points, but I couldn't figure out how to get them
working in React. This might be the easiest solutin.
I tried adding my own event listeners for mouseDown/Up/Enter/Leave,
but I was unable to attach these to the actual points. If I just have
access to <Line /> is there a way in React for me to attach to the
points themselves? If I could get the coordinates and have these
events, I could kludge together a solution.
I tried searching on the chartjs and react-chartjs-2 docs, but I
couldn't find anything that would let me drag, or attach the right
listeners.
Code
var Line = reactChartjs2.Line;
const options = {
tooltips: {enabled: true},
scales: {
xAxes: [{
gridLines: {display: false, color: 'grey',},
ticks: {fontColor: '#3C3C3C', fontSize: 14,},
}],
yAxes: [{
scaleLabel: {display: true, labelString: 'Color Strength', fontSize: 14,},
ticks: {
display: true,
min: -5,
max: 100,
scaleSteps: 50,
scaleStartValue: -50,
maxTicksLimit: 4,
fontColor: '#9B9B9B',
padding: 30,
callback: point => (point < 0 ? '' : point),
},
gridLines: {
display: false,
offsetGridLines: true,
color: '3C3C3C',
tickMarkLength: 4,
},
}],
},
};
class DraggableGraph extends React.Component {
state = {
dataSet: [0, 0, 0],
labels: ['red', 'green', 'blue'],
};
render() {
const data = {
labels: this.state.labels,
datasets: [{
data: this.state.dataSet,
borderColor: '9B9B9B',
borderWidth: 1,
pointRadius: 10,
pointHoverRadius: 10,
pointBackgroundColor: '#609ACF',
pointBorderWidth: 0,
spanGaps: false,
}],
};
return (
<div>
<Line
data={data}
options={options}
legend={{display: false}}
plugins={{}}
/>
</div>
);
}
}
ReactDOM.render(<DraggableGraph />, document.getElementById('app'));

You can do this by using chartjs-plugin-dragData. You have to set dragData: true within options object to use this plugin in chart js. Here I have provided updated code.
var Line = reactChartjs2.Line;
const options = {
tooltips: {enabled: true},
scales: {
xAxes: [{
gridLines: {display: false, color: 'grey',},
ticks: {fontColor: '#3C3C3C', fontSize: 14,},
}],
yAxes: [{
scaleLabel: {display: true, labelString: 'Color Strength', fontSize: 14,},
ticks: {
display: true,
min: -5,
max: 100,
scaleSteps: 50,
scaleStartValue: -50,
maxTicksLimit: 4,
fontColor: '#9B9B9B',
padding: 30,
callback: point => (point < 0 ? '' : point),
},
gridLines: {
display: false,
offsetGridLines: true,
color: '3C3C3C',
tickMarkLength: 4,
},
}],
},
legend:{
display: false
},
dragData: true,
onDragStart: function (e) {
console.log(e)
},
onDrag: function (e, datasetIndex, index, value) {
console.log(datasetIndex, index, value)
},
onDragEnd: function (e, datasetIndex, index, value) {
console.log(datasetIndex, index, value)
}
};
class DraggableGraph extends React.Component {
state = {
dataSet: [0, 0, 0],
labels: ['red', 'green', 'blue'],
};
render() {
const data = {
labels: this.state.labels,
datasets: [{
data: this.state.dataSet,
borderColor: '9B9B9B',
borderWidth: 1,
pointRadius: 10,
pointHoverRadius: 10,
pointBackgroundColor: '#609ACF',
pointBorderWidth: 0,
spanGaps: false,
}],
};
return (
<div>
<Line
data={data}
options={options}
/>
</div>
);
}
}
ReactDOM.render(<DraggableGraph />, document.getElementById('app'));
https://jsfiddle.net/4mdenzjx/

Related

How can I display different values on xAxes than on tooltip Chart.js V3

I am not really familiar with the Chart.js V3.7.0(This is the version I am currently using) So I tried in different ways to display different values on xAxes and different values on the tooltip.
What's important here to note, is that I am using Php to echo the values inside the dataset.
For example (the data variable I use):
const data = {
datasets: [{
backgroundColor: 'black',
borderColor: '#2d84b4',
borderWidth: 2,
fill: false,
pointBorderColor: 'rgba(0, 0, 0, 0)',
pointBackgroundColor: 'rgba(0, 0, 0, 0)',
pointHoverBackgroundColor: 'rgba(0,0,0,0)',
pointHoverBackgroundWidth: 5,
pointHoverBorderColor: 'rgba(45, 132, 180, 0.5)',
pointHoverBorderWidth: 4,
pointStyle: 'circle',
pointHoverRadius: 5.5,
data: [ <? echo $values; ?> ],
}],
labels: [ <? echo $labels; ?> ],
};
So my php echoes the $values variable with values in this form:
{0.504, 0.675, 0.305, etc...}
Same applies for the $labels variable but because labels are data related to time I display them as strings:
example: {'Dec 28', '01:00', '02:00', etc...}
Here is the config variable which includes the data variable inside of it, and then the config variable is being called inside the initialization of the chart.
Config:
const config = {
type: 'line',
data: data,
options: {
onHover: (event, activeElements) => {
if (activeElements?.length > 0) {
event.native.target.style.cursor = 'pointer';
} else {
event.native.target.style.cursor = 'auto';
}
},
animation: {
duration: 0
},
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
stacked : true,
ticks: {
autoSkip: true,
maxTicksLimit: <? echo $ticks_val; ?>,
maxRotation: 0
},
grid: {
display: true,
drawBorder: false,
drawOnChartArea: false,
drawTicks: true,
tickLength: 3,
},
},
yAxes: {
min: '0.0',
ticks: {
autoSkip: true,
maxTicksLimit: 6,
},
grid: {
drawBorder: false,
drawTicks: false
}
},
},
elements: {
point: {
radius: 5
}
},
locale: 'en-EN',
plugins: {
tooltip: {
displayColors: false,
backgroundColor: 'rgba(45,132,180,0.8)',
bodyFontColor: 'rgb(255,255,255)',
callbacks: {
title: () => {
return
},
label: (ttItem) => ( `${ttItem.parsed.y} ppm` ),
afterBody: (ttItems) => (ttItems[0].label)
}
},
legend: {
display: false
}
}
}
};
Goal:
I want to echo a different php variable inside the tooltip, but I dont want to change what I am currently displaying on xAxes. Problem is that If I just echo another php variable in the tooltip, I end up with the wrong Date date being displayed simply because the index values are not connected with the chart and so on with the tooltip popup.
Notes:
1): Chart.js version: 3.7.0
Picture of the chart:
This is achievable. One solution would be to reformat the values stored at $labels so that they are consistent. For example, store each value in the format that you want to render, i.e. {'Dec 28 00:00', 'Dec 28 01:00', 'Dec 02:00'}. You can then use a callback to create a custom tick format.
options: {
scales: {
x: {
stacked : true,
ticks: {
autoSkip: true,
maxTicksLimit: 12,
maxRotation: 0,
callback: function(value) {
let labelValue = this.getLabelForValue(value);
if(labelValue.slice(-5, -3) == '00') {
return labelValue.slice(0, -6);
} else {
return labelValue.slice(-5);
}
}
}
}
}
}
JSFiddle showing this working

chartjs-plugin-streaming: onRefresh() callback isn't working

Hello I'm new in javascript world; I'm trying to display random numbers in real time with Chart.js and chartjs-plugin-streaming starting from a tutorial code which I started to modify for my scope.
const Chart= require ("chart.js");
const chartStreaming = require('chartjs-plugin-streaming');
const boxWhiteColor = "#2043CE";
const pressure_graph = document.getElementById('pressureGraph').getContext('2d');
Chart.register(chartStreaming);
let pressure_graph_config = {
type: 'line',
data: {
datasets: [{
label: 'Pressure',
data: [],
borderColor: boxWhiteColor,
fill: false
}]
},
options: {
title: {
display: true,
text: 'PRESSURE',
fontColor: boxWhiteColor,
fontSize: 30
},
scales: {
xAxes: [{
gridLines: {
display: true,
drawBorder: true,
color: boxWhiteColor,
lineWidth: 5},
ticks: {
fontColor: boxWhiteColor,
display: false
},
type: 'realtime',
realtime: {
duration: 10000,
refresh: 100, // plot at 10 Hz
delay:200,
pause: false, // chart is not paused
ttl: undefined, // data will be automatically deleted as it disappears off the chart
frameRate: 100, // data points are drawn 100 times every second
onRefresh: chart => {
console.log(trocarP.data.datasets.data);
chart.config.data.datasets.forEach(function(dataset) {
chart.data.dataset[0].data.push({
x: Date.now(),
y: Math.random() //pressure16bits[pressure16bits.length-1]
});
});
}
}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: '[mmHg]', fontColor: boxWhiteColor, fontSize: 30, fontStyle: 900,
},
ticks: {
fontColor: boxWhiteColor,
fontSize: 25,
fontStyle: 700,
maxTicksLimit: 5,
min: 0,
max: 40,
},
gridLines: {display: true, drawBorder: true, color: boxWhiteColor, lineWidth: 5},
}]
},
elements: {
point: {radius: 0},
},
legend: { display: false}
}
}
trocarP = new Chart (pressure_graph, pressure_graph_config);
The problem is the graph is being created on a canvas via the .html file, but then it doesn't display anything; trying to debug the code I found out that the console.log() I placed inside the onRefresh callback is not printing anything, so I'm assuming the callback isn't working. Any clue on what's happening?
Screenshot of the graph
Edit: I noticed that also Y axis label has not been displayed. I don't get what's wrong with this code.
You are using v2 syntax while using v3 of the lib this wont work as there are several breaking changes, see migration guide for all of them.
For example, way of defining scales has changed, you need an adapter for dates and more.
working basic v3 example:
var options = {
type: 'line',
data: {
datasets: [{
label: '# of Votes',
data: [],
borderColor: 'pink'
}]
},
options: {
scales: {
x: {
type: 'realtime',
realtime: {
duration: 20000,
refresh: 100,
delay: 200,
onRefresh: chart => {
const now = Date.now();
chart.data.datasets.forEach(dataset => {
dataset.data.push({
x: now,
y: Math.random()
});
});
}
}
}
}
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-streaming/2.0.0/chartjs-plugin-streaming.js"></script>
</body>

ChartJS passing data dynamically, then passing data into variables

I am trying to figure out how to take the 3 hardcoded variables [index, fieldName, aggType] and pass data in to them from a json file dynamically, and then pass that variable into the (inData) function that I created, and then finally pass those into the charts that I have created. I am not exactly sure how to go about referencing the data and getting it into the variable. I placed my code below. Please, let me know if I can clarify this any, it is a bit confusing. Index name is the ES index, fieldname is the field to aggregate against and aggType is the type of aggregation like count, sum etc...There was a suggested answer to this question, using the forEach option, however, this was not a good fit for my situation, as it would be necessary to pass multiple lines in some queries, or singular lines in others. The forEach option would force us to deal with each option in an individual way. We wanted to be able to pass the query as a whole. Thank you for all your help. The anser I came up with is below.
mainchart.js
$('#chartType').change(function(index, val) {
buildChart($(this).val())
})
function line() {
let labels = []
let data = []
let index = 'issflightplan';
let fieldName = 'VehicleType';
let aggtype = 'count';
$.getJSON("http://localhost:3000/data/issflightplan/VehicleType/count/", function (inData) {
aggregationName = index + "|" + fieldName + "|" + aggtype
for (keyNo in inData.aggregations[aggregationName].buckets) {
labels.push(inData.aggregations[aggregationName].buckets[keyNo].key)
data.push(inData.aggregations[aggregationName].buckets[keyNo].doc_count)
}
// });
console.log(data)
var ctx = document.getElementById('myChart').getContext('2d');
var gradient = ctx.createLinearGradient(450, 0, 0, 0);
gradient.addColorStop(0, 'rgb(0,99,132)');
gradient.addColorStop(1, 'rgb(255,99,132)')
var gradient2 = ctx.createLinearGradient(0, 0, 0, 450);
gradient2.addColorStop(0, 'rgb(0,50,600)');
gradient2.addColorStop(1, 'rgb(150,0,100)')
function drillDownChart(click, mydata) {
if (mydata[0]) {
var x = mydata[0]['_index'];
window.location.href = 'https://chart-js.com/'
}
};
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'My New Line Chart',
data: data,
backgroundColor: gradient,
borderColor: 'blue',
borderWidth: 1,
borderDash: [10, 5, 10, 2],
borderDashOffset: 10,
borderCapStyle: 'round',
borderJoinStyle: 'bevel',
cubicInterpolationMode: '',
fill: true,
lineTension: 0.2,
pointBackgroundColor: ['red', 'yellow', 'purple', 'orange', 'green', 'blue', 'pink'],
pointBorderColor: ['red', 'yellow', 'purple', 'orange', 'green', 'blue', 'pink'],
pointBorderWidth: 3,
pointRadius: 2,
pointStyle: 'circle',
pointHitRadius: 20,
pointHoverBackgroundColor: 'purple',
pointHoverBorderColor: 'pink',
pointHoverBorderWidth: 5,
pointHoverRadius: 10,
showLine: true,
spanGaps: true,
steppedLine: false
}, {
label: ['My New Line Chart 2'],
data: data.datapoints2,
// backgroundColor: gradient2,
borderColor: gradient2,
fill: false
}]
},
// Configuration options go here
options: {
onClick: drillDownChart,
// onClick: updateChart,
legendCallback: function (line) {
var text = [];
text.push('<ul class="legendClass">');
for (var i = 0; i < chart.data.datasets.length; i++) {
text.push('<li class = style = "background: ' + chart.data.datasets[i].backgroundColor + ' ">');
text.push(chart.data.datasets.label[i]);
text.push('</li>')
}
text.push('</ul>');
return text.join("");
},
ticks: {
autoSkip: true
},
responsive: true,
maintainAspectRatio: false,
legend: {
display: true,
fontSize: 16,
responsive: true,
},
plugins: {
title: {
display: true,
// text: obj.title,
position: "top",
fontSize: 16,
fontFamily: "New Times Roman",
fontColor: 'blue',
fontStyle: 'bold',
padding: 10,
lineHeight: 1.2,
},
legend: {
display: true,
//need to be able to pass the options into you from the data
position: "bottom",
align: 'center',
fullWidth: true,
// onClick: alertBox,
// onHover: changeFontColor,
labels: {
boxWidth: 20,
fontSize: 10,
fontStyle: 'bold',
fontColor: 'black',
fontFamily: 'Times New Roman',
padding: 10,
usePointStyle: 'circle',
},
annotation: {
annotations: [{
type: 'line',
mode: 'vertical',
value: '18B',
borderColor: 'red',
borderWidth: 2
}],
tooltips: {
enabled: true,
mode: 'index',
intersect: false,
position: 'nearest',
backgroundColor: 'rgb(0, 0, 132)',
titleFontFamily: 'New Times Roman',
titleFontSize: 16,
titleFontStyle: 'normal',
titleFontColor: '#yellow',
titleSpacing: 10,
titleMarginbottom: 15,
bodyFontFamily: 'New Times roman',
bodyFontSize: 15,
bodyFontStyle: 'normal',
bodyFontColor: 'rgb(0,15,132)',
bodySpacing: 3,
xPadding: 10,
yPadding: 10,
caretPadding: 5,
cornerRadius: 20,
// multiKeyBackground: '()',
displayColors: true,
callbacks: {
title: function (tooltipItems, data) {
// Pick first xLabel for now
var title = chartType;
var labels = data.labels;
var labelCount = labels ? labels.length : 0;
if (tooltipItems.length > 0) {
var item = tooltipItems[0];
if (item.xLabel) {
title = labels[item.index];
} else if (labelCount > 0 && item.index < labelCount) {
title = labels[item.index];
}
}
return title;
},
events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"],
onClick: function (event, arry) {
getcurrentfilter(event, arry)
}
}
}
}
}
}
}
});
console.log(data)
})
}
mydata.json
{
"hits": [...],
"aggregations": {
"issflightplan|VehicleType|count":{
"meta": {...},
"buckets": [
{
"key": "Progress",
"doc_count":77
},
"issflightplan|CrewSize|count": {
"meta": {...},
"buckets": [
{
"key": "",
"doc_count": 32
},
I wanted to post the answer that I came up, and it is working well, and gives the ability to add some different options to the function later on.
function line() {
let labels = []
let data = []
$.getJSON("http://localhost:3000/data/issflightplan/VehicleType/count/", function (inData) {
let aggregationName = []
for (aggName in inData.aggregations) {
console.log(aggName)
for( bucket in inData.aggregations[aggName].buckets) {
labels.push(inData.aggregations[aggName].buckets[bucket].key)
data.push(inData.aggregations[aggName].buckets[bucket].doc_count)
}
}

issue in creating stacked horizontal bar-chart in chartJs

I need to create a stacked horizontal bar chart and for the below code the chart is coming as horizontal but it is not stacked. I have created a seperate component for chart and using it in another component I gone through few stacked overflow articles but didn't help. I not able to find out the issue. any help is very appreciated.
horizontal-chart.component.ts
export class HorizontalBarchartComponent implements OnInit {
dataCtx: any;
bar_chart: any;
#ViewChild('horizontalbarChart') horizontalbarChartRef: ElementRef;
#Input() data = [];
#Input() yLabel: any;
#Input() xLabel: any;
#Input() nameObj?: any;
#Input() stacked:boolean;
#Input() hoverData:Array<any>;
colors: Array<string> = ["rgba(98, 228, 98, 1)", "rgba(248, 227, 117, 1)", "rgba(250, 99,131, 1)"]
constructor() { }
ngAfterViewInit() {
this.renderChart();
}
ngOnChanges() {
if (this.horizontalbarChartRef){
this.renderChart();
}
}
renderChart() {
this.dataCtx = this.horizontalbarChartRef.nativeElement.getContext('2d');
this.bar_chart = new Chart(this.dataCtx, {
type: 'horizontalBar',
data: {
labels: this.nameObj.map(obj => obj.name),
datasets:this.setData()
},
options: {
legend: {
display: true,
position: 'bottom',
labels: {
padding: 20,
fontStyle: 'bold',
fontSize: 12,
}
},
scales: {
xAxes: [{
stacked: true,
position:'top',
display: true,
scaleLabel: {
display: true,
fontStyle: 'bold',
labelString: this.yLabel
},
ticks: {
autoSkip: false,
beginAtZero: true,
max:10
},
gridLines: {
display:false,
}
}],
yAxes: [{
stacked: true,
categoryPercentage: 1,
maxBarThickness: 50,
display: true,
scaleLabel: {
display: true,
fontStyle: 'bold',
labelString: this.xLabel
},
ticks: {
min: 0,
},
gridLines: {
display:false
}
}]
},
responsive: true,
}
});
}
setData(){
let fixed_options ={
borderColor: 'transparent',
borderWidth: 2,
pointBorderColor: 'transparent',
pointBackgroundColor: 'transparent',
pointHoverBackgroundColor: "#fff",
pointHighlightFill: "#fff",
pointRadius: 3,
pointHitRadius: 20,
pointBorderWidth: 1,
}
if(this.stacked){
let subData = [];
this.data.filter((d,index) => { subData.push({"data":d, "backgroundColor": this.colors[index],"label": this.hoverData[index], fixed_options }) });
return subData;
}
else{
return [{"data": this.data,"backgroundColor":'rgba(98, 228, 98, 1)',"label":`Upload/Sketch's Per Factor Scores`}]
}
}
}
another.component.html
<horizontal-barchart class="col-md-10" [data]="horizontalBar.data" yLabel="Percentage"
[xLabel]="'student names'" [nameObj]="quizOrTestAnalysis" [stacked]="true"
[hoverData]="['Proficient Answers', 'Elimination Answers', 'Random Answers']">
</horizontal-barchart>
another.component.ts
horizontalBar = {
data:[[10],[20],[30]],
}
The output of the provided code
I had set ticks:{max:10} on x:axis which restricting the chart from showing further data

Chart.js: Minimum value for x-axis at horizontal stacked bar chart

I am using Chart.js to generate a horizontal stacked bar chart. The chart currently looks like this:
This chart shows the user after how many years they should restorate a specific component of a house. I am trying to change this to in which year the user should do the restoration. Adding the current year to the values results to the following:
This is pretty much what I need if I could set the starting value of the x-axis to the current year. I tried to do this setting the minimum value like this:
options: {
scales: {
xAxes: [{
ticks: {
min: 2017
},
...
Unfortunatly results in not displaying the datasets at all like this:
I tried all combinations with adding the current year and setting the minimum values but nothing results in a useful chart.
In the following you can see my current source code:
var mainChart_ctx = document.getElementById("main_chart").getContext("2d");
var mainChart_config = {
type: 'horizontalBar',
data: {
labels: ['Kellerdecke', 'Fenster', 'Außenwand', 'Erdberührter Boden', 'Dach'],
datasets: [{
label: 'Beginn ab heute',
backgroundColor: 'transparent',
data: [4, 21, 25, 25, 25],
borderColor: '#666',
borderWidth: 1
},
{
label: 'Sanierungsdauer',
backgroundColor: '#ffcc00',
data: [2, 5, 5, 5, 5],
borderColor: '#666',
borderWidth: 1
},
{
label: 'Mittlere Restlebensdauer',
backgroundColor: 'orange',
data: [39, 0, 38, 51, 37],
borderColor: '#666',
borderWidth: 1
},
{
label: 'Maximale Restlebensdauer',
backgroundColor: 'orangered',
data: [20, 0, 0, 0, 0],
borderColor: '#666',
borderWidth: 1
}
]
},
options: {
tooltips: {
enabled: true
},
legend: {
display: false
},
title: {
display: true,
text: 'Sanierungsfahrplan',
fontSize: 24
},
scales: {
xAxes: [{
ticks: {
min: 0 /* Todo: change to current year? */
},
stacked: true,
scaleLabel: {
display: true,
labelString: 'Jahre',
fontSize: 16
}
}],
yAxes: [{
ticks: {
stepSize: 10
},
stacked: false,
scaleLabel: {
display: true,
labelString: 'Bauteil',
fontSize: 16
},
}]
}
}
};
mainChart = new Chart(mainChart_ctx, mainChart_config)
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.min.js"></script>
<canvas id="main_chart"></canvas>
I was able to get a suitable result using a callback like this:
xAxes: [{
ticks: {
min: 0,
callback: function (value, index, values) {
return value + 2017;
}
},
...
]
I found that suggestedMin works well:
options: {
scales: {
xAxes: [{
display: true,
ticks: {
suggestedMin: 2017
}
}]
}
}

Categories