RaphaelJs Pie Chart Animation on Hover - javascript

i am customizing the pie chart given on the raphael site below link
http://raphaeljs.com/pie.html
this chart shows animation when hover a slice, this animation simply expand the slice a little
what i want is to separate the slice from the chart
i played with the transform property of following lines of code but could not make it as i want.
p.mouseover(function () {
var xis= p.getBBox(true);
p.stop().animate({transform: "s1.1 1.1 "+ cx + " " + cy }, ms, "linear");
txt.stop().animate({opacity: 1}, ms, "linear");
}).mouseout(function () {
p.stop().animate({transform: ""}, ms, "linear");
txt.stop().animate({opacity: 0}, ms);
});
Changing the line 3's cx and cy actually fixed the animation for every slice in the same manner, that is, on hover every slice will change the position constantly.
http://imageshack.us/photo/my-images/690/sliced1.png
anyone please help me out to solve this problem

If I understand your question correctly, you want the slice to completely disconnect from the pie chart when somebody hovers over it.
To do this, you want to translate the segment, which allows you to move an SVG object in a given direction, toward x, y co-ordinates. I'm no SVG pro, so I'd suggest taking a look into the full functionality of this yourself; regardless, to do these types of operations with Raphael, you can use the Element.transform, or can provide transform values in an animate call.
The second of these is what is happening in the example you provided, except a scale transformation is being used, as indicated by the leading s in transform: "s1.1 1.1.. A scale will make the object bigger.
Here, you want to use a translation which moves the object but doesn't make it bigger - it uses a t.
Here is a slightly edited block of code that will do this:
p.mouseover(function () {
var distance = 20;
var xShiftTo = distance*Math.cos(-popangle * rad);
var yShiftTo = distance*Math.sin(-popangle * rad);
p.stop().animate({transform: "t" + xShiftTo + " " + yShiftTo}, ms, "bounce");
txt.stop().animate({opacity: 1}, ms, "bounce");
}).mouseout(function () {
p.stop().animate({transform: ""}, ms, "bounce");
txt.stop().animate({opacity: 0}, ms);
});
In the example, distance refers to how far the slice should move away (so feel free to adjust this), xShiftTo and yShiftTo calculate the x, y values that the object should shift by, relative to where they currently are. Note that this is a little complicated - you need to figure out the x, y values at a given angle away from the pie centre. The positioning of the text also does something like this, so I just took the required maths from there. Also, I just left the bounce animation, but you can change that to linear or whatever you want. Hope that helps.

You should probably be using .hover which is built into Raphael. See documentation here: http://raphaeljs.com/reference.html#Element.hover
Working off of Oli's example I was able to figure out most of the basic animation principles. Not being a math guru there were a lot of gaps to fill in for the example. Here is a fully functioning version (tested). Enjoy!
pie.hover(function () {
// Stop all existing animation
this.sector.stop();
// Collect/Create variables
var rad = Math.PI / 180;
var distance = 50; // Amount in pixels to push graph segment out
var popangle = this.sector.mangle; // Pull angle right out of segment object
var ms = 300; // Time in milliseconds
// Setup shift variables to move pie segments
var xShiftTo = distance*Math.cos(-popangle * rad);
var yShiftTo = distance*Math.sin(-popangle * rad);
this.sector.animate({transform: "t" + xShiftTo + " " + yShiftTo}, ms, "linear");
}, function () {
// Passing an empty transform property animates the segment back to its default location
this.sector.animate({ transform: '' }, ms, "linear");
});

Related

How can I calculate the new cursor position relative to my canvas after zooming in?

