Home | Blog | Projects
Last Updated: 27/Jan/2019

Dynamically position button using polynomial regression

Added on 27/Jan/2019

But why?

I wanted to implement a design similar to the one below, where the button is always be positioned above the portal.

Expected design
Fig.1 - The design to implement.
Background
Fig.2 - Background.

Solution

I wanted to experiment with a bruteforce solution to position the button dynamically based on the screen size.

To achieve this I:

  1. Collected datapoints for the optimal position on different screen sizes.
  2. Then trained a model based on these datapoints to predict optimal button position.
  3. Finally positioned the button based on the returned coordinates when the page load.

Step 1: Collect datapoints

To collect the datapoints I added a new target at the center of the button on the background:

Background with target on button
Fig.3 - Background with target on button.

Then I added a JavaScript function to log the coordinates of my mouse & the screen size on each click:

var inputs = [];
var outputs = [];

document.onmousedown = function(e) {
  var x = e.pageX;
  var y = e.pageY;
  var h = screen.availHeight;
  var w = screen.availWidth;

  console.log('w:', w, 'h:', h, 'x:', x, 'y:', y);

  inputs.push({w: w, h: h});
  outputs.push({x: x, y: y});
}

Then I started clicking on the target while changing the screen size as shown in the video below:

Vid.1 - Video showing datapoints collection in action.

Step 2: Find optimal button position

Once I collected enough datapoints I converted them to a list of inputs (w, h) & outputs (x, y):

> 'X = [' + inputs.map((e, i) => {return `(${e.w}, ${e.h})`;}).join(', ') + ']';
< X = [(2318, 1422), (2290, 1422), (2262, 1422), (2202, 1422), ...]

> 'Y = [' + outputs.map((e, i) => {return `(${e.x}, ${e.y})`;}).join(', ') + ']';
< Y = [(416, 210), (413, 207), (414, 210), (413, 212), (414, 208), ...]

Then I used Gradient boosting technique with Multi Output Regressor from sklearn to train a regression model on these points:

from sklearn.ensemble import GradientBoostingRegressor
from sklearn.multioutput import MultiOutputRegressor

X = [...]
Y = [...]

model = MultiOutputRegressor(GradientBoostingRegressor(), n_jobs=-1)
model.fit(X, Y)

model.predict([[(2262, 1422)]])

Then I wrapped this model into a small Flask app that takes screen width & height as arguments and returns the optimal (x, y) coordinates for the button:

from flask import Flask, request, jsonify
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.multioutput import MultiOutputRegressor

app = Flask(__name__)

@app.route('/button_position')
def my_route():
  w = request.args.get('w', default = 100, type = int)
  h = request.args.get('h', default = 100, type = int)

  X = [...]
  Y = [...]

  model = MultiOutputRegressor(GradientBoostingRegressor(), n_jobs=-1)
  model.fit(X, Y)

  proposed_position = model.predict([[w, h]])

  return jsonify([proposed_position[0][0], proposed_position[0][1]])

Step 3: Position button on page load

Now that we have the model & api ready, we have to request the optimal button position from the api when loading the page and update the button position based on the coordinates that we get.

function position_apply_btn() {
  var h = window.innerHeight;
  var w = window.innerWidth;

  fetch('http://127.0.0.1:5000/button_position?w=' + w + '&h=' + h)
  .then(response => {
    return response.json();
  })
  .then(json => {
    let x = json[0];
    let y = json[1];

    set_position(x, y);
  });
}

function set_position(x, y) {
  e = document.getElementById('action-btn');

  e.style.left = x - e.offsetWidth / 2 + 'px';
  e.style.top = y - e.offsetHeight / 2 + 'px';
}

position_apply_btn();

Step 4: Result

Vid.2 - Video showing the end result.

Conclusion

I was surprised how well this turned out. I wonder if a similar but more optimized method (e.g. automatic screen resizes, optimal screen sizes …) can be used in a practical way to facilitate responsive web design?




✉️ Subscribe via email
Thanks for Subscribing!
Subscription failed. Please try again later.