Unable to get updated circle position with Raphael.js - raphael

I am trying to drag around a circle with raphael.js, but it seems that cx and cy does not get updated for the circle in order to set the correct new positions of the circles.
The code can be seen and tested here: http://jsfiddle.net/MXFWW/

As you've discovered, applying a transformation to a Raphael object does not alter its positional attributes.
Check out the ellipsis syntax in the transform method. Because transformations are such a headache, I prefer in simple cases to directly alter the attributes. You just have to remember where you started in the dragStart function using the .data() method to store arbitrary data.
var paper = Raphael(0, 0, 320, 320);
var innerC = paper.circle(320 / 2, 320 / 2, 20);
innerC.attr("stroke", "#000");
innerC.attr("fill", "#000");
var dragMove = function (dx, dy, x, y, e) {
console.log(innerC.attr('cx'));
this.attr("cx", this.data("ox") + dx);
this.attr("cy", this.data("oy") + dy);
this.animate({
"fill-opacity": 1
}, 500);
},
dragStart = function (x, y) {
this.data("ox", this.attr("cx"));
this.data("oy", this.attr("cy"));
},
dragEnd = function () {
this.animate({
"fill-opacity": 1
}, 500);
};
innerC.drag(dragMove, dragStart, dragEnd);
jsFiddle

Related

How to force re-rendering after adding point to a vtkPlot in a qvtkWidget?

In QT I have a qvtkWidget in which I plot a graph.
I use two functions:
The following function initializes the plot with two points (0,0) and (0,1):
bool VTKPlotter::initPlot(){
// Create a table:
plotTable = vtkSmartPointer<vtkTable>::New();
vtkSmartPointer<vtkFloatArray> arrT = vtkSmartPointer<vtkFloatArray>::New();
vtkSmartPointer<vtkFloatArray> arrUSD = vtkSmartPointer<vtkFloatArray>::New();
arrT->SetName("Time");
plotTable->AddColumn(arrT);
arrUSD->SetName("USD");
plotTable->AddColumn(arrUSD);
//Set up the view:
view = vtkSmartPointer<vtkContextView>::New();
view->GetRenderer()->SetBackground(0.8, 0.8, 0.8);
//Set up chart:
vtkSmartPointer<vtkChartXY> chart = vtkSmartPointer<vtkChartXY>::New();
view->GetScene()->AddItem(chart);
//Set renderer, interactor:
view->SetInteractor(plot_qvtkWidget->GetInteractor());
plot_qvtkWidget->SetRenderWindow(view->GetRenderWindow());
plot_qvtkWidget->show();
//Add line and initial values:
vtkPlot *line;
line = chart->AddPlot(vtkChart::LINE);
line->SetInputData(plotTable, 0, 1);
line->SetColor(25, 25, 230, 220);
line->SetWidth(1.5);
int numPoints = 2;
plotTable->SetNumberOfRows(numPoints);
plotTable->SetValue(0, 0, 0);
plotTable->SetValue(1, 0, 1);
plotTable->SetValue(0, 1, 0);
plotTable->SetValue(1, 1, 1);
return true;
}
where plotTable is a vtkSmartPointer<vtkTable>, view is vtkSmartPointer<vtkContextView> and plot_qvtkWidget is a QVTKWidget*.
When the above function is called, the plot is immediately shown in the widget.
Then I have this function which is called when I click a certain button in my QT application:
void VTKPlotter::addValue(double v){
plotTable->InsertNextBlankRow();
int r = plotTable->GetNumberOfRows() - 1;
plotTable->SetValue(r, 0, r);
plotTable->SetValue(r, 1, v);
}
The problem is, when I call the above function, the plot is not immediately updated. If I zoom in and out and pan around the plot, it eventually updates, and I can see the new point.
I've tried these methods:
view->ResetCamera();
view->Update();
view->Render();
view->GetRenderWindow()->Render();
view->GetRenderer()->ResetCamera();
view->ResetCameraClippingRange();
plot_qvtkWidget->update();
How do I force my plot to update?
One of possible solution is to call chart->ClearPlots() with subsequent appropriate calls chart->AddPlot(...)

