I'm using d3 to populate a cartesian plane with a bunch of svg:image elements spread out over different coordinates.
I'd like to add mouserover and mouseout logic that zooms the image the mouse is over in and lightens the opacity of the others. I'm filtering my selection on mouseover to only select the desired element and everything is working great, except my scaling logic doesn't seem to get the desired effect. The images expand downward and to the right rather than in the outward from the diagonal center.
Here's what I've tried:
transform: scale(1.5) Which expands, but also totally shifts the image's position
transform: translate(-(width/2), -(height/2)) combined with scale, which does the same but from a different starting position
Changing the x and y coords to ones adjusted for half widths and heights, which has the same effect.
Is there no text-anchor equivalent for image elements with which I could set an "anchor point" to scale from? I'm not sure what the html svg parlance is, but I guess I'm thinking of something similar to the anchor points a lot of vector editors have.
Current approach, mouseover handler:
function fade(dir){
return function(d){
var others = svg.selectAll("image.movie_cover")
.filter(function(g,i){
return g != d
})
.transition().duration(800)
.style("opacity",.3);
var single = svg.selectAll("image.movie_cover")
.filter(function(g,i){
return g === d;
})
.transition().duration(900)
.attr("transform", "translate(-40,-40) scale(1.4)")
var title = keys[coords.indexOf(d)];
var url = "/static/eshk/"+hash+"_images/" + title + ".jpg";
tt.transition()
.duration(200)
.style("opacity", .9);
tt.html(title)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}
}
Using this method, the images move inconsistent distances despite all being the same size.
Set up: A 50 x 50 box at 200, 200. It needs to transition to a 100 x 100. It is 50 larger and wider, so needs to move back and up 25, eg 175, 175. Replace hard coded values with functions that look up the current width on mouse hover to calculate the exact values.
d3.select('svg').append('rect');
rect = d3.select('rect');
rect.attr({
height: 50,
width: 50,
x: 200,
y: 200,
color: 'steelblue'
})
.transition()
.attr({
width: 100,
height: 100,
x: 175,
y: 175
});
This could also be done without modifying width or position attributes:
images.on("mouseover", function(d, i) {
var selection = d3.select(this);
var offsetX = parseFloat(selection.attr("x"))+
parseFloat(selection.attr("width")/2.0);
var offsetY = parseFloat(selection.attr("y"))+
parseFloat(selection.attr("height")/2.0);
selection.attr({
transform:"translate("+offsetX+ ","+offsetY+") "+
"scale(1.2) "+
"translate(-"+offsetX+",-"+offsetY+ ")"
});
});
And on mouse out, you'd just set the transform to null to remove it.
Basically, this is just translating the center point to the origin, scaling around that, and translating back to the correct position. Remember that transforms are applied in reverse order (right to left).
I think you were on the right track with the use of translate with scale, but translating back and forth from the origin is what allows it to work while remaining centered at the original location.
Related
Creating a rectangle in FabricJs is straightforward using top, left, width and height values.
After moving, scaling and rotating the rectangle around, one can get the rectangle's definitive coordinate using the aCoords of the object, returning bl, br, tl, tr x and y coordinates of the rectangles four edges.
How can one recreate the same rectangle using only these four coordinates, which also include rotation and scaling?
I was only able to recreate the rectangle after it has been moved and scaled, but not with rotations.
This above image is a screenshot of the following jsFiddle: https://jsfiddle.net/7neukojd/32/
As you can see, the left side is the original rectangle (blue), which has been scaled, moved and rotated. The right side rectangle (red) is me, trying to copy it using only the bl, br, tl and tr coordinates.
Thanks to asturur, this issue has been solved with the following code
let rectObj = canvas1.getObjects()[0];
const tl = rectObj.aCoords.tl;
const tr = rectObj.aCoords.tr;
const bl = rectObj.aCoords.bl;
let rectCopy = new fabric.Rect({
left: rectObj.aCoords.tl.x,
top: rectObj.aCoords.tl.y,
width: (new fabric.Point(tl.x, tl.y).distanceFrom(tr)),
height: (new fabric.Point(tl.x, tl.y).distanceFrom(bl)),
angle: fabric.util.radiansToDegrees(Math.atan2(tr.y - tl.y, tr.x - tl.x)),
fill: "red",
})
Source: https://github.com/fabricjs/fabric.js/discussions/6834#discussioncomment-314599
In order to make a joint map/scatterplot chart of mine responsive, I've adjusted the SVG dimensions based on the container size (as Chris Amico suggests doing here). My original map works well enough — when you click the "posts/population" button, there's a simple fade out of the map, and the circles line up in a logical way.
Here's what the code looks like for my SVG setup (nothing fancy, just giving this as background to show the changes I made). In the body, I had:
<svg width="100%" height="100%">
</svg>
In the relevant portion of the script, I had:
var padding = 45,
h = 500,
w = 700,
svg = d3.select("body").select("svg"),
projection = d3.geo.albersUsa().translate([w/2,h/2]).scale([900]),
path = d3.geo.path().projection(projection);
The new map, which I've thrown up onto a block, remains more or less the same, save a key difference in the way I work with the SVG. First, I only include a div container within the body, rather than an SVG element, like so:
<div id="mapGeo">
</div>
Second, I've changed a few things in the setup of the SVG to account for the container width:
var paddingGeo = {top: 10, left: 10, bottom: 10, right: 10},
wGeo = parseInt(d3.select('#mapGeo').style('width')),
wGeo = wGeo - paddingGeo.left - paddingGeo.right,
mapRatio = .5,
hGeo = wGeo * mapRatio,
projection = d3.geo.albersUsa().scale(wGeo).translate([wGeo/2, hGeo/2]);
path = d3.geo.path().projection(projection);
After loading my data, I finally append my SVG to the div:
var svgGeo = d3.select('#mapGeo').append('svg')
.style('height', hGeo+ 'px')
.style('width', wGeo +'px')
Doing this, however, mangles the scatterplot. Is this some sort of ridiculous rookie error I'm coming up across, or is there something more serious happening here?
To sum up: I'd made a slight change to a working map/scatterplot chart which should have allowed me to remain responsive, but ended up throwing a spanner in the works, and now am completely baffled.
Check out the block here: http://bl.ocks.org/iblind/6c75592f5053a86c5480
Ok, here's the solution, albeit on that doesn't flatter me much. The issue came where I was setting up the scales:
var xScaleGeo = d3.scale
.linear()
.domain([1, d3.max(data, function(d){ return +d.ratio_rank;})])
.rangeRound([0+paddingGeo, wGeo-paddingGeo]);
paddingGeo was a variable with the value of 45 in the original, but in the updated code I'd changed it to an object:
var paddingGeo = {top: 10, left: 10, bottom: 10, right: 10}
Trying to add the object to 0 was the culprit. Sigh.
I have two issue to request for help,
Have an issue with the highcharts, it looks like some calculation needs to be done for the chart, but could not ale to figure out the actual thing.
please look at this Fiddle , In this the label at the right hand side $500 is at the correct position that is ok, but when the value is big/large, lets say $555555555555, then the
chart label goes out of chart. Have a look at this fiddle now
Fiddle having Issue
Error:
what should happen is that the $555555555555 should be inside the chart only, having the same position as show in the first fiddle.
What I tried.
Adding/subtracting the length of the string (point.y) with + 30
var chart = new Highcharts.Chart({
...
....
/*********RIGHT SIDE CPM*******************/
var point = chart.series[0].data[7];
var text = chart.renderer.text(
'$'+point.y,
point.plotX + chart.plotLeft - 30,
// point.plotX + chart.plotLeft - ( ((point.y).toString().length) + 30 ), //My change
...
});
But this point.plotX + chart.plotLeft - ( ((point.y).toString().length) + 30 ), did not support when the value again changes from $555555555555 to $5
so Basically the position should not vary when the length of the label is big/small.
My next question, The chart is starting with a small displacement at both sides.
have a look at the image below
As you can see the gap between the two red lines, at both the corners in the image, I need to remove that, It may be a padding issue, but I could not able to figure it out.
Please help me in this two issues.
Thanks for helping in advance.
Issue: Aligning rendered text
So from reading your code I think you are trying to adjust the position of the text and box from the length of the content. Problem here is you have a font that has variable width characters while subtracting a constant value per character. Also, the renderer.text method always anchors in the bottom left, which isn't in your favor.
What I would do here is leverage that the text.getBBox knows the exact bounding box of the text Element. You might be able to get this to look a bit better, but here's a crude example:
/*********RIGHT SIDE CPM*******************/
var point = chart.series[0].data[7];
// Draw dummy text, just to get bounding box
var text = chart.renderer.text('$'+point.y, 0, 0)
.attr({
zIndex: 5
}).add();
// Get bounding box
var box = text.getBBox();
// Destroy dummy text
text.destroy();
// Draw actual text based on bounding box
text = chart.renderer.text(
'$'+point.y,
point.plotX + chart.plotLeft - box.width + 2 ,
point.plotY + chart.plotTop - 10
).attr({
zIndex: 5
}).add();
// Get actual box
box = text.getBBox();
// Draw actual box
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();
And a JFiddle example (I only bothered to fix your right side CPM, but you get the point).
Issue: Spacing on sides of graph
Set the min and max of your x-axis to specific values, as shown in the JFiddle example, with the code:
xAxis: {
...,
min: 1,
max: 8
}
What I have:
Text along a path made out of circle. It uses Raphael.js and a function called textOnPath (found here: Raphael JS Text Along path ):
var pathTest = r.path(getCircletoPath(286, 322, radius)).attr({stroke:"#b9b9b9"});
textOnPath(message, pathTest, fontSize, fontSpacing, kerning, kerning, point, textFill, fontNormal, fontFamily);
JSFiddle: http://jsfiddle.net/zorza/62hDH/1/
What I need:
The text to be centered on top of the circle.
My approach:
Try to calculate where the text should start depending on the arc size and text width. I tried to calculate the text width by creating it's invisible clone with text() function and get it's BBox width.
It doesn't quite work and the results vary depending on the web browser, font used and number of letters and spaces:
var length = r.text(100,400,message)
.attr({"font-size":fontSize,'opacity': 0, 'font-family': fontFamily})
.getBBox()
.width;
var point = (Math.PI*radius - length*fontSpacing)/2;
JSFiddle: http://jsfiddle.net/zorza/k8vBy/3/
Could anyone point me in the right direction?
The easiest way, IMHO, is to create additional helper path that is raised by half of text size. http://jsfiddle.net/p84VQ/
Also, I find it a bit more convenient to define a circle and then get points at specified angle:
var Circle = function(cx, cy, r) {
return function (a) {
return {
x: cx + r*Math.sin(Math.PI*-a/180),
y: cy - r*Math.cos(Math.PI*-a/180)
}
}
};
I am quite new to javascript and to Raphael. I am trying to move a button-like rectangle with text inside. Here is my code :
window.onload = function() {
var paper = new Raphael(document.getElementById('canvas_container'), "100%", "100%");
var box1 = paper.rect(100, 100, 120, 50, 10).attr({fill: 'darkorange', stroke: '#3b4449', 'stroke-width': 2, cursor: 'pointer'});
var box2 = paper.rect(400,100,120,50,10).attr({fill: 'lightblue', stroke: '#3b4449', 'stroke-width': 2});
var text2 = paper.text(box2.attrs.x + box2.attrs.width/2,box2.attrs.y + box2.attrs.height/2,"[x: " + box2.attrs.x + " y: " + box2.attrs.y + "]").attr({"font-family": "arial", "font-size": 16});
var button2 = paper.set();
button2.push(box2);
button2.push(text2);
box1.click(function(){
// this did not work
// button2.animate({x: 100, y: 50 }, 1000, 'bounce', function() { // callback function
// text2.attr('text',"[x: " + box2.attrs.x + " y: " + box2.attrs.y + "]");
// });
button2.animate({transform: "t100,100"}, 1000, 'bounce', function() { // callback function
text2.attr('text',"[x: " + box2.attrs.x + " y: " + box2.attrs.y + "]");
});
});
}
The button2.animate({x: 100, y: 50 }, 1000, 'bounce'); line did not worked properly, the text was not in the right position at the end. By using the transform: I can not use coordinates, I would have to compute them. Also I am not able to get the right coordinates of the blue box at the end when using the transform method.
I was not able to find any answer yet, hope someone can help me.
Thank you
Since you didn't explain how exactly you want to move your button, I'm assuming you want to move the box2 above box1.
There are some misunderstandings and errors in your code, allow me explain one by one.
Why the first way cause text move to wrong position at end ?
Because a set is NOT a group of element which knows its relative position inside the group. A set is merely a collection of elements which is designed for us to operate them in a more convenient way.
So, the code below will move all element in the set to (100, 50)
set.animate({x: 100, y: 50 }, 1000);
and that's why the text is there.
I couldn't find the document, but you can find some explanation here .
Why x, y in attributes seems to be wrong when using transform ?
No, the attribute is correct.
When you transform an element, the result of the transformation will not reflect back to the attributes. You can think like this, when transform(), you are actually attach "transformation" to the elements. Therefore :
paper.circle(100, 100, 5).transform("t100");
You can describe the circle as :
a circle at (100, 100) which will be moved 100px horizontally.
but not - a circle at (200, 100) which will be moved 100px horizontally.
So, here is the code that dose what you want, note that I'm using getBBox() to get coordinate of the button2 set.
box1.click(function(){
var moveX = box1.attr("x") - button2.getBBox().x;
var moveY = (box1.attr("y") - 50) - button2.getBBox().y;
button2.animate({transform: "t" + moveX + "," + moveY}, 1000, 'ease-in-out', function () {
text2.attr('text', "[x: " + button2.getBBox().x + "y: " + button2.getBBox().x + "]");
});
});
Welcome to SO, and suggest you to write a SSCCE next time.
UPDATE
I do not fully understand why the transformation does not reflect back
to the attributes. If I move the circle at the position (100,100)
100px horizontally it will results in a circle at position (200,100).
This is what the bounding box gives me. So why I am not able to get
the coordinates from the circle after the transformation and have to
use the bounding-box method ?
Transform DOSE NOT change the original attribute in the element, because it is something you attach to a element, not function that change a element directly. If you want to know attributes AFTER the transformation applied, you have to use getBBox(), or take a look about matrix.
This is how Raphael.js works. Either you use bounding box function, or extend the Raphael.js by yourself like this
I have changed my previous answer about how I describe transformation a little bit, hope it can help you to understand better this time.
Your code works great but it has the drawback, that you have to
compute the transformation values instead of simply setting the
position. Is there any other way to move a rectangle with text inside
to a position of your choice ?
You can always write helper functions to do these ugly jobs for you anyway, I don't see there's anything wrong with it.