Context:
I try to create a coloring pixels game with Canvas.
As of right now, I render a few rects via strokeRect that can be painted onClick via fillRect.
Since the canvas is not full screen but just a fixed size I need to calculate the offset. When I have the coordinates I just divide the x with the rect width (10).
Here is the code I have.
First I get the correct cursor position:
function getCursorPosition(canvas, event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left
const y = event.clientY - rect.top
return { x: x, y: y };
}
Then I will calculate where to fillRectso it seems that I filled the strokeRect on exactly that position:
const paint = (e, isClick) => {
if (!isDrawing && !isClick) {
return;
}
const coordinates = getCursorPosition(canvas, e);
let rectX = Math.floor(coordinates.x / 10);
let rectY = Math.floor(coordinates.y / 10);
// stop drawing when it's outside of the bounds (i have a grid 100 x 100)
if (rectX > 99 || rectY > 99) {
return;
}
ctx.fillStyle = "black";
ctx.fillRect(rectX * 10, rectY * 10, 10, 10);
};
The Problem:
So that works like a charm. But today I installed React-zoom-pan-pinch
Obviously, after I zoom into the canvas everything is messed up since the getCursorPosition function has to do more work. I need to calculate the new correct position after zooming. But I can't figure out how.
So after I zoom in and I click on the rects (pixels), the colored rect appears on the far right and far bottom. So it's very off right now.
The package gives me this function onZoom that gets these parameters: ReactZoomPanPinchRef and event. These have many attributes on them for example x, y, offsetX, .. and many more.
I tried several combinations but I can't get it to work.
The question:
How can I calculate the new Cursor Position relative to my Canvas so I can draw rectangles over it? What is the calculation I need to make given all the attributes that the onZoom event/props give me.
Here are all the attributes from this package:
https://prc5.github.io/react-zoom-pan-pinch/?path=/story/docs-props--page
Unfortunately, I couldn't find a list of props that ReactZoomPanPinchRef and event gives me. I could make screenshots but it's a long list.
What I found out so far:
I found a react drawing boar repo made with react + canvas.
He uses a mousewheel function which you can see here:
https://github.com/dilidili/react-drawing-board/blob/master/src/SketchPad.tsx#L858
Also this matrix I could locate here:
https://github.com/dilidili/react-drawing-board/blob/master/src/utils.ts#L4
Never heard of that. But maybe I need something like that.
Also the package react-zoom-pan-pinch provides in the onZoom function this parameter:
ReactZoomPanPinchRef that has state on it that looks like that:
https://imgur.com/a/8t1FpJR
So I went back and tried this out like so:
let rectX = Math.floor((coordinates.x + zoomState.offsetX) / rectSize);
let rectY = Math.floor((coordinates.y + zoomState.offsetY) / rectSize);
Now it's much better but the further I zoom in the worse it gets.
Last but not least, here is a codesandbox where you can try this all out:
https://codesandbox.io/s/dark-darkness-iqwku?file=/src/components/canvas.js:2023-2171
Relevant files: index.js + components\canvas.js
If you need any more information please let me know.
Thx guys appreciate it.
So far it seems you've been fiddling with the offset of the zoom state. The offset however is perfectly captured by canvas.getBoundingClientRect() as it still returns the position of the top left corner even after the CSS transform.
The problem lies in your conversion to the rectX and rectY: by zooming in or out the size of your rectangles change, which is not yet reflected in your calculations. The following snippet solves this issue:
const scale = zoomState?.scale ?? 1;
let rectX = Math.floor(coordinates.x / (rectSize * scale));
let rectY = Math.floor(coordinates.y / (rectSize * scale));
A working example can be witnessed in this fork of your CodeSandbox.

QML Create tooltips dynamically