Legends for line charts in Chart.js

I'd like to customize a legend for line data so that the legend graphic is a line (styled like the actually data line) rather than a box.
As far as I can tell from the source, the graphic can be a point or a box, and the height of the box is fixed to the font size. The 'generateLabels' option does not seem to allow for extending around these contraints.
Version 2.2.1.
Thanks for any help.
NOTE: This solution only works if you have a local version of Chart.js since it needs to edit a function in the source code of the library, which can't be done if you import it form a CDN.
To achieve what you want, you will need to edit the drawLegendBox function (link to source here).
First, as if you wanted to do a pointStyle legend, add the useLineStyle and set it to true like this :
options: {
legend: {
labels : {
useLineStyle: true
}
}
}
Then you need to go to your local version of Chart.js (obvisouly, you cannot edit it if you import it from a CDN) and search for the function drawLegendBox (on Chart.js v2.2.1, it is roughly line 6460; in Chart.js v2.9.4 search for labelOpts && labelOpts.usePointStyle).
Scroll down a little bit to see something like this :
if (opts.labels && opts.labels.usePointStyle) {
// Recalulate x and y for drawPoint() because its expecting
// x and y to be center of figure (instead of top left)
var radius = fontSize * Math.SQRT2 / 2;
var offSet = radius / Math.SQRT2;
var centerX = x + offSet;
var centerY = y + offSet;
// Draw pointStyle as legend symbol
Chart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
}
// --- NEW CONDITION GOES HERE ---
else {
// Draw box as legend symbol
ctx.strokeRect(x, y, boxWidth, fontSize);
ctx.fillRect(x, y, boxWidth, fontSize);
}
And add this between the two conditions :
else if (opts.labels && opts.labels.useLineStyle) {
ctx.beginPath();
ctx.moveTo(x, y + fontSize * 0.45);
ctx.lineTo(x + boxWidth, y + fontSize * 0.45);
ctx.stroke();
}
With this edit, everytime you will set useLineStyle to true, legend boxes will be drawn as lines, as the following screenshot :
I was able to use pointStyle: line, in the dataset and then under options use labels: {usePointStyle: true,},
Just to improve on this solution from tektiv.
If you want to show a dashed line too use this code in the same spot.
(chartJs 2.7.2 around Line 16289):
if (opts.labels && opts.labels.usePointStyle) {
// CHARTJS CODE
} else if (opts.labels && opts.labels.useLineStyle) {
if (legendItem.borderDash) {
ctx.setLineDash(legendItem.borderDash);
}
ctx.beginPath();
ctx.moveTo(x, y + fontSize / 2);
ctx.lineTo(x + boxWidth, y + fontSize / 2);
ctx.stroke();
} else {
// CHARTJS CODE
}
chart.js v3
For this version, none of the previously mentioned built-in configurations work. You can set boxHeight: 0 on the legend labels in order to get a line instead of a box:
{
legend: {
labels: {
boxHeight: 0
}
}
}
You can make line legend by changing width of legend box (for example 2px), it will be vertical line but it's looks nice too
plugins: {
legend: {
display: true,
labels: {
boxWidth: 2
}
}
}

RaphaelJS palette behaviour

