rect function does not show top border under certain parameters - image

When I try to use the function below, I notice that my top row is missing the horizontal line in the plot. I've tracked this down to the line that specifies my rect dimensions, specifically how high and low it should be. Is there a parameter I can set so that the top line always appears? The code below does not have the top line..
Plot.Lines <- function(colCount, rowCount, cex){
colCount <- colCount # number per row
rowCount <- rowCount
plot( c(1,colCount), c(0,rowCount), type="n", ylab="", xlab="",
axes=FALSE, ylim=c(rowCount,0))
title("My Lines")
for (j in 0:(rowCount-1))
{
base <- j*colCount
remaining <- length(colors()) - base
RowSize <- ifelse(remaining < colCount, remaining, colCount)
for(i in 1:RowSize){
rect(i-0.5,j-0.5,i+0.5, j+0.5, border = "black", col = colors()[base + (1:RowSize)])
}
for(i in 1:RowSize){
text(x = i, y =j, labels = paste(base + i), cex =cex, col = "black")
}
}
}
Plot.Lines(25, 6, cex = 0.5)
However, if I change the line to:
rect(i-0.5,j-0.2,i+0.5, j+0.2, border = "black", col = colors()[base + (1:RowSize)])
then it works, although there is a space between the rows. Is there a way I an set it automatically given the colCount and the rowCount?

The top edge is cut off because it's beyond the plotting region. You can use par(xpd=TRUE) to allow plotting outside the inner region. Put this before your call to plot.

Related

Algorithm for tiling video views

Im making an app with video chat, and need to layout the participants in a zoom/teams like screen, filling a rectangle completely. Im locking the rotation to landscape, so I expect most video will be around 16/9 aspect ration, but this CAN be cropped, so its just something to aim for.
So given n tiles and an x times y rectangle, return a list of n rectangles with position and size which will together fill completely the outer rectangle.
Hoping someone knows about an algorithm which can do this while preserving aspect ratio as good as possible!
(I tried making a simple algorithm just progressively adding a column or a row, depending on which will make tiles aspect ratio match 16/9 closest, until there is enough sub-tiles, and then "joining" unused tiles afterwards, but it came out more complex and not as good as I hoped for...)
public static List<Tile> GetTilePartitionResult(
double width, double height,
int partitions, double preferredAspectRatio = 16d/9d)
{
var columns = 1;
var rows = 1;
var lastAddedRow = false;
while (columns * rows < partitions)
{
// Find out if we should add a row or a column
var rowAddedAspect = GetAspectRatio(width, height, rows + 1, columns);
var columnAddedAspect = GetAspectRatio(width, height, rows, columns + 1);
var rowAddedDiffFromIdeal = Math.Abs(preferredAspectRatio - rowAddedAspect);
var columnAddedDiffFromIdeal = Math.Abs(preferredAspectRatio - columnAddedAspect);
if (rowAddedDiffFromIdeal < columnAddedDiffFromIdeal)
{
rows++;
lastAddedRow = true;
}
else
{
columns++;
lastAddedRow = false;
}
}
// Since after adding the "last" divider we might have an excess number of cells
// So trim the "other" dimension until there is just enough tiles
if (lastAddedRow)
{
while (((columns - 1) * rows) >= partitions) columns--;
}
else
{
while (((rows - 1) * columns) >= partitions) rows--;
}
// Assume we have the optimal grid/column setup, now distribute
// the tiles over this grid
var tileHeight = height / rows;
var tileWidth = width / columns;
var tiles = new List<Tile>();
for (var row = 0; row < rows; row++)
{
for (var column = 0; column < columns; column++)
{
var newTile = new Tile
{
Height = tileHeight,
Width = tileWidth,
XOffSet = column * tileWidth,
YOffSet = row * tileHeight,
GridX = column,
GridY = row
};
tiles.Add(newTile);
// Was this the last tile:
if (tiles.Count == partitions)
{
// Yes -> check if there is free space on this column
var extraColumns = columns - 1 - column;
if (extraColumns > 0)
{
// this extra space can be used in 2 ways,
// either expand current tile with, or expand
// height of previous row columns(the cells that are "above" the empty space)
// We decide which is best by choosing the resulting aspect ratio which
// most closely matches desired aspect ratio
var newWidthIfExpandingHorizontally = newTile.Width + (extraColumns * tileWidth);
var newHeightIfExpandingVertically = height * 2;
var aspectRatioIfExpandingHorizontally =
GetAspectRatio(newWidthIfExpandingHorizontally, height, 1, 1);
var aspectRationIfExpandingVertically =
GetAspectRatio(width, newHeightIfExpandingVertically, 1, 1);
if (Math.Abs(aspectRatioIfExpandingHorizontally - preferredAspectRatio) <
Math.Abs(aspectRationIfExpandingVertically - preferredAspectRatio))
{
// TODO: Should consider widening multiple "right" places tiles
// and move some down if extra cells > 1 .... Next time...
newTile.Width = newWidthIfExpandingHorizontally;
}
else
{
// Find all tiles in previous row above empty space and change height:
var tilesToExpand = tiles.Where(t => t.GridY == row - 1 && t.GridX > column);
foreach (var tile in tilesToExpand)
{
tile.Height = newHeightIfExpandingVertically;
}
}
}
// Nothing else to do on this column(we filled it...)
break;
}
}
}
return tiles;
}
P.S. My code is in C#, but this is really a generic algorithm-question...

