chairmanmow
chairmanmow

Reputation: 650

Return Optimized x coordinates to normalize/maximize area for an array of rectangles with defined y positions

I've included a code snippet that hopefully should sum things up nicely and is represented in a sort of 'Fill in the blanks' kind of state.

If it helps to understand the origin of the problem by seeing it in a larger context, what I'm ultimately doing is showing a daily view schedule on calendar on a phone, probably similar to how the calendar on your phone works. When events begin to overlap in time, represented by the vertical y axis here I want to be able to optimize the width and positioning of those events, not overlapping them and not hiding more than I have to for the content - but when there are too many being able to indicate things are hidden.

I'm not looking for any CSS/HTML based solutions despite the sample being in javascript - the DOM structure is more or less set in stone, just looking for an algorithm that might do what I'm looking for, if it's in C++, TurboPascal, Assembly, Java, whatever doesn't really matter. In my example expected results the more complex the scenario the results are more like rough estimates, and it comes across in the demo as well when rendering my expected results - I don't even really have a terrific method for doing the math in my head once things start getting weird.

The objective is to fill in the function optimizeLeftAndRightXStartPointsForNormalizedRectangleAreaWithoutOverlapping = (rectangles,minimumArea,minimumWidth,xMin,xMax)=>{}

// Let's say I have an array like this of rectangles where they have set y coordinates
// Some constraints on how this array comes out: it will besorted by the yTop property as shown below, but with no secondary sort criteria.
// The rectangles difference between yBottom and yTop is always at least 15, in other words this array won't contain any rectangles less than 15 in height

const rectanglesYcoordinatesOnlyExample1 = [
{"rectangle_id":"b22d","yTop":0,"yBottom":60},
{"rectangle_id":"8938","yTop":60,"yBottom":120},
{"rectangle_id":"e78a","yTop":60,"yBottom":120},
{"rectangle_id":"81ed","yTop":207,"yBottom":222},
{"rectangle_id":"b446","yTop":207,"yBottom":222},
{"rectangle_id":"ebd3","yTop":207,"yBottom":222},
{"rectangle_id":"2caf","yTop":208,"yBottom":223},
{"rectangle_id":"e623","yTop":227,"yBottom":242},
{"rectangle_id":"e6a3","yTop":270,"yBottom":320},
{"rectangle_id":"e613","yTop":272,"yBottom":460},
{"rectangle_id":"c2d1","yTop":272,"yBottom":290},
{"rectangle_id":"e64d","yTop":274,"yBottom":300},
{"rectangle_id":"b653","yTop":276,"yBottom":310},
{"rectangle_id":"e323","yTop":276,"yBottom":310},
{"rectangle_id":"fca3","yTop":300,"yBottom":315}
]

// I want to get a result sort of like this, explanations provided, although I'm not sure if my internal calculations in my head are 100% on the further I go.  

// And I want to a run a function like so:
// optimizeLeftAndRightXStartPointsForNormalizedRectangleAreaWithoutOverlapping(rectanglesYcoordinatesOnlyExample1,(33.3 * 15),10,0,100);  
// I will make this call later but I need to hoist my expected results here to enable the mocking to work for now at the point of the function definiton for my example. (see below)
// like so I'd get a result something like this, but I start becoming less certain of what the correct result should be the more I go into fringe stuff.

