Trying to round the bars on bar chart as found in this post that works as displayed in the jsFiddle provided. This is for version 1.
In the chart that I am using, it fails to load for the reference to extend in Chart.types.Bar.extend crashes the script.
If I use the default option, the chart loads no problems. I had to place the Chart.types.Bar.extend at the end for the default option to load correctly. Run and View this in Full Screen.
I tried implementing this with my version of Chart.js 2.4.0.
Chrome reports:
Uncaught TypeError: Cannot read property 'extend' of undefined
chart.js
This code will not even run here. Why is this occurring? Could someone please assist me.
This code works with an older version of Chart.js 1.0. Could anyone please further display how this could work with version Chart.js 2.0? Thank you.
$(document).ready(function(){
var myBarChart1 = new Chart($('#appBarChart2_NoRound'), {
type: 'bar',
data: dataBar2,
options: optionsBar
});
var ctx = $("#appBarChart2").getContext("2d");
var myBarChart2 = new Chart(ctx).BarAlt(dataBarAlt2, {
// 0 (flat) to 1 (more curvy)
curvature: 1
});
});
var dataBarAlt2 = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [
{
fillColor: "#1A9BFC",
strokeColor: "#1A9BFC",
data: [65, 59, 80, 81, 56, 55, 40],
}
]
};
var dataBar2 = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [
{
label: "My First dataset",
backgroundColor: '#1A9BFC',
borderColor:'#1A9BFC',
borderWidth: 1,
data: [65, 59, 80, 81, 56, 55, 40],
}
]
};
var optionsBar =
{
scales: {
xAxes: [{
stacked: true,
barThickness: 20,
gridLines:{
display:false,
}
// barPercentage:0.5,
}],
yAxes: [{
stacked: true,
// barPercentage:0.5,
}]
},
legend: {
display: false,
// position: 'left'
}
};
Chart.types.Bar.extend({
name: "BarAlt",
initialize: function (data) {
Chart.types.Bar.prototype.initialize.apply(this, arguments);
if (this.options.curvature !== undefined && this.options.curvature <= 1) {
var rectangleDraw = this.datasets[0].bars[0].draw;
var self = this;
var radius = this.datasets[0].bars[0].width * this.options.curvature * 0.5;
// override the rectangle draw with ours
this.datasets.forEach(function (dataset) {
dataset.bars.forEach(function (bar) {
bar.draw = function () {
// draw the original bar a little down (so that our curve brings it to its original position)
var y = bar.y;
// the min is required so animation does not start from below the axes
bar.y = Math.min(bar.y + radius, self.scale.endPoint - 1);
// adjust the bar radius depending on how much of a curve we can draw
var barRadius = (bar.y - y);
rectangleDraw.apply(bar, arguments);
// draw a rounded rectangle on top
Chart.helpers.drawRoundedRectangle(self.chart.ctx, bar.x - bar.width / 2, bar.y - barRadius + 1, bar.width, bar.height, barRadius);
ctx.fill();
// restore the y value
bar.y = y;
}
})
})
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<p>Bar Chart - Working</p>
<canvas id="appBarChart2_NoRound" height="100" >
</div>
<div>
<p>Rounded Bar Chart - Not Working</p>
<canvas id="appBarChart2" height="100" >
</div>
The code that you were trying to use is actually for chart.js v1 and, as you discovered, does not work for chart.js v2 (which is almost a full chart.js re-write).
To achieve the same results in chart.js v2, you need to extend Chart.elements.Rectangle and overwrite it's draw method in order to paint the rounded top. There is already a chart.js helper method that will draw a rounded rectangle (Chart.helpers.drawRoundedRectangle), so we will modify it slightly and create a new helper method that will only draw a rounded top (instead of all sides).
// draws a rectangle with a rounded top
Chart.helpers.drawRoundedTopRectangle = function(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
// top right corner
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
// bottom right corner
ctx.lineTo(x + width, y + height);
// bottom left corner
ctx.lineTo(x, y + height);
// top left
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
};
Chart.elements.RoundedTopRectangle = Chart.elements.Rectangle.extend({
draw: function() {
var ctx = this._chart.ctx;
var vm = this._view;
var left, right, top, bottom, signX, signY, borderSkipped;
var borderWidth = vm.borderWidth;
if (!vm.horizontal) {
// bar
left = vm.x - vm.width / 2;
right = vm.x + vm.width / 2;
top = vm.y;
bottom = vm.base;
signX = 1;
signY = bottom > top? 1: -1;
borderSkipped = vm.borderSkipped || 'bottom';
} else {
// horizontal bar
left = vm.base;
right = vm.x;
top = vm.y - vm.height / 2;
bottom = vm.y + vm.height / 2;
signX = right > left? 1: -1;
signY = 1;
borderSkipped = vm.borderSkipped || 'left';
}
// Canvas doesn't allow us to stroke inside the width so we can
// adjust the sizes to fit if we're setting a stroke on the line
if (borderWidth) {
// borderWidth shold be less than bar width and bar height.
var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
borderWidth = borderWidth > barSize? barSize: borderWidth;
var halfStroke = borderWidth / 2;
// Adjust borderWidth when bar top position is near vm.base(zero).
var borderLeft = left + (borderSkipped !== 'left'? halfStroke * signX: 0);
var borderRight = right + (borderSkipped !== 'right'? -halfStroke * signX: 0);
var borderTop = top + (borderSkipped !== 'top'? halfStroke * signY: 0);
var borderBottom = bottom + (borderSkipped !== 'bottom'? -halfStroke * signY: 0);
// not become a vertical line?
if (borderLeft !== borderRight) {
top = borderTop;
bottom = borderBottom;
}
// not become a horizontal line?
if (borderTop !== borderBottom) {
left = borderLeft;
right = borderRight;
}
}
// calculate the bar width and roundess
var barWidth = Math.abs(left - right);
var roundness = this._chart.config.options.barRoundness || 0.5;
var radius = barWidth * roundness * 0.5;
// keep track of the original top of the bar
var prevTop = top;
// move the top down so there is room to draw the rounded top
top = prevTop + radius;
var barRadius = top - prevTop;
ctx.beginPath();
ctx.fillStyle = vm.backgroundColor;
ctx.strokeStyle = vm.borderColor;
ctx.lineWidth = borderWidth;
// draw the rounded top rectangle
Chart.helpers.drawRoundedTopRectangle(ctx, left, (top - barRadius + 1), barWidth, bottom - prevTop, barRadius);
ctx.fill();
if (borderWidth) {
ctx.stroke();
}
// restore the original top value so tooltips and scales still work
top = prevTop;
},
});
Next, you will also have to extend the bar chart controller (Chart.controllers.bar) and overwrite dataElementType to use the new "rounded rectangle" for the chart instead of a regular rectangle.
Chart.defaults.roundedBar = Chart.helpers.clone(Chart.defaults.bar);
Chart.controllers.roundedBar = Chart.controllers.bar.extend({
dataElementType: Chart.elements.RoundedTopRectangle
});
Lastly, we will modify the chart's config to use the new chart type created above and add a new options property called barRoundness to control how round the top is (0 is flat, 1 is a semi-circle).
var ctx = document.getElementById("canvas").getContext("2d");
var myBar = new Chart(ctx, {
type: 'roundedBar',
data: {
labels: ["Car", "Bike", "Walking"],
datasets: [{
label: 'Students',
backgroundColor: chartColors.blue,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
]
}, {
label: 'Teachers',
backgroundColor: chartColors.red,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
]
}, {
label: 'Visitors',
backgroundColor: chartColors.green,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
]
}]
},
options: {
responsive: true,
barRoundness: 1,
title: {
display: true,
text: "Chart.js - Bar Chart with Rounded Tops (drawRoundedTopRectangle Method)"
},
}
});
You can see a full working example at this codepen.
Also, in case you want a slightly different "rounded top" look, here is another codepen that uses a different approach to drawing the top (a single quadratic curve).
The following only customizes the Chart.elements.Rectangle.prototype.draw - no need to create an entirely new chart type.
Other pros:
It works well with both vertical & horizontal bar charts
It works well with both vertical & horizontal stacked bar charts - it only rounds the last box in the series
Noted issue: because this only rounds the box in the last dataset, if the value of the datapoint in the last dataset is < either of the previous data points, the visual top-most box will not be rounded.
However, if the last data point is negative and the lowest value, it will round that box on the bottom corners.
Credit: original code belongs to https://github.com/uffo. Code below and linked fiddle demonstrate increasing positive values in each dataset for each stack, and also modifies some of the default radius options.
/**Customize the Rectangle.prototype draw method**/
Chart.elements.Rectangle.prototype.draw = function() {
var ctx = this._chart.ctx;
var vm = this._view;
var left, right, top, bottom, signX, signY, borderSkipped, radius;
var borderWidth = vm.borderWidth;
// If radius is less than 0 or is large enough to cause drawing errors a max
// radius is imposed. If cornerRadius is not defined set it to 0.
var cornerRadius = this._chart.config.options.cornerRadius;
var fullCornerRadius = this._chart.config.options.fullCornerRadius;
var stackedRounded = this._chart.config.options.stackedRounded;
var typeOfChart = this._chart.config.type;
if (cornerRadius < 0) {
cornerRadius = 0;
}
if (typeof cornerRadius == 'undefined') {
cornerRadius = 0;
}
if (typeof fullCornerRadius == 'undefined') {
fullCornerRadius = false;
}
if (typeof stackedRounded == 'undefined') {
stackedRounded = false;
}
if (!vm.horizontal) {
// bar
left = vm.x - vm.width / 2;
right = vm.x + vm.width / 2;
top = vm.y;
bottom = vm.base;
signX = 1;
signY = bottom > top ? 1 : -1;
borderSkipped = vm.borderSkipped || 'bottom';
} else {
// horizontal bar
left = vm.base;
right = vm.x;
top = vm.y - vm.height / 2;
bottom = vm.y + vm.height / 2;
signX = right > left ? 1 : -1;
signY = 1;
borderSkipped = vm.borderSkipped || 'left';
}
// Canvas doesn't allow us to stroke inside the width so we can
// adjust the sizes to fit if we're setting a stroke on the line
if (borderWidth) {
// borderWidth shold be less than bar width and bar height.
var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
borderWidth = borderWidth > barSize ? barSize : borderWidth;
var halfStroke = borderWidth / 2;
// Adjust borderWidth when bar top position is near vm.base(zero).
var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
// not become a vertical line?
if (borderLeft !== borderRight) {
top = borderTop;
bottom = borderBottom;
}
// not become a horizontal line?
if (borderTop !== borderBottom) {
left = borderLeft;
right = borderRight;
}
}
ctx.beginPath();
ctx.fillStyle = vm.backgroundColor;
ctx.strokeStyle = vm.borderColor;
ctx.lineWidth = borderWidth;
// Corner points, from bottom-left to bottom-right clockwise
// | 1 2 |
// | 0 3 |
var corners = [
[left, bottom],
[left, top],
[right, top],
[right, bottom]
];
// Find first (starting) corner with fallback to 'bottom'
var borders = ['bottom', 'left', 'top', 'right'];
var startCorner = borders.indexOf(borderSkipped, 0);
if (startCorner === -1) {
startCorner = 0;
}
function cornerAt(index) {
return corners[(startCorner + index) % 4];
}
// Draw rectangle from 'startCorner'
var corner = cornerAt(0);
ctx.moveTo(corner[0], corner[1]);
var nextCornerId, nextCorner, width, height, x, y;
for (var i = 1; i < 4; i++) {
corner = cornerAt(i);
nextCornerId = i + 1;
if (nextCornerId == 4) {
nextCornerId = 0
}
nextCorner = cornerAt(nextCornerId);
width = corners[2][0] - corners[1][0];
height = corners[0][1] - corners[1][1];
x = corners[1][0];
y = corners[1][1];
var radius = cornerRadius;
// Fix radius being too large
if (radius > Math.abs(height) / 2) {
radius = Math.floor(Math.abs(height) / 2);
}
if (radius > Math.abs(width) / 2) {
radius = Math.floor(Math.abs(width) / 2);
}
var x_tl, x_tr, y_tl, y_tr, x_bl, x_br, y_bl, y_br;
if (height < 0) {
// Negative values in a standard bar chart
x_tl = x;
x_tr = x + width;
y_tl = y + height;
y_tr = y + height;
x_bl = x;
x_br = x + width;
y_bl = y;
y_br = y;
// Draw
ctx.moveTo(x_bl + radius, y_bl);
ctx.lineTo(x_br - radius, y_br);
// bottom right
ctx.quadraticCurveTo(x_br, y_br, x_br, y_br - radius);
ctx.lineTo(x_tr, y_tr + radius);
// top right
fullCornerRadius ? ctx.quadraticCurveTo(x_tr, y_tr, x_tr - radius, y_tr) : ctx.lineTo(x_tr, y_tr, x_tr - radius, y_tr);
ctx.lineTo(x_tl + radius, y_tl);
// top left
fullCornerRadius ? ctx.quadraticCurveTo(x_tl, y_tl, x_tl, y_tl + radius) : ctx.lineTo(x_tl, y_tl, x_tl, y_tl + radius);
ctx.lineTo(x_bl, y_bl - radius);
// bottom left
ctx.quadraticCurveTo(x_bl, y_bl, x_bl + radius, y_bl);
} else if (width < 0) {
// Negative values in a horizontal bar chart
x_tl = x + width;
x_tr = x;
y_tl = y;
y_tr = y;
x_bl = x + width;
x_br = x;
y_bl = y + height;
y_br = y + height;
// Draw
ctx.moveTo(x_bl + radius, y_bl);
ctx.lineTo(x_br - radius, y_br);
// Bottom right corner
fullCornerRadius ? ctx.quadraticCurveTo(x_br, y_br, x_br, y_br - radius) : ctx.lineTo(x_br, y_br, x_br, y_br - radius);
ctx.lineTo(x_tr, y_tr + radius);
// top right Corner
fullCornerRadius ? ctx.quadraticCurveTo(x_tr, y_tr, x_tr - radius, y_tr) : ctx.lineTo(x_tr, y_tr, x_tr - radius, y_tr);
ctx.lineTo(x_tl + radius, y_tl);
// top left corner
ctx.quadraticCurveTo(x_tl, y_tl, x_tl, y_tl + radius);
ctx.lineTo(x_bl, y_bl - radius);
// bttom left corner
ctx.quadraticCurveTo(x_bl, y_bl, x_bl + radius, y_bl);
} else {
var lastVisible = 0;
for (var findLast = 0, findLastTo = this._chart.data.datasets.length; findLast < findLastTo; findLast++) {
if (!this._chart.getDatasetMeta(findLast).hidden) {
lastVisible = findLast;
}
}
var rounded = this._datasetIndex === lastVisible;
if (rounded) {
//Positive Value
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
// top right
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
// bottom right
if (fullCornerRadius || typeOfChart == 'horizontalBar')
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
else
ctx.lineTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
// bottom left
if (fullCornerRadius)
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
else
ctx.lineTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
// top left
if (fullCornerRadius || typeOfChart == 'bar')
ctx.quadraticCurveTo(x, y, x + radius, y);
else
ctx.lineTo(x, y, x + radius, y);
}else {
ctx.moveTo(x, y);
ctx.lineTo(x + width, y);
ctx.lineTo(x + width, y + height);
ctx.lineTo(x, y + height);
ctx.lineTo(x, y);
}
}
}
ctx.fill();
if (borderWidth) {
ctx.stroke();
}
};
/**Chart Data**/
var data = {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: 'data 0',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 0
}, {
label: 'data 1',
data: [20, 24, 10, 15, 12, 13],
backgroundColor: [
'rgba(255, 159, 64, 1)',
'rgba(255, 99, 132, 1)',
'rgba(255, 206, 86, 1)',
'rgba(54, 162, 235, 1)',
'rgba(153, 102, 255, 1)',
'rgba(75, 192, 192, 1)'
],
borderWidth: 0
}, {
label: 'data 2',
data: [20, 30, 30, 20, 14, 20],
backgroundColor: [
'rgba(75, 192, 192, 1)',
'rgba(255, 159, 64, 1)',
'rgba(255, 99, 132, 1)',
'rgba(255, 206, 86, 1)',
'rgba(54, 162, 235, 1)',
'rgba(153, 102, 255, 1)'
],
borderWidth: 0
}]
};
/**Chart Options - Radius options are here**/
var options = {
//Border radius; Default: 0; If a negative value is passed, it will overwrite to 0;
cornerRadius: 10,
//Default: false; if true, this would round all corners of final box;
fullCornerRadius: false,
//Default: false; if true, this rounds each box in the stack instead of only final box;
stackedRounded: false,
elements: {
point: {
radius: 25,
hoverRadius: 35,
pointStyle: 'rectRounded',
}
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
},
stacked: true,
radius: 25
}],
xAxes: [{
ticks: {
beginAtZero: true
},
stacked: true,
}]
}
};
/**Generate Chart**/
var ctxBar = document.getElementById("myChart");
var myBarChart = new Chart(ctxBar, {
type: 'bar',
data: data,
options: options
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.js"></script>
<canvas id="myChart" height="300" width="800"></canvas>
Fiddle: https://jsfiddle.net/adammoisa/v0dthnyr/7/
Solution by #jordanwillis doesn't work with chart.js version after 2.8.0.
To make it work just add following code before drawRoundedTopRectangle definition
Chart.helpers.merge(Chart.defaults.global, {
datasets: {
roundedBar: {
categoryPercentage: 0.8,
barPercentage: 0.9
}
}
});
Got this working in Angular 11 and Charts.js version 2.9.4 (might work in other versions too)
Add this at the very end of your .component.ts file after export class { .. }
Chart['elements'].Rectangle.prototype.draw = function() {
let ctx = this._chart.ctx;
let view = this._view;
//////////////////// edit this to change how rounded the edges are /////////////////////
let borderRadius = 10;
let left = view.x - view.width / 2;
let right = view.x + view.width / 2;
let top = view.y;
let bottom = view.base;
ctx.beginPath();
ctx.fillStyle = view.backgroundColor;
ctx.strokeStyle = view.borderColor;
ctx.lineWidth = view.borderWidth;
let barCorners = [
[left, bottom],
[left, top],
[right, top],
[right, bottom]
];
//start in bottom-left
ctx.moveTo(barCorners[0][0], barCorners[0][1]);
for (let i = 1; i < 4; i++) {
let x = barCorners[1][0];
let y = barCorners[1][1];
let width = barCorners[2][0] - barCorners[1][0];
let height = barCorners[0][1] - barCorners[1][1];
//Fix radius being too big for small values
if(borderRadius > width/2){
borderRadius = width/2;
}
if(borderRadius > height/2){
borderRadius = height/2;
}
// DRAW THE LINES THAT WILL BE FILLED - REPLACING lineTo with quadraticCurveTo CAUSES MORE EDGES TO BECOME ROUNDED
ctx.moveTo(x + borderRadius, y);
ctx.lineTo(x + width - borderRadius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + borderRadius);
ctx.lineTo(x + width, y + height - borderRadius);
ctx.lineTo(x + width, y + height, x + width - borderRadius, y + height);
ctx.lineTo(x + borderRadius, y + height);
ctx.lineTo(x, y + height, x, y + height - borderRadius);
ctx.lineTo(x, y + borderRadius);
ctx.quadraticCurveTo(x, y, x + borderRadius, y);
}
//FILL THE LINES
ctx.fill();
};
example ngOnInit method:
ngOnInit() {
console.log("asdf entering getChart");
this.canvas = document.getElementById('NumberOfSessionsChart');
this.ctx = this.canvas.getContext('2d');
this.myChart = new Chart(this.ctx, {
type: 'bar',
data: {
labels: ['Jan 20', 'Feb 20', 'Mar 20', 'Apr 20', 'May 20', 'Jun 20', 'Jul 20', 'Aug 20', 'Sept 20'],
datasets: [{
label: 'vVals',
backgroundColor: 'blue',
data: [0, 50, 20, 30, 40, 50, 60, 70, 80, 90, 100],
}]
},
options: {
devicePixelRatio: 2.2,
//tooltips are the things that appear when you hover over the data that show the counts.
//for some reason we can't choose where the tooltip is oriented.
tooltips: {
enabled: false
},
onClick: (e) => {
console.log("asdf entering on click");
},
legend: {
display: true,
position: 'right',
reverse: true,
labels: {
fontColor: 'black',
fontSize: 15,
padding: 20,
usePointStyle: true,
//width of circles in legend
boxWidth: 9
}
},
scales: {
xAxes: [{
ticks: {
padding: 10,
fontSize: 13,
fontFamily: 'Roboto',
fontColor: 'black',
beginAtZero: true
},
gridLines: {
tickMarkLength: 0,
color: '#9da0a2',
drawOnChartArea: false,
},
}],
yAxes: [{
ticks: {
padding: 10,
fontSize: 13,
fontFamily: 'Roboto',
fontColor: 'black',
beginAtZero: true,
precision:0
},
gridLines: {
tickMarkLength: 0,
color: '#9da0a2',
drawOnChartArea: false,
},
}],
},
responsive: false,
}
});
}
}
Also worth a mention is this solution which takes you 30 seconds to implement:
https://github.com/jedtrow/Chart.js-Rounded-Bar-Charts
Download and put the .js file into your projects folder, load it and use var options = {
cornerRadius: 20,
};
to get rounded bars.
Credits: https://github.com/jedtrow
Checkout chartjs-top-round-bar a usefull
You just need to
import 'chartjs-top-round-bar';
...
new Chart('myChart',
{
options: {
barRoundness: 0.3
}
}
Related
I am currently attempting to use react-chartjs-2 to bring this chart to life.
design:
enter image description here
After hours of searching online, I have only been able to create rounded corners for the top and bottom of each bar.
Here is a picture of what I have so far:
enter image description here
I am importing a custom JS file into my react component to make the image above.
Here is the custom JS I am using:
Chart.elements.Rectangle.prototype.draw = function () {
var ctx = this._chart.ctx;
var vm = this._view;
var left, right, top, bottom, signX, signY, borderSkipped, radius;
var borderWidth = vm.borderWidth;
var cornerRadius = 20;
if (!vm.horizontal) {
left = vm.x - vm.width / 2;
right = vm.x + vm.width / 2;
top = vm.y;
bottom = vm.base;
signX = 1;
signY = bottom > top ? 1 : -1;
borderSkipped = vm.borderSkipped || 'bottom';
} else {
left = vm.base;
right = vm.x;
top = vm.y - vm.height / 2;
bottom = vm.y + vm.height / 2;
signX = right > left ? 1 : -1;
signY = 1;
borderSkipped = vm.borderSkipped || 'left';
}
if (borderWidth) {
var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
borderWidth = borderWidth > barSize ? barSize : borderWidth;
var halfStroke = borderWidth / 2;
var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
if (borderLeft !== borderRight) {
top = borderTop;
bottom = borderBottom;
}
if (borderTop !== borderBottom) {
left = borderLeft;
right = borderRight;
}
}
ctx.beginPath();
ctx.fillStyle = vm.backgroundColor;
ctx.strokeStyle = vm.borderColor;
ctx.lineWidth = borderWidth;
var corners = [
[left, bottom],
[left, top],
[right, top],
[right, bottom]
];
var borders = ['bottom', 'left', 'top', 'right'];
var startCorner = borders.indexOf(borderSkipped, 0);
if (startCorner === -1) {
startCorner = 0;
}
function cornerAt(index) {
return corners[(startCorner + index) % 4];
}
var corner = cornerAt(0);
ctx.moveTo(corner[0], corner[1]);
for (var i = 1; i < 4; i++) {
corner = cornerAt(i);
nextCornerId = i + 1;
if (nextCornerId == 4) {
nextCornerId = 0
}
nextCorner = cornerAt(nextCornerId);
width = corners[2][0] - corners[1][0];
height = corners[0][1] - corners[1][1];
x = corners[1][0];
y = corners[1][1];
var radius = cornerRadius;
if (radius > height / 2) {
radius = height / 2;
} if (radius > width / 2) {
radius = width / 2;
}
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.fill();
if (borderWidth) {
ctx.stroke();
}
};
You can just use the borderRadius property with a stacked x axes like so:
const options = {
type: 'bar',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [3, 5, 3, 5, 2, 3],
backgroundColor: 'orange',
borderSkipped: false,
borderRadius: 20,
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
backgroundColor: 'pink',
borderSkipped: false,
borderRadius: 20,
}
]
},
options: {
scales: {
x: {
stacked: true
}
}
}
}
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.7.1/chart.js"></script>
</body>
Here's code that I took from another topic:
var ORDER_STATS = {
"2016": [0, 400010, 400110, 400110, 401000, 401000, 400100, 401000, 400001],
"Source": [330865, 332865, 318865, 332865, 320865, 334865, 322865, 320865, 340865],
"Moving average LONG": [304493, 315040, 325809, 329532, 332643, 330421, 329754, 327309, 326865]
};
var colors = ['206,191,26', '119,206,26', '26,200,206', '236,124,98', '206,26,140', '26,77,206', '236,124,98', '206,26,140', '26,77,206'];
// Definning X
var ordersChartData = {
labels: ['2022-02-10', '2022-02-11', '2022-02-12', '2022-02-13', '2022-02-14', '2022-02-15', '2022-02-16', '2022-02-17', '2022-02-18'],
datasets: []
}
Object.keys(ORDER_STATS).forEach(function(key) {
color = colors.shift();
ordersChartData.datasets.push({
label: key,
lineTension: 0,
type: 'line',
backgroundColor: "rgba(" + color + ",0.1)",
borderColor: "rgba(" + color + ",1)",
borderWidth: 2,
pointBackgroundColor: "rgba(" + color + ",1)",
pointBorderColor: "#fff",
pointBorderWidth: 1,
pointRadius: 4,
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(" + color + ",1)",
pointHoverBorderWidth: 1,
data: ORDER_STATS[key]
});
});
var ctx = document.getElementById("myChart").getContext("2d");
Chart.defaults.global.defaultFontColor = 'grey';
Chart.defaults.global.defaultFontFamily = "Tahoma";
Chart.defaults.global.defaultFontSize = 11;
Chart.defaults.global.defaultFontStyle = 'normal';
var myChart = new Chart(ctx, {
type: 'line',
data: ordersChartData,
defaultFontSize: 11,
options: {
responsive: true,
title: {
display: true,
text: 'Intersection realization',
fontColor: "#444",
fontFamily: 'Tahoma',
padding: 0
},
legend: {
display: true,
labels: {
fontColor: 'grey',
usePointStyle: true
}
},
tooltips: {
mode: "index",
intersect: true,
position: 'nearest',
bodySpacing: 4
}
}
});
Chart.plugins.register({
afterDatasetsDraw: function(chartInstance, easing) {
var Y = chartInstance.scales['y-axis-0'];
var X = chartInstance.scales['x-axis-0'];
zeroPointY = Y.top + ((Y.bottom - Y.top) / (Y.ticks.length - 1) * Y.zeroLineIndex);
zeroPointX = Y.right;
yScale = (Y.bottom - Y.top) / (Y.end - Y.start);
xScale = (X.right - X.left) / (X.ticks.length - 1);
console.log("aaa1", Y.top, Y.bottom, Y.ticks.length, Y.zeroLineIndex, zeroPointY);
console.log("aaa2", Y.bottom, Y.top, Y.end, Y.start, yScale);
var intersects = findIntersects(ORDER_STATS['Source'], ORDER_STATS['Moving average LONG']);
var context = chartInstance.chart.ctx;
intersects.forEach(function(result, idx) {
context.fillStyle = 'red';
context.beginPath();
context.arc((result.x * xScale) + zeroPointX, (Y.end - Y.start) - (result.y * yScale) - ((Y.end - Y.start) - zeroPointY), 3, 0, 2 * Math.PI, true);
context.fill();
});
}
});
function findIntersects(line1, line2) {
var intersects = [];
line1.forEach(function(val, idx) {
var line1StartX = idx;
var line1StartY = line1[idx];
var line1EndX = idx + 1;
var line1EndY = line1[idx + 1];
var line2StartX = idx;
var line2StartY = line2[idx];
var line2EndX = idx + 1;
var line2EndY = line2[idx + 1];
result = checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY);
if (result.onLine1 && result.onLine2) {
intersects.push(result);
}
});
return intersects;
}
function checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
// if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
var denominator, a, b, numerator1, numerator2, result = {
x: null,
y: null,
onLine1: false,
onLine2: false
};
denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
if (denominator == 0) {
return result;
}
a = line1StartY - line2StartY;
b = line1StartX - line2StartX;
numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
a = numerator1 / denominator;
b = numerator2 / denominator;
// if we cast these lines infinitely in both directions, they intersect here:
result.x = line1StartX + (a * (line1EndX - line1StartX));
result.y = line1StartY + (a * (line1EndY - line1StartY));
// it is worth noting that this should be the same as:
x = line2StartX + (b * (line2EndX - line2StartX));
y = line2StartX + (b * (line2EndY - line2StartY));
// if line1 is a segment and line2 is infinite, they intersect if:
if (a > 0 && a < 1) {
result.onLine1 = true;
}
// if line2 is a segment and line1 is infinite, they intersect if:
if (b > 0 && b < 1) {
result.onLine2 = true;
}
// if line1 and line2 are segments, they intersect if both of the above are true
return result;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.js"></script>
<canvas id="myChart" width="650" height="241" style="display: block; width: 650px; height: 241px;"></canvas>
It's working great, but if here:
"2016" : [0, 400010, 400110, 400110, 401000, 401000, 400100, 401000, 400001]
I change first value, 0, on 300000, code can't show intersection anymore.
Problem is, as I think, in Y.zeroLineIndex.
I've tried a lot of variants, logged to console almost all values and noticed that when first value is zero (like in example), Y.zeroLineIndex is 9. But if u change first value to 300000, it becomes to -1.
I am unsure how to fix it after many hours attempting to detect a problem and fix it. Nothing helped.
In JS I'm not good, so requesting for a help
EDIT:
[DEMO][1]
[1]: https://i.stack.imgur.com/0t3Gu.png
I sat down a bit on your problem and found out that you had a problem setting the height according to the Y-axis. Use this code and you will see that it works in any situation. I made changes on zeroPointY, yScale and on Y value of context.arc function.
Chart.plugins.register({
afterDatasetsDraw: function (chartInstance, easing) {
var Y = chartInstance.scales['y-axis-0'];
var X = chartInstance.scales['x-axis-0'];
zeroPointY = (Y.bottom - Y.top)/(Y.ticks.length-1);
zeroPointX = Y.right;
yScale = (Y.end - Y.start)/ (Y.ticks.length - 1);
xScale = (X.right - X.left) / (X.ticks.length - 1);
console.log("aaa1", Y.top, Y.bottom, Y.ticks.length, Y.zeroLineIndex, zeroPointY);
console.log("aaa2", Y.bottom, Y.top, Y.end, Y.start, yScale);
var intersects = findIntersects(ORDER_STATS['Source'], ORDER_STATS['Moving average LONG']);
var context = chartInstance.chart.ctx;
intersects.forEach(function (result1, idx) {
context.fillStyle = 'red';
context.beginPath();
context.arc((result1.x * xScale) + zeroPointX, Y.top + (Y.end - result1.y)/yScale*zeroPointY, 3, 0, Math.PI * 2, true);
context.fill();
});
}
});
I'm trying to identify the following parameters from the example for oval triangle but when i modify the line:
drawCircle(canvas.width / 3,canvas.height / 2,2.5,'red');
//want to replace with specific values but its not working
drawCircle(32 / 3,33 / 2,2.5,'red');
I want to identify the correct parameter so the demo can change the red point into other space inside the triangle
CH4= 20
C2H4= 70
C2H2= 20
Demo:
https://codepen.io/Barak/pen/WwdPxQ
I read the post from stackoverflow community and cannot see values
how to create Duval Triangle in canvas
In the post you've mentioned, markE did a great job replicating the look of a Duval triangle. The only problem is that the codepen including the drawCircle() function is just a dummy and does nothing more than placing a dot at an arbitrary position, given by it's x and y parameters.
To make this function show the correct position of actual data on the triangle - e.g. CH4=20 | C2H4=70 | C2H2=20 - there's a lot more involved.
Let's have a more in-depth look, if we were to solve this graphically on paper.
(The following is based on this paper)
The Duval triangle is basically an equilateral triangle like this, where side b=%CH4,
side a=%C2H4 and side c=%C2H2.
If we consider the following example data %CH4=25 | %C2H4=35 | %C2H2=40
we would have to go from point A to point C, 25% the length of side b and draw a line parallel to side c:
then from point C to point B, 35% the length of side a and draw a line parallel to side b:
and finally from point B to point A, 40% the length of side c and draw a line parallel to side a:
So where those three lines intersect - so the paper says - we have our target position indicating the status. This can be done programatically using plain trigonometry. Well, I'm not too sure why we need all three lines though. As th percentage for CH4 is always parallel to the triangle's base and C2H4 is always parallel to side b, we have an intersection yet and the line for C2H2 is given automatically.
Basically we just need a function which calculates the intersection between the CH4 and the C2H4 line.
I've taken markE's existing code and enhanced it by a function plotResult(), which takes three parameters for the CH4, C2H2 and C2H4 ppm values:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// https://www.researchgate.net/publication/4345236_A_Software_Implementation_of_the_Duval_Triangle_Method
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
var v0 = {
x: 114,
y: 366
};
var v1 = {
x: 306,
y: 30
};
var v2 = {
x: 498,
y: 366
};
var triangle = [v0, v1, v2];
// Define all your segments here
var segments = [{
points: [{
x: 114,
y: 366
}, {
x: 281,
y: 76
}, {
x: 324,
y: 150
}, {
x: 201,
y: 366
}],
fill: 'rgb(172,236,222)',
label: {
text: 'D1',
cx: 200,
cy: 290,
withLine: false,
endX: null,
endY: null
},
},
{
points: [{
x: 385,
y: 366
}, {
x: 201,
y: 366
}, {
x: 324,
y: 150
}, {
x: 356,
y: 204
}, {
x: 321,
y: 256
}],
fill: 'deepskyblue',
label: {
text: 'D2',
cx: 290,
cy: 290,
withLine: false,
endX: null,
endY: null
},
},
{
points: [{
x: 297,
y: 46
}, {
x: 392,
y: 214
}, {
x: 372,
y: 248
}, {
x: 441,
y: 366
}, {
x: 385,
y: 366
}, {
x: 321,
y: 256
}, {
x: 356,
y: 204
}, {
x: 281,
y: 76
}],
fill: 'lightCyan',
label: {
text: 'DT',
cx: 370,
cy: 290,
withLine: false,
endX: 366,
endY: 120
},
},
{
points: [{
x: 306,
y: 30
}, {
x: 312,
y: 40
}, {
x: 300,
y: 40
}],
fill: 'black',
label: {
text: 'PD',
cx: 356,
cy: 40,
withLine: true,
endX: 321,
endY: 40
},
},
{
points: [{
x: 312,
y: 40
}, {
x: 348,
y: 103
}, {
x: 337,
y: 115
}, {
x: 297,
y: 46
}, {
x: 300,
y: 40
}],
fill: 'navajoWhite',
label: {
text: 'T1',
cx: 375,
cy: 70,
withLine: true,
endX: 340,
endY: 75
},
},
{
points: [{
x: 348,
y: 103
}, {
x: 402,
y: 199
}, {
x: 392,
y: 214
}, {
x: 337,
y: 115
}],
fill: 'tan',
label: {
text: 'T2',
cx: 400,
cy: 125,
withLine: true,
endX: 366,
endY: 120
},
},
{
points: [{
x: 402,
y: 199
}, {
x: 498,
y: 366
}, {
x: 441,
y: 366
}, {
x: 372,
y: 248
}],
fill: 'peru',
label: {
text: 'T3',
cx: 425,
cy: 290,
withLine: false,
endX: null,
endY: null
},
},
];
// label styles
var labelfontsize = 12;
var labelfontface = 'verdana';
var labelpadding = 3;
// pre-create a canvas-image of the arrowhead
var arrowheadLength = 10;
var arrowheadWidth = 8;
var arrowhead = document.createElement('canvas');
premakeArrowhead();
var legendTexts = ['PD = Partial Discharge', 'T1 = Thermal fault < 300 celcius', '...'];
// start drawing
/////////////////////
// draw colored segments inside triangle
for (var i = 0; i < segments.length; i++) {
drawSegment(segments[i]);
}
// draw ticklines
ticklines(v0, v1, 9, 0, 20);
ticklines(v1, v2, 9, Math.PI * 3 / 4, 20);
ticklines(v2, v0, 9, Math.PI * 5 / 4, 20);
// molecules
moleculeLabel(v0, v1, 100, Math.PI, '% CH4');
moleculeLabel(v1, v2, 100, 0, '% C2H4');
moleculeLabel(v2, v0, 75, Math.PI / 2, '% C2H2');
// draw outer triangle
drawTriangle(triangle);
// draw legend
drawLegend(legendTexts, 10, 10, 12.86);
plotResult(25, 40, 35);
// end drawing
/////////////////////
function drawSegment(s) {
// draw and fill the segment path
ctx.beginPath();
ctx.moveTo(s.points[0].x, s.points[0].y);
for (var i = 1; i < s.points.length; i++) {
ctx.lineTo(s.points[i].x, s.points[i].y);
}
ctx.closePath();
ctx.fillStyle = s.fill;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
// draw segment's box label
if (s.label.withLine) {
lineBoxedLabel(s, labelfontsize, labelfontface, labelpadding);
} else {
boxedLabel(s, labelfontsize, labelfontface, labelpadding);
}
}
function moleculeLabel(start, end, offsetLength, angle, text) {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = '14px verdana';
var dx = end.x - start.x;
var dy = end.y - start.y;
var x0 = parseInt(start.x + dx * 0.50);
var y0 = parseInt(start.y + dy * 0.50);
var x1 = parseInt(x0 + offsetLength * Math.cos(angle));
var y1 = parseInt(y0 + offsetLength * Math.sin(angle));
ctx.fillStyle = 'black';
ctx.fillText(text, x1, y1);
// arrow
var x0 = parseInt(start.x + dx * 0.35);
var y0 = parseInt(start.y + dy * 0.35);
var x1 = parseInt(x0 + 50 * Math.cos(angle));
var y1 = parseInt(y0 + 50 * Math.sin(angle));
var x2 = parseInt(start.x + dx * 0.65);
var y2 = parseInt(start.y + dy * 0.65);
var x3 = parseInt(x2 + 50 * Math.cos(angle));
var y3 = parseInt(y2 + 50 * Math.sin(angle));
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x3, y3);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
var angle = Math.atan2(dy, dx);
ctx.translate(x3, y3);
ctx.rotate(angle);
ctx.drawImage(arrowhead, -arrowheadLength, -arrowheadWidth / 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
function boxedLabel(s, fontsize, fontface, padding) {
var centerX = s.label.cx;
var centerY = s.label.cy;
var text = s.label.text;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = fontsize + 'px ' + fontface
var textwidth = ctx.measureText(text).width;
var textheight = fontsize * 1.286;
var leftX = centerX - textwidth / 2 - padding;
var topY = centerY - textheight / 2 - padding;
ctx.fillStyle = 'white';
ctx.fillRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.lineWidth = 1;
ctx.strokeRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.fillStyle = 'black';
ctx.fillText(text, centerX, centerY);
}
function lineBoxedLabel(s, fontsize, fontface, padding) {
var centerX = s.label.cx;
var centerY = s.label.cy;
var text = s.label.text;
var lineToX = s.label.endX;
var lineToY = s.label.endY;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = fontsize + 'px ' + fontface
var textwidth = ctx.measureText(text).width;
var textheight = fontsize * 1.286;
var leftX = centerX - textwidth / 2 - padding;
var topY = centerY - textheight / 2 - padding;
// the line
ctx.beginPath();
ctx.moveTo(leftX, topY + textheight / 2);
ctx.lineTo(lineToX, topY + textheight / 2);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
// the boxed text
ctx.fillStyle = 'white';
ctx.fillRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.strokeRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.fillStyle = 'black';
ctx.fillText(text, centerX, centerY);
}
function ticklines(start, end, count, angle, length) {
var dx = end.x - start.x;
var dy = end.y - start.y;
ctx.lineWidth = 1;
for (var i = 1; i < count; i++) {
var x0 = parseInt(start.x + dx * i / count);
var y0 = parseInt(start.y + dy * i / count);
var x1 = parseInt(x0 + length * Math.cos(angle));
var y1 = parseInt(y0 + length * Math.sin(angle));
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
if (i == 2 || i == 4 || i == 6 || i == 8) {
var labelOffset = length * 3 / 4;
var x1 = parseInt(x0 - labelOffset * Math.cos(angle));
var y1 = parseInt(y0 - labelOffset * Math.sin(angle));
ctx.fillStyle = 'black';
ctx.fillText(parseInt(i * 10), x1, y1);
}
}
}
function premakeArrowhead() {
var actx = arrowhead.getContext('2d');
arrowhead.width = arrowheadLength;
arrowhead.height = arrowheadWidth;
actx.beginPath();
actx.moveTo(0, 0);
actx.lineTo(arrowheadLength, arrowheadWidth / 2);
actx.lineTo(0, arrowheadWidth);
actx.closePath();
actx.fillStyle = 'black';
actx.fill();
}
function drawTriangle(t) {
ctx.beginPath();
ctx.moveTo(t[0].x, t[0].y);
ctx.lineTo(t[1].x, t[1].y);
ctx.lineTo(t[2].x, t[2].y);
ctx.closePath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
}
function drawLegend(texts, x, y, lineheight) {
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillStyle = 'black';
ctx.font = '12px arial';
for (var i = 0; i < texts.length; i++) {
ctx.fillText(texts[i], x, y + i * lineheight);
}
}
function plotResult(val1, val2, val3) {
let deltaX, length;
let sum = val1 + val2 + val3;
const cos60 = Math.cos(Math.PI / 3);
const sin60 = Math.sin(Math.PI / 3);
let ch4 = val1 / sum;
let c2h2 = val2 / sum;
let c2h4 = val3 / sum;
length = Math.sqrt(Math.pow((v1.x - v0.x), 2) + Math.pow((v1.y - v0.y), 2));
let ch4PointA = new Point(v0.x + (length * ch4) * cos60, v0.y - (length * ch4) * sin60);
length = Math.sqrt(Math.pow((v2.x - v1.x), 2) + Math.pow((v2.y - v1.y), 2));
let ch4PointB = new Point(v2.x - (length * ch4) * cos60, v2.y - (length * ch4) * sin60);
length = Math.sqrt(Math.pow((v1.x - v2.x), 2) + Math.pow((v1.y - v2.y), 2));
let c2h4PointA = new Point(v1.x + (length * c2h4) * cos60, v1.y + (length * c2h4) * sin60);
deltaX = (v2.x - v0.x) * c2h4;
let c2h4PointB = new Point(v0.x + deltaX, v0.y);
let point = getIntersection(ch4PointA, ch4PointB, c2h4PointA, c2h4PointB);
ctx.beginPath();
ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI, false);
ctx.fillStyle = "red";
ctx.fill();
}
function getIntersection(pointA, pointB, pointC, pointD) {
let denominator, a, b, numeratorA, numeratorB;
denominator = ((pointD.y - pointC.y) * (pointB.x - pointA.x)) - ((pointD.x - pointC.x) * (pointB.y - pointA.y));
a = pointA.y - pointC.y;
b = pointA.x - pointC.x;
numeratorA = ((pointD.x - pointC.x) * a) - ((pointD.y - pointC.y) * b);
numeratorB = ((pointB.x - pointA.x) * a) - ((pointB.y - pointA.y) * b);
a = numeratorA / denominator;
b = numeratorB / denominator;
return new Point(pointA.x + (a * (pointB.x - pointA.x)), pointA.y + (a * (pointB.y - pointA.y)));
}
body {
background-color: ivory;
padding: 10px;
}
#canvas {
border: 1px solid red;
margin: 0 auto;
}
<canvas id="canvas" width=650 height=500></canvas>
semi-pie progress, what i want
Hello guys. How can i implement this kind of semi-pie progress bar with highcharts?
I tried alot and my result show as below photo. i want to create dataLabels same as first photo. if there is any way, please let me know.
i use this code for datalables:
dataLabels: {
formatter: function() {
return this.y > 1 ? '<b>' + this.point.name + ': ' + this.y + '%</b>' : null;
}
my result
So I created a guideline with custome functionality how to render a dataLabel which is 'attaching' to the pie point via using the SVGRenderer feature.
See code below and the demo: https://jsfiddle.net/BlackLabel/23ybcdjm/ and the version with path: https://jsfiddle.net/BlackLabel/u843xnoe/
chart: {
events: {
render() {
let chart = this,
series = chart.series[0],
point1 = series.points[0],
point2 = series.points[1],
shape1 = point1.shapeArgs,
shape2 = point2.shapeArgs,
distance,
distance2 = 50,
PI = -Math.PI,
label1,
label2,
x1,
y1,
x2,
y2;
//check if label exist while resize
if (chart.label1) {
chart.label1.destroy()
}
if (chart.label2) {
chart.label2.destroy()
}
//set x and y positions for labels
x1 = shape1.x + (shape1.r * Math.cos(shape1.end));
y1 = shape1.y + (shape1.r * Math.sin(shape1.end));
x2 = shape2.x + shape2.r + distance2;
y2 = shape2.y + chart.plotTop;
//render label1
chart.label1 = chart.renderer.label('Actual: ' + point1.y, x1, y1)
.css({
color: '#FFFFFF'
})
.attr({
fill: 'rgba(0, 0, 0, 0.75)',
padding: 8,
zIndex: 6,
})
.add();
//render label2
chart.label2 = chart.renderer.label('Target: ' + point2.y, x2, y2)
.css({
color: '#FFFFFF'
})
.attr({
fill: 'rgba(0, 0, 0, 0.75)',
padding: 8,
zIndex: 6,
})
.add();
//find distance for label which is moving around the pie
if (shape1.end > PI && shape1.end <= 0.55 * PI) {
distance = -chart.label2.getBBox().width
} else if (shape1.end > 0.55 * PI && shape1.end <= 0.45 * PI) {
distance = -chart.label2.getBBox().width / 2
} else if (shape1.end > 0.5 * PI && shape1.end < 0.25 * PI) {
distance = chart.label2.getBBox().width / 3
} else if (shape1.end > 0.25 * PI && shape1.end <= 0) {
distance = chart.label2.getBBox().width / 2
}
label1 = chart.label1;
label2 = chart.label2;
//translate labels
label1.translate(label1.x + distance + chart.plotLeft, label1.y)
chart.label2.translate(chart.label2.x, chart.label2.y - chart.label2.getBBox().height)
}
}
},
API: https://api.highcharts.com/highcharts/chart.events.render
API: https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#label
How to show the min and max or x and x2 values in the chart and Need the partial fill to in value than %.
min = 0;
max = 150;
y=95; //points scored.
{
showInLegend: false,
pointWidth: 25,
data: [{
x: 0,
x2: 150,
y: 0,
partialFill: 0.75
}],
dataLabels: {
enabled: true
}
}
0 -------------------95points---------------150
Fiddle Example: https://jsfiddle.net/bv4uyazq/
If I understood you correctly you can achieve it following these approaches:
1) render custom labels using SVGRenderer:
Code:
chart: {
type: 'xrange',
events: {
render: function() {
var chart = this,
offsetTop = -8,
offsetLetf = 5,
x1,
x2,
y,
label1,
label2,
label2BBox;
if (chart.customLabels && chart.customLabels.length) {
chart.customLabels.forEach(function(label) {
label.destroy();
});
}
chart.customLabels = [];
chart.series[0].points.forEach(function(point) {
x1 = point.plotX + chart.plotLeft + offsetLetf;
x2 = x1 + point.shapeArgs.width - 2 * offsetLetf;
y = point.plotY + chart.plotTop + point.shapeArgs.height / 2 + offsetTop;
label1 = chart.renderer.text(chart.xAxis[0].dataMin, x1, y).css({
fill: '#fff'
}).add().toFront();
label2 = chart.renderer.text(chart.xAxis[0].dataMax, x2, y).css({
fill: '#fff'
}).add().toFront();
label2BBox = label2.getBBox();
label2.translate(-label2BBox.width, 0);
chart.customLabels.push(label1);
chart.customLabels.push(label2);
});
}
}
}
Demo:
https://jsfiddle.net/BlackLabel/twuv96ex/1/
2) set xAxis labels positions as dataMin and dataMax and translate it using offset property:
Code:
xAxis: {
type: 'linear',
visible: true,
offset: -110,
tickPositioner: function() {
return [this.dataMin, this.dataMax];
}
}
Demo:
https://jsfiddle.net/BlackLabel/m7s4pfo5/
API reference:
https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#text
https://api.highcharts.com/class-reference/Highcharts.DataLabelsOptionsObject#formatter
https://api.highcharts.com/highcharts/xAxis.offset