So I have a piechart that changes dynamically. I want to show the value of each slice when mouse over the slice, but I am not sure how to create the tooltip when onHovered is triggered. I use
qt 5.9.1 & import QtQuick.Controls 2.2
UPDATE: I have added some code to explain how I create the slices.
Here is the code:
function onUpdateValues(values){
switch(values.type){
case PIE_CHART:
createPieChart(values.data);
break;
...
default:
console.debug("CHART TYPE ERROR");
break;
}
}
}
function createPieChart(data){
pieserieschart.clear();
for (var prop in data) {
var new_slice = pieserieschart.append(prop, data[prop]);
new_slice.tooltip = prop + ": " + data[prop]
//I tried using hovered signal (and without), but it's not doing any difference
new_slice.hovered.connect(function(state) { new_slice.tooltip.visible = state })
//If I replace the above line by the next one, I can see the console.log info, but the tooltip is not enabled
new_slice.hovered.connect(function(state) { sliceHovered(new_slice, state) })
}
}
function sliceHovered(slice, value){
slice.enabled = true
console.log("Slice hovered: " + slice.tooltip + " " + value)
}
ChartView { /* Chart */
id:chartView
PieSeries {
id: pieserieschart;
size: 1;
holeSize: 0.55;
onClicked: sliceClicked(slice);
}
}
I can see the console.log but I am not able to see the tooltip, and the application output doesn't show any error, but tooltip is not triggered
Relevant docs:
PieSeries::hovered(PieSlice slice, bool state)
ToolTip::show(string text, int timeout = -1)
ToolTip::hide()
Example:
ChartView {
id: chartView
PieSeries {
onHovered: {
if (state)
chartView.ToolTip.show(slice.label + ":" + slice.value)
else
chartView.ToolTip.hide()
}
}
}
I know that maybe you don't need this anymore, but as I had the same problem and reached this question, I'll post my solution to the position on the slice.
First, I did not used the chartView.ToolTip, because it didn't allow me to move on the plane... So I make a ToolTip{ } element, with visibility = false.
The high school math now made it worth it. The PieSlice has 2 useful properties: startAngle (on the circle, where the pie starts) and angleSpan (angle used by the pie). As I have the angle, I can use some math magics to find the X and Y.
Tricky parts: the angle is clockwise, and the geometry stuff is measure counter-clockwise. So I have to convert the angle from clockwise to counter first... And the start point in geometry is different from Qt Charts, I have to compensate by reducing -90 from the origin...
As i want the hint be presented in the middle of the slice, I sum half of the angleSpan to the startAngle.
And after suffer a lot, I realize that the Math.cos and Math.sin uses the input in RADIANS, not in degree. I converted from angle to rad and... that's it. I hope this can help someone else lost like me =) .
ToolTip{
id:sliceToolTip
visible: false;
}
onHovered: {
if (state){
// 360 + angle clockwise to convert to counter-clw.
//-90 to compensate the QML charts start point
var angle =(360+ (-1*(slice.startAngle-90+(slice.angleSpan/2))));
if(angle > 360){ //Reduce to one spin, preventing high angles
angle -=360;
}
angle = (angle*Math.PI)/180; //convert to radians
var offsetX = Math.cos(angle);
var offsetY = Math.sin(angle);
sliceToolTip.x = (chartView.width/2 - (sliceToolTip.width/2)) + ((chartView.width/16)* offsetX*3);
sliceToolTip.y = (chartView.height/2 - (sliceToolTip.height/2)) - ((chartView.height/9)*offsetY*3); // My screen is 16:9, so I made a proportion to match the circle, as my charts goes with anchors.centerIn: parent
}
else{
sliceToolTip.hide()
}
}

How to position a custom label in highcharts

