QML Canvas clipping - non rectangular possible? - qml

Is it possible to display non rectangular items in an app?
The top right edge of each element is clipped:
I turned off clipping on the canvas element and set the clipping
region of the context. I even allowed for the stroke drawing outside
the path. Here's what I'm using to draw it:
Canvas
{
//id: root
// canvas size
height: parent.height - 8
width: height
anchors.top: parent.top + 4
clip: false
z: index + 1
// handler to override for drawing
onPaint:
{
// get context to draw with
var ctx = getContext("2d")
ctx.reset();
// path that includes 1 pixel margin on all sides
ctx.beginPath()
ctx.moveTo( 8, 0 )
ctx.lineTo( width + 4, 0 )
ctx.lineTo( width - 4, height )
ctx.lineTo( 0, height )
ctx.closePath()
ctx.clip();
// setup the stroke
ctx.lineWidth = 2
ctx.strokeStyle = "white"
ctx.beginPath()
ctx.moveTo( 9, 1 )
ctx.lineTo( 9 + width, 1 )
ctx.lineTo( 1 + width, height - 1 )
ctx.lineTo( 1, height - 1 )
ctx.closePath()
ctx.fillStyle = (roleStatus.toLowerCase().indexOf("success")!==-1) ? "green" : "red"
ctx.fill()
ctx.stroke()
}
}
This will be used on Windows and android.
Thanks

Yes... You can use PaintedItem to paint directly on items using Native Paint tools from C++ like QPainterPath
check out http://doc.qt.io/qt-5/qtquick-customitems-painteditem-example.html
the reason that your canvas is clipping is due to the fact that you are drawing width + 4 which should be (width - 8), but since you move to (8,0) first, then you end up drawing an extra 4 pixels too far. try either moving the item over 4 pixels by doing moveTo(4,0) or make the line shorter by doing just width instead of width + 4
Also check out : anchors.fill: parent which will work better in your case most likely.
The way that I avoid crazy bugs like this is by not ever hard coding width, height, x or y into my application.. instead use percentages such as
(parent.width * 0.25) to get 1/4 of the parent
Here's ONE way you could fix your code...
Canvas
{
//id: root
// canvas size
height: parent.height * 0.95
width: height
anchors.top: parent.top
clip: false
z: index + 1
// handler to override for drawing
onPaint:
{
// get context to draw with
var ctx = getContext("2d")
ctx.reset();
// path that includes 1 pixel margin on all sides
ctx.beginPath()
ctx.moveTo( width * 0.1, 0 )
ctx.lineTo( width * 0.9, 0 )
ctx.lineTo( width * 0.7, height )
ctx.lineTo( 0, height )
ctx.closePath()
ctx.clip();
/* etc etc */
}
}

I was unable to find a way to draw outside the bounds of the item. I was able to achieve the effect I wanted though. I drew the polygon within the bounds of the item and set the 'spacing' property of the ListView to a negative value. This overlaps the drawn items to achieve the desired look:

Related

How can i draw one-sided thickening stoke on canvas in Android Jetpack Compose?

Here is my code;
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
){
Canvas(
modifier = Modifier
.fillMaxSize(0.5f)
.border(1.dp, Color.Red)
){
drawRect(
color =Color.Black,
size = size,
style = Stroke(
width = 100f
)
)
}
}
}
Result;
I want the thickness to increase from the inside or outside of the red line, NOT both side. How can i achieve this?
You have to consider the offset and the width of the Rect.
To draw outside you can use:
val width = 100f
drawRect(
topLeft = Offset(-width/2, -width/2),
color =Color.Black,
size = Size(size.width + width, size.height + width),
style = Stroke(
width = width
)
)
To draw inside:
val width = 100f
drawRect(
topLeft = Offset(+ width/2, + width/2),
color =Color.Black,
size = Size(size.width - width, size.height - width),
style = Stroke(
width = width
)
)
I couldnt able to find any direct solution modifying stroke placement with inner or outer.
But here is a way to achieve it,by increasing/ decreasing size and offset.
Here size(width and height) is increased by the (stroke value+size) of rect. And topleft offset will hold (-0.5*stroke value). Here it will increase size of rect and moves slightly left*top from topleft.
drawRect(
color = Color.Black,
size = Size(size.width+100f,size.height+100f),
style = Stroke(
width = 100f
),
topLeft = Offset(-50f,-50f),
)
Here size(width and height) is increased by the (size-stroke value) of rect. And topleft offset will hold (0.5*stroke value). Here it will decrease size of rect and moves slightly right & bottom from topleft.
drawRect(
color = Color.Black,
size = Size(size.width-100f,size.height-100f),
style = Stroke(
width = 100f
),
topLeft = Offset(50f,50f),
)
You need to offset draw position. Border is drawn from center position to both sides in Canvas so offset it by half of your stroke width and reduce size as stroke width. Also using, for instance 20.dp.px instead of 100f will make sure that your borderSroke will look similar on every device.
#Composable
fun BorderCanvasSample() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Canvas(
modifier = Modifier
.fillMaxSize(0.5f)
.border(1.dp, Color.Red)
) {
val borderStroke = 100f
val halfStore = borderStroke / 2
drawRect(
color = Color.Black,
topLeft = Offset(halfStore, halfStore),
size = Size(
width = size.width - borderStroke,
height = size.height - borderStroke
),
style = Stroke(
width = borderStroke
)
)
}
}
}