const expectedResultMoreOrLessForExample1 = [
{"rectangle_id":"b22d","leftX":0,"rightX":100,"yTop":0,"yBottom":60},
{"rectangle_id":"8938","leftX":0,"rightX":50,"yTop":60,"yBottom":120},
{"rectangle_id":"e78a","leftX":50,"rightX":100,"yTop":60,"yBottom":120},
{"rectangle_id":"81ed","leftX":0,"rightX":33.3,"yTop":207,"yBottom":222}, // Three rectangles side by side with minimum Area ["81ed","b446","ebd3"] from this point
{"rectangle_id":"b446","leftX":33.3,"rightX":66.6,"yTop":207,"yBottom":222},
{"rectangle_id":"ebd3","isMax":true,"leftX":66.7,"rightX":100,"yTop":207,"yBottom":222},  // has isMax property because there would be an overlap if it tried the next result, and it can't take area away from the other rectangles
// This rectangle gets thrown out because it would be there are 3 other rectangles in that area each with the minimum area (33.3 * 15);
// {"rectangle_id":"2caf","yTop":208,"yBottom":223}, This one gets thrown out from the result the time being because there are too many rectangles in one area of vertical space.
{"rectangle_id":"e623","yTop":227,"yBottom":242,"leftX":0,"rightX":100},
{"rectangle_id":"e6a3","leftX":0,"rightX":25,"yTop":270,"yBottom":320},
{"rectangle_id":"e613","leftX":25,"rightX":35,"yTop":272,"yBottom":460},
{"rectangle_id":"c2d1","leftX":71.28,"rightX":100,"yTop":272,"yBottom":290},  // fill the remaining space since optimizing to max area would take 99% 
{"rectangle_id":"e64d","leftX":35,"rightX":61.28,"yTop":274,"yBottom":300},
{"rectangle_id":"b653","yTop":276,"yBottom":940,"leftX":61.28,rightX:71.28},
{"rectangle_id":"fca3","leftX":35,"rightX":61.28,"yTop":300,"yBottom":315}
]

// the function name is really long to reflect what it is what I want to do.  Don't normally make functions this intense

const optimizeLeftAndRightXStartPointsForNormalizedRectangleAreaWithoutOverlapping = (rectangles,minimumArea,minimumWidth,xMin,xMax)=>{
	// TODO : fill in the optimization function.
	// Code I'm looking for would be swapped in here if you wanted to make changes to demo it do it here
	if(rectangles === rectanglesYcoordinatesOnlyExample1 && minimumArea === (33.3 * 15) && minimumWidth === 10 && xMin === 0 && xMax === 100){  // Just handling the example
		return expectedResultMoreOrLessForExample1;
	} else {
		console.log('I only know how to handle example 1, as computed by a human, poorly.  fill in the function and replace the block with working stuff');
		return [];
	}
}


const displayResults = (completedRectangleList) => {
	const rectangleColors = ['cyan','magenta','green','yellow','orange']
	completedRectangleList.forEach((rectangle,index) =>{
	 	let newRectangle = document.createElement('div');
	 	newRectangle.style.position = 'absolute';
	 	newRectangle.style.height = rectangle.yBottom - rectangle.yTop + 'px';
	  	newRectangle.style.top = rectangle.yTop + 'px';
	  	newRectangle.style.left = parseInt(rectangle.leftX)+'%';
	  	newRectangle.style.width = rectangle.rightX - rectangle.leftX + "%";
	  	newRectangle.style.backgroundColor = rectangleColors[index % rectangleColors.length];
	  	newRectangle.innerHTML = rectangle.rectangle_id;
	  	if(rectangle.isMax){
	  		newRectangle.innerHTML += '- more hidden';
	  	}
		document.body.appendChild(newRectangle);
})
}
// I am calling this function with minimum Area of 33.3 * 15, because it represents 3 min height rectangles taking up a third of the minX,maxX values, which are 0 & 100, representing a percentage value ultimately
let resultForExample1 = optimizeLeftAndRightXStartPointsForNormalizedRectangleAreaWithoutOverlapping(rectanglesYcoordinatesOnlyExample1,(33.3 * 15),10,0,100);
displayResults(resultForExample1);

As far as what I've tried, I start something then I kind of think of fringe cases and things get a little haywire. Even in the expected results I've calculated in my head I think my own humanized calculations are a little off, so when evaluating this issue and looking at my expectedResult and then the rendering of it, it's a little off. Hopefully the intent and meaning behind the optimizeLeftAndRightXStartPointsForNormalizedRectangleAreaWithoutOverlapping() is more or less clear.

I'm still working on potential approaches, but in the meantime seeking out the wisdom of the crowd until it comes to me. I'm curious as to the solution, but I haven't discovered the right track.

Upvotes: 4

Views: 561

Answers (1)

user3386109
user3386109

Reputation: 34839

