I have implemented a Highchart.js Variable pie chart in my application. I need show outer circle / border to the whole chart, not to the individual slices, so that we can easily see how much of the slice is filled completely or partially.
I tried borderWidth, lineWidth etc, but no luck. Is there any way to implement the same? Need the output somewhat like the image.
You can render all additional svg elements by using Highcharts.SVGRenderer class. Here you can find an example with the required circle:
events: {
render: function() {
const series = this.series[0],
center = series.center,
x = center[0] + this.plotLeft,
y = center[1] + this.plotTop,
r = Math.max(...series.radii);
if (!this.customCircle) {
this.customCircle = this.renderer.circle(
x, y, r
).attr({
stroke: 'red',
zIndex: 3,
fill: 'rgba(0,0,0,0)',
'stroke-width': 1
}).add();
} else {
this.customCircle.attr({ x, y, r });
}
}
}
Live demo: https://jsfiddle.net/BlackLabel/r985mdkh/
API Reference: https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#circle
Related
in my Highcharts project, I successfully make all the shapes in a single annotation draggable, but when I add the SVG (the type of this shape is "path"), the SVG doesn't move along with other shapes in the annotation.
I need to have some customised shapes in the annotation. Can anyone point out what the issue is for this SVG? Is it possible to put the SVG within annotation and still be draggable? Or is it some mistake I made that causes the issue?
My example here. https://jsfiddle.net/roy_jaide/754xuhtk/ As you can see, the label, circle and the line are all draggable, but the SVG shape just doesn't move at all.
Thanks for reading my question, and much appreciated if any solution provided.
Highcharts.Annotation.prototype.onDrag = function (e) {
if (this.chart.isInsidePlot(e.chartX - this.chart.plotLeft, e.chartY - this.chart.plotTop)) {
var translation = this.mouseMoveToTranslation(e),
xAxis = this.chart.xAxis[0],
yAxis = this.chart.yAxis[0],
xStep = this.options.stepX,
yStep = this.options.stepY,
pxStep = xAxis.toPixels(xStep) - xAxis.toPixels(0),
pyStep = yAxis.toPixels(yStep) - yAxis.toPixels(0);
if (this.options.draggable === 'x') { //for now, it's exclusive for age handle
this.potentialTranslationX += translation.x;
if (Math.abs(this.potentialTranslationX) >= Math.abs(pxStep)) {
translation.x = (this.potentialTranslationX > 0) ? pxStep : -pxStep;
translation.y = 0;
this.currentXValue += (this.potentialTranslationX > 0) ? xStep : -xStep;
this.potentialTranslationX -= (this.potentialTranslationX > 0) ? pxStep : -pxStep; //minus the step and continue to cumulate
//this.potentialTranslation = 0; //not correct, the mouse will go faster than the handle
if (this.points.length) {
this.translate(translation.x, 0);
} else {
this.shapes.forEach(function (shape) {
shape.translate(translation.x, 0);
});
this.labels.forEach(function (label) {
label.translate(translation.x, 0);
label.text = label.annotation.options.preText + label.annotation.currentXValue;
});
}
}
}
this.redraw(false);
}
}
Update 1: After trying Sebastian's answer on my chart, it turns out to be difficult to calculate the correct coordinates. At last I use type "image" to put display the shape. The shape is a Font-Awesome icon so before using "image" I did try to add a label with "useHTML" : true, but it seems the icon is moved a little after the first redraw(false), not sure why.
The image of the shape. I achieved this by adding "image" shape.
d: ["M", 440, 72, "L", 410, 45, 470, 45, "Z"],
I wouldn't recommend this way to create a draggable custom shape. This option creates a shape as expected but also sets a fixed position. Probably it is possible to implement move functionality, but I think that it will require a lot of changes into draggable core code.
What I can suggest is to make it this way:
annotations: [{
draggable: 'x',
shapes: [{
points: [{
x: 440,
y: 72
}, {
x: 410,
y: 45
}, {
x: 470,
y: 45
}, {
x: 440,
y: 72
}],
fill: 'red',
type: 'path',
stroke: "blue",
strokeWidth: 3,
markerStart: 'circle',
}]
}]
Where markerStart is a defined shape.
See a Demo
I want a circular chart where i can pass start angle and end angle and the portion between then get filled with some color. Is it possible in chart.js pie chart? And not then is there any other chart which is giving such kind of functionality?
Any help would be appreciated.
You can use Highcharts pie chart and wrote custom function that will 'fill' your chart depending on start angle and end angle you will pass in your point.
Your data may look like that:
data: [{
startAngle: 0,
endAngle: 105,
color: 'green'
}, {
startAngle: 315,
endAngle: 360,
color: 'green'
}]
And your custom function may be similar to this function:
function(chart) {
var series = chart.series[0],
start = series.data[0].startAngle,
updatedData = [start],
pointBetween,
point;
Highcharts.each(series.data, function(p) {
pointBetween = p.startAngle - start;
if (pointBetween !== 0) {
updatedData.push({
y: p.startAngle - start
});
}
point = {
color: p.color,
y: p.endAngle - p.startAngle
}
updatedData.push(point);
start = p.endAngle;
});
updatedData.push({
y: 360 - start
})
series.setData(updatedData);
}
I have made very simple example showing how your chart may look with this function:
http://jsfiddle.net/46vfygqu/
I just want to show table just below the X-axis column charts (respective bars), I know grouping can create table but i don't want grouping to be used. just need table for x-axis values. As shown in below image,
Can i do this using highcharts properties. Only table will be present to x-axis labels.
Is it possible using highcharts lib ?
You can make ticks longer by setting xAxis.tickLength property.
And then, render an additional line in the bottom of the ticks.
function renderBottomLine() {
var chart = this,
axis = chart.xAxis[0],
line = axis.bottomLine,
x1 = chart.plotLeft,
y = chart.plotTop + chart.plotHeight + axis.options.tickLength,
x2 = x1 + chart.plotWidth,
path = [
'M', x1, y,
'L', x2, y
];
if (!line) {
axis.bottomLine = chart.renderer.path(path).attr({
'stroke-width': 1,
stroke: '#ccd6eb'
}).
add();
} else {
line.animate({
d: path
});
}
}
Highcharts.chart('container', {
chart: {
events: {
load: renderBottomLine,
redraw: renderBottomLine
}
},
example: http://jsfiddle.net/xuyyt6nd/
I have the following code which creates me rectangle that contains some text. I need to create multiple addressable instances of this rectangle so that I can individually animate them. Each rectangle needs to contain a different text label.
var s = Snap(800, 600);
var block = s.rect(50, 50, 100, 100, 5, 5);
block.attr({
fill: "rgb(236, 240, 241)",
stroke: "#1f2c39",
strokeWidth: 3
});
var text = s.text(70, 105, "Hello World");
text.attr({
'font-size':20
});
block.attr({
width: (text.node.clientWidth + 50)
});
Rather than repeating my code I would like to create a function that accepts the text and the coordinates for placing the rectangle. What is the best way to achieve this ? Is this capability already included within snap.svg ?
UPDATE
I created another plugin, this time to import and scale SVG images. Is this the best approach to take for this ? Is the only way to scale the image using the `transform attribute ?
Import SVG plugin example.
Snap.plugin( function( Snap, Element, Paper, global ) {
Paper.prototype.importImage = function( x, y, scale ) {
var ig1 = s.group();
var image = Snap.load("../package.svg", function ( loadedFragment ) {
ig1.attr({transform: 'translate(' + x + ',' + y + ') scale('+ scale +')'});
ig1.append( loadedFragment);
} );
return ig1;
}
});
You could create a plugin to give you a new element option that does it for you, for example...
Snap.plugin( function( Snap, Element, Paper, global ) {
Paper.prototype.textRect = function( text, x, y ) {
var block = s.rect(x, y, 100, 100, 5, 5)
.attr({
fill: "rgb(236, 240, 241)",
stroke: "#1f2c39",
strokeWidth: 3,
});
var text = s.text(x + 20, y + 50, text).attr({ 'font-size': 20 });
block.attr({ width: (text.node.clientWidth + 50) });
}
});
var s = Snap(800,800)
s.textRect('Hi', 100, 100 );
s.textRect('There', 100, 200 );
example fiddle
You may want to put them both in a 'g' group element if you will move them around or drag them or something, so you can perform operations on the group.
I have a Highcharts bar chart that I'm trying to add custom shapes to based on the bar values and position. To start with, I'm just trying to use highcharts.renderer.path, to add a line for each bar, as tall as the bar, positioned on the x axis based on a hard coded value. Here's a picture of what I mean:
This should be easy, and it is when the chart.type = "column". In the highcharts callback, I would use getBBox() on each bar, and translate() to convert the x axis value to a pixel value.
However, I've run into several problems when trying to do this with chart.type = "bar". First, all x and y values are switched (I assume this is how the author created the bar chart from a column chart in the first place). This is true for all the properties of the chart as well: plotLeft is now the top, plotTop is now the left.
This should work:
function (chart) {
$.each(chart.series[0].data, function (pointIndex, point) {
var plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
yStart = chart.plotTop+elem.x;
xStart = chart.plotLeft+elem.height;
plotLine.path = ["M", xStart, yStart+1, "L", xStart, yStart+point.pointWidth];
plotLine.attr = {
'stroke-width': 1,
stroke: point.color,
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
});
});
Full example: http://jsfiddle.net/Bh3J4/9/
The second issue may be a bug that can't be overcome. It appears that when there is more than one data point, all of the x and y values get mixed up between the points. Notice in the fiddle that the colors don't match the positions. I've created an issue on GitHub.
When there's just one point, it's not a problem. When there are two points, I could easily switch the values to get the right positioning. However when there are 3 or more points, I can't seem to figure out the logic for how the values get mixed up.
The third issue, is that the translate function doesn't seem to work on the xAxis for a bar chart, even though it does on the yAxis.
chart.yAxis[0].translate(4); // correct for bottom axis
chart.xAxis[0].translate(1); // incorrect for side axis
Is there another way to achieve what I'm looking for? Am I missing something in that Fiddle that's not actually a bug?
I was able to achieve the result I wanted, but I don't know if it's coincidental or a workaround for an actual bug. Regardless, it seems that using the x value from the reverse sorted array helped me line everything up correctly. Here's the callback function for highcharts:
function (chart) {
var benchmarks = { A: 1.5, B: 3.6, C: 2 },
reverseData = _.clone(chart.series[0].data).reverse();
_.each(chart.series[0].data, function (point, pointIndex) {
var plotLine = {},
elem = point.graphic.element.getBBox(),
reverseElem = reverseData[pointIndex].graphic.element.getBBox(),
benchmark = benchmarks[point.category],
yStart = chart.plotTop+reverseElem.x,
xStart = chart.plotLeft+chart.yAxis[0].translate(benchmark),
yEnd = yStart+point.pointWidth-1;
plotLine.path = ["M", xStart, yStart+1, "L", xStart, yEnd];
plotLine.attr = {
'stroke-width': 1,
stroke: "red",
zIndex: 5
};
chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
var margin = 5,
xPadding = 10,
yPadding = 5,
xSplit = xPadding/2,
ySplit = yPadding/2,
text,
box;
text = chart.renderer.text("Top Perf Avg " + benchmark, xStart, yEnd+margin+16).attr({
color: "#646c79",
align: "center",
"font-family": "Arial, sans-serif",
"font-size": 9,
"font-weight": "bold",
style: "text-transform: uppercase",
zIndex: 7
}).add();
box = text.getBBox();
chart.renderer.path(["M", box.x-xSplit, box.y-ySplit,
"l", (box.width/2)+xSplit-margin, 0,
margin, -margin,
margin, margin,
(box.width/2)+xSplit-margin, 0,
0, box.height+yPadding,
-(box.width+xPadding), 0,
0, -(box.height+yPadding)])
.attr({
'stroke-width': 1,
stroke: "#cccccc",
fill: "#ffffff",
zIndex: 6
}).add();
});
}
See the complete working graph here: http://jsfiddle.net/Bh3J4/18/
In the fact, Highcharts rotate everything using transform, so use the same to rotate these lines, see example: http://jsfiddle.net/Bh3J4/19/
function (chart) {
var d = chart.series[0].data,
len = d.length;
for(var i =0; i < len; i++){
var point = d[i],
plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
console.log(point,point.color);
xStart = point.plotX - point.pointWidth / 2;
yStart = point.plotY;
plotLine.path = ["M", xStart, yStart, "L", xStart+point.pointWidth, yStart];
plotLine.attr = {
transform: 'translate(491,518) rotate(90) scale(-1,1) scale(1 1)',
'stroke-width': 1,
stroke: point.color,
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
};
}
Slight adjustment that seems to give precise alignment:
Pls note: changes to calc of xStart/yStart and change to transform translate parameter.
My approach was to make it work for column chart and then get translate refined.
The only unsatisfactory part is that xStart needs: xStart = elem.x+chart.plotLeft; in 'column' mode vs xStart = elem.x; in 'bar' mode...
function (chart) {
var d = chart.series[0].data,
len = d.length;
for(var i =0; i < len; i++){
var point = d[i],
plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
console.log(point,point.color);
xStart = elem.x;
yStart = chart.plotHeight - (elem.height/2) + chart.plotTop;
plotLine.path = ["M", xStart, yStart, "L", xStart+point.pointWidth, yStart];
plotLine.attr = {
transform: 'translate(542.5,518) rotate(90) scale(-1,1) scale(1 1)',
'stroke-width': 5,
stroke: 'blue',
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
};
}