Is it possible to create context menu with chart.js? - chart.js

I wanted to create a context menu on my line graph. When the user hovers across a point, they can right click and will see a context menu so that they can trigger more action.
Is this possible with chart.js?
Thanks,
Derek

AFAIK there is no native support for context menu from Chart.js, but you can create it using HTML and handle it.
This is an example:
var timestamp = [],
speed = [10, 100, 20, 30, 40, 100, 40, 60];
for (var k = speed.length; k--; k>0) {
timestamp.push(new Date().getTime()-60*60*1000*k);
}
var canvas = document.getElementById('chart');
var BB = canvas.getBoundingClientRect(),
offsetX = BB.left,
offsetY = BB.top;
var ctx = canvas.getContext("2d");
var data = {
labels: timestamp,
datasets: [{
data: speed,
label: "speed",
backgroundColor: ['rgba(0, 9, 132, 0.2)'],
borderColor: ['rgba(0, 0, 192, 1)'],
borderWidth: 1,
}
]
};
var options = {
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
}
}],
xAxes: [{
type: 'time',
}]
}
};
var chart = new Chart(ctx, {
type: 'line',
data: data,
options: options
});
var $menu = $('#contextMenu');
canvas.addEventListener('contextmenu', handleContextMenu, false);
canvas.addEventListener('mousedown', handleMouseDown, false);
function handleContextMenu(e){
e.preventDefault();
e.stopPropagation();
var x = parseInt(e.clientX-offsetX);
var y = parseInt(e.clientY-offsetY);
$menu.css({left:x,top:y});
$menu.show();
return(false);
}
function handleMouseDown(e){
$menu.hide();
}
menu = function(n){
console.log("select menu "+n);
$menu.hide();
}
#chart {
width: 100%;
height: 100%;
}
#contextMenu{
position:absolute;
border:1px solid red;
background:white;
list-style:none;
padding:3px;
}
.menu-item:hover {
background: #dddddd;
text-decoration: underline;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id=canvasContainer>
<canvas id="chart"></canvas>
<ul id="contextMenu" style="display:none;">
<li class="menu-item" onclick="menu(1)">Menu 1</li>
<li class="menu-item" onclick="menu(2)">Menu 2</li>
</ul>
</div>

For anyone looking for this, #beaver's answer does not have access to the clicked item (data point), you can save the state in onHover and use it in contextmenu.
options: {
events: ['click', 'mousemove'],
onHover: function(e, fields){
const lx = e.layerX;
const bars = fields.filter(({_view: {x,width}}) => (x - width/2) <= lx && lx <= (x + width/2));
const data = bars.map(({_index, _datasetIndex}) => this.data.datasets[_datasetIndex].data[_index]);
this.hoveredItem = {bars, data, fields};
},
},
plugins: [
{
afterInit: (chart) =>
{
var menu = document.getElementById("contextMenu");
chart.canvas.addEventListener('contextmenu', handleContextMenu, false);
chart.canvas.addEventListener('mousedown', handleMouseDown, false);
function handleContextMenu(e){
e.preventDefault();
e.stopPropagation();
menu.style.left = e.clientX + "px";
menu.style.top = e.clientY + "px";
menu.style.display = "block";
console.log(chart.hoveredItem);
return(false);
}
function handleMouseDown(e){
menu.style.display = "none";
}
}
}
],
https://codepen.io/elizzk/pen/xxZjQvJ

Related

Why labels on x-asis does not shows well?

Here is my div with canvas for line chart:
<div
x-data="{
lineChartLabels: #entangle('lineChartLabels'),
lineChartData: #entangle('lineChartData'),
init() {
const labels = `${this.lineChartLabels}`;
const data = {
labels: labels,
datasets: [{
backgroundColor: 'lightblue',
data: this.lineChartData,
fill: true,
label: `{{__('site.report.values')}}`,
}]
};
const config = {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
ticks: {
callback: function (value) {
const date = new Date(Number(value) * 1000);
return date.toISOString().substring(11,23);
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function (context) {
const date = new Date(Number(context.formattedValue) * 1000);
return date.toISOString().substring(11,23);
}
}
}
}
}
};
const myChart = new Chart(
this.$refs.canvas,
config
);
Livewire.on('updateTheChart', () => {
myChart.destroy();
myChart.data.datasets[0].data = this.lineChartData;
myChart.data.labels = this.lineChartLabels;
myChart.update();
})
}
}">
<canvas id="myChart" x-ref="canvas" style="height: 33vh;width: 50vw;"></canvas>
</div>
And here is an example of values(which represent seconds):
[49.66,47.26,46.88,49.81]
and for x axis which represents dates:
["04-Feb-17","06-May-17","28-Oct-17","20-Dec-17"]
But when it renders it shows like this:
Can somebody tell me why chartjs does not show x-asis in the proper way and how to fix it?
The problem is this line const labels = `${this.lineChartLabels}`; you are creating a script, what you need ist the array, so with other words: const labels = this.lineChartLabels; remove the string interpolation.
Disclaimer: I'm guessing abit, because I'm not acquainted with laravel-livewire and alpine.js, but that line of code looks like the culprit to me.
Here the two different Version, side by side:
const labels = ["04-Feb-17","06-May-17","28-Oct-17","20-Dec-17"];
const data = {
labels: labels,
datasets: [{
backgroundColor: 'lightblue',
data: [49.66,47.26,46.88,49.81],
fill: true,
label: `Correct Version`,
}]
};
const config = {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
ticks: {
callback: function (value) {
const date = new Date(Number(value) * 1000);
return date.toISOString().substring(11,23);
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function (context) {
const date = new Date(Number(context.formattedValue) * 1000);
return date.toISOString().substring(11,23);
}
}
}
}
}
};
new Chart(
document.getElementById('chart'),
config
);
config.data.labels = `${["04-Feb-17","06-May-17","28-Oct-17","20-Dec-17"]}`;
config.data.datasets[0].label = `Incorrect Version`;
let chart2 = new Chart(
document.getElementById('chart2'),
config
);
<script src="//cdn.jsdelivr.net/npm/chart.js"></script>
<div class="chart" style="float:left;height:184px; width:300px;font-family: Arial">
<canvas id="chart" ></canvas>
</div>
<div class="chart" style="float:left;height:184px; width:300px;font-family: Arial">
<canvas id="chart2" ></canvas>
</div>

