SVG: Calculating bounding box without displaying object - svg

I need to get a text bounding box to adjust my layout before rendering anything. With some experimenting, I found that I had to actually render the text before 'getBBox' (or 'getComputedTextLength') will return a non-zero value:
var group = svgDocument.createElementNS(svgns, "g");
for(i=0; i <= nYblocks; ++i) {
str = svgDocument.createTextNode(strings[i]);
obj = tnode.cloneNode(true);
obj.setAttributeNS(null, "y", y1);
obj.appendChild(str);
group.appendChild(obj);
y1 += yBlockPx;
}
svgDocument.documentElement.appendChild(group); // **REQUIRED**
bb = vgroup.getBBox();
Problem: is there a good way to render the text so that it doesn't actually display? Should I just adjust the colours or opacity, or is there something clever I can do to render somewhere else, perhaps in a different tree?
Thanks -
Al

I think the easiest option is to draw it with the visibility set to hidden:
obj.setAttributeNS(null, "visibility", "hidden");

Related

Fabricjs selection or hover based on mouse location on object bounding rectangle for inner objects

I want to achieve selection based on the bounding rectangle but with a different approach.
Scenario: If I draw object inside object, like first text, then rectangle over it, then ellipse and then triangle. Now I should be able to select the text or rectangle or ellipse OR the reverse order anyhow.
As I start hovering the triangle's bounding rect, the selection or active object should be triangle, but as I move my mouse over ellipse's bounding rect, the current object should be shown as ellipse and so on, irrespective of the order I have added the objects on canvas.
I tried with perPixelTargetFind and following solution Fabricjs - selection only via border, both the solutions are not working meeting my requirement.
I am using FabricJS version 3.6.3
Thanks in advance.
First you need to set perPixelTargetFind: true and targetFindTolerance:5.
Now you will face the issue for selection.
Issue: If you mousedown and drag on empty space, the object was getting selected.
Solution: Found a way to do that. I debugged through the fabric's mechanism to get objects on current mouse pointer location. There is a function _collectObjects which checks for intersectsWithRect (intersect with boundingRect points of the current object), isContainedWithinRect (do the points come inside the boundingRect), containsPoint(current mouse pointer points come in the current object location). So you need to override the _collectObjects function and remove containsPoint check. That will work.
Overridden function:
_collectObjects: function(e) {
var group = [],
currentObject,
x1 = this._groupSelector.ex,
y1 = this._groupSelector.ey,
x2 = x1 + this._groupSelector.left,
y2 = y1 + this._groupSelector.top,
selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)),
allowIntersect = !this.selectionFullyContained,
isClick = x1 === x2 && y1 === y2;
// we iterate reverse order to collect top first in case of click.
for (var i = this._objects.length; i--; ) {
currentObject = this._objects[i];
if (!currentObject || !currentObject.selectable || !currentObject.visible) {
continue;
}
if ((allowIntersect && currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2)) ||
currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2))
) {
group.push(currentObject);
// only add one object if it's a click
if (isClick) {
break;
}
}
}
if (group.length > 1) {
group = group.filter(function(object) {
return !object.onSelect({ e: e });
});
}
return group;
}

Scrolling through colors effect in RGB