Cytoscape zoom alignment

I'm having an issue with my background image maintaining proper alignment with the network image, when using mouse to zoom in or out. Without zoom, the network matches the background. With zoom in however, the background shifts right (out of alignment). The background image stays same width as network, only shifted. Same with zoom out, but to the left.
In order to get zoom to work to this degree, I needed to adjust zoom factor:
cy.on('zoom', function(evt){
var newpct = 135.0 * cy.zoom();
document.getElementById('cy').style.backgroundSize = newpct + '%';
});
The header CSS for image, etc:
#cy {
background-image: url("background6.png");
background-repeat: no-repeat;
background-size: 100%;
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
}
The image is from an 11" x 8.5" MS Publisher file, saved as a PNG
Pan is working well, but an adjustment was needed:
cy.on('pan', function(evt){
var pan = cy.pan();
var x = pan.x - 92;
var y = pan.y - 48;
document.getElementById('cy').style.backgroundPosition = x +'px ' + y + 'px ';
});
Any help is much appreciated
You have to keep your co-ordinates synched, and this has to be done exactly. If you adjust some scale values and translation offsets experimentally or with relative values, you'll probably be off.
For scale, s, and translation, t, applied to your image in CSS, find the constants s0 and t0 such that
s = zoom * s0 and t = { x: pan.x + t0.x, y: pan.y + t0.y }
You can find s0 and t0 by aligning the image at zoom 1 and pan (0, 0).
You can't use percents for anything; you ought to use pixels (or ems if you measure everything in ems).
Thank you so much for your answer. After some more work I learned that the zoom event was afterwards triggering the pan event. So I put the translation (covering both pan and zoom shifts) only in pan event function, like this:
var ax = -120 * cy.zoom();
var ay = -60 * cy.zoom();
var x = pan.x + ax;
var y = pan.y + ay;
document.getElementById('cy').style.backgroundPosition = x +'px ' + y + 'px ';
It is working perfectly now. Thanks again for your support!

Plot Array as Graph in ImageView

How can I plot an array to an imageview as a graph?
I've been testing this in Playground and it works, but how can plot this as an imageview in an actual project?
let sineArraySize = 64
let frequency1 = 4.0
let phase1 = 0.0
let amplitude1 = 2.0
let sineWave = (0..<sineArraySize).map {
amplitude1 * sin(2.0 * M_PI / Double(sineArraySize) * Double($0) * frequency1 + phase1)
}
func plotArrayInPlayground<T>(arrayToPlot:Array<T>, title:String) {
for currentValue in arrayToPlot {
XCPCaptureValue(title, currentValue)
}
}
plotArrayInPlayground(sineWave, "Sine wave 1")
One way you could do this:
// this function creates a plot of an array of doubles where it scales to the provided width and the x-axis is on half height
func plotArray(arr: [Double], width: Double, height: Double) -> NSImage {
if arr.isEmpty { return NSImage() }
let xAxisHeight = height / 2
let increment = width / Double(arr.count)
let image = NSImage(size: NSSize(width: width, height: height))
image.lockFocus()
// set background color
NSColor.whiteColor().set()
NSRectFill(NSRect(x: 0, y: 0, width: width, height: height))
let path = NSBezierPath()
// line width of plot
path.lineWidth = 5
path.moveToPoint(NSPoint(x: 0, y: arr[0] * increment + xAxisHeight))
var i = increment
for value in dropFirst(sineWave) {
path.lineToPoint(NSPoint(x: i, y: value * increment + xAxisHeight))
i += increment
}
// set plot color
NSColor.blueColor().set()
path.stroke()
image.unlockFocus()
return image
}
var imageView = NSImageView()
imageView.image = plotArray(sineWave, 500, 200)
// have fun