Implementing bar chart in .NET -6 Razor Pages using Chart.js plugin

chart is displaying but vertical bar is not showing. data is coming from handler but it is not able to assign into variable 'data:data.datasets'. Please help me.*
Model Class
ChartVM.cs
*use for definig the properties for chart displaying *
public class ChartVM
{
public List<string> Labels{get;set;}
public List<DataSetRow> Datasets{get;set;}
}
public class DataSetRow
{
public List<int> Data{get;set;}
public string BackgroundColor { get;set;}
public string Color { get; set; }
}
ChartDemo.cs
public JsonResult OnGetBarChart()
{
var vm = new ChartVM();
vm.Labels = new List<string>();
vm.Datasets = new List<DataSetRow>();
var result = context.CountryPopulations.ToList();
vm.Labels = result.Select(x => x.Name).ToList();
var ds1 = new DataSetRow
{
BackgroundColor = "#f2f2f2",
Color = "#24248f",
Data = result.Select(x => x.Male).ToList()
};
var ds2 = new DataSetRow
{
BackgroundColor = "#f2f2f2",
Color = "#ff0080",
Data = result.Select(x => x.Female).ToList()
};
vm.Datasets.Add(ds1);
vm.Datasets.Add(ds2);
return new JsonResult(vm);
}
ChartDemo.cshtml
HTML code
<div>
<canvas id="myChart" style="max-height:400px; max-width:500px"></canvas>
</div>
javaScript
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/chart.min.js" asp-append-version="true"></script>
<script type="text/javascript">
$(document).ready(function () {
BarChart();
});
function BarChart() {
var XaxisTitle = 'Colour Bar';
var Yaxistitle = 'Rupees';
var legendTitle = 'Months';
const ctx = document.getElementById('myChart');
debugger;
$.ajax({
type:'GET',
url:'?handler=BarChart',
data:{},
success:function(data){
new Chart(ctx, {
type: 'bar',
data: {
labels:data.labels,
datasets: [{
label: legendTitle,
data:data.datasets,
borderWidth: 1,
}]
},
options: {
scales: {
x: {
display: true,
beginAtZero: true,
title: {
display: true,
text: XaxisTitle,
color: '#911',
font: {
family: 'Comic Sans MS',
size: 20,
weight: 'bold',
lineHeight: 1.2,
},
padding: { top: 20, left: 0, right: 0, bottom: 0 }
}
},
y: {
display: true,
title: {
display: true,
text: Yaxistitle,
color: '#191',
font: {
family: 'Times',
size: 20,
style: 'normal',
lineHeight: 1.2
},
padding: { top: 30, left: 0, right: 0, bottom: 0 }
}
}
}
}
});
},
error:function(){
alert('Something went wrong!!');
}
});
}
</script>
see output image vertical bar is not showing
but it is not able to assign into variable 'data:data.datasets'
Error is here:
datasets: [{
label: legendTitle,
data:data.datasets,
borderWidth: 1,
}]
Single dataset in chartjs is complex object with data points and options.
So, in sample provided you are trying to pass to data points property (array of primitive integer values) the bunch of datasets.
Quick fix:
data: {
labels:data.labels,
datasets: [...data.datasets]
}
Changed colors and scrapped data from here.
Displaying TOP-10 countries:
public JsonResult OnGetBarChart()
{
var vm = new ChartVM();
vm.Datasets = new List<DataSetRow>();
var result = context.Populations.OrderByDescending(x => x.TotalPop).Take(10).ToList();
vm.Labels = result.Select(x => x.Name).ToList();
var ds1 = new DataSetRow
{
Label = "Male",
BackgroundColor = "#ffA500",
BorderColor = "#ffA500",
Data = result.Select(x => x.MalePop).ToList()
};
var ds2 = new DataSetRow
{
Label = "Female",
BackgroundColor = "#00B612",
BorderColor = "#00B612",
Data = result.Select(x => x.FemalePop).ToList()
};
vm.Datasets.Add(ds1);
vm.Datasets.Add(ds2);
return new JsonResult(vm);
}

