Is it possible to draw dotted lines using Chart.js?
This is an example of what I want to do:
The current options I'm using is not letting me do what I aim to do:
var lineChartData = {
"datasets": {
"label": "defi score",
"data": dataset[i],
"pointStrokeColor": "#fff",
"fillColor": "rgba(220,220,220,0.5)",
"pointColor": "rgba(220,220,220,1)",
"strokeColor": "rgba(220,220,220,1)",
pointHighlightFill: "#19283F",
pointHighlightStroke: "#28AFFA",
bezierCurve: false
},
"labels": labels
};
var ctx = document.getElementById("chart_per_week").getContext("2d");
var myLine = new Chart(ctx).Line(lineChartData, {
responsive: true,
scaleFontColor: "#FF5972",
bezierCurve: false
});
Dotted Line using Chart.js
You can extend the line chart type to do this
Preview
Script
Chart.types.Line.extend({
name: "LineAlt",
initialize: function (data) {
var strokeColors = [];
data.datasets.forEach(function (dataset, i) {
if (dataset.dottedFromLabel) {
strokeColors.push(dataset.strokeColor);
dataset.strokeColor = "rgba(0,0,0,0)"
}
})
Chart.types.Line.prototype.initialize.apply(this, arguments);
var self = this;
data.datasets.forEach(function (dataset, i) {
if (dataset.dottedFromLabel) {
self.datasets[i].dottedFromIndex = data.labels.indexOf(dataset.dottedFromLabel) + 1;
self.datasets[i]._saved = {
strokeColor: strokeColors.shift()
}
}
})
},
draw: function () {
Chart.types.Line.prototype.draw.apply(this, arguments);
// from Chart.js library code
var hasValue = function (item) {
return item.value !== null;
},
nextPoint = function (point, collection, index) {
return Chart.helpers.findNextWhere(collection, hasValue, index) || point;
},
previousPoint = function (point, collection, index) {
return Chart.helpers.findPreviousWhere(collection, hasValue, index) || point;
};
var ctx = this.chart.ctx;
var self = this;
ctx.save();
this.datasets.forEach(function (dataset) {
if (dataset.dottedFromIndex) {
ctx.lineWidth = self.options.datasetStrokeWidth;
ctx.strokeStyle = dataset._saved.strokeColor;
// adapted from Chart.js library code
var pointsWithValues = Chart.helpers.where(dataset.points, hasValue);
Chart.helpers.each(pointsWithValues, function (point, index) {
if (index >= dataset.dottedFromIndex)
ctx.setLineDash([3, 3]);
else
ctx.setLineDash([]);
if (index === 0) {
ctx.moveTo(point.x, point.y);
}
else {
if (self.options.bezierCurve) {
var previous = previousPoint(point, pointsWithValues, index);
ctx.bezierCurveTo(
previous.controlPoints.outer.x,
previous.controlPoints.outer.y,
point.controlPoints.inner.x,
point.controlPoints.inner.y,
point.x,
point.y
);
}
else {
ctx.lineTo(point.x, point.y);
}
}
ctx.stroke();
}, this);
}
})
ctx.restore();
}
});
and then
var data = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [
{
...
dottedFromLabel: "April"
}
],
};
...
new Chart(ctx).LineAlt(data);
Fiddle - https://jsfiddle.net/3gxjfndm/3/
With chart.js V3 you can make use of segments to achieve this:
const options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'orange',
segment: {
borderDash: ctx => ((ctx.chart.data.datasets[ctx.datasetIndex].data.length - 1) === ctx.p1DataIndex ? [2, 2] : undefined)
}
}]
},
options: {}
}
const 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.6.0/chart.js"></script>
</body>
Related
I have a chart.js in my website
When hovering over a point in the graph, I need to draw a line that goes down to the x-axis and bold his label
like this:
[chart][1]
Can someone help me?
You will need a custom plugin for this, see live example:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'red',
backgroundColor: 'red'
}]
},
options: {
plugins: {
selector: {
color: 'blue',
width: 2,
// If canvas has a different backgroundColor set this to match
boxColor: 'white'
}
}
},
plugins: [{
id: 'selector',
beforeDatasetsDraw: (chart, args, opts) => {
// Return if no hover
if (chart._active.length === 0) {
return;
}
// Set all variables needed
const activeEl = chart._active[0].element;
const labelItem = chart.scales.x._labelItems[chart._active[0].index];
const {
ctx,
scales: {
y
}
} = chart;
const metrics = ctx.measureText(labelItem.label);
const labelWidth = metrics.width;
const labelHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
// Draw white box over old label
ctx.save();
ctx.fillStyle = opts.boxColor || '#FFFFFF';
ctx.fillRect((labelItem.translation[0] - labelWidth / 2), labelItem.translation[1], labelWidth, labelHeight);
ctx.restore();
// Draw new text on canvas
ctx.save();
ctx.font = Chart.helpers.toFontString(Object.assign(labelItem.font, {
style: 'bold'
}));
ctx.fillText(labelItem.label, (labelItem.translation[0] - labelWidth / 2), labelItem.translation[1] + labelItem.textOffset)
ctx.restore();
// Draw vertical line down from point
ctx.save();
ctx.lineWidth = opts.width || 1;
ctx.strokeStyle = opts.color || 'black';
ctx.beginPath();
ctx.moveTo(activeEl.x, activeEl.y);
ctx.lineTo(activeEl.x, y.getPixelForValue(y.min));
ctx.stroke();
ctx.restore();
}
}]
}
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.4.0/chart.js"></script>
</body>
I have a line chart in chart.js. Now I would like to have a tooltip, when I hover over the line not just when i hover over a point.
The tooltip should then be displayed at the position of the mouse.
Is this somehow possible?
I have tried all interaction.mode options, but non of them have this behavior.
Thank You
You can use the croshair plugin for this if you are still using v2 of the lib: https://chartjs-plugin-crosshair.netlify.app/
Live example:
var ctx = document.getElementById("myChart");
function generateDataset(shift, label, color) {
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
let data = [];
let index = 0;
while (index < 5) {
data.push({
x: index,
y: getRandomInt(10, 40)
});
index++;
}
var dataset = {
backgroundColor: color,
borderColor: color,
showLine: true,
fill: false,
pointRadius: 2,
label: label,
data: data,
lineTension: 0,
interpolate: true,
xAxisID: 'x-axis-0'
};
return dataset;
}
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D', 'E'],
datasets: [
generateDataset(0, "A", "red"),
generateDataset(1, "B", "green")
]
},
options: {
scales: {
xAxes: [{
display: true,
type: 'linear',
position: 'bottom',
id: 'x-axis-0',
ticks: {
source: 'labels'
}
}]
},
tooltips: {
mode: "interpolate",
intersect: false,
callbacks: {
title: function(a, d) {
// return a[0].xLabel.toFixed(2);
return a[0].xLabel;
},
label: function(i, d) {
return (
d.datasets[i.datasetIndex].label + ": " + i.yLabel.toFixed(2)
);
}
}
},
plugins: {
crosshair: {
line: {
// Add alpha chanel so line becomes transparant so you dont see it
color: '#ffffff00',
},
sync: {
enabled: false
}
}
}
}
});
.myChartDiv {
max-width: 600px;
max-height: 400px;
}
<html>
<body>
<div class="myChartDiv">
<canvas id="myChart" width="600" height="400"></canvas>
</div>
<script src="https://npmcdn.com/chart.js#2.9.4/dist/Chart.bundle.min.js"></script>
<script src="https://unpkg.com/chartjs-plugin-crosshair#1.1.6/dist/chartjs-plugin-crosshair.js"></script>
</body>
</html>
If you are using V3 of the lib you will need to use a custom own plugin
Example:
// Options for the indicators
const indicatorOptions = {
radius: 4, borderWidth: 1, borderColor: 'red', backgroundColor: 'transparent'
};
// Override getLabelAndValue to return the interpolated value
const getLabelAndValue = Chart.controllers.line.prototype.getLabelAndValue;
Chart.controllers.line.prototype.getLabelAndValue = function(index) {
if (index === -1) {
const meta = this.getMeta();
const pt = meta._pt;
const vScale = meta.vScale;
return {
label: 'interpolated',
value: vScale.getValueForPixel(pt.y)
};
}
return getLabelAndValue.call(this, index);
}
// The interaction mode
Chart.Interaction.modes.interpolate = function (chart, e, option) {
const x = e.x;
const items = [];
const metas = chart.getSortedVisibleDatasetMetas();
for (let i = 0; i < metas.length; i++) {
const meta = metas[i];
const pt = meta.dataset.interpolate({ x }, "x");
if (pt) {
const element = new Chart.elements.PointElement({...pt, options: {...indicatorOptions}});
meta._pt = element;
items.push({element, index: -1, datasetIndex: meta.index });
} else {
meta._pt = null;
}
}
return items;
};
// Plugin to draw the indicators
Chart.register({
id: 'indicators',
afterDraw(chart) {
const metas = chart.getSortedVisibleDatasetMetas();
for (let i = 0; i < metas.length; i++) {
const meta = metas[i];
if (meta._pt) {
meta._pt.draw(chart.ctx);
}
}
},
afterEvent(chart, args) {
if (args.event.type === 'mouseout') {
const metas = chart.getSortedVisibleDatasetMetas();
for (let i = 0; i < metas.length; i++) {
metas[i]._pt = null;
}
args.changed = true;
}
}
})
var ctx = document.getElementById("myChart").getContext("2d");
var chart = new Chart(ctx, {
type: "line",
data: {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [
{
fill: true,
label: "My First dataset",
backgroundColor: "rgba(132, 0, 0, 1)",
borderColor: "rgb(255, 99, 132)",
data: [0, 10, 5, 2, 20, 30, 45]
},
{
data: [30, 40, 50],
label: 'My Second Dataset',
fill: true,
backgroundColor: "lightgreen",
borderColor: "green"
}
]
},
options: {
interaction: {
mode: "interpolate",
intersect: false,
axis: "x"
},
plugins: {
tooltip: {
position: 'nearest',
displayColors: false,
}
}
},
});
.myChartDiv {
max-width: 600px;
max-height: 400px;
}
<h1>Interpolating line values</h1>
<div class="myChartDiv">
<canvas id="myChart" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.3.2/chart.js"></script>
</div>
I have tried to make a pie chart using react-chartjs 2, It is working fine in desktop view but in mobile view the legend is not responsive it is taking much space and due to this the size of pie chart become very small.
my code:
function Chart(props: any) {
const option = {
tooltips: {enter image description here
callbacks: {
label: function (tooltipItem: any, data: any) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var meta = dataset._meta[Object.keys(dataset._meta)[0]];
var total = meta.total;
var currentValue = dataset.data[tooltipItem.index];
var percentage = parseFloat(
((currentValue / total) * 100).toFixed(1)
);
return currentValue + " (" + percentage + "%)";
},
title: function (tooltipItem: any, data: any) {
return data.labels[tooltipItem[0].index];
},
},
},
legend: {
display: true,
labels: {
fontSize: 12,
},
position: "right",
},
};
return (
<div className="chart">
<Pie data={props.ChartData} options={option} />
</div>
);
You can set your fontSize object as a ternery operator that checks the widts (or something else) to see if you are on a mobile device and give back the right fontSize according to it
If you want to update it real time because screen sizes change you can do that by mutating the chart options itself in a resizeEvent listner
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
},
options: {
legend: {
labels: {
fontSize: window.innerWidth > 350 ? 20 : 10
}
},
scales: {
yAxes: [{
ticks: {
reverse: false
}
}]
}
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
const chart = new Chart(ctx, options);
window.addEventListener('resize', () => {
if (window.innerWidth < 350) {
chart.options.legend.labels.fontSize = 10;
} else {
chart.options.legend.labels.fontSize = 20
}
});
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js" integrity="sha512-hZf9Qhp3rlDJBvAKvmiG+goaaKRZA6LKUO35oK6EsM0/kjPK32Yw7URqrq3Q+Nvbbt8Usss+IekL7CRn83dYmw==" crossorigin="anonymous"></script>
</body>
Here is the way to have a responsive legend font size (also explained here)
options: {
plugins: {
legend: {
labels: {
// This more specific font property overrides the global property
font: {
size: 14
}
}
}
}
}
Using Chart.js I declare two charts, using the same options. I would like to have different titles for the charts. But titles are declared in options.
I tried, adding title: after the opt that are used for both charts. But this did not work.
var ctx = document.getElementById("myChart");
myLineChart = new Chart(ctx, {
type: 'horizontalBar',
data: chartData,
options: opt,
title: {
display: true,
text: 'title',
}
});
var ctx2 = document.getElementById("myChart2");
myLineChart = new Chart(ctx2, {
type: 'horizontalBar',
data: chartData2,
options: opt,
title: {
display: true,
text: 'title2',
}
});
And the var options are:
var opt = {
events: false,
legend: {
display: false
},
scales: {
xAxes: [{
ticks: {
display: true,
callback: function(value, index, values) {
return Intl.NumberFormat('$form->{countrycode}').format((value));
}
}
}]
},
responsive: false,
tooltips: {
enabled: false
},
hover: {
animationDuration: 0
},
animation: {
duration: 1,
onComplete: function() {
var chartInstance = this.chart;
ctx = chartInstance.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function(dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function(bar, index) {
var data = '$form->{currency}' + Intl.NumberFormat('$form->{countrycode}').format(dataset.data[index]);
ctx.fillText(data, bar._model.x + 10, bar._model.y - 1);
});
});
}
}
};
The important step is to do a deep copy of the options object.
Write down the options you want to use for both charts. You don't need title: { ... } here because you will override it later.
let optionsDefault = {
responsive: true,
scales: { ... }
// ...
}
Deep copy the options for both charts (JSON.parse & stringify is just one method for deep cloning, look here for more information about deep cloning).
let options1 = JSON.parse(JSON.stringify(optionsDefault))
options1.title = {
display: true,
text: "Chart 1"
}
let options2 = JSON.parse(JSON.stringify(optionsDefault))
options2.title = {
display: true,
text: "Chart 2"
}
Assign both options to the charts.
let chart1 = new Chart(ctx1, {
type: "line",
data: data,
options: options1
});
let chart2 = new Chart(ctx2, {
type: "line",
data: data,
options: options2
});
I am trying to replicate the NSE Stock exchange comparison graph as displayed here.
I am using Chartjs 2.0. I have 2 line graphs in the chart.
On hovering on the data points I want to show the tooltip's ylabel in a div which is outside the canvas, (like the changing values are displayed in the top right corner in the chart above)
I found GRUNT's code helpful
Moving vertical line when hovering over the chart using chart.js
You can use Label Callback
document.getElementById('y-label').textContent = tooltipItem.yLabel;
Chart.defaults.LineWithLine = Chart.defaults.line;
Chart.controllers.LineWithLine = Chart.controllers.line.extend({
draw: function(ease) {
Chart.controllers.line.prototype.draw.call(this, ease);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
topY = this.chart.scales['y-axis-0'].top,
bottomY = this.chart.scales['y-axis-0'].bottom;
// draw line
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.lineWidth = 2;
ctx.strokeStyle = '#07C';
ctx.stroke();
ctx.restore();
}
}
});
var chart = new Chart(ctx, {
type: 'LineWithLine',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],
datasets: [{
label: 'Statistics',
data: [3, 1, 2, 5, 4, 7, 6],
backgroundColor: 'rgba(0, 119, 204, 0.8)',
borderColor: 'rgba(0, 119, 204, 0.3)',
fill: false
}]
},
options: {
responsive: false,
tooltips: {
intersect: false,
callbacks: {
label: function(tooltipItem, data) {
var label = data.datasets[tooltipItem.datasetIndex].label || '';
if (label) {
label += ': ';
}
label += Math.round(tooltipItem.yLabel * 100) / 100;
document.getElementById('y-label').textContent = tooltipItem.yLabel;
return label;
}
}
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<h4 id="y-label"> </h4>
<canvas id="ctx" height="200"></canvas>