Openlaszlo colorTransform Issue

I am using color transformation to set color on a view using this code in OL 3.3 this works perfecty fine.
obj.setColorTransform({ab: 0, aa: 100,
bb: b-rB, ba: 100,
gb: g-rG, ga: 100,
rb: r-rR, ra: 100});
But i am facing the problem when i have upgraded it to 5.0 swf runtime
obj.setAttribute('colortransform',{ab: 0, aa: 100,
bb: b-rB, ba: 100,
gb: g-rG, ga: 100,
rb: r-rR, ra: 100});
The obj here is a view in this format,
<view name="borders"
width="${parent.bg.width}"
height="${parent.bg.height}"
y="10">
<simplelayout axis="x"/>
<view name="left"
resource="border_left"
stretches="height"
height="${parent.height}"
x="5"
y="1">
</view>
<view name="middle"
resource="border_mid"
y="1"
stretches="width"
height="${parent.height - 2}"
width="${parent.width - parent.left.width - parent.right.width}">
</view>
<view name="right"
resource="border_right"
stretches="height"
height="${parent.height}">
</view>
</view>
Any Idea why this is not working?
The API has changed, when the setColorTransform({}) method was deprecated in favor of the .setAttribute('colortransform', {}). Instead of ra or rb the property keys are now redMultiplier, redOffset. See the corresponding doc sections quoted below.
Another change in the API is the values. Quoting the documentation for setColorTransform():
The dictionary has the following possible keys: o.ra: percentage alpha
for red component (-100 to 100); o.rb: offset for red component (-255
to 255); o.ga: percentage alpha for green component (-100 to 100);
o.gb: offset for green component (-255 to 255); o.ba: percentage alpha
for blue component (-100 to 100); o.bb: offset for blue component
(-255 to 255); o.aa: percentage overall alpha (-100 to 100); o.ab:
overall offset (-255 to 255);
Compare that with the documentation of a view's colortransform attribute:
The dictionary has the following possible keys: o.redMultiplier:
multiplier for red component (0 to 1) defaults to 1 o.redOffset:
offset for red component (-255 to 255) defaults to 0
o.greenMultiplier: multiplier for green component (0 to 1) defaults to
1 o.greenOffset: offset for green component (-255 to 255) defaults to
0 o.blueMultiplier: multiplier for blue component (0 to 1) defaults to
1 o.blueOffset: offset for blue component (-255 to 255) defaults to 0
o.alphaMultiplier: multiplier for alpha component (0 to 1) defaults to
1 o.alphaOffset: offset for alpha component (-255 to 255) defaults to
0
As you can see from the docs, the value range for the alpha offset changed from -100 to 100 to 0 to 1. The following syntax works when you use setAttribute:
var transformValues =
{redMultiplier: 0 to 1, redOffset: -255 to 255,
greenMultiplier: 0 to 1, greenOffset: -255 to 255,
blueMultiplier: 0 to 1, blueOffset: -255 to 255,
alphaMultiplier: 0 to 1, alphaOffset: -255 to 255}
this.setAttribute('colortransform', transformValues);

Resizing N # of squares to be as big as possible while still fitting into box of X by Y dimensions. (Thumbnails!)