The algorithm in this answer attempts to arrange rectangles inside a fixed-width bounding box. Input to the algorithm is an array of rectangles whose topY and bottomY are specified. The algorithm computes the leftX and rightX for each rectangle. Rectangles are sized and positioned to avoid overlap. For example, the image below shows 7 rectangles that have been arranged by the algorithm. In region 1, the maximum overlap is 2, so the rectangles are drawn half width. Region 2 has no overlap, so the rectangle is full width. And region 3 has overlap 3, and therefore rectangles are one-third the width of the bounding box.

enter image description here

The algorithm has three major steps:

  1. Fill the eventQueue based on the information in the input array. Each rectangle spawns two events: an EVENT_START with the topY, and an EVENT_STOP with the bottomY. The eventQueue is a priority queue, where the priority is based on the three values that define an event (evaluated in the order shown):
    • y: lower y values have priority.
    • type: EVENT_STOP has priority over EVENT_START.
    • rectID: lower rectID values have priority.
  2. Search for the end of a region while:
    • storing the events into a regionQueue. The regionQueue is a simple FIFO, which allows the events to be processed a second time, after the extent of the region has been determined.
    • keeping track of the maxOverlap (which is limited by a function parameter). The maxOverlap determines the width of all rectangles within the region.
  3. Drain the regionQueue while computing the X values for each rectangle in the region. This part of the algorithm employs an array called usedColumns. Each entry in that array is either -1 (indicates that the column is not in use) or a rectID (indicates which rectangle is using the column). When an EVENT_START is popped from the regionQueue, a column is assigned to the rectangle. When an EVENT_STOP is popped from the regionQueue, the column is returned to the unused (-1) state.

The input to the algorithm is an array of rectangles. The topY and bottomY values of those rectangles must be filled by the caller. The leftX and rightX values must be initialized to -1. If the X values are still -1 when the algorithm finishes, the rectangle could not be assigned a location because the overlapLimit was exceeded. All the other rectangles have a full set of coordinates, and are ready to be drawn.

typedef struct
{
    int topY;       // input parameter, filled in by the caller
    int bottomY;    // input parameter, filled in by the caller
    int leftX;      // output parameter, must be initially -1
    int rightX;     // output parameter, must be initially -1
}
stRect;

typedef struct
{
    int y;          // this is the 'topY' or 'bottomY' of a rectangle
    int type;       // either EVENT_START or EVENT_STOP
    int rectID;     // the index into the input array for this rectangle
}
stEvent;

enum { EVENT_START, EVENT_STOP };

void arrangeRectangles(stRect *rectArray, int length, int overlapLimit, int containerWidth)
{
    stQueue *eventQueue  = queueCreate();
    stQueue *regionQueue = queueCreate();

    // fill the event queue with START and STOP events for each rectangle
    for (int i = 0; i < length; i++)
    {
        stEvent startEvent = {rectArray[i].topY, EVENT_START, i};
        queueAdd(eventQueue, &startEvent);
        stEvent stopEvent  = {rectArray[i].bottomY, EVENT_STOP, i};
        queueAdd(eventQueue, &stopEvent);
    }

    while (queueIsNotEmpty(eventQueue))
    {
        // search for the end of a region, while keeping track of the overlap in that region
        int overlap = 0;
        int maxOverlap = 0;
        stEvent event;
        while (queuePop(eventQueue, &event))   // take from the event queue
        {
            queueAdd(regionQueue, &event);     // save in the region queue
            if (event.type == EVENT_START)
                overlap++;
            else
                overlap--;
            if (overlap == 0)                  // reached the end of a region
                break;
            if (overlap > maxOverlap)
                maxOverlap = overlap;
        }

        // limit the overlap as specified by the function parameter
        if (maxOverlap > overlapLimit)
            maxOverlap = overlapLimit;

        // compute the width to be used for rectangles in this region
        int width = containerWidth / maxOverlap;

        // create and initialize an array to keep track of which columns are in use
        int usedColumns[maxOverlap];
        for (int i = 0; i < maxOverlap; i++)
            usedColumns[i] = -1;

        // process the region, computing left and right X values for each rectangle
        while (queuePop(regionQueue, &event))
        {
            if (event.type == EVENT_START)
            {
                // find an available column for this rectangle, and assign the X values
                for (int column = 0; column < maxOverlap; column++)
                    if (usedColumns[column] < 0)
                    {
                        usedColumns[column] = event.rectID;
                        rectArray[event.rectID].leftX = column * width;
                        rectArray[event.rectID].rightX = (column+1) * width;
                        break;
                    }
            }
            else
            {
                // free the column that's being used for this rectangle
                for (int i = 0; i < maxOverlap; i++)
                    if (usedColumns[i] == event.rectID)
                    {
                        usedColumns[i] = -1;
                        break;
                    }
            }
        }
    }

    queueDestroy(eventQueue);
    queueDestroy(regionQueue);
}

