Highcharts Annotation SVG doesn't move with other shapes - javascript

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

Related

Highcharts: Can I animate changing the order of bars on bar chart?

I want to create a chart like the below.
https://www.reddit.com/r/interestingasfuck/comments/9togwf/the_major_world_economies_over_time/
I created this chart by Highcharts. But, I can't animate changing the order of bars.
function rotate(array, times) {
while (times--) {
var temp = array.shift();
array.push(temp)
}
}
window.data = {
categories: ['Africa', 'America', 'Asia', 'Europe', 'Oceania'],
y_values: [100, 200, 300, 400, 500],
colors: ['#DC4D3A', '#E93D3F', '#83C6C7', '#46D388', '#D1D785']
};
document.getElementById("button").addEventListener("click", function () {
for (var i = 0; i < data['y_values'].length; i++) {
chart.series[0].data[i].update({y: data['y_values'][i]});
chart.series[0].data[i].update({color: data['colors'][i]});
}
chart.xAxis[0].update({categories: data['categories']});
rotate(data['y_values'], 1);
rotate(data['categories'], 1);
rotate(data['colors'], 1);
}, false);
All code I wrote are in JSFiddle.
https://jsfiddle.net/Shinohara/35e8gbyz/
Please anyone can help me?
Highcharts provides animate method for SVG elements, which you can use to achieve the wanted result. You need to animate columns, axis labels and data labels:
document.getElementById("button").addEventListener("click", function() {
var points = chart.series[0].points,
ticks = chart.xAxis[0].ticks;
points[0].graphic.animate({
x: points[1].shapeArgs.x
});
points[1].graphic.animate({
x: points[0].shapeArgs.x
});
points[0].dataLabel.animate({
y: points[1].dataLabel.translateY
});
points[1].dataLabel.animate({
y: points[0].dataLabel.translateY
});
ticks[0].label.animate({
y: ticks[1].label.xy.y
});
ticks[1].label.animate({
y: ticks[0].label.xy.y
});
}, false);
Live demo: https://jsfiddle.net/BlackLabel/ux59vcd6/
API Reference: https://api.highcharts.com/class-reference/Highcharts.SVGElement#animate
It seems there is no native implementation.
The only idea I have it to not display Y axis labels at all. Using SVGRenderer
draw the text and save it as a variable (for further updating)
chart.customText = chart.renderer.text('label name', 100, 100) // label name, X, Y
.attr({
useHTML: true
})
.css({
fontSize: '16px' // and other CSS if necessary
})
.add();
Then you can update x, y or text
chart.customText.attr({
str: 'new text. You can use HTML',
y: 150 // new value
})
Your next step is to do something with Y position. Or even you can try to draw only 1 text with HTML that contains all labels. Then using JS move them as you need.

Is it possible to fill particular portion between given start & end angle in pie chart in chart.js?

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/

c3 detect hover over bar