How can I make a palette behaviour (elements being dragged and dropped from a 'palette' to a 'canvas') in raphaelJS?
You'll have to add to every palette element this startFunction:
//DragFunctions is the object that has all the 3 d&d methods, clearer in the complete file
paletteStart: function () {
// keep the relative coords at the start of the drag
this.ox = 0;
this.oy = 0;
// as we are dragging the palette element, we clone it to leave one in his place.
var newPaletteObj = this.clone();
//we give the new palette element the behaviour of a palette element
DragFunctions.addDragAndDropCapabilityToPaletteOption(newPaletteObj);
//nice animation
this.animate({
"opacity": 0.5
}, 500);
}
Now we need the function while the element is being dragged:
move: function (dx, dy) {
// calculate translation coords
var new_x = dx - this.ox;
var new_y = dy - this.oy;
// transforming coordinates
this.transform('...T' + new_x + ',' + new_y);
// save the new values for future drags
this.ox = dx;
this.oy = dy;
}
And finally, the function executed at finish dropping:
paletteUp: function () {
if (!DragFunctions.isInsideCanvas(this)) {
this.remove();
//notify the user as you want!
} else {
//Giving the new D&D behaviour
this.undrag();
//give the element the new d&d functionality!
this.animate({
"opacity": 1
}, 500);
}
}
2 things to comment here, when the element is dropped, you will have to remove the palette behaviour and give it another one (a plain d&d functionality), if not, it will continue cloning elements all around.
Here I give you some nice behaviour to give them:
start: function () {
// keep the relative coords at the start of the drag
this.ox = 0;
this.oy = 0;
// animate attributes to a "being dragged" state
this.animate({
"opacity": 0.5
}, 500);
},
//same move function
up: function () {
if (!DragFunctions.isInsideCanvas(this)) {
this.animate({
transform: '...T' + (-this.ox) + ',' + (-this.oy)
}, 1000, "bounce");
}
this.animate({
"opacity": 1
}, 500);
},
//and the method that gives the behaviour
addDragAndDropCapabilityToSet: function (compSet) {
compSet.drag(this.move, this.start, this.up, compSet, compSet, compSet);
}
And as you may also see, we have a validator that sees if the element is inside the canvas, it is a very useful function, here:
isInsideCanvas: function (obj) {
var canvasBBox = //get your 'canvas'
var objectBBox = obj.getBBox();
var objectPartiallyOutside = !Raphael.isPointInsideBBox(canvasBBox, objectBBox.x, objectBBox.y) || !Raphael.isPointInsideBBox(canvasBBox, objectBBox.x, objectBBox.y2) || !Raphael.isPointInsideBBox(canvasBBox, objectBBox.x2, objectBBox.y) || !Raphael.isPointInsideBBox(canvasBBox, objectBBox.x2, objectBBox.y2);
return !(objectPartiallyOutside);
} Finally,
the place to call to give the element all this behaviour:
//this works for elements and sets
addDragAndDropCapabilityToPaletteOption: function (compSet) {
compSet.drag(this.move, this.paletteStart, this.paletteUp, compSet, compSet, compSet);
}
A demo of this is in a website I created to play with raphael, called comoformamos.com
The hole code is in a github gist or hosted on github so if you want to get a little deeper in the code feel free to do it.
Explained more beautifully at this blog entry: devhike, I'm the author.

raphaeljs: drag and apply transformation to Paper.set()

