I have a chart with an events.load function that draws some lines based on the charts properties.
The loading function works exactly how I'd like it to, but I'd like to erase and re-draw the lines each time the chart gets redrawn (such as hiding a series).
I added the same function (with erasing) on the chart.events.redraw function thinking this would do the trick, but the object given to the redraw() function is the previous chart properties, not the new properties.
For example in the fiddle, if you hide Canada, the x axis changes, but the lines don't get rendered. But click Canada again to un-hide, and the chart is re-drawn, but with the prior properties.
Is there a way to redraw but with the newly redrawn chart properties?
Thanks!
See (fiddle).
events : {
load: function(){
var ren = this.renderer;
// Get data from the highcharts object
var plot = this.plotBox;
var zeroGridLine = this.yAxis[0].ticks[0].gridLine.d;
var zeroGridLineArray = zeroGridLine.split(' ');
var topPos = plot.y; // top of the chart
var zeroPos = parseFloat(zeroGridLineArray.slice(-1)[0]); // position of the zero line
var bottomPos = topPos + plot.height; // bottom of the chart
var vertLinePos = parseFloat(zeroGridLineArray.slice(-2)[0]) + 8; // vertical line position
var horizWidth = 5; // width of the horizontal lines
var strokeWidth = 1; // thickness of the line
var stroke = 'black'; // color of the line
// exports vertical line
ren.path(['M', vertLinePos, topPos, 'L', vertLinePos, zeroPos])
.attr({
stroke: stroke,
'stroke-width': strokeWidth,
id: 'impExpLines_0'
})
.add();
// imports vertical line
ren.path(['M', vertLinePos, zeroPos, 'L', vertLinePos, bottomPos])
.attr({
stroke: stroke,
'stroke-width': strokeWidth,
id: 'impExpLines_1'
})
.add();
// Horizontal line to separate import/export
ren.path(['M', vertLinePos - horizWidth, zeroPos, 'L', vertLinePos + horizWidth, zeroPos])
.attr({
stroke: stroke,
'stroke-width': strokeWidth,
id: 'impExpLines_2'
})
.add();
// top horizontal line
ren.path(['M', vertLinePos - horizWidth, topPos, 'L', vertLinePos + horizWidth, topPos])
.attr({
stroke: stroke,
'stroke-width': strokeWidth,
id: 'impExpLines_3'
})
.add();
// bottom horizontal line
ren.path(['M', vertLinePos - horizWidth, bottomPos, 'L', vertLinePos + horizWidth, bottomPos])
.attr({
stroke: stroke,
'stroke-width': strokeWidth,
id: 'impExpLines_4'
})
.add();
// label imports and exports
ren.text('Exports',vertLinePos + 5,((zeroPos - topPos) / 2) + topPos + 3 )
.attr({id: 'impExpLines_5'})
.add();
// label imports and exports
ren.text('Imports',vertLinePos + 5,((bottomPos - zeroPos) / 2) + zeroPos + 3 )
.attr({id: 'impExpLines_6'})
.add();
},
redraw : function(){
// clear previosuly drawn lines
$("[id^=impExpLines_]").remove();
var ren = this.renderer;
// Get data from the highcharts object
var plot = this.plotBox;
var zeroGridLine = this.yAxis[0].ticks[0].gridLine.d;
var zeroGridLineArray = zeroGridLine.split(' ');
var topPos = plot.y; // top of the chart
var zeroPos = parseFloat(zeroGridLineArray.slice(-1)[0]); // position of the zero line
var bottomPos = topPos + plot.height; // bottom of the chart
var vertLinePos = parseFloat(zeroGridLineArray.slice(-2)[0]) + 8; // vertical line position
var horizWidth = 5; // width of the horizontal lines
var strokeWidth = 1; // thickness of the line
var stroke = 'black'; // color of the line
// exports vertical line
ren.path(['M', vertLinePos, topPos, 'L', vertLinePos, zeroPos])
.attr({
stroke: stroke,
'stroke-width': strokeWidth,
id: 'impExpLines_0'
})
.add();
// imports vertical line
ren.path(['M', vertLinePos, zeroPos, 'L', vertLinePos, bottomPos])
.attr({
stroke: stroke,
'stroke-width': strokeWidth,
id: 'impExpLines_1'
})
.add();
// Horizontal line to separate import/export
ren.path(['M', vertLinePos - horizWidth, zeroPos, 'L', vertLinePos + horizWidth, zeroPos])
.attr({
stroke: stroke,
'stroke-width': strokeWidth,
id: 'impExpLines_2'
})
.add();
// top horizontal line
ren.path(['M', vertLinePos - horizWidth, topPos, 'L', vertLinePos + horizWidth, topPos])
.attr({
stroke: stroke,
'stroke-width': strokeWidth,
id: 'impExpLines_3'
})
.add();
// bottom horizontal line
ren.path(['M', vertLinePos - horizWidth, bottomPos, 'L', vertLinePos + horizWidth, bottomPos])
.attr({
stroke: stroke,
'stroke-width': strokeWidth,
id: 'impExpLines_4'
})
.add();
// label imports and exports
ren.text('Exports',vertLinePos + 5,((zeroPos - topPos) / 2) + topPos + 3 )
.attr({id: 'impExpLines_5'})
.add();
// label imports and exports
ren.text('Imports',vertLinePos + 5,((bottomPos - zeroPos) / 2) + zeroPos + 3 )
.attr({id: 'impExpLines_6'})
.add();
}
}
In short, instead of using the actual drawn grid lines and parsing their location, use the toPixels function which is a utility method to translate an axis value to pixel position. In your code you have the line:
var zeroPos = parseFloat(zeroGridLineArray.slice(-1)[0]); // position of the zero line
Replace that line with:
var zeroPos = this.yAxis[0].toPixels(0); // position of the zero line
See this JSFiddle demonstration.
I didn't specifically read through the rest of your code, but you might also be able to more easily establish the top and bottom of the axis using this method instead of parsing.
Related
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
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 created the BBox on a Average line. i have created the BBox by the following code
$('#container').highcharts({
chart: {
zoomType: 'x',
},
.
.
.
.
}, function(chart){
if(chart.series[0].data[1])
{
var point = chart.series[0].data[1],
text = chart.renderer.text(
'Average='+Math.ceil(point.y)+" s",
point.plotX + chart.plotLeft - 45,
point.plotY + chart.plotTop - 13
).attr({
zIndex: 5
}).add(),
box = text.getBBox();
chart.renderer.rect(box.x - 5, box.y - 5, box.width + 10, box.height + 10, 5)
.attr({
fill: '#FFFFEF',
stroke: 'gray',
'stroke-width': 1,
zIndex: 4
})
.add();
}
});
the box have been created
I want to change the BBox location according to average line when the zoom property in role.
How can i do this ..Thanks in Advance
You need to use your code in the redraw() event in highcahrts. Take care about destroy (old rendered object) and create new one, or translate it.
Example:
http://jsfiddle.net/be2ebcg2/1/
I'm making an interactive diagram website and I'm trying to easily draw arrows in kineticJS relative to the stagesize, i've got the following:
http://jsfiddle.net/K5Zhg/17/
But as you can see the rotated arrow is not matching the intended start and ending point. I tried offset, but that messes up the correct (not rotated) arrow. Also don't know why jsfiddle show's my arrows this messy (missing bottom line), on my own machine it seems to be working fine (using v5.1.0), see also here http://tomzooi.com/dump/ip/ (using kineticjs and binding bootstrap to it, working nice so far)
code:
var stage = new Kinetic.Stage({
container: 'container',
width: 1140,
height: 500
});
var layer = new Kinetic.Layer();
var border = new Kinetic.Rect( {
x: 0,
y: 0,
width: stage.getWidth(),
height: stage.getHeight(),
stroke: ' red',
strokeWidth: 1
});
layer.add(border);
var dot = new Kinetic.Circle({
x: 0.1*stage.getWidth(),
y: 0.1*stage.getHeight(),
radius: 10,
fill: 'red'
});
var dot2 = new Kinetic.Circle({
x: 0.5*stage.getWidth(),
y: 0.1*stage.getHeight(),
radius: 10,
fill: 'red'
});
var dot3 = new Kinetic.Circle({
x: 0.5*stage.getWidth(),
y: 0.5*stage.getHeight(),
radius: 10,
fill: 'red'
});
layer.add(dot);
layer.add(dot2);
layer.add(dot3);
var arrow1 = arrow(0.1,0.1,0.5,0.1,10);
var arrow2 = arrow(0.1,0.2,0.5,0.5,10);
layer.add(arrow1);
layer.add(arrow2);
// add the layer to the stage
stage.add(layer);
function arrow(psx, psy, pex, pey, pw) {
var w = stage.getWidth();
var h = stage.getHeight();
var sx = psx*w;
var ex = pex*w;
var sy = psy*h;
var ey = pey*h;
var pr = (Math.atan2(ey-sy, ex-sx)/(Math.PI/180));
var pl = Math.sqrt(Math.pow((ex-sx),2)+Math.pow((ey-sy),2));
ex = sx+pl;
ey = sy;
var poly = new Kinetic.Line({
points: [sx,sy+pw, sx,sy-pw, ex-3*pw,ey-pw, ex-3*pw,ey-2*pw, ex,ey, ex-3*pw,ey+2*pw, ex-3*pw, sy+pw],
fill: '#EDECEB',
stroke: '#AFACA9',
strokeWidth: 2,
closed: true,
rotation: pr,
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {x:2,y:2},
shadowOpacity: 0.5
});
return poly;
}
Here is a solution: http://jsfiddle.net/K5Zhg/20/
The problem was that when you rotate the arrow shape, it rotates around x=0 and y=0. However your point was drawn on x=sx and y=sy (in your example case x=60 and y=50).
To fix this draw the points around x=0 and y=0 (and translating the other points in the array using the correct variables) and then setting the x and y property of the Kinetic.Line to sx and sy in order set the position back to its intended location. I.e.
function arrow(psx, psy, pex, pey, pw) {
var w = stage.getWidth();
var h = stage.getHeight();
var sx = psx*w;
var ex = pex*w;
var sy = psy*h;
var ey = pey*h;
// console.log(sx);
var pr = (Math.atan2(ey-sy, ex-sx)/(Math.PI/180));
var pl = Math.sqrt(Math.pow((ex-sx),2)+Math.pow((ey-sy),2));
ex = sx+pl;
ey = sy;
var poly = new Kinetic.Line({
points: [0,0+pw,
0,0-pw, ex-sx-3*pw,ey-sy-pw, ex-sx-3*pw,ey-sy-2*pw, ex-sx,ey-sy, ex-sx-3*pw,ey-sy+2*pw, ex-sx-3*pw, 0+pw],
fill: 'blue',
stroke: 'black',
strokeWidth: 2,
closed: true,
rotation: pr,
x: sx,
y: sy,
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {x:2,y:2},
shadowOpacity: 0.5
});
return poly;
}
I also changed the y value to 0.1 instead of the 0.2 such that the start of the second arrow connects with the red dot.
Oh and I updated the Fiddle to uses v5.0.1.
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();
};
}