I want to create a colour scroller effect. I have a function that I give it RGB values (eg. setColor(189,234,45)) and I want to change the colour rapidly but I don't want to get many repeats to create an effect of scrolling through the colours.
I have tried tried the following but it doesn't quite generate the effect that I am looking for.
for (int i = 0; i < 256; i++) {
for (int j = 0; j < 256; j++) {
for (int k = 0; k < 256; k++) {
setColor(i, j, k);
}
}
}
I wanted to know if anyone knows how the colour scroller's colours are arranged next to each other. The arrangement I am looking for looks like the scroll on the right.
The colors you are working with are represented as R,G,B (red green blue) values. However, another
way to think about color is hue, saturation, value. In the scroll image you are trying to emulate,
it is the hue that is changing - the saturation and value (brightness) are unaffected.
Here is a function that happens to make a hue-cycle gradient like the one in the image you linked to:
int n = 256; // number of steps
float TWO_PI = 3.14159*2;
for (int i = 0; i < n; ++i) {
int red = 128 + sin(i*TWO_PI/n + 0) + 127;
int grn = 128 + sin(i*TWO_PI/n + TWO_PI/3) + 127;
int blu = 128 + sin(i*TWO_PI/n + 2*TWO_PI/3) + 127;
setColor(red, grn, blu);
}
To understand how that function works, I recommend that you read my color tutorial that GreenAsJade linked to.
However, that kind of gradient function isn't quite what you need, because you want to start from a particular color you are passing in, and then go to the next color in the sequence. It's much easier to do this kind of thing if you represent your colors as HSV triplets (or HSB triplets), instead of RGB triplets. Then you can manipulate just the hue component, and get those kind of rainbow effects. In helps to have a set of function that can convert from RGB to HSV and back again.
This site contains a bunch of color conversion source code, including the ones you need for those conversions. Using the two conversion functions supplied on that page, your code might look like:
void cycleMyColor(int *r, int *g, int *b) {
float h,s,v, fr,fg,fb;
RGBtoHSV(*r/255.0,*g/255.0,*b/255.0,&h,&s,&v);
h += 1/256.0; // increment the hue here
h -= (int) h; // and cycle around if necessary
HSVtoRGB(&fr,&fg,&fb,h,s,v);
*r = fr*255; *g = fg*255; *b = fb*255;
setColor(*r,*g,*b);
}
This code is a little more complicated than it needs to be because the color conversions on that site use floating point color components that go from 0-1, instead of integers that go from 0-255, as you were using, so I'm spending a few lines converting between those two representations. You may find it simpler to just keep your color in HSB space, and then convert to RGB when you want to display it.
As you mentioned in your edit, you don't like the sequence of colours, because you start from black an end at white, instead of starting at one end of the rainbow and going to the other.
So you are going to need to work out a sequence of RGB that goes from blue through green and yellow to red. That means you need to start with (0,0,255) and end at (255, 0, 0), and don't pass through (255,255,255) or (0,0,0) - in a nutshell, that's how its done.
There are many ways you could do this and get a pleasing effect - beyond the scope of an answer here. This article explores it in depth:
http://krazydad.com/tutorials/makecolors.php

How to avoid the overlapping of text elements on the TreeMap when child elements are opened in D3.js?

