Raphael JS Pie: Add ID to path slices - javascript

I've seen this question asked over at the Raphael Google Groups, but after hours of searching there, and also on here, and Google, I cannot seem to find a solution.
I would simply like to be able to target my pie chart (svg path) slices using jQuery, but I cannot figure out how to add custom id's to the path tags - there is no ID attribute in there by default:
<path fill="#764c29" stroke="none" d="M350,350L350.6911881148345,94.00093308961084A256,256,0,0,1,561.8463375189659,206.2741175716762Z" style="stroke-width: 1; stroke-linejoin: round;" stroke-width="1" stroke-linejoin="round"/>
What would be ideal would be this:
<path **id="my_id"** fill="#764c29" stroke="none" d="M350,350L350.6911881148345,94.00093308961084A256,256,0,0,1,561.8463375189659,206.2741175716762Z" style="stroke-width: 1; stroke-linejoin: round;" stroke-width="1" stroke-linejoin="round"/>
Has anyone got an idea how this could be achieved?
This is the code I'm using to create the pie chart:
window.onload = function () {
var r = Raphael("holder");
var pie = r.g.piechart(350, 350, 256, [56, 104, 158, 23, 15, 6]);
pie.hover(function () {
this.sector.stop();
this.sector.animate({scale: [1.1, 1.1, this.cx, this.cy]}, 500, "bounce");
}, function () {
this.sector.animate({scale: [1, 1, this.cx, this.cy]}, 500, "bounce");
});
};
Essentially, the reason I need to be able to do this, is so I can create some separate anchor triggers to perform the scale animations shown above.
Any help greatly appreciated.

the piechart object provides 3 ways to reach their sectors.
1) each function
pie.each(function(sector, cover, i) {
sector.attr({/*...*/}); //raphael
$(sector.node).foo(); //jquery
});
2) series object (for styling and transforming)
var i = 0; // 0 = 56, 1 = 104, 2 = 158 …
//raphael way to hide the first sector
pie.series.items[i].attr({ opacity : 0 });
//jquery way to hide the first sector
$(pie.series.items[i].node).hide();
whereby i is the index of your data-array
demo: http://jsbin.com/eriqa5/2/edit
3) covers object (for mouse and touch events)
//raphael way to hover the first sector
pie.covers.items[0].hover(...);
//jquery way to hover the first sector
$(pie.covers.items[0].node).hover(...);
demo: http://jsbin.com/eriqa5/4/edit

Related

The XY coordinates in a d3 drag event are inverting

I have a force layout with the following structure:
<svg width="300" height="220">
<g class="scaleWrapper" transform="scale(0.3)">
<g class="transformWrapper" transform="translate(-110, -80)">
<g class="backgroundWrapper">
<rect class="backgroundRect" width="300" height="220"></rect>
</g>
<g class="forceNode"></g>
<g class="forceNode"></g>
<g class="forceLink"></g>
</g>
</g>
</svg>
I also have a drag behavior linked to the rect to drag it around (scaling is handled through a separate slider).
let transformElement = d3.select('.transformWrapper');
let svgBackground = transformElement.append('g')
.classed('backgroundWrapper', true);
function originFunction() {
let d = d3.select('.transformWrapper');
return {
x: d.attr('x'),
y: d.attr('y')
};
}
let svgDrag = d3.behavior.drag()
.origin(originFunction)
.on('dragstart', function(){
d3.event.sourceEvent.stopPropagation();
})
.on('drag', function(){
transformElement.attr("transform", `translate(${d3.event.x}, ${d3.event.y})`);
});
svgBackground.call(svgDrag);
It mostly works, but it jumps around as i drag it. I did a log and saw that the d3.event object is alternating between relative XY coordinates and absolute ones, here's a sample of what I'm seeing:
-111 -80
-29 -6
-110 -80
-29 -5
I don't see any other elements that have behavior bound to them. All the d3.event objects have the 'drag' type property and the same srcElement. How can I silence the events that are returning the relative positions?
Found the problem, it's the same bug as in d3.event.y has strange values during drag behavior
Moving the drag behavior to the transform group fixed it right up.

Setting D3 svg.transition to go from slow to fast to slow