I started to play a little bit with raphaeljs, however I'm having a small problem when dragging and applying a transformation to a Paper.set()
Here is my example: http://jsfiddle.net/PQZmp/2/
1) Why is the drag event added only to the marker and not the slider?
2) The transformation is supposed to be relative(i.e. translate by and not translate to), however if I drag the marker twice, the second dragging starts from the beginning and not from the end of the first.
EDIT:
After the response of Zero, I created a new JSFiddle example: http://jsfiddle.net/9b9W3/1/
1) It would be cool if this referenced the set instead of the first element of the set. Can't this be done with dragger.apply(slider)? I tried it, but only works on the first execution of the method (perhaps inside Raphael it is already being done but to the first element inside the set instead of the set)
2) According to Raphael docs the transformation should be relative to the object position (i.e. translate by and not translate to). But it is not what is happening according to the jsfiddle above (check both markers drag events).
3) So 2) above creates a third question. If a transform("t30,0") is a translation by 30px horizontally, how is the origin calculated? Based on attr("x") or getBBox().x?
The drag event is actually being added to both the marker and the slider -- but your slider has a stroke-width of 1 and no fill, so unless you catch the exact border, the click "falls through" to the canvas.
Behind that is another issue: the drag is being applied to both elements, but this in your drag handler references a specific element, not the set -- so both elements will drag independently from each other.
Lastly: the reason that each drag is starting from the initial position is because the dx, dy parameters in dragger are relative to the coordinates of the initial drag event, and your transform does not take previous transforms into account. Consider an alternative like this:
var r = new Raphael(0, 0, 400, 200);
var marker = r.path("M10,0L10,100").attr({"stroke-width": 5});
var button = r.rect(0, 0, 20, 20, 1).attr( { 'stroke-width': 2, fill: 'white' } );
var slider = r.set( marker, button );
var startx, starty;
var startDrag = function(){
var bbox = slider.getBBox();
startx = bbox.x;
starty = bbox.y;
console.log(this);
}, dragger = function(dx, dy){
slider.transform("t" + ( startx + dx ) + "," + starty );
}, endDrag = function(){
};
slider.drag(dragger, startDrag, endDrag);
To address your updates:
I believe you can specify the context in which the drag function will be executed as optional fourth, fifth, and six parameters to element.drag. I haven't tried this myself, but it looks like this should work great:
slider.drag( dragger, startDrag, endDrag, slider, slider, slider );
The transformation is relative to the object position. This works great for the first slider because its starting position is 0, but not so great for the second slider because...
...the transformation for min/max sliders should actually be relative to the scale, not the individual markers. Thus you will notice that your max slider (the red one) returns to its initial position just as you drag the mouse cursor back over the zero position. Make sense?
var position;
var rect = paper.rect(20, 20, 40, 40).attr({
cursor: "move",
fill: "#f00",
stroke: "#000"
});
t = paper.text(70,70, 'test').attr({
"font-size":16,
"font-family":
"Arial, Helvetica, sans-serif"
});
var st = paper.set();
st.push(rect, t);
rect.mySet = st;
rect.drag(onMove, onStart, onEnd);
onStart = function () {
positions = new Array();
this.mySet.forEach(function(e) {
var ox = e.attr("x");
var oy = e.attr("y");
positions.push([e, ox, oy]);
});
}
onMove = function (dx, dy) {
for (var i = 0; i < positions.length; i++) {//you can use foreach but I want to
// show that is a simple array
positions[i][0].attr({x: positions[i][1] + dx, y: positions[i][2] + dy});
}
}
onEnd = function() {}

making one side of a path draggable in Raphael 2.0

I am reading a JSON file and foreach element I am creating a path and a circle. I need to make the path drag with the circle. The path terminates at the exact x,y coordinates of the circle center. I only want the circle end of the path to drag with the circle. The other end of the path is fixed.
I have drag working for the circles but it is not doing anything for the paths. I have posted code that is dumbed down and does not contain the intelligence for positioning circles. I only need help with dragging one end of the path. My script is reading the JSON fine and painting the canvas with circles and paths at the correct coordinates. Thanks in advance for your help.
I do not answers that require an additional plug in please.
<script type="text/javascript">
$.getJSON('jsonScript.php?view=json', function( json ){
var start = function () {
this.ox = this.attr("cx");
this.oy = this.attr("cy");
this.animate();
},
move = function (dx, dy) {
this.attr({cx: this.ox + dx, cy: this.oy + dy});
},
up = function () {
this.animate();
};
var paper = Raphael( canvas.leftMargin, canvas.topMargin, canvas.width, canvas.height );
$.each( json, function( a , z ) {
var circleObj = paper.circle( x, y, radius );
circleObj.attr({"fill":"black","stroke":"red","stroke-width":5});
circleObj.node.id = jsonVar;
var pathObj = paper.path( "M396,16L641,187" );
path.attr({"stroke":"#fdfdfd","stroke-width":3}).toBack();
paper.set( circleObj, pathObj ).drag( move, start, up )
});
});
</script>
You can attach path to circleObject by just assigning it within your .each block...
$.each(json, function(a, z) {
...
circleObject.path = path;
});
This way you can access it from within the start, up and move functions...
move = function (dx, dy) {
this.path.attr(path)[1][1] = this.ox + dx;
this.path.attr(path)[1][2] = this.oy + dy;
},