I am trying to do exactly what is in this official HighCharts fiddle: http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/members/renderer-label-on-chart/
However the example's "label" function has hardcoded the x(270) and y(50) parameters:
function (chart) { // on complete
var point = chart.series[0].points[8];
chart.renderer.label('Max observation', 270, 50, 'callout',
point.plotX + chart.plotLeft, point.plotY + chart.plotTop)
My chart obviously will require different parameters. I tried using point's plotX etc. However these are undocumented. in fact the are are not part of API as a (presumably) HighCharts developer points out in another answer - they are just inner properties to get coordinates where plot point. in other word, undocumented.
Using them is shorthand for getting values from point:
http://api.highcharts.com/highcharts#Point.x
http://api.highcharts.com/highcharts#Point.y
And translating to position via:
http://api.highcharts.com/highcharts#Axis.toPixels()
The links above seem completely unrelated.
I tried this to divine what those coordinates provide
}, function (chart) { // on complete
var point = chart.series[0].points[8];
chart.renderer.label('.'
, point.plotX.toFixed(0), point.plotY.toFixed(0), 'callout', point.plotX + chart.plotLeft, point.plotY + chart.plotTop)
.add();
});
seems plotX is some point situated a random set of pixels to the left of the chart series point that provides it (about 60ish) and seems to depend on the font you use.
This seems to be what you're looking for:
var point = chart.series[0].points[8];
var pxX = chart.xAxis[0].toPixels(point.x, true);
var pxY = chart.yAxis[0].toPixels(point.y, true);
chart.renderer.label('Max observation', pxX, pxY, 'callout', point.plotX + chart.plotLeft, point.plotY + chart.plotTop);
Fiddle - Notice .toPixels works per Axis, so you need to determine the pixel representation of point X and point Y separately. The true parameter to the function positions the callout based on its point, rather than the top left corner of the callout.

In PaperJS rotating a shape around point is not working properly