I have a D3 graph that allows a user to click a button to take them to a specified node. The button looks like this:
<button type="button" class="btn btn-primary" ng-click="ctrl.panGraph(9)">Go to End</button>
This button will take the user from wherever they are in the svg at the time of click, to the x and y coordinates of the last node, with the id of 9. On click this function is called:
function panGraph (nodeId:any) {
svgWidth = parseInt(svg.style("width").replace(/px/, ""), 10);
svgHeight = parseInt(svg.style("height").replace(/px/, ""), 10);
for (var i = 0; i < renderedNodes.length; i++) {
if (nodeID === renderedNodes[i].id) {
ctrl.selectedNode = renderedNodes[i];
var translate = [svgWidth / 2 - renderedNodes[i].x, svgHeight / 2 - renderedNodes[i].y];
var scale = 1;
svg.transition().duration(4000).ease(d3.easeExpInOut).call(zoom.translate(translate).scale(scale).event);
}
}
}
In the above function I have all the rendered nodes that have been rendered on the page, once I find the matching id I use its x and y coordinates to center the specified node in the middle of the svg. That all works fine.
I am trying to use some animations during the time that the graph is translating to the specified node on button click. When the user clicks the button that takes him or her to the specified node, is it possible to animate the transition so that the transition initially starts slow, then speeds up, but then slows down again at the end as it gets close to the specified node? Thanks
UPDATE:
The above code with the "ease" incluided gives me this console error:
angular.js:13550 TypeError: Cannot read property 'indexOf' of undefined
at Object.d3.ease (d3.js:5844)
at Array.d3_transitionPrototype.ease (d3.js:8838)
at zoomOnNode (DiagramComponent.ts:1128)
at DiagramComponent.ts:1072
at Scope.$digest (angular.js:17073)
at Scope.$apply (angular.js:17337)
at HTMLButtonElement.<anonymous> (angular.js:25023)
at HTMLButtonElement.dispatch (jquery.js:4737)
at HTMLButtonElement.elemData.handle (jquery.js:4549)
Here is the v3 equivalent to Gerardo's post regarding v4:
svg.transition().duration(1000).ease("exp-in-out").call(zoom.translate(translate).scale(scale).event);
For a list of all the easing equivalents from v3 to v4 and other changes:
https://github.com/d3/d3/blob/master/CHANGES.md
One (out of several) solution is to use ease with d3.easeExpInOut, or d3.easePolyInOut.exponent(x) with a high exponent (like x=4 or x=5).
See this snippet. Click the circle to see it moving from left to right, starting slow, speeding up and then slowing down again:
d3.select("circle").on("click", function(){
d3.select(this).transition()
.duration(4000)
.ease(d3.easeExpInOut)
.attr("cx", 360)
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400" height="200">
<circle cx="40" cy="100" r="30" fill="teal"></circle>
<line x1="40" x2="40" y1="100" y2="150" stroke="black" stroke-width="1"></line>
<line x1="360" x2="360" y1="100" y2="150" stroke="black" stroke-width="1"></line>
</svg>

How to add a second tooltip to highcharts

I would like to add an external popover to highcharts. I'm currently looking at using WebUI Popover
So the standard highcharts popover displays the standard data, however, I would like to use WebUI Popover to display some data to explain a specific column, which can be fetched from the DB.
The actual fetching of data etc is fine, but I can't figure out how to display a specific popover for a column
The way WebUI Popover works is that it requires some HTML identifier to know where to show the popover:
$(#identifier).webuiPopover({"content"})
I cant find any identifier to use to link the popover to each column:
<g class="highcharts-series highcharts-tracker" visibility="visible" zIndex="0.1" transform="translate(55,48) scale(1 1)" style="" clip-path="url(#highcharts-3)">
<rect x="63.5" y="33.5" width="119" height="162" stroke="#FFFFFF" fill="rgb(124, 181, 236)" rx="0" ry="0" stroke-width="1" data-target="webuiPopover0"></rect>
<rect x="309.5" y="98.5" width="119" height="97" stroke="#FFFFFF" fill="rgb(124, 181, 236)" rx="0" ry="0" stroke-width="1"></rect>
<rect x="555.5" y="65.5" width="119" height="130" stroke="#FFFFFF" fill="rgb(124, 181, 236)" rx="0" ry="0" stroke-width="1"></rect>
</g>
So I want to show a popover for each coloumn (rect), but I really don't know how. I've tried:
var thePoint = "rect[x='" + 63.5 + "'][y='" + 33.5 + "']";
$(thePoint).webuiPopover(...)
This works to some extent, but obviously I've hard coded the 63,5 and 33,5. I've tried everything to dynamically get the x and y values, but I can never get those exact numbers.
Any other suggestions? Maybe if someone could explain how the built in popover gets the position?
There is this demo of clickable points: http://www.highcharts.com/demo/line-ajax
It uses Highslide.
If you want to use WebUI Popover, then you can find columns' rectangles and add popups. Example: http://jsfiddle.net/j57me5w1/
Looks like popups will start from top left corner.
$(function () {
$('#container').highcharts({
series: [{
type: 'column',
data: [1, 2, 3, 4, 5, 6, 7]
}]
});
var columns = $('.highcharts-tracker > rect');
$.each(columns, function (i, c) {
$(c).webuiPopover({
title: 'test',
content: '<div id="popup">popup content</div>',
closeable: true,
arrow: true,
placement: 'auto'
});
});
});

How to find the radius of the piechart/donut chart

I have a simplest highchart donut chart question. The question I have is what is the default radius of this donut chart. I have not specified the radius anywhere, but seems to have a default radius set somewhere. I could not figure out how this radius was set and was wondering if somebody could help me understand how this radius is set. I did go thru the api reference but could not find this info. Not sure if I missed something.
Here's a demo
$(function () {
$('#container').highcharts({
chart: {
type: 'pie'
},
plotOptions: {
pie: {
startAngle: 90,
animation: false,
innerSize: '60%'
}
},
series: [{
data: [
['Firefox', 44.2],
['IE7', 26.6],
['IE6', 20],
['Chrome', 3.1],
['Other', 5.4]
]
}]
});
You cannot find out size value using options or API because it seems there is no any for that. It seems that you cannot find it out using for example:
var chart = $('#container').highcharts();
console.log(chart);
and inspecting properties. You will find out that size is set to null.
You can find out size inspecting DOM svg elements. To make my job easier I changed option
innerSize: 180
and find out following svg elements (Note: it is not circle element):
...
<path fill="#8bbc21" d="M 532.7575883045118 30.763342348475817 A 140 140 0 0 1 685.1378163408548 95.11881271952285 L 642.2314533619781 120.79066531969326 A 90 90 0 0 0 544.2727353386148 79.41929150973445 Z" stroke="#FFFFFF" stroke-width="1" stroke-linejoin="round" transform="translate(0,0)" visibility="visible"></path>
<path fill="#910000" d="M 685.2096374472518 95.23898645643143 A 140 140 0 0 1 696.8833100589593 120.0234896156345 L 649.7821278950453 136.8008147529079 A 90 90 0 0 0 642.2776240732333 120.86791986484879 Z" stroke="#FFFFFF" stroke-width="1" stroke-linejoin="round" transform="translate(0,0)" visibility="visible"></path>
...
Those are svg path elements which build slices of pie chart donuts. A 140 140... and A 90 90... are elliptical arc command and 90,90 is rx, ry for inner size, and 140,140 for outer size. So radius is 140 in this case.
I don't know how it is calculated exactly but radius is calculated according to your container size and all different margins, plot borders, spacing... if you do not set size using option size.
It doesn't have a radius option it seems. But there is an option:
width: Number
An explicit width for the chart. By default the width is calculated from the offset width of the containing element.
Try it: 800px wide
Refer to this link
No radius option, but there is a diameter. You can express it in pixels (given by number) or as a percent of the chart area (give as string 'N%'):
size: String|Number The diameter of the pie relative to the plot area.
Can be a percentage or pixel value. Pixel values are given as
integers. The default behaviour (as of 3.0) is to scale to the plot
area and give room for data labels within the plot area. As a
consequence, the size of the pie may vary when points are updated and
data labels more around. In that case it is best to set a fixed value,
for example "75%". Defaults to .
Here's your fiddle with the pie chart 150% of the chart area.

Is path animation possible with SVG.js

There are many examples of SVG path animation, both natively
http://jsfiddle.net/FVqDq/
and with Raphael.js
http://jsfiddle.net/d7d3Z/1/
p.animate({path:"M140 100 L190 60"}, 2000, function() {
r.animate({path:"M190 60 L 210 90"}, 2000);
});
How is this possible with the svg.js library?
No, this is not yet possible with svg.js. I have been looking into it and it will be a rather large implementation. As I try to keep the library small it will never be part of the library itself, but I might write a plugin. Although at the moment I do not have much time on my hands so all help will be appreciated.
UPDATE:
This is now possible with SVG.js out of the box if you use paths with equal commands but different values.
But we also have a path morphing plugin for SVG.js which is probably the thing you are looking for.
There is a quick and dirty way to animate a line with svg.js:
http://jsfiddle.net/c4FSF/1/
draw
.line(0, 0, 0, 0)
.stroke({color: '#000', width: 2})
.animate(1000, SVG.easing.bounce) // Using svg.easing.js plugin(not required)
.during(function(t, morph) {
this.attr({x2:morph(0, 100), y2: morph(0, 100)})
})
Animating complex SVG paths as wout said will require a plugin.
Unfortunately I don't (yet) know enough about SVG, but I'm thinking of writing a plugin which would use the SMIL animation tag. Which is what is used in the first link of the question.
We can make path animation by finding the bounding box of your path and the do like this.
if your path having some clipping -rectangle means like that below
<g id="container_svg_SeriesGroup_0" transform="translate(128.8,435)" clip-path="url(#container_svg_SeriesGroup_0_ClipRect)"><path id="container_svg_John_0" fill="none" stroke-dasharray="5,5" stroke-width="3" stroke="url(#container_svg_John0Gradient)" stroke-linecap="butt" stroke-linejoin="round" d="M 0 -17.25 L 21.7 -112.12499999999999 M 21.7 -112.12499999999999 L 43.4 -51.75 M 43.4 -51.75 L 86.8 -25.875 M 86.8 -25.875 L 108.5 -155.25 "/><defs><clipPath id="container_svg_SeriesGroup_0_ClipRect"><rect id="container_svg_SeriesGroup_0_ClipRect" x="0" y="-155.25" width="118.5" height="148" fill="white" stroke-width="1" stroke="transparent" style="display: inline-block; width: 118.5px;"/></clipPath></defs></g>
var box = $("#"+ path.id")[0].getBBox();
create the rectangle based on the box and the set this rectangle as your clip-path in path.
then increase the width of the rectangle step by step in jquery.animate.
doAnimation: function () {
//cliprect is your clipped rectangle path.
$(clipRect).animate(
{ width: 1000},
{
duration: 2000,
step: function (now, fx) {
$(clipRect).attr("width", now);
}
});
},
jquery.animate step function is used to increase the width of your clip-rect step by step.
You can animate paths using the svg.path.js plugin.
See the first examples (using the .drawAnimated method).
Another option, which we've resorted to, is to use textPath and then use a character.
In our case we're using the • entity, but I'm thinking if you create your own typography in .svg, .woff etc, you can have flat shapes of any kind.
So you would use your character as in here:
http://jsfiddle.net/wbx8J/3/
/* create canvas */
var draw = SVG('canvas').size(400,400).viewbox(0, 0, 1000, 1000)
/* create text */
var text = draw.text(function(add) {
add.tspan('•').dy(27)
})
text.font({ size: 80, family: 'Verdana' })
/* add path to text */
text.path('M 100 400 C 200 300 300 200 400 300 C 500 400 600 500 700 400 C 800 300 900 300 900 300')
/* visualise track */
draw.use(text.track).attr({ fill: 'none'/*, 'stroke-width': 1, stroke: '#f09'*/ })
/* move text to the end of the path */
function up() {
text.textPath.animate(3000).attr('startOffset', '100%').after(down)
}
/* move text to the beginning of the path */
function down() {
text.textPath.animate(3000).attr('startOffset', '0%').after(up)
}
/* start animation */
up()

Categories