void example(void)
{
    stRect inputArray[] = {
        {  0,150,-1,-1},
        { 30,180,-1,-1},
        {180,360,-1,-1},
        {360,450,-1,-1},
        {420,540,-1,-1},
        {450,570,-1,-1},
        {480,540,-1,-1}
    };
    int length = sizeof(inputArray) / sizeof(inputArray[0]);
    arrangeRectangles(inputArray, length, 3, 100);
}

Note: I make no claims about the validity of this code. Thorough review and testing is left as an exercise for the reader.


@chairmanmow has graciously translated the algorithm to javascript to save time for others looking for a javascript solution. Here is that translation:

const topZero = 0;
const parent = i => ((i + 1) >>> 1) - 1;
const left = i => (i << 1) + 1;
const right = i => (i + 1) << 1;

class PriorityQueue {

  constructor(comparator = (a, b) => a > b) {
    this._heap = [];
    this._comparator = comparator;
  }

  size() {
    return this._heap.length;
  }

  isEmpty() {
    return this.size() == 0;
  }

  peek() {
    return this._heap[topZero];
  }

  push(...values) {
    values.forEach(value => {
      this._heap.push(value);
      this._siftUp();
    });
    return this.size();
  }

  pop() {
    const poppedValue = this.peek();
    const bottom = this.size() - 1;
    if (bottom > topZero) {
      this._swap(topZero, bottom);
    }
    this._heap.pop();
    this._siftDown();
    return poppedValue;
  }

  replace(value) {
    const replacedValue = this.peek();
    this._heap[topZero] = value;
    this._siftDown();
    return replacedValue;
  }

  _greater(i, j) {
    return this._comparator(this._heap[i], this._heap[j]);
  }

  _swap(i, j) {
    [this._heap[i], this._heap[j]] = [this._heap[j], this._heap[i]];
  }

  _siftUp() {
    let node = this.size() - 1;
    while (node > topZero && this._greater(node, parent(node))) {
      this._swap(node, parent(node));
      node = parent(node);
    }
  }

  _siftDown() {
    let node = topZero;
    while (
      (left(node) < this.size() && this._greater(left(node), node)) ||
      (right(node) < this.size() && this._greater(right(node), node))
      ) {
      let maxChild = (right(node) < this.size() && this._greater(right(node), left(node))) ? right(node) : left(node);
      this._swap(node, maxChild);
      node = maxChild;
    }
  }
}

const rectangles = [{
  rectID: "b22d",
  "yTop": 0,
  "yBottom": 60,
  "leftX": -1,
  "rightX": -1
},
  {
    rectID: "8938",
    "yTop": 60,
    "yBottom": 120,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "e78a",
    "yTop": 60,
    "yBottom": 120,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "81ed",
    "yTop": 207,
    "yBottom": 222,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "b446",
    "yTop": 207,
    "yBottom": 222,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "ebd3",
    "yTop": 207,
    "yBottom": 222,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "2caf",
    "yTop": 208,
    "yBottom": 223,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "e623",
    "yTop": 227,
    "yBottom": 242,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "e6a3",
    "yTop": 270,
    "yBottom": 320,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "e613",
    "yTop": 272,
    "yBottom": 460,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "c2d1",
    "yTop": 272,
    "yBottom": 290,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "e64d",
    "yTop": 274,
    "yBottom": 300,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "b653",
    "yTop": 276,
    "yBottom": 310,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "e323",
    "yTop": 276,
    "yBottom": 310,
    "leftX": -1,
    "rightX": -1
  },
  {
    rectID: "fca3",
    "yTop": 300,
    "yBottom": 315,
    "leftX": -1,
    "rightX": -1
  }
];