Rectangle Packing - Subset

I'm working on an algorithm that figures out how to pack rectangles into a larger rectangle. I am aware this is similar to the rectangle packing problem but my particular problem has a few quirks; namely the rectangles that I'm fitting only have a defined height, the width can (and should) vary such that the rectangles that share a vertical overlap with other rectangles will end up having equal widths with respect to the other rectangles they overlap with.
Here is the best formalization I could come up with:
Given a possibly infinite set R of real number ranges between -inf, +inf and an area A defined by the points (0, -inf), (100, +inf).
For each range r in R find a rectangle Ar which resides inside area A with a height abs(r1 - r2) and a width which results in a rectangle that fills as much horizontal space as possible without overlapping any other rectangle.
Here is an image showing an example input set, and the expected output:
Does anyone have any idea the best way to approach this? I have a somewhat working solution but it fails under conditions where there are a lot of overlapping ranges. Here is the code:
val laidOutRectangles = mutableListOf<Rectangle>() // Rectangle is defined by x1, y1, x2, y2
val ranges = mutableListOf<Range>() // Range just has lower and upper bounds
val width = 100
ranges.sortedByDescending { it.first }.forEach { range ->
// Get top and bottom of range
val lower = range.first
val upper = range.second
// Work out overlapping rectangles
var overlappingRectangles = 0
laidOutRectangles.forEach { rectangle ->
if ((lower >= rectangle.y1 && lower <= rectangle.y2) ||
(upper >= rectangle.y1 && upper <= rectangle.y2)) {
overlappingRectangles++
}
}
val rectangleWidth = (width / (overlappingRectangles + 1)).toInt()
val newRectangle = Rectangle(0, lower, rectangleWidth, upper)
laidOutRectangles.add(newRectangle)
var repositionedRectangles = 0
laidOutRectangles.filter {
((lower >= it.y1 && lower <= it.y2) ||
(upper >= it.y1 && upper <= it.y2))
}.sortedByDescending {
Math.abs(upper - lower)
}.forEach { rectangle ->
rectangle.width = rectangleWidth
rectangle.x1 = (rectangleWidth * (repositionedRectangles + 1)).toInt()
rectangle.x2 = (rectangle.x1 + rectangleWidth)
repositionedRectangles++
}
}

efficiently calculate locations for rectangles in a unit grid