Is it possible to draw a bar and arrow/pointer on the right side of a chart?

I currently have this chart.
I'm attempting to create something like this.
The bar on the side is static, but the section "heights" need to be programmable when the page loads. I attempted encoding in an SVG but I wasn't able to get it to stick to the chart.
I've got no clue how to make the final (current value) node to appear as an arrow pointing to the bar (or alternatively, as just a horizontal bar across the vertical one).
I made a sample codepen to simulate the dynamic chart I currently have.
(Or, per StackOverflow's requirements, the JS code used: )
var randoms = [...Array(11)].map(e=>~~(Math.random()*11));
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: randoms,
datasets: [{
label: 'value',
data: randoms
}]
},
options: {
scales: {
y: {
grace: 10,
}
}
}
});
function populate(){
let temp = ~~(Math.random()*11);
myChart.data.datasets[0].data.shift();
myChart.data.datasets[0].data.push(temp);
myChart.update();
}
setInterval(populate, 10000);
Any general pointers are also appreciated - I'm very new to all of this.
You can write a custom plugin for this, I added padding on the right to give space for the arrow to be drawn. You can play with the multiplyer values to make the arrow bigger/smaller
Example:
var randoms = [...Array(11)].map(e => ~~(Math.random() * 11));
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: randoms,
datasets: [{
label: 'value',
data: randoms,
borderColor: 'red',
backgroundColor: 'red'
}]
},
options: {
layout: {
padding: {
right: 25
}
},
scales: {
y: {
grace: 10
}
}
},
plugins: [{
id: 'arrow',
afterDraw: (chart, args, opts) => {
const {
ctx
} = chart;
chart._metasets.forEach((meta) => {
let point = meta.data[meta.data.length - 1];
ctx.save();
ctx.fillStyle = point.options.backgroundColor;
ctx.moveTo(point.x, (point.y - point.y * 0.035));
ctx.lineTo(point.x, (point.y + point.y * 0.035));
ctx.lineTo((point.x + point.x * 0.025), point.y)
ctx.fill();
ctx.restore();
})
}
}]
});
function populate() {
let temp = ~~(Math.random() * 11);
myChart.data.datasets[0].data.shift();
myChart.data.datasets[0].data.push(temp);
myChart.update();
}
//setInterval(populate, 10000);
<script src="https://npmcdn.com/chart.js#latest/dist/chart.min.js"></script>
<div class="myChartDiv">
<canvas id="myChart" width="600" height="400"></canvas>
</div>

Chart.js: Thousand Separator and Tittle ind the Middle of the Doughnut