I wish to have tooltips AND do some custom stuff when a user hovers over a .c3-bar. This is both not supported by the c3 api and made difficult because the way c3 manages pointer events (by using css to turn them off). If I turn .c3-bar to pointer-events:auto !important tooltips stop working, but otherwise I can't listen to events on .c3-bar. Does anyone know how to address this?
I got it to work, but its ugly ugly
const c3Hover = 'c3-hover'
const barSelector = '.c3-bar'
const $wrapper = $('#' + this.chart.id)
const getBarRects = () =>
_.map($wrapper.find(barSelector),
(bar) => bar.getBoundingClientRect())
$wrapper.on('mousemove', ({clientX,clientY}) =>
_.reduce(getBarRects(), (acc, {left,right,top,bottom}) =>
acc || left <= clientX && clientX <= right
&& top <= clientY && clientY <= bottom
, false) ? $wrapper.addClass(c3Hover)
: $wrapper.removeClass(c3Hover))
There's a built-in way that reports an event when it hovers over data points, but it seems to fire once per series over a particular x value - i.e. on a bar chart with 2 series it will fire twice. Don't know if that's good enough for your situation?
var chart = c3.generate({
data: {
columns: [
['data1', 30, 200, 100, 400, 150, 250],
['data2', 130, 100, 140, 200, 150, 50]
],
type: 'bar',
onmouseover: function (d) {
console.log ("yo", d);
},
},
bar: {
width: {
ratio: 0.5 // this makes bar width 50% of length between ticks
}
}
});
outputs:
yo Object {x: 0, value: 30, id: "data1", index: 0, name: "data1"}
yo Object {x: 0, value: 130, id: "data2", index: 0, name: "data2"}
when moved over the first pair of bars (regardless of the particular one it's actually over)
http://c3js.org/reference.html#data-onmouseover
Edit: Aah, I see it also fires when you're not over the actual bar but also in the general vicinity. I guess this doesn't meet your requirements then.

FabricJs and Polygon transformed coordinates

We're new to Fabric.js. We're using it to draw polygons on our canvas in overlay on a real scene. We need to move, to rotate and resize the polygon and then get back the veterxes coordinates.
After the transformation we used the API.
var originalPolyPoints = pol.get('points').map(function (p)
{
return {x: p.x , y: p.y };
});
This way, the coordinates are always the original but not the changed ones.
How can we get the new coordinates?
Tried to get the transformMatrix of the polygon, but it was null. Even if we have the matrix, how can we apply it?
Answer is simple, just check code below:
var polygon = new fabric.Polygon(
[
{ x: 0, y: 0 },
{ x: 0, y: 100 },
{ x: 100, y: 100 },
{ x: 100, y: 0 },
{ x: 100, y: 200 },
],
{
fill: "",
stroke: "blue",
strokeWidth: 2,
}
);
polygon.on("modified", function () {
var matrix = this.calcTransformMatrix();
var transformedPoints = this.get("points")
.map(function (p) {
return new fabric.Point(
p.x - polygon.pathOffset.x,
p.y - polygon.pathOffset.y
);
})
.map(function (p) {
return fabric.util.transformPoint(p, matrix);
});
console.log(transformedPoints);
});
It has worked for me, hope works for you also:) .
Ref: https://stackoverflow.com/a/53710375/15481471
I think maybe you need to call setCoords on the objects after you modify them. This is a purposeful abstraction leak for performance reasons, according to the documentation.
The point is that all polygon's points have absolute coordinates to the canvas. In order to get actual points coordinates, you have to perform some transformation. It's required because there could be some movements and/or transformations applied to the polygon.
Check out the following answer: https://stackoverflow.com/a/53710375/4681279

Kineticjs Mouseover and click

I'm working on a board game with kineticjs and I'm facing a problem with mouse handling.
I have an image with a mousemove event that draw a preview selection.
The problem is with the click event on the same image that is firing only 1 on 9 times..
If I remove the on_mousemove the on_click is working perfectly..
Anyone has an explanation and/or workaround ?
Thanks
Edit
Fiddle link: http://fiddle.jshell.net/gTuCE/12/
Your code was a bit messy so I cleaned it up and it's working now: jsfiddle
In your function createSquareItem(), squareLayer was undefined.
You only have to add Kinetic.Layers once. In both your createSquareItem createTemporarySquareItems functions, you were adding mainLayer to the stage every time you call the function. This is wrong since you already added mainLayer to the stage earlier.
In your createTemporarySquareItems function, I had to add mainLayer.drawScene() and in createSquareItem I had to add mainLayer.draw()
function createSquareItem(point, color) {
var boardPoint = point;
var rect = new Kinetic.Rect({
x: boardPoint.x,
y: boardPoint.y,
width: 18,
height: 18,
fill: color,
stroke: 'black',
strokeWidth: 0
});
mainLayer.add(rect);
//squareLayer.drawScene(); //squareLayer is undefined.
mainLayer.draw();
}
function createTemporarySquareItems(point, cleanLayer) {
if (cleanLayer == true) {
do {
var r = tempSquareArray.pop();
if (r != null) {
//tempSquareLayer.remove(r);
r.destroy();
}
} while (r != null);
}
var boardPoint = point;
var rect = new Kinetic.Rect({
x: boardPoint.x,
y: boardPoint.y,
width: 18,
height: 18,
fill: 'orange',
stroke: null,
strokeWidth: 0
});
tempSquareArray.push(rect);
mainLayer.add(rect);
mainLayer.drawScene();
}
For more information on KineticJS' draw methods, see here: What is the difference between KineticJS draw methods?

Categories