I am trying to recreate the game Asteroids. This is a sample of the code for the Ship object constructor (I am using a constructor function and not an object literal because this doesn't work properly when referring to variables in a literal):
function Ship(pos) {
var position = pos ? pos : view.center;
var segments = [
new Point(position) + new Point(0, -7.5), // Front of ship
new Point(position) + new Point(-5, 7.5), // Back left
new Point(position) + new Point(0, 3.5), // Rear exhaust indentation
new Point(position) + new Point(5, 7.5) // Back right
]
this.shipPath = new Path.Line({
segments: segments,
closed: true,
strokeColor: '#eee',
strokeWidth: 2
});
this.velocity = new Point(0, -1);
this.steering = new Point(0, -1);
this.rot = function(ang) {
this.steering.angle += ang;
this.shipPath.rotate(ang, this.shipPath.position);
}
this.drive = function() {
this.shipPath.position += this.velocity;
}
}
var ship = new Ship();
var path = new Path({
strokeColor: '#ddd',
strokeWidth: 1
});
function onFrame(event) {
path.add(ship.shipPath.position);
ship.drive();
}
I've left out the key handlers which is how the ship is steered, but basically what they do is call the this.rot() function with different angles depending whether the right or left buttons were clicked.
Basically my problem is that according to this, when steering the ship, the ship should rotate around its shipPath.position, which would leave that point travelling in a straight line as the ship revolves around it. Instead this is happening:
The curly bit in the path is from when I continuously steered the ship for a few seconds. Why is this happening? If the ship is revolving around its position, why should the position judder sideways as the ship rotates?
Here is a link to where I've got this working on my own website: http://aronadler.com/asteroid/
I would have loved to put this on jsbin or codepen but despite hours work I have never been able to actually get the paperscript working in javascript.
Here is a sketch. Because for some reason Sketch won't let arrow keys being detected I've given it an automatic constant rotation. The effect is the same.
The reason for this is that path.bounds.center is not the center of the triangle. The default center for rotation is path.bounds.center. See sketch. The red dots are bounds.center, the green rectangles are the bounds rectangle.
You want to rotate around the triangle center (technically centroid) which can be calculated by finding the point 2/3 of the way from a vertex to the midpoint of the opposite side.
Here's some code to calculate the centroid of your triangle:
function centroid(triangle) {
var segments = triangle.segments;
var vertex = segments[0].point;
var opposite = segments[1].point - (segments[1].point - segments[2].point) / 2;
var c = vertex + (opposite - vertex) * 2/3;
return c;
}
And an updated sketch showing how the center doesn't move, relative to your triangle, as it is rotated, when calculating the centroid.
And I've updated your sketch to use the centroid rather than position. It now moves in a straight line.

Raphael JS Implementing a "Pencil" tool efficiently

I am working on a project that requires end users to be able draw in the browser much like svg-edit and send SVG data to the server for processing.
I've started playing with the Raphael framework and it seems promising.
Currently I am trying to implement a pencil or freeline type tool. Basically I am just drawing a new path based on percentage of mouse movement in the drawing area. However, in the end this is going to create massive amount of paths to deal with.
Is it possible to shorten an SVG path
by converting mouse movement to use
Curve and Line paths instead of line
segments?
Below is draft code I whipped up to do the job ...
// Drawing area size const
var SVG_WIDTH = 620;
var SVG_HEIGHT = 420;
// Compute movement required for new line
var xMove = Math.round(SVG_WIDTH * .01);
var yMove = Math.round(SVG_HEIGHT * .01);
// Min must be 1
var X_MOVE = xMove ? xMove : 1;
var Y_MOVE = yMove ? yMove : 1;
// Coords
var start, end, coords = null;
var paperOffset = null;
var mouseDown = false;
// Get drawing area coords
function toDrawCoords(coords) {
return {
x: coords.clientX - paperOffset.left,
y: coords.clientY - paperOffset.top
};
}
$(document).ready(function() {
// Get area offset
paperOffset = $("#paper").offset();
paperOffset.left = Math.round(paperOffset.left);
paperOffset.top = Math.round(paperOffset.top);
// Init area
var paper = Raphael("paper", 620, 420);
// Create draw area
var drawArea = paper.rect(0, 0, 619, 419, 10)
drawArea.attr({fill: "#666"});
// EVENTS
drawArea.mousedown(function (event) {
mouseDown = true;
start = toDrawCoords(event);
$("#startCoords").text("Start coords: " + $.dump(start));
});
drawArea.mouseup(function (event) {
mouseDown = false;
end = toDrawCoords(event);
$("#endCoords").text("End coords: " + $.dump(end));
buildJSON(paper);
});
drawArea.mousemove(function (event) {
coords = toDrawCoords(event);
$("#paperCoords").text("Paper coords: " + $.dump(coords));
// if down and we've at least moved min percentage requirments
if (mouseDown) {
var xMovement = Math.abs(start.x - coords.x);
var yMovement = Math.abs(start.y - coords.y);
if (xMovement > X_MOVE || yMovement > Y_MOVE) {
paper.path("M{0} {1}L{2} {3}", start.x, start.y, coords.x, coords.y);
start = coords;
}
}
});
});
Have a look at the Douglas-Peucker algorithm to simplify your line.
I don't know of any javascript implementation (though googling directed me to forums for google maps developers) but here's a tcl implementation that is easy enough to understand: http://wiki.tcl.tk/27610
And here's a wikipedia article explaining the algorithm (along with pseudocode): http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
Here is a drawing tool which works with the iPhone or the mouse
http://irunmywebsite.com/raphael/drawtool2.php
However also look at Daves "game utility" #
http://irunmywebsite.com/raphael/raphaelsource.php which generates path data as you draw.
I'm working on something similar. I found a way to incrementally add path commands by a little bypass of the Raphael API as outlined in my answer here. In the modern browsers I tested on, this performs reasonably well but the degree to which your lines appear smooth depends on how fast the mousemove handler can work.
You might try my method for drawing paths using line segments and then perform smoothing after the initial jagged path is drawn (or as you go somehow), by pruning the coordinates using Ramer–Douglas–Peucker as slebetman suggested, and converting the remaining Ls to SVG curve commands.
I have a simillar problem , I draw using the mouse down and the M command. I then save that path to a database on the server. The issue I am having is to do with resolution. I have a background image where the users draw lines and shapes over parts of the image, but if the image is displayed on one resolution and the paths are created in that resolution then reopened on a different perhaps lower resolution, my paths get shifted and are not sized correctly. I guess what I am asking is : is there a way to draw a path over an image and make sure no matter the size of the underlying image the path remains proprtionally correct.

Categories