I have searched a lot and included a thousand separator in tooltips. But I would like to make it work everywhere there is text. Where I live we use "." to separate thousands and "," for decimal.
I didn't find a simple way to put the title in the middle of the doughnut.
This is what I have:
Chart.defaults.global.defaultFontColor = '#7792b1';
var ctx = document.getElementById('myChart').getContext('2d');
var dataset = [{
label: 'Volume de Operações',
data: [254000.87, 355000.57],
backgroundColor: ['#4bb8df', '#6290df']
}]
var chart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['CALL', 'PUT'],
datasets: dataset
},
options: {
rotation: 1 * Math.PI,
circumference: 1 * Math.PI,
legend: {
display: false
},
cutoutPercentage: 60,
plugins: {
labels: [{
render: 'label',
arc: true,
fontStyle: 'bold',
position: 'outside'
}, {
render: 'percentage',
fontColor: '#ffffff',
precision: 1
}],
},
title: {
display: true,
fontSize: 15,
text: [
dataset.reduce((t, d) => t + d.data.reduce((a, b) => a + b), 0),
'Volume Total'
],
position: 'bottom'
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var dataLabel = data.labels[tooltipItem.index];
var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].toLocaleString();
if (Chart.helpers.isArray(dataLabel)) {
dataLabel = dataLabel.slice();
dataLabel[0] += value;
} else {
dataLabel += value;
}
return dataLabel;
}
}
}
}
});
<canvas id="myChart" style="max-width: 450px"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0"></script>
<script src="https://cdn.jsdelivr.net/gh/emn178/chartjs-plugin-labels/src/chartjs-plugin-labels.js"></script>
In short:
Global thousand separator: (.)
Title in the middle of doughnut
Tooltip without label: Only value
To not show a title in your tooltip you will have to only return the value in your custom label calback. So your callback will become this:
label: function(tooltipItem, data) {
return data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].toLocaleString();
}
There is no build in way to get the title in the middle of the circle, you will have to write a custom plugin for that.
To replace the thousand serperator in the percentage label, you will have to write a custom render. So instead render: 'percentage'. You will get something like this:
// custom render
{
render: function (args) {
// args will be something like:
// { label: 'Label', value: 123, percentage: 50, index: 0, dataset: {...} }
return '$' + args.value;
}
}
You will have to make the logic so the value gets transformed to a percentage still
EDIT custom tooltip so you dont see the color in front.
Chart.defaults.global.defaultFontColor = '#7792b1';
var ctx = document.getElementById('myChart').getContext('2d');
var dataset = [{
label: 'Volume de Operações',
data: [254000.87, 355000.57],
backgroundColor: ['#4bb8df', '#6290df']
}]
var chart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['CALL', 'PUT'],
datasets: dataset
},
options: {
rotation: 1 * Math.PI,
circumference: 1 * Math.PI,
legend: {
display: false
},
cutoutPercentage: 60,
plugins: {
labels: [{
render: 'label',
arc: true,
fontStyle: 'bold',
position: 'outside'
}, {
render: 'percentage',
fontColor: '#ffffff',
precision: 1
}],
},
title: {
display: true,
fontSize: 15,
text: [
dataset.reduce((t, d) => t + d.data.reduce((a, b) => a + b), 0),
'Volume Total'
],
position: 'bottom'
},
tooltips: {
enabled: false,
custom: function(tooltipModel) {
// Tooltip Element
var tooltipEl = document.getElementById('chartjs-tooltip');
// Create element on first render
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.id = 'chartjs-tooltip';
tooltipEl.innerHTML = '<table></table>';
document.body.appendChild(tooltipEl);
}
// Hide if no tooltip
if (tooltipModel.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set caret Position
tooltipEl.classList.remove('above', 'below', 'no-transform');
if (tooltipModel.yAlign) {
tooltipEl.classList.add(tooltipModel.yAlign);
} else {
tooltipEl.classList.add('no-transform');
}
function getBody(bodyItem) {
return bodyItem.lines[0].split(': ')[1].replace('.', ',');
}
// Set Text
if (tooltipModel.body) {
var titleLines = tooltipModel.title || [];
var bodyLines = tooltipModel.body.map(getBody);
var innerHtml = '<thead>';
innerHtml += '</thead><tbody>';
bodyLines.forEach(function(body, i) {
var colors = tooltipModel.labelColors[i];
var style = 'background:' + colors.backgroundColor;
style += '; border-color:' + colors.borderColor;
style += '; border-width: 2px';
var span = '<span style="' + style + '"></span>';
innerHtml += '<tr><td>' + span + body + '</td></tr>';
});
innerHtml += '</tbody>';
var tableRoot = tooltipEl.querySelector('table');
tableRoot.innerHTML = innerHtml;
}
// `this` will be the overall tooltip
var position = this._chart.canvas.getBoundingClientRect();
// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
tooltipEl.style.position = 'absolute';
tooltipEl.style.left = position.left + window.pageXOffset + tooltipModel.caretX + 'px';
tooltipEl.style.top = position.top + window.pageYOffset + tooltipModel.caretY + 'px';
tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
tooltipEl.style.fontSize = tooltipModel.bodyFontSize + 'px';
tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;
tooltipEl.style.padding = tooltipModel.yPadding + 'px ' + tooltipModel.xPadding + 'px';
tooltipEl.style.pointerEvents = 'none';
}
/*
callbacks: {
label: function(tooltipItem, data) {
return data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].toLocaleString();
},
}*/
}
}
});
#chartjs-tooltip {
opacity: 1;
position: absolute;
background: rgba(0, 0, 0, .7);
color: white;
border-radius: 3px;
-webkit-transition: all .1s ease;
transition: all .1s ease;
pointer-events: none;
-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);
}
<canvas id="myChart" style="max-width: 450px"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0"></script>
<script src="https://cdn.jsdelivr.net/gh/emn178/chartjs-plugin-labels/src/chartjs-plugin-labels.js"></script>
You might want to add some dots as thousand seperators in the tooltip but thats up to you. Best place to do it is in the getBody method.