I have N squares.
I have a Rectangular box.
I want all the squares to fit in the box.
I want the squares to be as large as possible.
How do I calculate the largest size for the squares such that they all fit in the box?
This is for thumbnails in a thumbnail gallery.
int function thumbnailSize(
iItems, // The number of items to fit.
iWidth, // The width of the container.
iHeight, // The height of the container.
iMin // The smallest an item can be.
)
{
// if there are no items we don't care how big they are!
if (iItems = 0) return 0;
// Max size is whichever dimension is smaller, height or width.
iDimension = (iWidth min iHeight);
// Add .49 so that we always round up, even if the square root
// is something like 1.2. If the square root is whole (1, 4, etc..)
// then it won't round up.
iSquare = (round(sqrt(iItems) + 0.49));
// If we arrange our items in a square pattern we have the same
// number of rows and columns, so we can just divide by the number
// iSquare, because iSquare = iRows = iColumns.
iSize = (iDimension / iSquare);
// Don't use a size smaller than the minimum.
iSize = (iSize max iMin);
return iSize;
}
This code currently works OK. The idea behind it is to take the smallest dimension of the rectangular container, pretend the container is a square of that dimension, and then assume we have an equal number of rows and columns, just enough to fit iItems squares inside.
This function works great if the container is mostly squarish. If you have a long rectangle, though, the thumbnails come out smaller than they could be. For instance, if my rectangle is 100 x 300, and I have three thumbnails, it should return 100, but instead returns 33.
Probably not optimal (if it works which I haven't tried), but I think better than you current approach :
w: width of rectangle
h: height of rectangle
n: number of images
a = w*h : area of the rectangle.
ia = a/n max area of an image in the ideal case.
il = sqrt(ia) max length of an image in the ideal case.
nw = round_up(w/il): number of images you need to stack on top of each other.
nh = round_up(h/il): number of images you need to stack next to each other.
l = min(w/nw, w/nh) : length of the images to use.
The solution on https://math.stackexchange.com/a/466248 works perfectly.
An unoptimized javascript implementation:
var getMaxSizeOfSquaresInRect = function(n,w,h)
{
var sw, sh;
var pw = Math.ceil(Math.sqrt(n*w/h));
if (Math.floor(pw*h/w)*pw < n) sw = h/Math.ceil(pw*h/w);
else sw = w/pw;
var ph = Math.ceil(Math.sqrt(n*h/w));
if (Math.floor(ph*w/h)*ph < n) sh = w/Math.ceil(w*ph/h);
else sh = h/ph;
return Math.max(sw,sh);
}
I was looking for a similar solution, but instead of squares I had to fit rectangles in the container. Since a square is also a rectangle, my solution also answers this question.
I combined the answers from Neptilo and mckeed into the fitToContainer() function. Give it the number of rectangles to fit n, the containerWidth and containerHeight and the original itemWidth and itemHeight. In case items have no original width and height, use itemWidth and itemHeight to specify the desired ratio of the items.
For example fitToContainer(10, 1920, 1080, 16, 9) results in {nrows: 4, ncols: 3, itemWidth: 480, itemHeight: 270}, so four columns and 3 rows of 480 x 270 (pixels, or whatever the unit is).
And to fit 10 squares in the same example area of 1920x1080 you could call fitToContainer(10, 1920, 1080, 1, 1) resulting in {nrows: 2, ncols: 5, itemWidth: 384, itemHeight: 384}.
function fitToContainer(n, containerWidth, containerHeight, itemWidth, itemHeight) {
// We're not necessarily dealing with squares but rectangles (itemWidth x itemHeight),
// temporarily compensate the containerWidth to handle as rectangles
containerWidth = containerWidth * itemHeight / itemWidth;
// Compute number of rows and columns, and cell size
var ratio = containerWidth / containerHeight;
var ncols_float = Math.sqrt(n * ratio);
var nrows_float = n / ncols_float;
// Find best option filling the whole height
var nrows1 = Math.ceil(nrows_float);
var ncols1 = Math.ceil(n / nrows1);
while (nrows1 * ratio < ncols1) {
nrows1++;
ncols1 = Math.ceil(n / nrows1);
}
var cell_size1 = containerHeight / nrows1;
// Find best option filling the whole width
var ncols2 = Math.ceil(ncols_float);
var nrows2 = Math.ceil(n / ncols2);
while (ncols2 < nrows2 * ratio) {
ncols2++;
nrows2 = Math.ceil(n / ncols2);
}
var cell_size2 = containerWidth / ncols2;
// Find the best values
var nrows, ncols, cell_size;
if (cell_size1 < cell_size2) {
nrows = nrows2;
ncols = ncols2;
cell_size = cell_size2;
} else {
nrows = nrows1;
ncols = ncols1;
cell_size = cell_size1;
}
// Undo compensation on width, to make squares into desired ratio
itemWidth = cell_size * itemWidth / itemHeight;
itemHeight = cell_size;
return { nrows: nrows, ncols: ncols, itemWidth: itemWidth, itemHeight: itemHeight }
}
The JavaScript implementation form mckeed gave me better results then the other answers I found. The idea to first stretch the rectangle to a square came from Neptilo.
you want something more like
n = number of thumbnails
x = one side of a rect
y = the other side
l = length of a side of a thumbnail
l = sqrt( (x * y) / n )
Here is my final code based off of unknown (google)'s reply:
For the guy who wanted to know what language my first post is in, this is VisualDataflex:
Function ResizeThumbnails Integer iItems Integer iWidth Integer iHeight Returns Integer
Integer iArea iIdealArea iIdealSize iRows iCols iSize
// If there are no items we don't care how big the thumbnails are!
If (iItems = 0) Procedure_Return
// Area of the container.
Move (iWidth * iHeight) to iArea
// Max area of an image in the ideal case (1 image).
Move (iArea / iItems) to iIdealArea
// Max size of an image in the ideal case.
Move (sqrt(iIdealArea)) to iIdealSize
// Number of rows.
Move (round((iHeight / iIdealSize) + 0.50)) to iRows
// Number of cols.
Move (round((iWidth / iIdealSize) + 0.50)) to iCols
// Optimal size of an image.
Move ((iWidth / iCols) min (iHeight / iRows)) to iSize
// Check to make sure it is at least the minimum.
Move (iSize max iMinSize) to iSize
// Return the size
Function_Return iSize
End_Function
This should work. It is solved with an algorithm rather than an equation. The algorithm is as follows:
Span the entire short side of the rectangles with all of the squares
Decrease the number of squares in this span (as a result, increasing the size) until the depth of the squares exceeds the long side of the rectangle
Stop when the span reaches 1, because this is as good as we can get.
Here is the code, written in JavaScript:
function thumbnailSize(items, width, height, min) {
var minSide = Math.min(width, height),
maxSide = Math.max(width, height);
// lets start by spanning the short side of the rectange
// size: the size of the squares
// span: the number of squares spanning the short side of the rectangle
// stack: the number of rows of squares filling the rectangle
// depth: the total depth of stack of squares
var size = 0;
for (var span = items, span > 0, span--) {
var newSize = minSide / span;
var stack = Math.ceil(items / span);
var depth = stack * newSize;
if (depth < maxSide)
size = newSize;
else
break;
}
return Math.max(size, min);
}
In Objective C ... the length of a square side for the given count of items in a containing rectangle.
int count = 8; // number of items in containing rectangle
int width = 90; // width of containing rectangle
int height = 50; // width of container
float sideLength = 0; //side length to use.
float containerArea = width * height;
float maxArea = containerArea/count;
float maxSideLength = sqrtf(maxArea);
float rows = ceilf(height/maxSideLength); //round up
float columns = ceilf(width/maxSideLength); //round up
float minSideLength = MIN((width/columns), (height/rows));
float maxSideLength = MAX((width/columns), (height/rows));
// Use max side length unless this causes overlap
if (((rows * maxSideLength) > height) && (((rows-1) * columns) < count) ||
(((columns * maxSideLength) > width) && (((columns-1) * rows) < count))) {
sideLength = minSideLength;
}
else {
sideLength = maxSideLength;
}
My JavaScript implementation:
var a = Math.floor(Math.sqrt(w * h / n));
return Math.floor(Math.min(w / Math.ceil(w / a), h / Math.ceil(h / a)));
Where w is width of the rectangle, h is height, and n is number of squares you want to squeeze in.
double _getMaxSizeOfSquaresInRect(double numberOfItems, double parentWidth, double parentHeight) {
double sw, sh;
var pw = (numberOfItems * parentWidth / parentHeight).ceil();
if ((pw * parentHeight / parentWidth).floor() * pw < numberOfItems) {
sw = parentHeight / (pw * parentHeight / parentWidth).ceil();
} else {
sw = parentWidth / pw;
}
var ph = (sqrt(numberOfItems * parentHeight / parentWidth)).ceil();
if ((ph * parentWidth / parentHeight).floor() * ph < numberOfItems) {
sh = parentWidth / (parentWidth * ph / parentHeight).ceil();
} else {
sh = parentHeight / ph;
}
return max(sw, sh);
}