Need to "mark" a svg text item with a background color, it has to be generated with javascript.
Found a working method but it needs to code the text, get the bbox values and draws a rect at the text position. I can't use "fill-opacity" (the text/background need to be above other elements!) and with a solid colored rect the text isn't seen anymore.
With the idea "first element -> 'painted' first" or the last element will be seen, I rewrite the text element once more .. all is ok ... but is there a simpler method?
Roughly the code is this:
var textColored = function (parent, text, idTxt, anchor, xT, yT, textColor, fillColor) {
var labelText = $svg.text(idTxt,
xT, yT,
text, // "xTime.hh+":"+xTime.mm + " Sunset",
{
fill: textColor,
"font-size": defaultOptions.fontSize,
"text-anchor": anchor,
"font-style": "italic"
}
)
labelText.appendTo(parent);
// background for text
var bbox = $("#" + idTxt)[0].getBBox();
var sRect = $svg.rect(
// "x","y","width","height"
bbox.x -2, bbox.y, bbox.width + 4, (+defaultOptions.fontSize + 3)
)
.attr("fill", fillColor)
// .prependTo(parent); // not working! added to first position of 'parent', see Inspector
.appendTo(parent);
labelText.appendTo(parent);
}
Tried with the edited code to use '.prependTo' but that adds the background element to the top of 'parent' and so at lowest level - behind all other svg elements.
Also failed with <g> element as recommended by Robert failed (for the moment).
Probably will stay with the double adding the text element which works fine using scripting with bbox. As described the background size and position has to be 'generated' based on the text.
Related
I'm trying to place text dynamically into an svg created by Snap, this is what I tried:
this.setContent(
`<svg id="${this.svgId}"></svg>`
);
var snap = Snap($(`#${this.svgId}`)[0]);
text = "asdfsdfsdsfd";
var rect = snap.paper.rect(0, 0, 50, text.length*3 + 4, 10);
snap.text(1.5,10, text);
console.log("rect", rect);
console.log("snap", snap);
rect.attr({
fill: "#FFFFFF",
fillOpacity: 0.6,
});
I get this:
I want the rectangle to be just a little bigger than the text, but there must be a better way to do it than to calculate the length and height of the text, and that's assuming the font size won't change.
This is the only result I found regarding text in the snap docs: http://snapsvg.io/docs/#Paper.text
You could try using getBBox() on the text element, and use that to figure the size of the rect. getBBox() wll give you the x,y,width,height,x2,y2 figures to help.
var text = s.text(0,0,'blah blah')
var bb = text.getBBox();
var rect = s.rect(bb.x, bb.y, bb.width, bb.height )
Adjusting with offsets for whatever padding etc that you want. You may also need to allow for stroke widths, as I don't think that's included.
I want to recreate the effect shown in this fiddle
According to StackOverflow rules I apparently have to present some code if I link to jsfiddle.net, so here's the main function from that link. Although to see the effect you obviously have to follow the link instead.
$(document).ready(function() {
$(".textWrapper").hover(function() {
$(".highlight", this).show();
$(this).mousemove(function(e) {
var relativePos = e.pageY - this.offsetTop;
var textRow = (Math.ceil(relativePos / 18) * 18) - 18;
if (textRow >= 0) { $(".highlight", this).css("top", textRow + "px"); }
});
}, function() {
$(".highlight", this).hide();
});
});
Rather than highlight the text in yellow I'd prefer to change the color of the text itself.
I'd like the text to be light grey, and darken when highlighted, to bring that line into focus. This seems a lot more difficult than simply changing CSS, because the actual text properties do not change.
How do I accomplish this?
Take a look:
http://jsfiddle.net/5nxr6my4/
Using the same principle I created 2 white opaque divs #highTop and #highBot in order to overlay the text when the mouse pointer hovers over it. Their height and top properties are adjusted to the pointer position, so underlying black text appears gray, except the line at which the mouse pointer points to:
$(document).ready(function() {
$('#highTop').css('height', $('.textWrapper').height()).show();
$('.textWrapper').hover(function() {
$('#highBot').show();
$(this).mousemove(function(e) {
var relativePos = e.pageY - this.offsetTop;
var textRow = (Math.ceil(relativePos / 18) * 18) - 18;
if (textRow >= 0) {
// change height of #hightTop to make it cover upper part of text
$('#highTop').css('height', textRow + 'px');
// change position and height of #highBot to make it cover lower part of text
$('#highBot').css('height', $('.textWrapper').height() - 18 - textRow + "px")
.css('top', textRow + 18 + 'px');
}
});
}, function() {
// when the pointer goes out of the text, hide #highBot and make #highTop cover entire text
$('#highTop').css('height', $('.textWrapper').height() + 'px');
$('#highBot').hide();
});
});
I've done a little research and it appears that the only way that this would become possible would be to put each line in a separate HTML tag.
The reason that it is not possible the way you want it, is that the .highlight div doesn't contain the text itself so you can only apply an overlay rather than editing the underneath text.
It might help to have a look at http://jsbin.com/ukaqu3/91 which is about only displaying certain lines of text.
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.
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.