let eventQueue = new PriorityQueue((a, b) => {
  if (a.y !== b.y) return a.y < b.y;
  if (a.type !== b.type) return a.type > b.type;
  return a.rectID > b.rectID;
})
let regionQueue = []; // FIFO
const EVENT_START = 0;
const EVENT_STOP = 1;

const queueAdd = (queue, toAdd, type, priority) => {

  return queue.push(toAdd);
}


const queuePop = (queue) => {
  return queue.pop();
}

const queueDestroy = (queue) => {
  return queue = [];
}

const optimizeLeftAndRightXStartPointsForNormalizedRectangleAreaWithoutOverlapping = (rectArray, length, overlapLimit, containerWidth) => {
  // fill the event queue with START and STOP events for each rectangle
  for (let i = 0; i < length; i++) {
    let startEvent = {
      y: rectArray[i].yTop,
      type: EVENT_START,
      index: i
    };
    eventQueue.push(startEvent);
    let stopEvent = {
      y: rectArray[i].yBottom,
      type: EVENT_STOP,
      index: i
    };
    eventQueue.push(stopEvent);
  }
  while (eventQueue.size()) { // queueIsNotEmpty(eventQueue)
    // search for the end of a region, while keeping track of the overlap in that region
    let overlap = 0;
    let maxOverlap = 0;
    let event;
    while (event = eventQueue.pop()) { // take from the event queue
      queueAdd(regionQueue, event); // save in the region queue
      if (event.type === 0) {
        overlap++;
      } else {
        overlap--;
      }
      if (overlap === 0) { // reached the end of a region
        break;
      }
      // if we have a new maximum for the overlap, update 'maxOverlap'
      if (overlap > maxOverlap) {
        maxOverlap = overlap;
      }
    }
    // limit the overlap as specified by the function parameter
    if (maxOverlap > overlapLimit) {
      maxOverlap = overlapLimit;
    }

    // compute the width to be used for rectangles in this region
    const width = parseInt(containerWidth / maxOverlap);

    // create and initialize an array to keep track of which columns are in use
    let usedColumns = new Array(maxOverlap);
    for (let i = 0; i < maxOverlap; i++) {
      if (usedColumns[i] == event.rectID) {
        usedColumns[i] = -1;
        break;
      }
    }
    // process the region, computing left and right X values for each rectangle
    while (event = queuePop(regionQueue)) {
      if (event.type == 0) {
        // find an available column for this rectangle, and assign the X values
        for (let column = 0; column < maxOverlap; column++) {
          if (usedColumns[column] < 0) {
            usedColumns[column] = event.rectID;
            rectArray[event.index].leftX = column * width;
            rectArray[event.index].rightX = (column + 1) * width;
            break;
          }

        }
      } else {
        // free the column that's being used for this rectangle
        for (let i = 0; i < maxOverlap; i++)
          if (usedColumns[i] == event.rectID) {
            usedColumns[i] = -1;
            break;
          }
      }
    }

  }
  return rectArray;
}


const displayResults = (completedRectangleList) => {
  const rectangleColors = ['cyan', 'magenta', 'green', 'yellow', 'orange']
  completedRectangleList.forEach((rectangle, index) => {
    if (rectangle.leftX > -1 && rectangle.rightX > -1) {
      let newRectangle = document.createElement('div');
      newRectangle.style.position = 'absolute';
      newRectangle.style.height = rectangle.yBottom - rectangle.yTop + 'px';
      newRectangle.style.top = rectangle.yTop + 'px';
      newRectangle.style.left = parseInt(rectangle.leftX) + '%';
      newRectangle.style.width = rectangle.rightX - rectangle.leftX + "%";
      newRectangle.style.backgroundColor = rectangleColors[index % rectangleColors.length];
      newRectangle.innerHTML = rectangle.rectID;
      if (rectangle.isMax) {
        newRectangle.innerHTML += '- more hidden';
      }
      document.body.appendChild(newRectangle);
    }

  })
}
let results = optimizeLeftAndRightXStartPointsForNormalizedRectangleAreaWithoutOverlapping(rectangles, (rectangles.length), 3, 100);
console.log('results ' + JSON.stringify(results));
displayResults(results);

Below is the java code