I created a Tree in D3.js based on Mike Bostock's Node-link Tree. The problem I have and that I also see in Mike's Tree is that the text label overlap/underlap the circle nodes when there isn't enough space rather than extend the links to leave some space.
As a new user I'm not allowed to upload images, so here is a link to Mike's Tree where you can see the labels of the preceding nodes overlapping the following nodes.
I tried various things to fix the problem by detecting the pixel length of the text with:
d3.select('.nodeText').node().getComputedTextLength();
However this only works after I rendered the page when I need the length of the longest text item before I render.
Getting the longest text item before I render with:
nodes = tree.nodes(root).reverse();
var longest = nodes.reduce(function (a, b) {
return a.label.length > b.label.length ? a : b;
});
node = vis.selectAll('g.node').data(nodes, function(d, i){
return d.id || (d.id = ++i);
});
nodes.forEach(function(d) {
d.y = (longest.label.length + 200);
});
only returns the string length, while using
d.y = (d.depth * 200);
makes every link a static length and doesn't resize as beautiful when new nodes get opened or closed.
Is there a way to avoid this overlapping? If so, what would be the best way to do this and to keep the dynamic structure of the tree?
There are 3 possible solutions that I can come up with but aren't that straightforward:
Detecting label length and using an ellipsis where it overruns child nodes. (which would make the labels less readable)
scaling the layout dynamically by detecting the label length and telling the links to adjust accordingly. (which would be best but seems really difficult
scale the svg element and use a scroll bar when the labels start to run over. (not sure this is possible as I have been working on the assumption that the SVG needs to have a set height and width).
So the following approach can give different levels of the layout different "heights". You have to take care that with a radial layout you risk not having enough spread for small circles to fan your text without overlaps, but let's ignore that for now.
The key is to realize that the tree layout simply maps things to an arbitrary space of width and height and that the diagonal projection maps width (x) to angle and height (y) to radius. Moreover the radius is a simple function of the depth of the tree.
So here is a way to reassign the depths based on the text lengths:
First of all, I use the following (jQuery) to compute maximum text sizes for:
var computeMaxTextSize = function(data, fontSize, fontName){
var maxH = 0, maxW = 0;
var div = document.createElement('div');
document.body.appendChild(div);
$(div).css({
position: 'absolute',
left: -1000,
top: -1000,
display: 'none',
margin:0,
padding:0
});
$(div).css("font", fontSize + 'px '+fontName);
data.forEach(function(d) {
$(div).html(d);
maxH = Math.max(maxH, $(div).outerHeight());
maxW = Math.max(maxW, $(div).outerWidth());
});
$(div).remove();
return {maxH: maxH, maxW: maxW};
}
Now I will recursively build an array with an array of strings per level:
var allStrings = [[]];
var childStrings = function(level, n) {
var a = allStrings[level];
a.push(n.name);
if(n.children && n.children.length > 0) {
if(!allStrings[level+1]) {
allStrings[level+1] = [];
}
n.children.forEach(function(d) {
childStrings(level + 1, d);
});
}
};
childStrings(0, root);
And then compute the maximum text length per level.
var maxLevelSizes = [];
allStrings.forEach(function(d, i) {
maxLevelSizes.push(computeMaxTextSize(allStrings[i], '10', 'sans-serif'));
});
Then I compute the total text width for all the levels (adding spacing for the little circle icons and some padding to make it look nice). This will be the radius of the final layout. Note that I will use this same padding amount again later on.
var padding = 25; // Width of the blue circle plus some spacing
var totalRadius = d3.sum(maxLevelSizes, function(d) { return d.maxW + padding});
var diameter = totalRadius * 2; // was 960;
var tree = d3.layout.tree()
.size([360, totalRadius])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
Now we can call the layout as usual. There is one last piece: to figure out the radius for the different levels we will need a cumulative sum of the radii of the previous levels. Once we have that we simply assign the new radii to the computed nodes.
// Compute cummulative sums - these will be the ring radii
var newDepths = maxLevelSizes.reduce(function(prev, curr, index) {
prev.push(prev[index] + curr.maxW + padding);
return prev;
},[0]);
var nodes = tree.nodes(root);
// Assign new radius based on depth
nodes.forEach(function(d) {
d.y = newDepths[d.depth];
});
Eh voila! This is maybe not the cleanest solution, and perhaps does not address every concern, but it should get you started. Have fun!

Win32 Text Drawing Puzzle

I've got a little text drawing puzzle under Win32. I'm trying to draw some instructions for users of my application at the top of the window.
Please refer to the following window (I've changed the background color on the text so you can see the boundaries)
(source: billy-oneal.com)
I'm currently using DrawTextEx to draw the text to my window, but the problem is that it does not fill the entire RECTangle that I give it. Not drawing that area is just fine, until the window resizes:
(source: billy-oneal.com)
When the text is re wrapped due to the window sizing, because DrawTextEx doesn't clear it's background, these artifacts are leftover.
I tried using FillRect to fill in the area behind the text drawing call, which does eliminate the visual artifacts, but then causes the text to flicker constantly, as it is completely erased and then completely redrawn to the display.
Any ideas on how one might get the area not containing text to be drawn with the background color?
EDIT: I'd like to avoid having to double buffer the form if at app possible.
EDIT2: I solved the problem by only redrawing the text when I detect that the wrapping changes during a resize.
Use double buffering?
Draw everything to a bitmap and draw the bitmap to the window. Flickering is commonly a double buffering issue.
There are many possible solutions and without seeing your code, it's hard to tell which method would be best so I'd suggest taking a look at this article on flicker free drawing
SetBkMode + SetBkColor ?
Well since nobody seems to know what to do about it, I implemented it this way:
std::vector<std::wstring> wrapString(HDC hDC, const std::wstring& text, const RECT& targetRect, HFONT font)
{
std::vector<std::wstring> result;
RECT targetRectangle;
CopyRect(&targetRectangle, &targetRect);
//Calculate the width of the bounding rectangle.
int maxWidth = targetRectangle.right - targetRectangle.left;
//Draw the lines one at a time
std::wstring currentLine;
for(std::wstring::const_iterator it = text.begin(); it != text.end(); currentLine.push_back(*it), it++)
{
if(*it == L'\r' || *it == L'\n')
{ //Hard return
while(it != text.end() && (*it == L'\r' || *it == L'\n')) it++;
result.push_back(currentLine);
currentLine.clear();
}
else
{ //Check for soft return
SIZE sizeStruct;
GetTextExtentPoint32(hDC, currentLine.c_str(), static_cast<int>(currentLine.length()), &sizeStruct);
if (sizeStruct.cx > maxWidth)
{
std::wstring::size_type lineLength = currentLine.find_last_of(L' ');
if (lineLength == currentLine.npos)
{ //Word is longer than a line.
for(;it != text.end() && !iswspace(*it);it++) currentLine.push_back(*it);
}
else
{ //Clip word to line.
//Backtrack our scan of the source text.
it -= currentLine.length() - lineLength - 1;
//Remove the clipped word
currentLine.erase(lineLength);
}
result.push_back(currentLine);
currentLine.clear();
}
}
}
//Last remaining text.
result.push_back(currentLine);
return result;
}
void DrawInstructionsWithFilledBackground(HDC hDC, const std::wstring& text, RECT& targetRectangle, HFONT font, COLORREF backgroundColor)
{
//Set up our background color.
int dcIdx = SaveDC(hDC);
HBRUSH backgroundBrush = CreateSolidBrush(backgroundColor);
SelectObject(hDC, backgroundBrush);
SelectObject(hDC, font);
SetBkColor(hDC, backgroundColor);
std::vector<std::wstring> lines(wrapString(hDC, text, targetRectangle, font));
for(std::vector<std::wstring>::const_iterator it = lines.begin(); it!=lines.end(); it++)
{
RECT backgroundRect = targetRectangle;
DrawText(hDC, const_cast<LPWSTR>(it->c_str()), static_cast<int>(it->length()), &backgroundRect, DT_CALCRECT | DT_NOCLIP | DT_SINGLELINE);
backgroundRect.left = backgroundRect.right;
backgroundRect.right = targetRectangle.right;
if (backgroundRect.right >= backgroundRect.left)
FillRect(hDC, &backgroundRect, backgroundBrush);
ExtTextOut(hDC, targetRectangle.left, targetRectangle.top, ETO_OPAQUE, NULL, it->c_str(), static_cast<UINT>(it->length()), NULL);
targetRectangle.top += backgroundRect.bottom - backgroundRect.top;
}
instructionsWrap = lines;
//Restore the DC to it's former glory.
RestoreDC(hDC, dcIdx);
DeleteObject(backgroundBrush);
}
Get/Calculate the rect used by the DrawText call and clip it with something like ExcludeClipRect before calling FillRect

Calculate a color fade

Given two colors and n steps, how can one calculate n colors including the two given colors that create a fade effect?
If possible pseudo-code is preferred but this will probably be implemented in Java.
Thanks!
Divide each colour into its RGB components and then calculate the individual steps required.
oldRed = 120;
newRed = 200;
steps = 10;
redStepAmount = (newRed - oldRed) / steps;
currentRed = oldRed;
for (i = 0; i < steps; i++) {
currentRed += redStepAmount;
}
Obviously extend that for green and blue.
There are two good related questions you should also review:
Generating gradients programatically?
Conditional formatting — percentage to color conversion
Please note that you're often better off doing this in the HSV color space rather than RGB - it generates more pleasing colors to the human eye (lower chance of clashing or negative optical properties).
Good luck!
-Adam
If you want a blend that looks anything like most color picker GUI widgets, you really want to translate to HSL or HSV. From there, you're probably fine with linear interpolation in each dimension.
Trying to do any interpolations directly in RGB colorspace is a bad idea. It's way too nonlinear (and no, gamma correction won't help in this case).
For those looking for something they can copy and paste. Made a quick function for RGB colors. Returns a single color that is the amount of ratio closer to rgbColor2.
function fadeToColor(rgbColor1, rgbColor2, ratio) {
var color1 = rgbColor1.substring(4, rgbColor1.length - 1).split(','),
color2 = rgbColor2.substring(4, rgbColor2.length - 1).split(','),
difference,
newColor = [];
for (var i = 0; i < color1.length; i++) {
difference = color2[i] - color1[i];
newColor.push(Math.floor(parseInt(color1[i], 10) + difference * ratio));
}
return 'rgb(' + newColor + ')';
}
The quesiton is what transformation do you want to occur? If you transpose into the HSV colourspace and given
FF0000 and 00FF00
It will transition from red through yellow to green.
However, if you define "black" or some other shade as being the mid-point of the blend, you have to shade to that colour first ff0000->000000->00ff00 or via white : ff0000 -> ffffff -> 00ff00.
Transforming via HSV however can be fun because you have to use a bit of trig to map the circular map into the vector components.
The easiest thing to do is linear interpolation between the color components (see nickf's response). Just be aware that the eye is highly nonlinear, so it won't necessarily look you're making even steps. Some color spaces attempt to address this (CIE maybe?), so you might want to transform into another color space first, interpolate, then transform back to RGB or whatever you're using.
How about this answer
- (UIColor *)colorFromColor:(UIColor *)fromColor toColor:(UIColor *)toColor percent:(float)percent
{
float dec = percent / 100.f;
CGFloat fRed, fBlue, fGreen, fAlpha;
CGFloat tRed, tBlue, tGreen, tAlpha;
CGFloat red, green, blue, alpha;
if(CGColorGetNumberOfComponents(fromColor.CGColor) == 2) {
[fromColor getWhite:&fRed alpha:&fAlpha];
fGreen = fRed;
fBlue = fRed;
}
else {
[fromColor getRed:&fRed green:&fGreen blue:&fBlue alpha:&fAlpha];
}
if(CGColorGetNumberOfComponents(toColor.CGColor) == 2) {
[toColor getWhite:&tRed alpha:&tAlpha];
tGreen = tRed;
tBlue = tRed;
}
else {
[toColor getRed:&tRed green:&tGreen blue:&tBlue alpha:&tAlpha];
}
red = (dec * (tRed - fRed)) + fRed;
green = (dec * (tGreen - fGreen)) + fGreen;
blue = (dec * (tBlue - fBlue)) + fBlue;
alpha = (dec * (tAlpha - fAlpha)) + fAlpha;
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}

Resources