In ChartJS is it possible to change the line style between different points?

Using ChartJs (v2.2.2) can you change the line style between the last 2 points on a graph. e.g. have a solid line all the way and then dashed at the end? see picture below
The borderDashproperty (scroll to Line Configuration) is the key to your problem.
The thing is, the full chart is drawn with a border dash, you cannot choose where it starts and where it ends.
A simple workaround is to create two identical datasets. One dotted and one with a plain line. Then you remvoe the last data of your plain one, and they both will be displayed as how you want it.
You can see the full code in this jsFiddle, and here is its result :
Note :
Since there are two datasets now, the legend will display both of them. Setting the display to false fixes it (more or less).
The declaration order doesn't matter since the plain line will always overwrite the dotted one.
Having a bezier curve (tension property > 0) can create a display problem since the data is not the same in both datasets.
You can create a scatter chart and draw the lines directly on the canvas using the Plugin Core API. The API offers a range of hooks that can be used for performing custom code. The advantage of this approach is that you can customize the style of every single connection line (width, color, dash pattern etc.).
const labels = [1, 2, 3, 4, 5, 6];
const values = [12, 19, 3, 5, 2, 3];
const data = labels.map((label, index) => ({ x: label, y: values[index]}));
var lineChart = new Chart(document.getElementById("chart"), {
type: "scatter",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-1'];
var yAxis = chart.scales['y-axis-1'];
chart.config.data.datasets[0].data.forEach((value, index) => {
if (index > 0) {
var valueFrom = data[index - 1];
var xFrom = xAxis.getPixelForValue(valueFrom.x);
var yFrom = yAxis.getPixelForValue(valueFrom.y);
var xTo = xAxis.getPixelForValue(value.x);
var yTo = yAxis.getPixelForValue(value.y);
ctx.save();
ctx.strokeStyle = '#922893';
ctx.lineWidth = 2;
if (index + 1 == data.length) {
ctx.setLineDash([5, 10]);
}
ctx.beginPath();
ctx.moveTo(xFrom, yFrom);
ctx.lineTo(xTo, yTo);
ctx.stroke();
ctx.restore();
}
});
}
}],
data: {
datasets: [{
label: "My Dataset",
data: data,
borderColor: '#922893',
pointBackgroundColor: "transparent"
}]
},
options: {
legend: {
display: false
},
scales: {
xAxes: [{
ticks: {
stepSize: 1
}
}],
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart" height="90"></canvas>
const labels = [1, 2, 3, 4, 5, 6];
const values = [12, 19, 3, 5, 2, 3];
const data = labels.map((label, index) => ({ x: label, y: values[index]}));
var lineChart = new Chart(document.getElementById("chart"), {
type: "scatter",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-1'];
var yAxis = chart.scales['y-axis-1'];
chart.config.data.datasets[0].data.forEach((value, index) => {
if (index > 0) {
var valueFrom = data[index - 1];
var xFrom = xAxis.getPixelForValue(valueFrom.x);
var yFrom = yAxis.getPixelForValue(valueFrom.y);
var xTo = xAxis.getPixelForValue(value.x);
var yTo = yAxis.getPixelForValue(value.y);
ctx.save();
ctx.strokeStyle = '#922893';
ctx.lineWidth = 2;
if (index + 1 == data.length) {
ctx.setLineDash([5, 10]);
}
ctx.beginPath();
ctx.moveTo(xFrom, yFrom);
ctx.lineTo(xTo, yTo);
ctx.stroke();
ctx.restore();
}
});
}
}],
data: {
datasets: [{
label: "My Dataset",
data: data,
borderColor: '#922893',
pointBackgroundColor: "transparent"
}]
},
options: {
legend: {
display: false
},
scales: {
xAxes: [{
ticks: {
stepSize: 1
}
}],
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart" height="90"></canvas>