I need to draw custom markers on a highchart at particular intervals.
I have managed to create a custom bar using the following javascript:
Highcharts.Renderer.prototype.symbols.hline =
function(x, y, width, height) {
return ['M',x-30 ,y + height / 2,'L',x+width+30,y + width / 2];
};
https://jsfiddle.net/jimmain/9gqca584/5/
My problem is that I need to also draw a single pixel border around the pink box.
I am using a stacked bar chart for the chart.
Ideally I would also like to increase the padding underneath the bar (between the bar and the x-axis) but I am not sure it can be independently increased.
It's not clear to me how I can use the renderer to change color. I could just draw one black box, and then another smaller inset pink box on top, but I am not clear on how to change colors in the SVG renderer.
SVG's path can not have border, so solution is to use renderer.rect(): https://jsfiddle.net/BlackLabel/9gqca584/43/
Note: It's bar series, so chart is inverted, that means we need to swap x with y and height with width.
Snippet:
function addRect(chart) {
return chart.renderer.rect(
chart.yAxis[0].toPixels(5) - 4, // 4 = half width
chart.xAxis[0].toPixels(0),
8,
5
).attr({
fill: 'rgba(253,0,154,0.9)',
stroke: 'black',
'stroke-width': 2,
zIndex: 5
}).add();
}
function positionRect(chart, rect) {
rect.animate({
x: chart.yAxis[0].toPixels(5) - 4, // 4 = half width
y: chart.xAxis[0].toPixels(0) - chart.series[0].points[0].pointWidth / 2,
height: chart.series[0].points[0].pointWidth
});
}
$(function() {
$('#container').highcharts({
legend: {
enabled: false
},
chart: {
type: 'bar',
events: {
load: function() {
this.customRect = addRect(this);
positionRect(this, this.customRect);
},
redraw: function() {
positionRect(this, this.customRect)
}
}
},
...
});
});
Related
I am trying to create a line graph, wherein if all the values for the particular series is zero then, I want the line to be drawn on the bottom over the x-axis line. But it seems like the line of the x-axis takes the preference than the series line. Is there any way to change that?
Below is my configuration:
xAxis: {
type: "datetime",
tickmarkPlacement: "on",
lineWidth: 10,
lineColor: 'red',
},
yAxis: {
min: 0,
minRange : 0.1,
title: {
text: ""
}
},
plotOptions: {
line: {
softThreshold: false
}
},
Example:
http://jsfiddle.net/shivajyothibalavikas/twxs7mL2/3
In the above example the axis line color is red and series line color is black. but red takes the preference. I want the black line to take the preference while displaying
It is not possible to move a series line above an axis line by options in Highcharts API. As a solution, you can use the Highcharts.SVGRenderer class and draw a custom line at the chart bottom.
chart: {
...,
events: {
render: function() {
const chart = this;
const x1 = chart.plotLeft;
const x2 = x1 + chart.plotWidth;
const y = chart.plotTop + chart.plotHeight;
if (!chart.customLine) {
chart.customLine = chart.renderer
.path().attr({
stroke: 'red',
'stroke-width': 10
}).add();
}
chart.customLine.attr({
d: ['M', x1, y, 'L', x2, y]
});
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/70d6sg2o/
API Reference: https://api.highcharts.com/class-reference/Highcharts.SVGRenderer
I have a line chart with some background polygons that need labels, much like in this post. However, my chart uses the zoom capabilities of HighCharts. The chart.events.render will draw new labels on zoom, but it doesn't remove the old ones, which do not zoom either. Using this.customLabel.destroy() doesn't seem to do the job. So I end up with excess labels in the wrong places. How do I remove the old labels with each new render, or is there now a better way to label polygons?
Try this approach:
chart: {
events: {
render() {
const chart = this;
if (chart.customLabel) {
chart.customLabel.destroy()
}
chart.customLabel = chart.renderer.label('test label', Math.random() * 200 + chart.plotLeft, Math.random() * 200 + chart.plotTop)
.css({
color: '#FFFFFF'
})
.attr({
fill: 'rgba(0, 0, 0, 0.75)',
padding: 8,
r: 5,
zIndex: 6
})
.add();
}
}
},
Demo: https://jsfiddle.net/BlackLabel/g1v4dwcn/ - resize the viewer in the jsfiddle to see the effect.
Edit by original poster: see comment about "two different SVG Elements"
I have a Highcharts activity gauge that has two series. I am trying to place labels at the starting point of each ring. I have it working with hardcoded x,y coordinates, but I'm wondering if there is a way to calculate the location instead. It looks like this currently:
Here is the code I am using to add the labels in the chart render event:
function render() {
var chart = this;
chart.renderer.label('Completed 65%', 24, 25, 'rect', 0, 0, true, true, '')
.add();
chart.renderer.label('Follow-up 45%', 28, 42, 'rect', 0, 0, true, true, '')
.add();
}
I'd like to calculate the x,y values in the chart.renderer.label() function instead of hardcoding them to 24,25 and 28,42. However, I have not been able to find anything in the object model to locate the physical location of the series starting x and y, or the size of the label. I have many of these activity gauges to complete and going through them all and trying to find the magic coordinates seems like the wrong approach.
You can follow the same approach as icons rendered in the official Activity gauge demo here: https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/gauge-activity/
There you will find position calculated using series point shapeArgs. I modified that function to render labels as you expected. Check it in the demo posted below.
Function code:
function renderLabels() {
var offsetTop = 5,
offsetLeft = 5;
if (!this.series[0].label) {
this.series[0].label = this.renderer
.label('Completed 65%', 0, 0, 'rect', 0, 0, true, true)
.add(this.series[1].group);
}
this.series[0].label.translate(
this.chartWidth / 2 - this.series[0].label.width + offsetLeft,
this.plotHeight / 2 - this.series[0].points[0].shapeArgs.innerR -
(this.series[0].points[0].shapeArgs.r - this.series[0].points[0].shapeArgs.innerR) / 2 + offsetTop
);
if (!this.series[1].label) {
this.series[1].label = this.renderer
.label('Follow-up 45%', 0, 0, 'rect', 0, 0, true, true)
.add(this.series[1].group);
}
this.series[1].label.translate(
this.chartWidth / 2 - this.series[1].label.width + offsetLeft,
this.plotHeight / 2 - this.series[1].points[0].shapeArgs.innerR -
(this.series[1].points[0].shapeArgs.r - this.series[1].points[0].shapeArgs.innerR) / 2 + offsetTop
);
}
Function invocation:
chart: {
type: 'solidgauge',
events: {
render: renderLabels
}
}
Demo:
https://jsfiddle.net/BlackLabel/vpj32tmy/
I'm having a hard time making the labels in the y-axis responsive.I want the labels to move to multiple lines and have responsive font sizes when the space is not enough.I'm using the chart.js datalabels library for labelling on top of horizontalBar Graph.Also the labels are getting hidden due to the outer chart container.
var chart = new Chart('ctx', {
type: 'horizontalBar',
data: {
labels: ["Something something something", "blah blah..", "blah blah..","Something something something"],
datasets: [{
data: [6, 87, 56,25,100,65],
backgroundColor: "#4082c4"
}]
},
options:{
responsive: true,
maintainAspectRatio: false,
plugins: {
datalabels: {
color: 'black',
anchor: "end",
align: "right",
offset: 20,
display: function (context) {
return context.dataset.data[context.dataIndex];
},
font: {
weight: 'bold',
size: 26
},
formatter: Math.round
}
},
legend: {
"display": false
},
tooltips: {
"enabled": false
},
scales: {
yAxes: [{
barPercentage: 1.0,
gridLines: {
display: false
},
ticks: {
fontSize: 20,
beginAtZero: true,
}
}],
xAxes: [{
gridLines: {
display: false
},
ticks: {
min: 0,
max: 100,
stepSize: 20
}
}]
}
}
})
The numbers in the right side of the bar also gets clipped of.I want the chart to be at the center horizontally.In the browser the chart looks like this-
Link to the fiddle:-https://jsfiddle.net/24wdpfxL/
You can do this, but it's a bit hack-y.
First the data labels. In the datalabels config section, you can try something like:
/* Adjust data label font size according to chart size */
font: function(context) {
var width = context.chart.width;
var size = Math.round(width / 32);
return {
weight: 'bold',
size: size
};
}
Change the size calculation as necessary.
For the y-axis labels, there's an answer here, however apparently since Chart.js 2.7.0, the line:
c.scales['y-axis-0'].options.ticks.fontSize
..should be changed to:
c.scales['y-axis-0'].options.ticks.minor.fontSize
(ref)
So to scale the y-axis labels font size according to chart height, it might look like:
plugins: [{
/* Adjust axis labelling font size according to chart size */
beforeDraw: function(c) {
var chartHeight = c.chart.height;
var size = chartHeight * 5 / 100;
c.scales['y-axis-0'].options.ticks.minor.fontSize = size;
}
}]
Note: This requires "maintainAspectRatio:" to be set to "true".
There's still one problem however, and that's that the part of the chart containing the y-axis labels will remain at the same pixel width even when resized.
We need to also resize this area to keep it at a constant % of the overall chart width, e.g. 40%, instead of a fixed pixel width (added to yAxes config section):
/* Keep y-axis width proportional to overall chart width */
afterFit: function(scale) {
var chartWidth = scale.chart.width;
var new_width=chartWidth*0.4;
scale.width = new_width;
}
(You might not notice this as a problem with your original example, since there is a oversized line that seems to cause the y-axis width to keep expanding when the window is enlarged. But when the labels don't overflow, then the width stays constant unless the above is used.)
Complete jsFiddle: https://jsfiddle.net/0kxt25v3/2/
(fullscreen)
I'm not sure about wrapping labels on to the next line, you might just need to pre-process the labels to limit the maximum number of characters per label.
I also haven't attempted to scale the x-axis label font sizes, but it should be easy enough to add it in to the "beforeDraw:" section.
If you're using chartjs-plugin-datalabels; here's how i was able to make the labels responsive. Considering the chart may have variable width and height, we can get the average of both (height and width) and calculate the font size. I am also setting the max font size limit to 12.
datalabels: {
font: function (context) {
var avgSize = Math.round((context.chart.height + context.chart.width) / 2);
var size = Math.round(avgSize / 32);
size = size > 12 ? 12 : size; // setting max limit to 12
return {
size: size,
weight: 'bold'
};
},
}
I ran into the clipping problem myself recently and fixed this by setting a suggestedMax value that was wider than the largest value in my dataset.
I would like to draw a circle on a scatter plot to highlight values outside of the acceptable range.
I tried to use chart.renderer.circle but that uses the x and y pixels of the SVG element. I still want to be able to zoom in or out, so absolute x and y values don't work.
Is this possible with Highchart?
Edit:
Added Mockup:
Two ideas...
ONE
Use chart.renderer.circle and translate the point values to pixels.
Say you want to draw a circle at:
var circleX = 161.2; // x coordinate
var circleY = 51.6; // y coordinate
var circleR = 1.0; // radius size in terms of x axis distance
Draw the circle as:
function addCircle(chart){
if (this.circle){
// on a redraw, remove old circle
$(this.circle.element).remove();
}
// translate my coordinates to pixel values
var pixelX = chart.xAxis[0].toPixels(circleX);
var pixelY = chart.yAxis[0].toPixels(circleY);
var pixelR = chart.xAxis[0].toPixels(circleR) - chart.xAxis[0].toPixels(0);
// add my circle
this.circle = chart.renderer.circle(pixelX, pixelY, pixelR).attr({
fill: 'transparent',
stroke: 'black',
'stroke-width': 1
});
this.circle.add();
}
You then call this in the load and redraw events and all is awesome:
events: {
load: function(){
addCircle(this);
},
redraw: function(){
addCircle(this);
}
}
Here's a fiddle example.
TWO
Add the circle as another series, make the marker a giant transparent circle:
{
data: [[circleX, circleY]],
linkedTo: 'other', // link it to other series to avoid legend entry
marker: {
radius: 40,
lineColor: 'red',
fillColor: 'transparent',
lineWidth: 1,
symbol: 'circle'
}
}
Here's a fiddle for this one.