public class Main {

public static void main(String[] args) {
    ArrayList<stRect> inputArray = new ArrayList<>();
    inputArray.add(new stRect( 0,150,-1,-1));
    inputArray.add(new stRect( 30,180,-1,-1));
    inputArray.add(new stRect( 180,360,-1,-1));
    inputArray.add(new stRect( 360,450,-1,-1));
    inputArray.add(new stRect( 420,540,-1,-1));
    inputArray.add(new stRect( 450,570,-1,-1));
    inputArray.add(new stRect( 480,540,-1,-1));
    arrangeRectangles(inputArray, inputArray.size(), 3, 100);

    for(int i = 0;i<inputArray.size();i++){
        System.out.println(inputArray.get(i).topY+" "+inputArray.get(i).bottomY+" "+inputArray.get(i).leftX+" "+inputArray.get(i).rightX);
    }
}

private static void arrangeRectangles(ArrayList<stRect>rectArray, int length, int overlapLimit, int containerWidth){
    int EVENT_START = 0, EVENT_STOP = 1;

    PriorityQueue<stEvent> eventQueue = new PriorityQueue<>(new MyComparator());
    Queue<stEvent> regionQueue = new LinkedList<>();

    for (int i = 0; i < length; i++) {
        stEvent startEvent = new stEvent(rectArray.get(i).topY,EVENT_START, i);
        eventQueue.add(startEvent);
        stEvent stopEvent = new stEvent(rectArray.get(i).bottomY,EVENT_STOP, i);
        eventQueue.add(stopEvent);
    }

    while (!eventQueue.isEmpty()){
        int overlap = 0;
        int maxOverlap = 0;
        stEvent event;

        while (!eventQueue.isEmpty()){ // take from the event queue
            event = eventQueue.remove();

            regionQueue.add(event);    // save in the region queue

            if (event.type == EVENT_START)
                overlap++;
            else
                overlap--;

            if (overlap == 0)          // reached the end of a region
                break;

            if (overlap > maxOverlap)
                maxOverlap = overlap;
        }

        // limit the overlap as specified by the function parameter
        if (maxOverlap > overlapLimit)
            maxOverlap = overlapLimit;

        // compute the width to be used for rectangles in this region
        int width = containerWidth / maxOverlap;

        int usedColumns[] = new int[maxOverlap];
        for (int i = 0; i < maxOverlap; i++)
            usedColumns[i] = -1;

        while (!regionQueue.isEmpty()){
            event = regionQueue.remove();

            if (event.type == EVENT_START) {
                // find an available column for this rectangle, and assign the X values
                for (int column = 0; column < maxOverlap; column++){
                    if (usedColumns[column] < 0) {
                        usedColumns[column] = event.rectID;
                        rectArray.get(event.rectID).leftX = column * width;
                        rectArray.get(event.rectID).rightX = (column+1) * width;
                        break;
                    }
                }
            }else {
                // free the column that's being used for this rectangle
                for (int i = 0; i < maxOverlap; i++){
                    if (usedColumns[i] == event.rectID)
                    {
                        usedColumns[i] = -1;
                        break;
                    }
                }
            }
        }
    }
    eventQueue.clear();
    regionQueue.clear();
    }
}

public class MyComparator implements Comparator<stEvent> {

    @Override
    public int compare(stEvent o1, stEvent o2) {
        if(o1.y<o2.y)
            return -1;
        if(o1.y>o2.y)
            return 1;

        if(o1.type == 0 && o2.type ==1)
            return 1;
        if(o1.type == 1 && o2.type ==0)
            return -1;

        if(o1.rectID<o2.rectID)
            return -1;
        if(o1.rectID>o2.rectID)
            return 1;

        return 0;
    }
}

class stEvent {
    int y;          
    int type;       
    int rectID;

    stEvent(int y, int type, int rectID) {
        this.y = y;
        this.type = type;
        this.rectID = rectID;
    }
}

class stRect {
    int topY;       // input parameter, filled in by the caller
    int bottomY;    // input parameter, filled in by the caller
    int leftX;      // output parameter, must be initially -1
    int rightX;

    stRect(int topY, int bottomY, int leftX, int rightX) {
        this.topY = topY;
        this.bottomY = bottomY;
        this.leftX = leftX;
        this.rightX = rightX;
    }

}

Upvotes: 3

Related Questions