I'm working on a specific layout algorithm to display photos in a unit based grid. The desired behaviour is to have every photo placed in the next available space line by line.
Since there could easily be a thousand photos whose positions need to be calculated at once, efficiency is very important.
Has this problem maybe been solved with an existing algorithm already?
If not, how can I approach it to be as efficient as possible?
Edit
Regarding the positioning:
What I'm basically doing right now is iterating every line of the grid cell by cell until I find room to fit the element. That's why 4 is placed next to 2.
How about keeping a list of next available row by width? Initially the next-available-row list looks like:
(0,0,0,0,0)
When you've added the first photo, it looks like
(0,0,0,0,1)
Then
(0,0,0,2,2)
Then
(0,0,0,3,3)
Then
(1,1,1,4,4)
And the final photo doesn't change the list.
This could be efficient because you're only maintaining a small list, updating a little bit at each iteration (versus searching the entire space every time. It gets a little complicated - there could be a situation (with a tall photo) where the nominal next available row doesn't work, and then you could default to the existing approach. But overall I think this should save a fair amount of time, at the cost of a little added complexity.
Update
In response to #matteok's request for a coordinateForPhoto(width, height) method:
Let's say I called that array "nextAvailableRowByWidth".
public Coordinate coordinateForPhoto(width, height) {
int rowIndex = nextAvailableRowByWidth[width + 1]; // because arrays are zero-indexed
int[] row = space[rowIndex]
int column = findConsecutiveEmptySpace(width, row);
for (int i = 1; i < height; i++) {
if (!consecutiveEmptySpaceExists(width, space[i], column)) {
return null;
// return and fall back on the slow method, starting at rowIndex
}
}
// now either you broke out and are solving some other way,
// or your starting point is rowIndex, column. Done.
return new Coordinate(rowIndex, column);
}
Update #2
In response to #matteok's request for how to update the nextAvailableRowByWidth array:
OK, so you've just placed a new photo of height H and width W at row R. Any elements in the array which are less than R don't change (because this change didn't affect their row, so if there were 3 consecutive spaces available in the row before placing the photo, there are still 3 consecutive spaces available in it after). Every element which is in the range (R, R+H) needs to be checked, because it might have been affected. Let's postulate a method maxConsecutiveBlocksInRow() - because that's easy to write, right?
public void updateAvailableAfterPlacing(int W, int H, int R) {
for (int i = 0; i < nextAvailableRowByWidth.length; i++) {
if (nextAvailableRowByWidth[i] < R) {
continue;
}
int r = R;
while (maxConsecutiveBlocksInRow(r) < i + 1) {
r++;
}
nextAvailableRowByWidth[i] = r;
}
}
I think that should do it.
How about a matrix (your example would be 5x9) where each cell has a value representing the distance from the top left corner (for instance (row+1)*(column+1) [+1 is only necessary if your first row and value are 0]). In this matrix you look for the area which has the lowest value (when summing up the values of empty cells).
A 2nd matrix (or a 3rd dimension of the first matrix) stores the status of each cell.
edit:
int[][] grid = new int[9][5];
int[] filledRows = new int [9];
int photowidth = 2;
int photoheight = 1;
int emptyRowCounter = 0;
boolean photoFits = true;
for(int i = 0; i < grid.length; i++){
for(int m = 0; m < filledRows.length; m++){
if(filledRows[m]-(photoHeight-1) > i || filledRows[m]+(photoHeight-1) < i){
for(int j = 0; j < grid[i].length; j++){
if(grid[i][j] == 0){
for(int k = 0; k < photowidth; k++){
for(int l = 0; k < photoheight){
if(grid[i+l][j+k]!=0){
photoFits = false;
}
}
}
} else{
emptyRowCounter++;
}
}
if(photoFits){
//place Photo at i,j
}
if(emptyRowCounter == 5){
filledRows[i] = 1;
}
}
}
}
In the gif you have above, it turned out nicely that there was a photo (5) that could fit into the gap under (1) and to the left of (2). My intuition suggests we want to avoid creating gaps like that. Here is an idea that should avoid these gaps.
Maintain a list of "open regions", where an open region has a int leftBoundary, an int topBoundary, and an optional int bottomBoundary. The first open region is just the whole grid (leftBoundary:0, topBoundary: 0, bottom: null).
Sort the photos by height, breaking ties by width.
Until you have placed all photos:
Choose the tallest photo (in case of ties, choose the widest of the tallest photos). Find the first open region it can fit in (such that grid.Width - region.leftBoundary >= photo.Width). Place the photo at the top left of this region. When you place this photo, it may span the entire width or height of the region.
If it spans both the width and the height of the region, the region is filled! Remove this region from the list of open regions.
If it spans the width, but not the height, add the photo's height to the topBoundary of the region.
If it spans the height, but not the width, add the photo's width to the leftBoundary of the region.
If it does not span the height or width of the boundary, we are going to conceptually divide this region into two: one region will cover the space directly to the right of this photo (call it rightRegion), and the other region will cover the space below this region (call it belowRegion).
rightRegion = {
leftBoundary = parentRegion.leftBoundary + photo.width,
topBoundary = parentRegion.topBoundary,
bottomBoundary = parentRegion.topBoundary + photo.height
}
belowRegion = {
leftBoundary = 0,
topBoundary = parentRegion.topBoundary + photo.height,
bottomBoundary = parentRegion.bottomBoundary
}
Replace the current region in the list of open regions with rightRegion, and insert belowRegion directly after rightRegion.
You can visualize how this algorithm would work on your example: First, it would sort the photos: (2,3,4,1,5).
It considers 2, which fits into the first region (the whole grid). When it places 2 at the top left, it splits that region into the space directly to the right of 2, and the space below 2.
Then, it considers 3. It considers the open regions in turn. The first open region is to the right of 2. 3 fits there, so that's where it goes. It spans the width of the region, so the region's topBoundary gets adjusted downward.
Then, it considers 4. It again fits in the first open region, so it places 4 there. 4 spans the height of the region, so the region's leftBoundary gets adjusted rightward.
Then, 1 gets put in the 1x1 gap to the right of 4, filling its region. Finally, 5 gets put just below 2.

how to account for linewidth in drawing lines within a bounding box

I am drawing a set of evenly spaced horizontal lines within the entirety of a bounding box.
The problem I am having is that the lines (when larger than 1px) get drawn beyond the top and bottom edges of my bounds. Half on each side of the top and bottom is missing, to be precise.
Here is some pseudo code that attempts a fix for this, but it didn't work. It should describe what I am trying to do:
var halfline = linewidth / 2.;
var maxheight = boxsize.height - halfline;
var minheight = halfline;
//draw h lines
for(i = 0; i < maxlines; i++)
{
var xloc = 0;
var xfrac = i / maxlines - 1;
var yloc = (xfrac * boxsize.height) + minheight;
move_to(xloc, yloc);
line_to(boxsize.width, yloc);
}
Please keep in mind that the lang is not important here, just the idea of how to offset and scale the lines (that are drawn within the for loop) properly.
Thanks for any tips... It's safe to assume the following:
the line width is in pixels
the coordinate system is pixel-based, from (0,0) to (n,n)
Your question is a little unclear, but I think this might help:
var availablespace = boxsize.height - linewidth;
...
var yloc = (xfrac * availablespace) + minheight;

kendoChart: Is there any way to display multiple series of differing value scales using a single valueAxis?

I'm using a single kendoChart to display up to 10 lines of data.
Each line represents process data that may have widely different context and min/max ranges, but all lines are related in time, the categoryAxis. When displayed, each valueAxis correctly shows the scale for the corresponding line.
However, with 10 lines, the 10 valueAxes take up far too much of the screen to be usable for my requirements.
I tried hiding all axes except one with the expectation that the chart would expand to fill up the space taken by the hidden axes, but that does
not happen. I get a lone axis surrounded by blank space and the chart's plot area remains the same size.
I tried setting all of the series to use the same valueAxis and then varying the valueAxis min/max per the active channel as chosen by clicking
a legend item. This expands the plot area as needed, but removes the ability to see all lines since the scale is specific to one line.
Is it possible for kendoChart to show multiple plots independently from a single valueAxis (e.g. a line with values between 0.5 and 0.7 would appear scaled to the full chart area, and so would a line with values between 25 and 100, but the valueAxis might display either scale.)
The solution I used for this problem is more code than I expected to need. Perhaps Telerik's other products have an API for this.
Essentially, I maintain a structure outside of the kendoChart that stores the real data for each series, and this real data is mapped to the expected scale of the currently visible valueAxis. The mapping function is the standard transform from one scale into another.
The valueAxis is 'swapped' depending on which legend item is clicked, and that event triggers a redraw on the chart where all the series data is mapped to the 'active' axis.
Some code snippets. A series is also described as a channel.
// The data structure.
this._channelDescriptors.push({
fullName: ch.fullName || "",
axisTitle: (ch.fullName + axisEUString) || "",
axisFont: ch.axisFont || "",
axisColor: ch.color || "#000000",
realData: [],
minData: Number.MAX_VALUE,
maxData: Number.MIN_VALUE
});
// This event causes the switching of valueAxis for all members of the series.
$("#" + chartID).kendoChart({
// Other kendoChart configurations
//
legendItemClick: function (e) {
var idx = e.seriesIndex;
sncTrender.updateAxis(idx);
e.preventDefault();
},
tooltip: {
visible: true,
template: "#=series.name# : #=kendo.format('{0:N4}', dataItem.realValue)#<br />#=kendo.format('{0:MM-dd HH:mm:ss.fff}', dataItem.Time)#",
},
//
// Other kendoChart configurations
});
// All code snippets are members of a wrapper object.
updateAxis: function (ch) {
if (this.series[ch].visible) {
this.setAxis(ch);
}
},
// Every series is set to the same valueAxis via the selected series' valueAxis.name property.
setAxis: function (ch) {
var i,
channel = this._channelDescriptors[ch];
this._currentChannel = ch;
for (i = 0; i < this.series.length; i++) {
this.series[i].axis = this._channelDescriptors[ch].fullName;
}
// Set the active valueAxis properties. This is the only axis visible maintained for the chart.
this.valueAxis.name = channel.fullName;
this.valueAxis.title.text = channel.axisTitle;
this.valueAxis.title.font = channel.axisFont;
this.valueAxis.line.color = channel.axisColor;
},
// The mapping occurs here, and the transform calculation is this line
// Y: (yRange * (chDesc.realData[k].realValue - newMin) / newRange) + this.valueAxis.min,
//
updateChart: function (allTrends) {
// ...
timeStamps = trendDataResponse.curve.Timestamp;
t1 = trendArgs.t1;
t2 = trendArgs.t2;
xValues = trendDataResponse.curve.X;
yValues = trendDataResponse.curve.Y;
pointCount = xValues.length;
min = Number.MAX_VALUE;
max = Number.MIN_VALUE;
categoryTimes = [pointCount];
newData = [];
for (l = 0; l < pointCount; l++) {
min = Math.min(min, yValues[l]);
max = Math.max(max, yValues[l]);
ts = new Date(timeStamps[l]);
categoryTimes[l] = ts;
// The Y data will be plotted on the chart, but the cursor tooltip will
// use the realValue data. In this way, the series can be visible regardless of
// the valueAxis scaling, but the actual data is also available. Refer to the
// tooltip template.
newData.push({ X: xValues[l], Y: yValues[l], realValue: yValues[l], Time: ts });
}
// Real data for each channel is stored in channelDescriptors.
chDesc = this._channelDescriptors[channelID];
chDesc.realData = newData;
chDesc.minData = min;
chDesc.maxData = max;
// The valueAxis min/max is set only for the 'active' series.
if (this._currentChannel === channelID) {
this.categoryAxis.categories = categoryTimes;
yRange = max - min;
scaleAdjustment = yRange * SNC.CONST_yAxisScaleAdjustmentFactor;
this.valueAxis.min = min - scaleAdjustment;
this.valueAxis.max = max + scaleAdjustment;
}
}
// Scale curves to current axis.
// Use real data for the current series.
for (j = 0; j < this.series.length; ++j) {
chDesc = this._channelDescriptors[j];
if (j === this._currentChannel) {
this.series[j].data = chDesc.realData;
continue;
}
// Use mapped data for all other series.
recalcData = [];
newMin = chDesc.minData;
newMax = chDesc.maxData;
newRange = newMax - newMin;
rangeAdjustment = newRange * SNC.CONST_yAxisScaleAdjustmentFactor;
newMin = newMin - rangeAdjustment;
newMax = newMax + rangeAdjustment;
for (k = 0; k < chDesc.realData.length; ++k) {
recalcData.push({
X: chDesc.realData[k].X,
Y: (yRange * (chDesc.realData[k].realValue - newMin) / newRange) + this.valueAxis.min,
realValue: chDesc.realData[k].realValue,
Time: chDesc.realData[k].Time,
});
}
this.series[j].data = recalcData;
}
chart.redraw();
}

Resources