I was using ChartJS v2 on a former project to create gauges looking like this:
During a React integration, I need to do the same thing but the freshly installed v3 version of this library, starting by the Doughnut modification itself.
Here is the current configuration:
export const GaugeChart: VFC<GaugeChartProps> = ({
value,
max = 100,
}) => {
const percent = value / max;
return <ChartJS
type="doughnut"
data={{
datasets: [
{
backgroundColor: [
'rgb(255, 99, 132)',
'#ccc',
],
data: [
percent * 100,
100 - (percent * 100),
],
},
],
}}
options={{
rotation: -1.0 * Math.PI, // start angle in radians
circumference: Math.PI, // sweep angle in radians
}}
/>
};
Note: type, data and options props are part of the ChartJS configuration itself, the component is just a wrapper.
Here is what I get:
What I am missing?
As per the migration guide in v3 the rotation and circumference options are in degrees instead of in radians, if you place them as degrees it works just fine:
var options = {
type: 'doughnut',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"]
}]
},
options: {
rotation: 270, // start angle in degrees
circumference: 180, // sweep angle in degrees
}
}
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>
</body>
Here is an example that I use and it works fine with version 3.5.1
HTML
<div class="chartBox">
<canvas id="gauge"></canvas>
</div>
CSS
.chartBox {
width: 400px;
padding: 20px;
border-radius: 20px;
}
JavaScript
// setup
const data = {
labels: ["Safe", "Risky", "High Risk"],
datasets: [
{
label: "Gauge",
data: [100, 65, 35],
backgroundColor: [
"rgba(75, 192, 192, 0.8)",
"rgba(255, 206, 86, 0.8)",
"rgba(255, 26, 104, 0.8)",
],
needleValue: 50,
borderColor: "white",
borderWidth: 2,
cutout: "95%",
circumference: 180,
rotation: 270,
borderRadius: 5,
},
],
};
//gaugeNeedle
const gaugeNeedle = {
id: "gaugeNeedle",
afterDatasetDraw(chart, args, options) {
const {
ctx,
config,
data,
chartArea: { top, right, bottom, left, width, height },
} = chart;
ctx.save();
const needleValue = data.datasets[0].needleValue;
const dataTotal = data.datasets[0].data.reduce((a, b) => a + b, 0);
if (needleValue <= 100) {
var angle = Math.PI + (1 / 200) * needleValue * Math.PI;
console.log(angle);
} else if (needleValue <= 10000) {
var angle =
Math.PI +
(1 / 200) * 100 * Math.PI +
((1 / 200) * needleValue * Math.PI * 65) / 10000;
} else if (needleValue <= 1000000) {
var angle =
Math.PI +
(1 / 200) * 100 * Math.PI +
((1 / 200) * 10000 * Math.PI * 65) / 10000 +
((1 / 200) * needleValue * Math.PI * 35) / 1000000;
} else {
var angle = 0;
}
const cx = width / 2;
const cy = chart._metasets[0].data[0].y;
//needle
ctx.translate(cx, cy);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(0, -2);
ctx.lineTo(height - ctx.canvas.offsetTop - 160, 0); // change 160 value if the needle size gets changed
ctx.lineTo(0, 2);
ctx.fillStyle = "#444";
ctx.fill();
//needle dot
ctx.translate(-cx, -cy);
ctx.beginPath();
ctx.arc(cx, cy, 5, 0, 10);
ctx.fill();
ctx.restore();
//text
ctx.font = "20px Ubuntu";
ctx.fillStyle = "#444";
ctx.fillText(needleValue + " CPM", cx, cy + 50);
ctx.font = "10px Ubuntu";
ctx.fillText(0, 5, cy + 20);
ctx.fillText(100, cx, 90);
ctx.fillText("10k", cx + 185, 200); // change values if the position gets changed
ctx.fillText("1M", cx + 193, 320); // change values if the position gets changed
ctx.textAlign = "center";
ctx.restore();
},
};
// config
const config = {
type: "doughnut",
data,
options: {
plugins: {
legend: {
display: false,
},
tooltip: {
yAlign: "bottom",
displayColors: false,
callbacks: {
label: function (tooltipItem, data, value) {
return tooltipItem.label;
},
},
},
},
},
plugins: [gaugeNeedle],
};
// render init block
const myChart = new Chart(document.getElementById("gauge"), config);
Note: This works fine when the width is set 400px. If you change this value other parameters need to be changed accordingly. However, The affected locations are commented in the JavaScript.
Related
I am trying to migrate this chart made by excel into chartJS:
important features:
having a horizontal bar showing a range
let range be from 'lower' (0%) til 'upper' (100%)
median is shown in by a vertical line (50%)
place one single point somewhere on the range (e.g. at 23%)
the single point is the only dynamic component here, everything else always looks the same
I know thats not a typical chart and a bit special.
Closest charts I found are:
1)
a simple stacked bar chart were instead of the bright obvious star I just another bar-stack
not very pretty the same
a mixed chart
with an line using the annotations plugin (here I draw the line with paint by hand)
in css the whole chart needs to be rotated by 90° (already done in the image shown below)
Another option my be creating it by using plane css stuff. But Id rather make it using chart js since there are some more charts and all my framework is made for it.
Any idea is appreciated.
Thanks.
Charts.js allows you to access the canvas and draw custom stuff, using a simple mechanism.
We could start with just the bar, as a horizontal bar chart with one item and most other stuff disabled:
const data = {
labels: [""],
datasets: [{
label: '100%',
data: [100],
backgroundColor: '#4af',
barThickness: 100
}]
};
const options = {
type: 'bar',
data,
options: {
animation: {duration: 0},
indexAxis: 'y',
layout: {
padding:{
left: 10,
right: 10
}
},
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip:{
enabled: false
}
}
}
};
const chart = new Chart(document.getElementById("myChart"), options);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.2.0/chart.umd.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<body>
<canvas id="myChart" style="height:250px; width: 90vw; border: 1px solid #ddd "></canvas>
</body>
Now we can access the canvas through a plugin a draw all the rest:
const plugin = {
id: 'customDraw', // to identify the plugin in the chart options
afterDraw: (chart, args, options) => {
const {ctx} = chart;
// read plugin options
const lineWidth = options.lineWidth || 1,
lineColor = options.lineColor || '#000',
textColor = options.textColor || '#000',
textFont = options.textFont,
starAt = options.starAt,
starColor = options.starColor || '#f44';
// get pixel coordinates for our bar, that is
// positioned at y = 0, from x = 0 to x = 100
const yCenter = chart.scales.y.getPixelForValue(0),
yTop = yCenter-50,
yBottom = yCenter+50,
x0 = chart.scales.x.getPixelForValue(0),
x50 = chart.scales.x.getPixelForValue(50),
x100 = chart.scales.x.getPixelForValue(100),
xStar = chart.scales.x.getPixelForValue(starAt);
ctx.save();
ctx.strokeStyle = lineColor;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(x50, yTop);
ctx.lineTo(x50, yBottom);
ctx.stroke();
ctx.fillStyle = starColor;
drawStar(ctx, xStar, yCenter, 10);
ctx.textBaseline = "top";
ctx.fillStyle = textColor;
if(textFont){
ctx.font = textFont;
}
ctx.textAlign = "start";
ctx.fillText("Lower", x0, yBottom + 2);
ctx.textAlign = "center";
ctx.fillText("Median", x50, yBottom + 2);
ctx.textAlign = "right";
ctx.fillText("Upper", x100, yBottom + 2);
ctx.restore();
}
};
Full code:
function drawStar(ctx, x0, y0, radius){
//https://stackoverflow.com/a/58043598/16466946
const nSpikes = 5;
ctx.beginPath();
for(let i = 0; i < nSpikes*2; i++){
let rotation = Math.PI/2;
let angle = (i/(nSpikes*2))*Math.PI*2+rotation;
let dist = radius*(i%2)+radius;
let x = x0+Math.cos(angle)*dist;
let y = y0+Math.sin(angle)*dist;
if(i === 0) {
ctx.moveTo(x, y);
continue; //skip
}
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
}
const plugin = {
id: 'customDraw',
afterDraw: (chart, args, options) => {
const {ctx} = chart;
// read plugin options
const lineWidth = options.lineWidth || 1,
lineColor = options.lineColor || '#000',
textColor = options.textColor || '#000',
textFont = options.textFont,
starAt = options.starAt,
starColor = options.starColor || '#f44';
// get pixel coordinates for our bar, that is
// positioned at y = 0, from x = 0 to x = 100
const yCenter = chart.scales.y.getPixelForValue(0),
yTop = yCenter-50,
yBottom = yCenter+50,
x0 = chart.scales.x.getPixelForValue(0),
x50 = chart.scales.x.getPixelForValue(50),
x100 = chart.scales.x.getPixelForValue(100),
xStar = chart.scales.x.getPixelForValue(starAt);
ctx.save();
ctx.strokeStyle = lineColor;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(x50, yTop);
ctx.lineTo(x50, yBottom);
ctx.stroke();
ctx.fillStyle = starColor;
drawStar(ctx, xStar, yCenter, 10);
ctx.textBaseline = "top";
ctx.fillStyle = textColor;
if(textFont){
ctx.font = textFont;
}
ctx.textAlign = "start";
ctx.fillText("Lower", x0, yBottom + 2);
ctx.textAlign = "center";
ctx.fillText("Median", x50, yBottom + 2);
ctx.textAlign = "right";
ctx.fillText("Upper", x100, yBottom + 2);
ctx.restore();
}
};
const data = {
labels: [""],
datasets: [{
label: '100%',
data: [100],
backgroundColor: '#4af',
barThickness: 100
}]
};
const options = {
type: 'bar',
data,
options: {
animation: {duration: 0},
indexAxis: 'y',
layout: {
padding:{
left: 10,
right: 10
}
},
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip:{
enabled: false
},
customDraw:{
lineWidth: 3,
textFont: '20px serif',
starAt: 23
}
}
},
plugins: [plugin],
};
new Chart(document.getElementById("myChart"), options);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.2.0/chart.umd.js" integrity="sha512-B51MzT4ksAo6Y0TcUpmvZnchoPYfIcHadIaFqV5OR5JAh6dneYAeYT1xIlaNHhhFAALd5FLDTWNt/fkxhwE/oQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<body>
<canvas id="myChart" style="height:250px; width: 90vw; border: 1px solid #ddd "></canvas>
</body>
Next step would be to add custom interaction - tooltips, click events and everything...
Is there a way to force the tooltip to stay inside the canvas?
right now if the window is too small, the pop up is not visible.
So, is there a way to force the tooltip to stay inside the canvas?
Well, if you are using a custom tooltip, like this one, you can create a offset so the tooltip will stay away from the borders:
var offset = tooltip.caretX + 20;
if (offset < tooltip.width)
offset = tooltip.width;
else if (tooltip.caretX > this._chart.width - tooltip.width)
offset = this._chart.width - tooltip.width;
// Hidden Code
tooltipEl.style.left = positionX + offset + 'px';
An working example, this code have been copied from another one of my answers in this post:
var customTooltips = function(tooltip) {
// Tooltip Element
var tooltipEl = document.getElementById('tooltip');
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.id = 'tooltip';
tooltipEl.innerHTML = '<table></table>';
this._chart.canvas.parentNode.appendChild(tooltipEl);
}
// Hide if no tooltip
if (tooltip.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set caret Position
tooltipEl.classList.remove('above', 'below', 'no-transform');
if (tooltip.yAlign) {
tooltipEl.classList.add(tooltip.yAlign);
} else {
tooltipEl.classList.add('no-transform');
}
function getBody(bodyItem) {
return bodyItem.lines;
}
// Set Text
if (tooltip.body) {
var titleLines = tooltip.title || [];
var bodyLines = tooltip.body.map(getBody);
var innerHtml = '<thead>';
titleLines.forEach(function(title) {
innerHtml += '<tr><th>' + title + '</th></tr>';
});
innerHtml += '</thead><tbody>';
bodyLines.forEach(function(body, i) {
var colors = tooltip.labelColors[i];
var style = 'background:' + colors.backgroundColor;
style += '; border-color:' + colors.borderColor;
style += '; border-width: 2px';
var span = '<span class="chartjs-tooltip-key" style="' + style + '"></span>';
var innerContent = '<td>' + span + body + '</td>';
// Every even/odd create a new tr
if (i % 2 == 0)
innerHtml += '<tr>' + innerContent;
else
innerHtml += innerContent + '</tr>';
});
// If is a odd number of itens close the last open tr
if (bodyLines.count % 2 == 1)
innerHtml += '</tr></tbody>';
else
innerHtml += '</tbody>';
var tableRoot = tooltipEl.querySelector('table');
tableRoot.innerHTML = innerHtml;
}
var positionY = this._chart.canvas.offsetTop;
var positionX = this._chart.canvas.offsetLeft;
var offset = tooltip.caretX + 20;
if (offset < tooltip.width)
offset = tooltip.width;
else if (tooltip.caretX > this._chart.width - tooltip.width)
offset = this._chart.width - tooltip.width;
// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
tooltipEl.style.left = positionX + offset + 'px';
tooltipEl.style.top = positionY + tooltip.caretY + 'px';
tooltipEl.style.fontFamily = tooltip._bodyFontFamily;
tooltipEl.style.fontSize = tooltip.bodyFontSize + 'px';
tooltipEl.style.fontStyle = tooltip._bodyFontStyle;
tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px';
};
var myChart = new Chart($('#myChart'), {
type: 'line',
data: {
labels: ['Day 1', 'Day 2', 'Day 3', 'Day 4'],
datasets: [{
label: 'Dats asd asda 1',
data: [12, 19, 3, 5],
pointRadius: 5,
pointHoverRadius: 5,
backgroundColor: 'rgba(255, 0, 0, 0.2)'
}, {
label: 'D 2',
data: [13, 17, 4, 6],
pointRadius: 5,
pointHoverRadius: 5,
backgroundColor: 'rgba(255, 255, 0, 0.2)'
}, {
label: 'D 3',
data: [14, 19, 3, 9],
pointRadius: 5,
pointHoverRadius: 5,
backgroundColor: 'rgba(0, 255, 0, 0.2)'
}, {
label: 'Data 4',
data: [15, 20, 2, 8],
pointRadius: 5,
pointHoverRadius: 5,
backgroundColor: 'rgba(0, 0, 255, 0.2)'
}]
},
options: {
responsive: false,
scales: {
yAxes: [{
display: true,
ticks: {
suggestedMax: 50,
}
}]
},
tooltips: {
enabled: false,
mode: 'index',
intersect: false,
custom: customTooltips
}
}
});
#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);
padding: 4px;
}
#tooltip td {
text-align: left;
}
.chartjs-tooltip-key {
display: inline-block;
width: 10px;
height: 10px;
margin-right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.css" integrity="sha256-aa0xaJgmK/X74WM224KMQeNQC2xYKwlAt08oZqjeF0E=" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" integrity="sha256-Uv9BNBucvCPipKQ2NS9wYpJmi8DTOEfTA/nH2aoJALw=" crossorigin="anonymous"></script>
<canvas id="myChart" width="400" height="200"></canvas>
Take a look at this: https://stackoverflow.com/a/64887282/8411093
Demo: https://codepen.io/themustafaomar/pen/wvWZrod
function customTooltips(tooltipModel) {
// Tooltip Element
var tooltipEl = document.getElementById("chartjs-tooltip");
const yAlign = tooltipModel.yAlign;
const xAlign = tooltipModel.xAlign;
// 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("top", "bottom", "center", "left", "right");
if (tooltipModel.yAlign || tooltipModel.xAlign) {
tooltipEl.classList.add(tooltipModel.yAlign);
tooltipEl.classList.add(tooltipModel.xAlign);
}
// Set Text
if (tooltipModel.body) {
var titleLines = tooltipModel.title || [];
var bodyLines = tooltipModel.body.map((bodyItem) => {
return bodyItem.lines;
});
var innerHtml = "<thead>";
titleLines.forEach(function (title) {
innerHtml += '<tr><th><div class="mb-1">' + title + "</div></th></tr>";
});
innerHtml += "</thead><tbody>";
bodyLines.forEach((body, i) => {
var colors = tooltipModel.labelColors[i];
// var style = 'background-color:' + colors.borderColor
var style =
"background-color:" + this._chart.data.datasets[i].borderColor;
var value = tooltipModel.dataPoints[i].value;
var label = this._chart.data.datasets[i].label;
style += "; border-color:" + colors.borderColor;
style += "; border-color:" + this._chart.data.datasets[i].borderColor;
style += "; border-width: 2px";
var span =
'<span class="chartjs-tooltip-key" style="' + style + '"></span>';
innerHtml += `<tr><td> ${span} $${value}K </td></tr>`;
});
innerHtml += "</tbody>";
var tableRoot = tooltipEl.querySelector("table");
tableRoot.innerHTML = innerHtml;
}
// Tooltip height and width
const { height, width } = tooltipEl.getBoundingClientRect();
// Chart canvas positions
const positionY = this._chart.canvas.offsetTop;
const positionX = this._chart.canvas.offsetLeft;
// Carets
const caretY = tooltipModel.caretY;
const caretX = tooltipModel.caretX;
// Final coordinates
let top = positionY + caretY - height;
let left = positionX + caretX - width / 2;
let space = 8; // This for making space between the caret and the element.
// yAlign could be: `top`, `bottom`, `center`
if (yAlign === "top") {
top += height + space;
} else if (yAlign === "center") {
top += height / 2;
} else if (yAlign === "bottom") {
top -= space;
}
// xAlign could be: `left`, `center`, `right`
if (xAlign === "left") {
left = left + width / 2 - tooltipModel.xPadding - space / 2;
if (yAlign === "center") {
left = left + space * 2;
}
} else if (xAlign === "right") {
left -= width / 2;
if (yAlign === "center") {
left = left - space;
} else {
left += space;
}
}
// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
// Left and right
tooltipEl.style.top = `${top}px`;
tooltipEl.style.left = `${left}px`;
// Font
tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
tooltipEl.style.fontSize = tooltipModel.bodyFontSize + "px";
tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;
// Paddings
tooltipEl.style.padding =
tooltipModel.yPadding + "px " + tooltipModel.xPadding + "px";
}
I'm trying to render a double tooltip when hovering on a point of a line chart. Like this:
I didn't find absolutely nothing on the web.
Someone knows how to achieve it?
It is possible to extend Chart.js functionality (see http://www.chartjs.org/docs/latest/developers/charts.html) drawing a vertical line and a second tooltip when hovering on a point:
Chart.defaults.MyLine = Chart.defaults.line;
Chart.controllers.MyLine = 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 = '#000000';
ctx.stroke();
var value = this.chart.data.datasets[activePoint._datasetIndex].data[activePoint._index];
ctx.font = this.chart.options.tooltips.titleFontStyle + " " + this.chart.options.tooltips.titleFontSize + "px Arial";
var xPad = this.chart.options.tooltips.xPadding;
var yPad = this.chart.options.tooltips.yPadding;
var width = ctx.measureText(value).width + xPad * 2;
var height = this.chart.options.tooltips.titleFontSize + yPad * 2;
var radius = this.chart.options.tooltips.cornerRadius;
console.log(activePoint, topY, xPad, yPad, ctx.font);
ctx.fillStyle = this.chart.options.tooltips.backgroundColor;
ctx.lineWidth = this.chart.options.tooltips.borderWidth;
var y = topY;
x = x - width / 2;
// draw rect upper tooltip
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
ctx.fill();
// draw text
ctx.textBaseline = 'top';
ctx.fillStyle = this.chart.options.tooltips.titleFontColor;
ctx.fillText(value, x + xPad, topY + yPad);
}
}
});
Chart.Tooltip.positioners.custom = function(elements, eventPosition) {
var tooltip = this;
return {
x: eventPosition.x,
y: elements[0]._chart.height
};
}
var ctx = document.getElementById('chart').getContext('2d');
var chart = new Chart(ctx, {
type: 'MyLine',
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,
legend: {
display: false
},
animation: false,
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
tooltips: {
// bottom tooltip
position: 'custom',
caretSize: 0,
callbacks: {
title: function(tooltipItem, data) {
return data['labels'][tooltipItem[0]['index']];
},
label: function(tooltipItem, data) {
return "";
},
afterLabel: function(tooltipItem, data) {
var dataset = data['datasets'][0];
return "";
}
},
backgroundColor: '#FF0000',
titleFontSize: 12,
titleFontColor: '#FFFFFF',
bodyFontColor: '#000',
titleMarginBottom: 2,
displayColors: false
}
}
});
#chart {
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
<canvas id="chart"></canvas>
P.S.: check also for others chart libraries for the same functionality.
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>
I'm trying to implement the Raphael image rotation demo:
http://raphaeljs.com/image-rotation.html
as a slider within the Foundation framework, but I can't get it to centre without cutting the sides off when it rotates, please see here
http://jcfolio.co.uk/current/index-raphael.html
<script>
window.onload = function () {
var src = document.getElementById("bee").src,
angle = 0;
document.getElementById("globe-holder").innerHTML = "";
var R = Raphael("globe-holder", 1000 , 500);
var img = R.image(src,-535, -150, 2000, 2000);
var butt1 = R.set(),
butt2 = R.set();
butt1.push(R.circle(24.833, 26.917, 26.667).attr({stroke: "none", fill: "#66c100", "fill-opacity": .5 }),
R.path("M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z").attr({stroke: "none", fill: "#fff"}),
R.circle(24.833, 26.917, 26.667).attr({fill: "#fff", opacity: 0, cursor: "pointer" }));
butt2.push(R.circle(24.833, 26.917, 26.667).attr({stroke: "none", fill: "#66c100", "fill-opacity": .5 }),
R.path("M37.566,9.551c9.331,6.686,11.661,19.471,5.502,29.014l2.36,1.689l-4.893,2.262l-4.893,2.262l0.568-5.36l0.567-5.359l2.365,1.694c4.657-7.375,2.83-17.185-4.352-22.33c-7.451-5.338-17.817-3.625-23.156,3.824C6.3,24.695,8.012,35.06,15.458,40.398l-2.857,3.988C2.983,37.494,0.773,24.109,7.666,14.49C14.558,4.87,27.944,2.658,37.566,9.551z").attr({stroke: "none", fill: "#fff"}),
R.circle(24.833, 26.917, 26.667).attr({fill: "#fff", opacity: 0, cursor: "pointer" }));
butt1.translate(35, 380);
butt2.translate(850, 380);
butt1[2].click(function () {
angle -= 120;
img.stop().animate({transform: "r" + angle}, 1000, "backOut");
}).mouseover(function () {
butt1[1].animate({fill: "#66c100"}, 300);
}).mouseout(function () {
butt1[1].stop().attr({fill: "#fff"});
});
butt2[2].click(function () {
angle += 120;
img.animate({transform: "r" + angle}, 1000, "backOut");
}).mouseover(function () {
butt2[1].animate({fill: "#66c100"}, 300);
}).mouseout(function () {
butt2[1].stop().attr({fill: "#fff"});
});
// setTimeout(function () {R.safari();});
};
</script>
When I increase the globe-holder width to 2000px (the full width of the png), it won't centre!
Think it's probably something simple I've missed! Any help greatly appreciated.
James