Change calculator from pennies - js - javascript

I am working on a small app to improve my js skills. It is meant to allow the user to enter an amount of pennies, and from here the correct and minimum amount of change will be calculated to make this amount.
Currently I have the entry of pennies being worked out by the proper denominations (1p, 2p, 5p, 10p, 20p, 50p, £1, £2) however I am not sure how to get this to display the minimum change needed to make up the total number of pennies?
Below is my code so far, any help will be appreciated as I really want to learn how to do something this:
function calculate() {
var list = []
var x = document.getElementById("pennies").value;
resultTwoPounds = x / 200;
resultTwoPounds * 2;
list.push(Math.floor(resultTwoPounds));
resultPounds = x / 100;
list.push(Math.floor(resultPounds));
remaining = x % 100;
remainPennyFifty = Math.floor(remaining / 50);
list.push(remainPennyFifty);
remaining = x % 100;
remainPennyTwenty = Math.floor(remaining / 20);
list.push(remainPennyTwenty);
remaining = x % 100;
remainPennyTen = Math.floor(remaining / 10);
list.push(remainPennyTen);
remaining = x % 10;
list.push(Math.floor(remaining / 5));
remaining = x % 10;
list.push(Math.floor(remaining / 2));
remaining = x % 10;
list.push(Math.floor(remaining));
if (x > 0) {
resultLine = "You have <strong>" + x + " pennies</strong>, breakdown as follows: <br><br><strong>£2</strong> *" + list[0] + "<br><br><strong>" + "£1</strong> *" + list[1] + "<br><br><strong>50p</strong>" + " *" + list[2] + "<br><br><strong>20p</strong>" + " *" + list[3] + "<br><br><strong>10p</strong>" + " *" + list[4] + "<br><br><strong>5p</strong>" + " *" + list[5] + "<br><br><strong>2p</strong>" + " *" + list[6] + "<br><br><strong>1p</strong>" + " *" + list[7]
} else {
resultLine = "Please enter an amount"
}
document.getElementById('result').innerHTML = resultLine;
$("#submit").submit(function(e) {
e.preventDefault();
});
}
#pennies {
width: 6em;
vertical-align: middle;
}
#submit {
text-align: center;
}
.mainCalc {
text-align: center;
align-content: center;
}
.headings {
text-align: center;
color: white;
}
body {
margin-top: 200px;
background-color: lightblue;
}
#printResult {
text-align: center;
padding-top: 40px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="headings">
<h1>pennyCalc
<h1>
</div>
<!-- Start of form -->
<form onsubmit="return false">
<div class="mainCalc">
<br>
<strong>p:</strong>
<input type="number" placeholder=" Amount" step="1" min="0" id="pennies">
<br>
<button type="buttom" id="submit" onclick="calculate()">Calculate!</button>
<br>
</div>
</form>
<div id="printResult"><span id="result"></span></div>

A proposal
I have made a version of your calculator, let me show you how it works if you may.
The function works the same, the way I've organized it is different though. You don't need to use jQuery (as you haven't put the jQuery tag on this question).
Let us create a function divide that will return an array of the division integer result and the remainder of the euclidean division between x and y. For this, I will use, as you did, the % modulus operator and the Math.floor() function:
const divide = (x, y) => { return [ Math.floor(x/y), x % y ] };
I'm using the shorthand arrow function expression to declare it.
Then we will write the actual function calculate(): declare obj, the object that will contain are values and x, the money count. On each step we will decrease the value of x and append new properties to the object obj.
With our previous divide function this is pretty easy to do, you just have to write this line:
[ obj['twoPounds'], x ] = divide(x, 200);
By destructuring the array returned by divide(x, 200), we are assigning the division result to the property twoPounds of obj and assigning the remainder of the division to x.
The same result would have been met with:
let result = division(x, 200);
obj['twoPounds'] = result[0];
x = result[1]
But instead of calling the function twice you only do once, and the code is cleaner.
After calling divide for each coin type, x will have the value of 0 and obj will be filled with the number of coins per coin type.
Lastly, we can format our response with template literals with backticks (`) and the ${} syntax. This way we can embed JavaScript variables inside the string and also jump lines while writing the HTML markup in the editor.
For instance:
`You have ${obj.twoPounds} coins of £2`
Is the same as writing:
'You have' + obj.twoPounds + 'coins of £2'
JavaScript code and preview
const divide = (x, y) => { return [Math.floor(x / y), x % y] };
const calculate = function() {
let obj = {};
let x = document.querySelector('#pennies').value;
[ obj['twoPounds'], x ] = divide(x, 200);
[ obj['onePound'], x ] = divide(x, 100);
[ obj['fiftyPence'], x ] = divide(x, 50);
[ obj['twentyPence'], x ] = divide(x, 20);
[ obj['tenPence'], x ] = divide(x, 10);
[ obj['fivePence'], x ] = divide(x, 5);
[ obj['twoPence'], x ] = divide(x, 2);
[ obj['onePence'], x ] = divide(x, 1);
document.querySelector('#result').innerHTML = `
<div>
<span>You have: </span>
<span><strong>${obj.twoPounds}</strong> x £2, </span>
<span><strong>${obj.onePound}</strong> x £1, </span>
<span><strong>${obj.fiftyPence}</strong> x 50p, </span>
<span><strong>${obj.twentyPence}</strong> x 20p, </span>
<span><strong>${obj.tenPence}</strong> x 10p, </span>
<span><strong>${obj.fivePence}</strong> x 5p, </span>
<span><strong>${obj.twoPence}</strong> x 2p, </span>
<span><strong>${obj.onePence}</strong> x 1p, </span>
</div>
`;
return false;
}
Preview:
const divide = (x, y) => { return [ Math.floor(x / y), x % y ] };
const calculate = function() {
let obj = {};
let x = document.querySelector('#pennies').value;
[ obj['twoPounds'], x ] = divide(x, 200);
[ obj['onePound'], x ] = divide(x, 100);
[ obj['fiftyPence'], x ] = divide(x, 50);
[ obj['twentyPence'], x ] = divide(x, 20);
[ obj['tenPence'], x ] = divide(x, 10);
[ obj['fivePence'], x ] = divide(x, 5);
[ obj['twoPence'], x ] = divide(x, 2);
[ obj['onePence'], x ] = divide(x, 1);
document.querySelector('#result').innerHTML = `
<div>
<span>You have: </span>
<span><strong>${obj.twoPounds}</strong> x £2, </span>
<span><strong>${obj.onePound}</strong> x £1, </span>
<span><strong>${obj.fiftyPence}</strong> x 50p, </span>
<span><strong>${obj.twentyPence}</strong> x 20p, </span>
<span><strong>${obj.tenPence}</strong> x 10p, </span>
<span><strong>${obj.fivePence}</strong> x 5p, </span>
<span><strong>${obj.twoPence}</strong> x 2p, </span>
<span><strong>${obj.onePence}</strong> x 1p, </span>
</div>
`;
return false;
}
#pennies {
width: 6em;
vertical-align: middle;
}
#submit {
width: 10em;
text-align: center;
}
.mainCalc {
text-align: center;
align-content: center;
}
.headings {
text-align: center;
color: white;
}
body {
margin-top: 200px;
background-color: lightblue;
}
#printResult {
text-align: center;
padding-top: 40px;
}
<div class="headings">
<h1>Penny Calculator</h1>
</div>
<div class="mainCalc">
<input type="number" placeholder=" Amount" value="593" step="1" min="0" id="pennies">
<button type="buttom" id="submit" onclick="calculate()">Calculate!</button>
</div>
<div id="printResult"><span id="result"></span></div>
An even shorter version
I went ahead and refactored my own version, the actual function itself is very short indeed, enjoy!
Instead of having to call the function divide() multiple time, I have created an object containing the name, label and value in pennies for each coin.
let coins = [
{ name: 'twoPounds', label: '£2', value: 200 },
{ name: 'onePound', label: '£1', value: 100 },
{ name: 'fiftyPence', label: '50p', value: 50 },
{ name: 'twentyPence', label: '£2', value: 20 },
{ name: 'tenPence', label: '£2', value: 10 },
{ name: 'fivePence', label: '£2', value: 5 },
{ name: 'twoPence', label: '£2', value: 2 },
{ name: 'onePence', label: '£2', value: 1 }
];
Then in the function I use the .map method to go through each element of the coins array. The calculation of the number of coins in the formatting of the spans happens on the same line. The array returned by .map is then converted into a string with .join added in the element #result.
document.querySelector('#result').innerHTML = `
<div>
<span>You have: </span>
${ coins.map( coin => `<span>${([x, x] = divide(x, coin.value))[0]} x ${coin.label}</span>` ).join(', ') }
</div>
`
That's the line of code that does basically everything:
${ coins.map( coin => `<span>${([x, x] = divide(x, coin.value))[0]} x ${coin.label}</span>` ).join(', ') }
Here is the final code (same result as preview version):
const divide = (x, y) => { return [ Math.floor(x / y), x % y ] };
let coins = [
{ name: 'twoPounds', label: '£2', value: 200 },
{ name: 'onePound', label: '£1', value: 100 },
{ name: 'fiftyPence', label: '50p', value: 50 },
{ name: 'twentyPence', label: '£2', value: 20 },
{ name: 'tenPence', label: '£2', value: 10 },
{ name: 'fivePence', label: '£2', value: 5 },
{ name: 'twoPence', label: '£2', value: 2 },
{ name: 'onePence', label: '£2', value: 1 }
];
const calculate = function() {
let x = document.querySelector('#pennies').value;
document.querySelector('#result').innerHTML = `
<div>
<span>You have: </span>
${ coins.map( coin => `<span>${([x, x] = divide(x, coin.value))[0]} x ${coin.label}</span>` ).join(', ') }
</div>
`
return false;
}
#pennies {
width: 6em;
vertical-align: middle;
}
#submit {
width: 10em;
text-align: center;
}
.mainCalc {
text-align: center;
align-content: center;
}
.headings {
text-align: center;
color: white;
}
body {
margin-top: 200px;
background-color: lightblue;
}
#printResult {
text-align: center;
padding-top: 40px;
}
<div class="headings">
<h1>Penny Calculator</h1>
</div>
<div class="mainCalc">
<input type="number" placeholder=" Amount" value="593" step="1" min="0" id="pennies">
<button type="buttom" id="submit" onclick="calculate()">Calculate!</button>
</div>
<div id="printResult"><span id="result"></span></div>

Related

Price and Discount Intervals Calculator

This code below applies a percentage discount.
How is it possible to adjust to apply discount intervals, in $? (according to attached images and Excel files)
Example :
N° Services: 0-9 / You Save: 0
N° Services: 10-19 / You Save: 360,00
N° Services: 20-29 / You Save: 720,00
N° Services: 30-39 / You Save: 1.080,00
N° Services: 40-49 / You Save: 1.440,00
N° Services: 50-59 / You Save: 1.800,00
N° Services: 60-69 / You Save: 2.160,00
N° Services: 70-79 / You Save: 2.520,00
N° Services: 80-89 / You Save: 2.880,00
N° Services: 90-99 / You Save: 3.240,00
window.onload = function () {
var $ = function (selector) {
return document.querySelector(selector);
};
var update = function () {
var amount = $range.value;
var cost = 10;
var percent = 30;
var discount = (amount * (percent / $range.max)).toFixed(2);
var total = cost * amount - discount / 10 * amount;
$amount.innerHTML = 'Number of Sharpenings: ' + amount;
$discount.innerHTML = 'Discount: ' + discount + '%';
$total.innerHTML = 'Total: $' + total.toFixed(2);
};
var $range = $('.range');
var $amount = $('.amount');
var $discount = $('.discount');
var $total = $('.total');
update();
$range.addEventListener('input', update);
$range.addEventListener('change', update);
};
<style class="INLINE__ID">
.wrapper {
max-width: 600px;
width: 100%;
margin: 0 auto;
}
.wrapper .range {
width: 100%;
}
.wrapper .discount {
color: #999;
border-bottom: 1px solid #efefef;
padding-bottom: 15px;
}
</style>
<div class="wrapper">
<h1 class="title">Price Calculator</h1>
<h3 class="amount">Number of Sharpenings: 100</h3>
<input class="range" type="range" min="0" max="100" value="0" step="1">
<h3 class="discount">Discount: 30.00%</h3>
<h2 class="total">Total: $700.00</h2>
</div>
Use Mathematics in your favor. The Save brackets table is, in reality, a function which increments 360 every 10 steps:
y = (x \ 10) * 360
which you can translate to:
function getSaveAmount(count){
return Math.floor(count / 10) * 360;
}
If it gets more complex, you can put those brackets in an array with values being entire discounts, sets, use switch/cases, if's, etc..
window.onload = function () {
var $ = function (selector) {
return document.querySelector(selector);
};
var getSaveAmount = function (count){
return Math.floor(count / 10) * 360;
}
var update = function () {
var amount = $range.value;
var cost = 10;
var percent = 30;
var discount = (amount * (percent / $range.max)).toFixed(2);
var total = cost * amount - discount / 10 * amount;
$amount.innerHTML = 'Number of Sharpenings: ' + amount;
$discount.innerHTML = 'Discount: ' + discount + '%';
$total.innerHTML = 'Total: $' + total.toFixed(2);
$save.innerHTML = 'Total: $' + getSaveAmount(amount).toFixed(2);
};
var $range = $('.range');
var $amount = $('.amount');
var $discount = $('.discount');
var $total = $('.total');
var $save = $('.save');
update();
$range.addEventListener('input', update);
$range.addEventListener('change', update);
};
<style class="INLINE__ID">
.wrapper {
max-width: 600px;
width: 100%;
margin: 0 auto;
}
.wrapper .range {
width: 100%;
}
.wrapper .discount {
color: #999;
border-bottom: 1px solid #efefef;
padding-bottom: 15px;
}
</style>
<div class="wrapper">
<h1 class="title">Price Calculator</h1>
<h3 class="amount">Number of Sharpenings: 100</h3>
<input class="range" type="range" min="0" max="100" value="0" step="1">
<h3 class="discount">Discount: 30.00%</h3>
<h2 class="total">Total: $700.00</h2>
<p class="save">Save: </p>
</div>

A discrepancy in rendering between Firefox vs Chrome, Edge, Safari

Some days ago, I posted a problem to SO. Admittedly, the initial post was composed in a bit of a hurry, and somewhat incomplete. This was voted as off-topic. Even though the final edited version of the question doesn't seem to me to be off-topic, the fact remains that the question was closed.
In order to try and comply with the inherent (and in my opinion, not always perfectly fair, as I believe a chance should be given to rephrase the problem if necessary, and some assistance could be given in the way of suggestions) limitations of SO, I'm attempting to rephrase the problem. It goes as follows.
A few years back, I wrote a little tool (app) to overlay a grid on an image to generate printable images for Cross-Stitch work. At the time, I specifically wrote this for Firefox, but I generally expected it would work in any modern browser. However, now I find that it only seems to work in Firefox! (Not even in Firefox Focus).
The problem statement, this time around, is: This page renders more or less as expected in Firefox Desktop Browser. Is Firefox handling the page (app) in a standards compliant manner or not? If not, can anyone possibly explain a bit what's going on? In either case, is there a simple workaround to get this page to work as expected in major modern browsers other than Firefox? (i.e. Chrome, Edge, Opera, Safari, and mobile versions of similar browsers, including Firefox Focus). In other words, if this code is standards compliant, how do I deal with non-compliant browsers, or else is it possible to make the code standards compliant without having to change it too much?
The expected behavior of the page (app): Click on Browse, and pick an image, then click Generate. The image should be displayed below with a grid overlaid on top of it (or alternatively, you could look at it as the image being divided into squares or rectangles). There are additional details to how exactly the image ought to be rendered, stating which would lengthen this post unnecessarily, but you'd probably be able to figure these out by taking a look at the available customizable fields of the app and possibly by experimenting with them. These parameters generally are there to change the grid cell size (height, width), page margins, options to fill the page/image with squares etc.
This is old code, and I don't want to rewrite it significantly...
IIRC, I was attempting to get the img to take the full size of the containing div, hence position:absolute, and wdth:100%, height:100%. But at the same time, I was trying to size the containing div to fit the content, i.e. the grid. It's the grid that defines the size. The image is supposed to scale into that space...
To me, it appears that browsers aren't honoring the sizes specified for the grid, probably because it's empty, but my attempts to fix that failed...
EDIT 1:
As per suggestions, I finally got around to reducing the code to the key parts of the HTML and CSS. I was hoping for an answer that applies broadly to the complete code, but anyone wanting to get to the core problem can take a look at the following snippet. Note: I haven't yet tested this code in browsers other than FF Desktop, but I believe this should demonstrate the issue...
table {
border-collapse: collapse;
}
col {
width: 30px;
}
tr {
height: 30px;
}
table, td {
border: 1px solid red;
}
#div {
display: inline-block;
position: relative;
}
#img {
position: absolute;
width: 100%;
height: 100%;
z-index: -1;
}
<!DOCTYPE html>
<html>
<body>
<div id="div">
<img id="img" src="https://www.w3schools.com/js/landscape.jpg">
<table>
<thead>
<col>
<col>
<col>
</thead>
<tbody>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
</tbody>
</table>
</div>
</body>
</html>
Complete Code:
/*********************************************\
** Copyright © 2017-2019 Thejaka Maldeniya **
\*********************************************/
'use strict';
(function (a) {
a.i()
}(
{
z: 0, // image file data URL
p: '3', // previous value of units (default: Millimeters)
f: { // functions
_: function (l, e, h) { l.addEventListener(e, h, 0) },
l: function (a, f) { // load files
if (f.length) {
f = f[0]
var l = a.u.l, r = new FileReader()
l.name.innerHTML = f.name
a.f._(r, 'loadend', function (e) {
l.preview.src = a.z = e.target.result
})
r.readAsDataURL(f)
}
},
r: function (x) { // round to 3 decimal places
return Math.round(x * 1000) / 1000
},
f: function (a, b) { // get conversion factor
var f = 0
if (a == b) f = 1
else {
// if either a or b is not known, factor will be 0
// first, convert to Millimeters
switch (a) { // previous units value
case '1': // Inches
f = 25.4
break
case '2': // Centimeters
f = 10
break
case '3': // Millimeters
f = 1
}
// second, convert from Millimeters
switch (b) { // new units value
case '1': // Inches
f /= 25.4
break
case '2': // Centimeters
f /= 10
break
case '3': // Millimeters
// no change
}
}
return f
},
c: function () { // units or size value changed
var t = this, l = a.u.l
, r = t.r // round function
, width = l.width.value, height = l.height.value // A4
, mt = l.top.value, mb = l.bottom.value, ml = l.left.value, mr = l.right.value // margins
, w = l.w.value, h = l.h.value // width, height of cell
, m = l.m.value, n = l.n.value // width, height of grid
, s = 'A4' // paper size
, f = t.f(a.p, a.p = l.units.value) // unit conversion factor
, g = t.f('3', l.units.value) // unit conversion factor for default values (currently in Millimeters)
l.width.setAttribute('readonly', '')
l.height.setAttribute('readonly', '')
switch (l.size.value) {
case '0': // Custom
l.width.removeAttribute('readonly')
l.height.removeAttribute('readonly')
g = f
break
case '1': // Letter
width = 216
height = 279
l.rotate.checked = 0
s = 'Letter'
break
case '3': // Legal
width = 216
height = 356
l.rotate.checked = 0
s = 'Legal'
break
case '6': // A3
width = 297
height = 420
l.rotate.checked = 0
s = 'A3'
break
case '7': // A4
width = 210
height = 297
l.rotate.checked = 0
s = 'A4'
break
case '8': // A5
width = 148
height = 210
l.rotate.checked = 0
s = 'A5'
}
l.width.value = r(width * g)
l.height.value = r(height * g)
l.top.value = r(mt * f)
l.bottom.value = r(mb * f)
l.left.value = r(ml * f)
l.right.value = r(mr * f)
l.w.value = r(w * f)
l.h.value = r(h * f)
l.m.value = m
l.n.value = n
}
},
u: {
l: {
style: 0,
ui: 0,
form: 0,
units: 0,
size: 0,
width: 0,
height: 0,
rotate: 0,
top: 0,
bottom: 0,
left: 0,
right: 0,
w: 0,
h: 0,
square: 0,
fill1: 0,
m: 0,
n: 0,
fill2: 0,
color: 0,
white: 0,
black: 0,
file: 0,
browse: 0,
name: 0,
preview: 0,
reset: 0,
print: 0,
page: 0,
content: 0
},
f: {
b: function (f, c) {
return function () {
var a = arguments, n = a.length, b = Array(n), i = 0
for (; i < n; ++i)
b[i] = a[i]
f.apply(c, b)
}
}
},
e: {
document: {
DOMContentLoaded: function () {
}
},
window: {
load: function () {
var a = this, l = a.u.l
a.p = l.units.value
if (l.size.value == '0') {
l.width.removeAttribute('readonly')
l.height.removeAttribute('readonly')
}
if (!l.square.checked)
l.h.removeAttribute('readonly')
}
},
ui: {
dragover: function (e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
drop: function (e) {
e.stopPropagation()
e.preventDefault()
this.f.l(this, e.dataTransfer.files)
}
},
rotate: {
change: function () {
var l = this.u.l, v = l.width.value
l.width.value = l.height.value
l.height.value = v
v = l.top.value
if (l.rotate.checked) {
l.top.value = l.left.value
l.left.value = l.bottom.value
l.bottom.value = l.right.value
l.right.value = v
} else {
l.top.value = l.right.value
l.right.value = l.bottom.value
l.bottom.value = l.left.value
l.left.value = v
}
v = l.w.value
l.w.value = l.h.value
l.h.value = v
v = l.m.value
l.m.value = l.n.value
l.n.value = v
}
},
w: {
change: function (e) {
var l = this.u.l
if (l.square.checked)
l.h.value = e.target.value
}
},
square: {
change: function (e) {
var l = this.u.l
if (e.target.checked) {
l.h.setAttribute('readonly', '')
l.h.value = l.w.value
} else l.h.removeAttribute('readonly')
}
},
file: {
change: function (e) {
e.stopPropagation()
e.preventDefault()
this.f.l(this, e.target.files)
}
},
units: {
change: function () {
this.f.c(this)
}
},
size: {
change: function () {
this.f.c(this)
}
},
fill1: {
click: function () {
var l = this.u.l
if (l.form.reportValidity()) {
l.m.value = (l.width.value - l.left.value - l.right.value) / l.w.value | 0
l.n.value = (l.height.value - l.top.value - l.bottom.value) / l.h.value | 0
}
}
},
fill2: {
click: function () {
var l = this.u.l
if (l.form.reportValidity()) {
var w = (l.width.value - l.left.value - l.right.value) / l.m.value
, h = (l.height.value - l.top.value - l.bottom.value) / l.n.value
if (l.square.checked)
if (w < h) h = w
else w = h
l.w.value = w
l.h.value = h
}
}
},
white: {
click: function () {
this.u.l.color.value = '#ffffff'
}
},
black: {
click: function () {
this.u.l.color.value = '#000000'
}
},
browse: {
click: function () {
this.u.l.file.click()
}
},
reset: {
click: function () {
var l = this.u.l
l.width.setAttribute('readonly', '')
l.height.setAttribute('readonly', '')
l.content.innerHTML = l.style.innerHTML = ''
}
},
print: {
click: function () {
window.print()
}
},
form: {
submit: function (e) {
e.stopPropagation()
e.preventDefault()
var a = this, l = a.u.l, u = l.units.value
, width = l.width.value, height = l.height.value // page size
, mt = l.top.value, mb = l.bottom.value // margins
, ml = l.left.value, mr = l.right.value // margins
, w = l.w.value, h = l.h.value // width, height of cell
, m = l.m.value, n = l.n.value // width, height of grid
switch (u) {
case '1': // Inches
u = 'in'
break
case '2': // Centimeters
u = 'cm'
break
case '3': // Millimeters
default: // use Millimeters
u = 'mm'
}
l.style.innerHTML = '#page{size:' + width + u + ' ' + height + u
+ (l.rotate.checked ? ';landscape' : '')
+ '}#page{margin:' + mt + u + ' ' + mr + u + ' ' + mb + u + ' ' + ml + u
+ '}#media screen{#page{width:' + width + u + ';height:' + height + u
+ '}}#content{width:' + (width - ml - mr) + u + ';height:' + (height - mt - mb) + u
+ '}#grid>colgroup>col{min-width:' + w + u
+ '}#grid>tbody>tr{height:' + h + u
+ '}#grid,#grid>colgroup>col,#grid>tbody>tr{border-color:' + l.color.value
+ '}'
l.content.innerHTML = '<div id="box"><img alt="" id="image" src="' + a.z
+ '"><table id="grid"><colgroup>'
+ '<col>'.repeat(m) + '</colgroup>'
+ '<tr></tr>'.repeat(n)
+ '</table></div>'
}
}
},
i: function (p) {
var t = this, l = t.l, b = t.f.b, e = t.e, i, j, k
for (i in l)
l[i] = document.getElementById(l[i] || i)
l.window = window
l.document = document
for (i in e) {
k = e[i]
for (j in k)
l[i].addEventListener(j, b(k[j], p), 0)
}
}
},
i: function () {
var t = this
t.u.i(t)
}
}
))
/*********************************************\
** Copyright © 2017-2019 Thejaka Maldeniya **
\*********************************************/
hr {
border: 1px solid #797;
}
table {
border-collapse: collapse;
}
h1 {
margin-top: 0;
font-size: x-large;
}
h3 {
margin-bottom: 0;
font-size: medium;
}
input, select, button {
margin: 1px;
padding: 1px 4px 2px;
}
input:not([type=checkbox]), select, button {
vertical-align: middle;
}
input[type=checkbox] {
margin-right: 5px;
}
label {
vertical-align: 1px;
}
input, select {
border: 1px solid #898;
background: #efe;
color: #353;
}
select {
padding: 0 0 1px;
}
input[readonly] {
background-color: #cdc;
}
input[type=file] {
display: none;
}
button {
border: 1px solid #898;
border-radius: 6px;
background: #bcb;
padding-right: 6px;
padding-left: 6px;
color: #353;
cursor: pointer;
}
button:hover {
border-color: #899;
background-color: #cdd;
color: #465;
}
.tt {
margin-left: 5px;
}
.tt td {
padding: 2px;
}
#ui {
border: 2px solid #8a8;
border-radius: 10px;
background: #cdc;
padding: 10px;
color: #575;
}
#name, #preview {
border: 1px solid #898;
padding: 5px;
}
#preview {
display: inline-block;
width: 256px;
}
#page, #content {
display: flex;
align-items: center;
justify-content: center;
}
#content {
overflow: hidden;
}
#box {
position: relative;
}
#image {
z-index: -1;
position: absolute;
width: 100%;
height: 100%;
object-position: center;
object-fit: contain;
}
#grid {
table-layout: fixed;
margin: auto;
border: 2px solid;
}
#grid > colgroup > col {
border-right: 1px solid;
}
#grid > colgroup > col:nth-child(10n) {
border-right-width: 2px;
}
#grid > tbody > tr {
border-bottom: 1px solid;
}
#grid > tbody > tr:nth-child(10n) {
border-bottom-width: 2px;
}
#footer {
margin-top: 20px;
border-top: 1px solid #797;
padding-top: 6px;
}
#media screen {
#page {
border: 1px solid #898;
}
}
#media print {
#ui {
display: none;
}
}
<!DOCTYPE html>
<html>
<head>
<title>Cross-Stitch Image Generation Tool</title>
<meta charset="utf-8">
<style id="style"></style>
</head>
<body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-142604605-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-142604605-1');
</script>
<div id="ui">
<h1>Cross-Stitch Image Generation Tool</h1>
<h3>For the most basic usage, Browse an image, then click Generate, then scroll down to view the image. Possibly only works in Firefox...</h3>
<br>
<form id="form">
<table class="tt">
<tr>
<td><label for="units">Units:</label></td>
<td>
<select id="units">
<option value="1">Inches</option>
<option value="2">Centimeters</option>
<option value="3" selected>Millimeters</option>
</select>
</td>
<td colspan="5"></td>
</tr>
<tr>
<td><label for="size">Page Size:</label></td>
<td>
<select id="size">
<option value="0">Custom</option>
<option value="1">Letter</option>
<option value="3">Legal</option>
<option value="6">A3</option>
<option value="7" selected>A4</option>
<option value="8">A5</option>
</select>
</td>
<td><label for="width">Width:</label></td>
<td><input id="width" type="text" required readonly pattern="\d+(.\d+)?" value="210"></td>
<td><label for="height">Height:</label></td>
<td><input id="height" type="text" required readonly pattern="\d+(.\d+)?" value="297"></td>
<td><input id="rotate" type="checkbox"><label for="rotate">Rotate</label></td>
</tr>
</table>
<h3>Margins</h3>
<table class="tt">
<tr>
<td><label for="top">Top:</label></td>
<td><input id="top" type="text" required pattern="\d+(.\d+)?" value="10"></td>
<td><label for="bottom">Bottom:</label></td>
<td><input id="bottom" type="text" required pattern="\d+(.\d+)?" value="10"></td>
</tr>
<tr>
<td><label for="left">Left:</label></td>
<td><input id="left" type="text" required pattern="\d+(.\d+)?" value="10"></td>
<td><label for="right">Right:</label></td>
<td><input id="right" type="text" required pattern="\d+(.\d+)?" value="10"></td>
</tr>
</table>
<h3>Grid</h3>
<table class="tt">
<tr>
<td><label for="w">Cell Width:</label></td>
<td><input id="w" type="text" required pattern="\d+(.\d+)?" value="2"></td>
<td><label for="h">Cell Height:</label></td>
<td><input id="h" type="text" required readonly pattern="\d+(.\d+)?" value="2"></td>
<td><input id="square" type="checkbox" checked><label for="rotate">Same (Square)</label></td>
<td><button id="fill1" type="button">Fill Page</button></td>
</tr>
<tr>
<td><label for="m">Grid Width (cells):</label></td>
<td><input id="m" type="text" required pattern="\d+" value="90"></td>
<td><label for="n">Grid Height (cells):</label></td>
<td><input id="n" type="text" required pattern="\d+" value="130"></td>
<td></td>
<td><button id="fill2" type="button">Fill Page</button></td>
</tr>
<tr>
<td><label for="color">Grid Color:</label></td>
<td>
<input id="color" type="color" value="#ffffff">
<button id="white" type="button">White</button>
<button id="black" type="button">Black</button>
</td>
<td colspan="4"></td>
</tr>
</table>
<h3>Image</h3>
<input id="file" type="file">
<table class="tt">
<tr>
<td><button id="browse" type="button">Browse</button></td>
<td><div id="name">(Select an image)</div></td>
</tr>
<tr>
<td></td>
<td><img id="preview" alt="(No Preview)" src="#"></td>
</tr>
</table>
<br>
<button id="generate">Generate</button>
<button id="reset" type="reset">Reset</button>
<button id="print" type="button">Print</button>
</form>
<div id="footer">
© 2017-2019 Thejaka Maldeniya. All rights reserved.
</div>
</div>
<div id="page">
<div id="content"></div>
</div>
</body>
</html>
Reducing the code pointed me in the direction of how to solve the core problem. Turns out I had omitted the tds because FF Desktop didn't require them, to reduce the size of the generated code. At the time I wrote this, I was optimizing just for FF Desktop as per requirements. Adding empty tds seems to solve the core issue. (Only tested in Edge, so far...)
/*********************************************\
** Copyright © 2017-2019 Thejaka Maldeniya **
\*********************************************/
'use strict';
(function (a) {
a.i()
}(
{
z: 0, // image file data URL
p: '3', // previous value of units (default: Millimeters)
f: { // functions
_: function (l, e, h) { l.addEventListener(e, h, 0) },
l: function (a, f) { // load files
if (f.length) {
f = f[0]
var l = a.u.l, r = new FileReader()
l.name.innerHTML = f.name
a.f._(r, 'loadend', function (e) {
l.preview.src = a.z = e.target.result
})
r.readAsDataURL(f)
}
},
r: function (x) { // round to 3 decimal places
return Math.round(x * 1000) / 1000
},
f: function (a, b) { // get conversion factor
var f = 0
if (a == b) f = 1
else {
// if either a or b is not known, factor will be 0
// first, convert to Millimeters
switch (a) { // previous units value
case '1': // Inches
f = 25.4
break
case '2': // Centimeters
f = 10
break
case '3': // Millimeters
f = 1
}
// second, convert from Millimeters
switch (b) { // new units value
case '1': // Inches
f /= 25.4
break
case '2': // Centimeters
f /= 10
break
case '3': // Millimeters
// no change
}
}
return f
},
c: function () { // units or size value changed
var t = this, l = a.u.l
, r = t.r // round function
, width = l.width.value, height = l.height.value // A4
, mt = l.top.value, mb = l.bottom.value, ml = l.left.value, mr = l.right.value // margins
, w = l.w.value, h = l.h.value // width, height of cell
, m = l.m.value, n = l.n.value // width, height of grid
, s = 'A4' // paper size
, f = t.f(a.p, a.p = l.units.value) // unit conversion factor
, g = t.f('3', l.units.value) // unit conversion factor for default values (currently in Millimeters)
l.width.setAttribute('readonly', '')
l.height.setAttribute('readonly', '')
switch (l.size.value) {
case '0': // Custom
l.width.removeAttribute('readonly')
l.height.removeAttribute('readonly')
g = f
break
case '1': // Letter
width = 216
height = 279
l.rotate.checked = 0
s = 'Letter'
break
case '3': // Legal
width = 216
height = 356
l.rotate.checked = 0
s = 'Legal'
break
case '6': // A3
width = 297
height = 420
l.rotate.checked = 0
s = 'A3'
break
case '7': // A4
width = 210
height = 297
l.rotate.checked = 0
s = 'A4'
break
case '8': // A5
width = 148
height = 210
l.rotate.checked = 0
s = 'A5'
}
l.width.value = r(width * g)
l.height.value = r(height * g)
l.top.value = r(mt * f)
l.bottom.value = r(mb * f)
l.left.value = r(ml * f)
l.right.value = r(mr * f)
l.w.value = r(w * f)
l.h.value = r(h * f)
l.m.value = m
l.n.value = n
}
},
u: {
l: {
style: 0,
ui: 0,
form: 0,
units: 0,
size: 0,
width: 0,
height: 0,
rotate: 0,
top: 0,
bottom: 0,
left: 0,
right: 0,
w: 0,
h: 0,
square: 0,
fill1: 0,
m: 0,
n: 0,
fill2: 0,
color: 0,
white: 0,
black: 0,
file: 0,
browse: 0,
name: 0,
preview: 0,
reset: 0,
print: 0,
page: 0,
content: 0
},
f: {
b: function (f, c) {
return function () {
var a = arguments, n = a.length, b = Array(n), i = 0
for (; i < n; ++i)
b[i] = a[i]
f.apply(c, b)
}
}
},
e: {
document: {
DOMContentLoaded: function () {
}
},
window: {
load: function () {
var a = this, l = a.u.l
a.p = l.units.value
if (l.size.value == '0') {
l.width.removeAttribute('readonly')
l.height.removeAttribute('readonly')
}
if (!l.square.checked)
l.h.removeAttribute('readonly')
}
},
ui: {
dragover: function (e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
drop: function (e) {
e.stopPropagation()
e.preventDefault()
this.f.l(this, e.dataTransfer.files)
}
},
rotate: {
change: function () {
var l = this.u.l, v = l.width.value
l.width.value = l.height.value
l.height.value = v
v = l.top.value
if (l.rotate.checked) {
l.top.value = l.left.value
l.left.value = l.bottom.value
l.bottom.value = l.right.value
l.right.value = v
} else {
l.top.value = l.right.value
l.right.value = l.bottom.value
l.bottom.value = l.left.value
l.left.value = v
}
v = l.w.value
l.w.value = l.h.value
l.h.value = v
v = l.m.value
l.m.value = l.n.value
l.n.value = v
}
},
w: {
change: function (e) {
var l = this.u.l
if (l.square.checked)
l.h.value = e.target.value
}
},
square: {
change: function (e) {
var l = this.u.l
if (e.target.checked) {
l.h.setAttribute('readonly', '')
l.h.value = l.w.value
} else l.h.removeAttribute('readonly')
}
},
file: {
change: function (e) {
e.stopPropagation()
e.preventDefault()
this.f.l(this, e.target.files)
}
},
units: {
change: function () {
this.f.c(this)
}
},
size: {
change: function () {
this.f.c(this)
}
},
fill1: {
click: function () {
var l = this.u.l
if (l.form.reportValidity()) {
l.m.value = (l.width.value - l.left.value - l.right.value) / l.w.value | 0
l.n.value = (l.height.value - l.top.value - l.bottom.value) / l.h.value | 0
}
}
},
fill2: {
click: function () {
var l = this.u.l
if (l.form.reportValidity()) {
var w = (l.width.value - l.left.value - l.right.value) / l.m.value
, h = (l.height.value - l.top.value - l.bottom.value) / l.n.value
if (l.square.checked)
if (w < h) h = w
else w = h
l.w.value = w
l.h.value = h
}
}
},
white: {
click: function () {
this.u.l.color.value = '#ffffff'
}
},
black: {
click: function () {
this.u.l.color.value = '#000000'
}
},
browse: {
click: function () {
this.u.l.file.click()
}
},
reset: {
click: function () {
var l = this.u.l
l.width.setAttribute('readonly', '')
l.height.setAttribute('readonly', '')
l.content.innerHTML = l.style.innerHTML = ''
}
},
print: {
click: function () {
window.print()
}
},
form: {
submit: function (e) {
e.stopPropagation()
e.preventDefault()
var a = this, l = a.u.l, u = l.units.value
, width = l.width.value, height = l.height.value // page size
, mt = l.top.value, mb = l.bottom.value // margins
, ml = l.left.value, mr = l.right.value // margins
, w = l.w.value, h = l.h.value // width, height of cell
, m = l.m.value, n = l.n.value // width, height of grid
switch (u) {
case '1': // Inches
u = 'in'
break
case '2': // Centimeters
u = 'cm'
break
case '3': // Millimeters
default: // use Millimeters
u = 'mm'
}
l.style.innerHTML = '#page{size:' + width + u + ' ' + height + u
+ (l.rotate.checked ? ';landscape' : '')
+ '}#page{margin:' + mt + u + ' ' + mr + u + ' ' + mb + u + ' ' + ml + u
+ '}#media screen{#page{width:' + width + u + ';height:' + height + u
+ '}}#content{width:' + (width - ml - mr) + u + ';height:' + (height - mt - mb) + u
+ '}#grid>colgroup>col{min-width:' + w + u
+ '}#grid>tbody>tr{height:' + h + u
+ '}#grid,#grid>colgroup>col,#grid>tbody>tr{border-color:' + l.color.value
+ '}'
l.content.innerHTML = '<div id="box"><img alt="" id="image" src="' + a.z
+ '"><table id="grid"><colgroup>'
+ '<col>'.repeat(m) + '</colgroup>'
+ ('<tr>' + '<td></td>'.repeat(m) + '</tr>').repeat(n)
+ '</table></div>'
}
}
},
i: function (p) {
var t = this, l = t.l, b = t.f.b, e = t.e, i, j, k
for (i in l)
l[i] = document.getElementById(l[i] || i)
l.window = window
l.document = document
for (i in e) {
k = e[i]
for (j in k)
l[i].addEventListener(j, b(k[j], p), 0)
}
}
},
i: function () {
var t = this
t.u.i(t)
}
}
))

how to make a stimulus (image, div, whichever's easiest) show up on right or left half of screen randomly using javascript

how to make a stimulus (image, div, whichever's easiest) show up on right or left half of screen randomly using javascript.
Any ideas about having the button clicks record the reaction time, which button is clicked (left or right), and which side the stimulus was presented on?? Also, the left button should be "true" when stimulus is presented on the right and vice versa.
<head>
<style >
.divStyleLeft {
width: 300px;
height: 300px;
background-color: lightblue;
float: left;
}
.divStyleRight {
width: 300px;
height: 300px;
background-color: lightgreen;
float: right;
}
.maxWidth {
width: 100%;
}
.button {
float: right;
}
.button2 {
float: left;
}
</style>
</head>
<body onload="presentStimulus()">
<div class="button">
<button onclick="presentStimulus()">Click Me</button>
</div>
<div class="button2">
<button onclick="presentStimulus()">Click Me </button>
</div>
<div class="maxwidth"></div>
<div id="float" class="divStyleLeft" onclick="recordClick()">
I AM NOT FLOATING
</div>
<script>
let numClicks= 0;
let timeStart = 0;
let timeEnd = 0;
function Trial(trialTime, sidePresented,buttonClicked,) {
this.trialTime = trialTime;
this.sidePresented= sidePresented;
this.buttonClicked= buttonClicked;
}
let allTrials = [];
for(x = 0; x < 12; x++)
allTrials.push(new Trial(0,0,0));
Trial.prototype.toString=function(){
return this.trialTime + "ms, Side : " + this.sidePresented + ", Reaction Time: " + this.buttonClicked
+ "<br>";
};
function presentStimulus() {
const elem = document.querySelector ( '#float' );
const min = 1;
const max = 2;
const v = Math.floor(Math.random() * (max - min + 1)) + min;
console.log ( 'Random num is ' + v + ": ", '1 will go left, 2 will go right' );
v === 1 ?
( () => {
elem.classList = [ 'divStyleLeft' ];
elem.innerText = 'Hello!';
} ) () :
( () =>{
elem.classList = [ 'divStyleRight' ];
elem.innerText = 'Hi!';
} ) ();
}
function recordClick()
{
let theData = document.getElementById("#float").data;
timeEnd = Date.now();
allTrials[numClicks].trialTime = timeEnd - timeStart;
allTrials[numClicks].sidePresented = theData.sidePresented;
allTrials[numClicks].buttonClicked = theData.buttonClicked;
if (numClicks < 11) {
numClicks++;
presentStimulus();
}
else {
document.getElementById("float").style.visibility = "hidden";
let output = "";
for (x = 0; x < allTrials.length; x++)
output = output + "<b>:" + (x + 1) + "</b>:" + allTrials[x].toString();
document.getElementById("display").innerHTML = output;
}
}
</script>
<p id="display"></p>
</body>
There's a bunch of ways you could go about this.
If you're using plain 'ol JS, I'd probably create classes in CSS that float left or right, possibly appear as a flex container that displays left or right, whatever your specific need might be (again, there's a lot of ways to go about it, and one might be better than the other given your context).
When you've determined left or right (gen a random number or whatever), update the classlist on the DOM elements with the desired class to make it go this way or that.
For what it's worth, here's a bare-bones vanilla JS example. Again, I don't know your specific context, but this should give you a start on how to look at it. Floating may not be ideal, you may want to just hide/show containers that already exist on the left or right or actually create whole new DIVs and insert them into known "holder" containers (usually just empty divs), but the idea is the same; gen the random number, alter the classlists of the elements you want to hide/show/move/whatever, and if necessary, alter the innerHTML or text as needed.
<html>
<head>
<style>
.divStyleLeft {
width: 300px;
height: 300px;
background-color: lightblue;
float: left;
}
.divStyleRight {
width: 300px;
height: 300px;
background-color: lightgreen;
float: right;
}
.maxWidth {
width: 100%;
}
</style>
</head>
<body>
<button onclick="onClick()">Click Me</button>
<div class="maxwidth">
<div id="floater" class="divStyleLeft">
I AM NOT FLOATING
</div>
<div>
<script>
function onClick () {
const elem = document.querySelector ( '#floater' );
const min = 1;
const max = 2;
const v = Math.floor(Math.random() * (max - min + 1)) + min;
console.log ( 'Random num is ' + v + ": ", '1 will go left, 2 will go right' );
v === 1 ?
( () => {
elem.classList = [ 'divStyleLeft' ];
elem.innerText = 'I am floating LEFT';
} ) () :
( () =>{
elem.classList = [ 'divStyleRight' ];
elem.innerText = 'I am floating RIGHT';
} ) ();
}
</script>
</body>
</html>

javascript: get random pairs of an array without duplicates

I have an array of students, that should be random paired by clicking the button "button-newPairs". These pairs should be shown in 8 divs "pair one", "pair two", ... that contains two spans "studentOne" and "studentTwo".
I get the pairs in console but not by clicking the button "button-newPairs" and I don´t know how to change or insert the text content in my spans. Can someone help me, please? Thank you in advance.
var students = ['Al', 'Ma', 'Pu', 'Mi', 'Ma', 'Me', 'Ca', 'Na', 'Ja', 'Go', 'Ha', 'Fa', 'Ti', 'Fi' ];
var studentOne = document.querySelector('#student1');
var studentTwo = document.querySelector('#student2');
if (students.length % 2 != 0) {
alert("You must have an even number of students. You currently have " + students.length + " students.");
} else {
var arr1 = students.slice(),
arr2 = students.slice();
arr1.sort(function () { return 0.5 - Math.random(); });
arr2.sort(function () { return 0.5 - Math.random(); });
while (arr1.length) {
var student1 = arr1.pop(),
student2 = arr2[0] == student1 ? arr2.pop() : arr2.shift();
$(".button-newPairs").click(function () {
studentOne.textContent = student1;
studentTwo.textContent = student2;
});
console.log(student1 + ' works with ' + student2);
}
}
.container-pairs {
display: grid;
grid-template-columns: 150px 150px;
grid-gap: 20px;
justify-content: space-around;
align-content: center;
margin-bottom:20px;
}
.one {
grid-column: 1 / 2;
grid-row: 1;
}
.two {
grid-column: 2 / 2;
grid-row: 1;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div class="container-pairs">
<div class="pair one">
<span id="studentOne">NEW </span> <br>
<span id="studentTwo"> PAIRS</span>
</div>
<div class="pair two">
<span id="studentOne">NEW </span><br>
<span id="studentTwo"> PAIRS</span>
</div>
<div id="container-footer">
<div class="button-newPairs">
<span>NEW PAIRS</span>
</div>
</div>
</body>
I've commented inline. Look for the lines with a //
// Let's wrap this in a function so that we can call it with our button.
function makePairs() {
// We will clear our lists before each run of the function.
$('#studentOne').html('<h1>Student 1</h1>');
$('#studentTwo').html('<h1>Student 2</h1>');
var students = ['Al', 'Ma', 'Pu', 'Mi', 'Ma', 'Me', 'Ca', 'Na', 'Ja', 'Go', 'Ha', 'Fa', 'Ti', 'Fi'];
// By capturing these nodes in variables, we can reference them as our targets for insertion, below.
var studentOne = document.querySelector('#studentOne');
var studentTwo = document.querySelector('#studentTwo');
if (students.length % 2 != 0) {
alert("You must have an even number of students. You currently have " + students.length + " students.");
} else {
var arr1 = students.slice(),
arr2 = students.slice();
arr1.sort(function() {
return 0.5 - Math.random();
});
arr2.sort(function() {
return 0.5 - Math.random();
});
// Here we make a function that will insert a DOM fragment inside a target node
const insertFragment = (output, target) => {
let el;
let fragment = document.createDocumentFragment();
el = document.createElement('p');
el.innerText = output
fragment.appendChild(el);
target.appendChild(fragment);
}
while (arr1.length) {
var student1 = arr1.pop(),
student2 = arr2[0] == student1 ? arr2.pop() : arr2.shift();
// Here we use the function, sending student1 (the student) to studentOne (the target DOM node specified at the very top, #studentOne)
insertFragment(student1, studentOne)
insertFragment(student2, studentTwo)
console.log(student1 + ' works with ' + student2);
}
}
}
// Run the function on load
makePairs();
.container-pairs {
display: grid;
grid-template-columns: 150px 150px;
grid-gap: 20px;
justify-content: space-around;
align-content: center;
margin-bottom: 20px;
}
.one {
grid-column: 1 / 2;
grid-row: 1;
}
.two {
grid-column: 2 / 2;
grid-row: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div class="container-pairs">
<div id="studentOne">
<h1>Student 1</h1>
</div>
<div id="studentTwo">
<h1>Student 2</h1>
</div>
</div>
<div id="container-footer">
<button class="button-newPairs" onclick="makePairs()">
NEW PAIRS
</button>
</div>
</body>
The button tag, just above this line, has registered an event handler for clicks that will run the makePairs() function again.
I would use splice to remove the user from the array and loop through each user like so:
let students = ['Al', 'Ma', 'Pu', 'Mi', 'Ma', 'Me', 'Ca', 'Na', 'Ja', 'Go', 'Ha', 'Fa', 'Ti', 'Fi'];
while (students.length) {
let student1 = students.splice((Math.random() * 1000) % students.length, 1)
let student2 = students.splice((Math.random() * 1000) % students.length, 1)
console.log(`${student1} works with ${student2}`)
}

Creating graph with timeline in javascript

I would like to create dynamically changing graph with timeline in javascript, That would look something like this.
Edit: I would like to decide by myself which node should be in which time slice.
I wonder, is there a library that I can use to do this, or I need to create it by myself ( by simply drawing on canvas )? I tried to find one, however it seems that there are many implementations of timelines and of graphs but the combination of those two is hard to find. The most suitable solution was using gojs. However I can't create a node with two parents in it because it is implemented as a tree data structure.
You may have to play around with the maths, but I hope this will be useful as a starting point:
DEMO: JSFiddle
HTML
<div id='board'>
<div id='titles'></div>
</div>
CSS
#board {
position: relative;
width: 500px;
height: 600px;
background-color:#f83213;
}
#titles {
color: #ffffff;
width: 100%;
height: 18px;
font-size: 12px;
}
#titles div {
display:inline-block;
margin: 10px;
}
.event{
border: 0px;
background-color: #3a2356;
color: #ffffff;
width: 18px;
height: 18px;
position: absolute;
padding: 4px;
font-size: 18px;
z-index: 2;
}
.line{
height: 1px;
width: 60px;
background-color: #3a2356;
position: absolute;
z-index: 1;
}
** JavaScript**
var margin = 20;
var events = {
"A": {
day: 0,
indexInDay: 0,
lineTos: ["D"]
},
"B": {
day: 0,
indexInDay: 1,
lineTos: ["D"]
},
"D": {
day: 1,
indexInDay: 0,
lineTos: ["E","F"]
},
"E": {
day: 2,
indexInDay: 0,
lineTos: null
},
"C": {
day: 0,
indexInDay: 2,
lineTos: ["F"]
},
"F": {
day: 2,
indexInDay: 2,
lineTos: null
},
};
drawAll(events);
function drawAll(events) {
drawTitles(events);
drawEvents(events);
drawLines(events);
}
function drawTitles(events) {
var titles = document.getElementById('titles');
var max = 0;
for (var name in events) {
if (events[name].day > max)
max = events[name].day;
}
for (var i = 0 ; i <= max ; i++)
titles.innerHTML += '<div>' + 'Day' + i + '</div>';
}
function drawEvents(events) {
var board = document.getElementById('board');
for (var name in events) {
var ev = events[name];
var eventDiv = document.createElement('DIV');
board.appendChild(eventDiv);
eventDiv.className = 'event';
setTopLeftEvent(ev, eventDiv);
eventDiv.innerText = name;
}
}
function drawLines(events) {
var board = document.getElementById('board');
for (var name in events) {
var from = events[name];
var tos = from.lineTos;
if (!tos) continue;
for (var j = 0 ; j < tos.length ; j++) {
var to = events[tos[j]];
var lineDiv = document.createElement('DIV');
board.appendChild(lineDiv);
lineDiv.className = 'line';
setTopLeftLine(from, lineDiv);
lineDiv.style.width = margin + 1 * margin * distance(to.indexInDay,from.indexInDay,to.day, from.day) + 'px';
var tan = (to.indexInDay - from.indexInDay) / (to.day - from.day);
lineDiv.style.top = lineDiv.offsetTop + (tan * margin) +'px';
var angle = Math.atan(tan) * 180/Math.PI;
// Code for Safari
lineDiv.style.WebkitTransform = "rotate(" + angle + "deg)";
// Code for IE9
lineDiv.style.msTransform = "rotate(" + angle + "deg)";
// Standard syntax
lineDiv.style.transform = "rotate(" + angle + "deg)";
}
}
}
function distance(x1, y1, x2, y2){
var res = Math.sqrt((y2-y1)*(y2-y1) + (x2-x1)*(x2-x1));
return res;
}
function setTopLeftEvent(event, eventDiv) {
eventDiv.style.left = (margin + event.day * (margin * 2)) + 'px';
eventDiv.style.top = (margin * 2 + event.indexInDay * (margin * 2)) + 'px';
}
function setTopLeftLine(event, lineDiv) {
lineDiv.style.left = (margin + event.day * (margin * 2)) + 'px';
lineDiv.style.top = (margin * 2.5 + event.indexInDay * (margin * 2)) + 'px';
}
As that GoJS sample mentions in the text, it is easy to replace the TreeLayout with a LayeredDigraphLayout and the TreeModel with a GraphLinksModel. Here's what I just did to modify the sample.
Replace go.TreeLayout with go.LayeredDigraphLayout, so that the custom layout no longer inherits from TreeLayout. Change the constructor not to bother setting TreeLayout specific properties. Change the diagram's layout to use LayeredDigraphLayout specific properties:
layout: $(LayeredTreeLayout, // custom layout is defined above
{
angle: HORIZONTAL ? 0 : 90,
columnSpacing: 5,
layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource
}),
Replace that sample's model with a GraphLinksModel holding the data that you want:
// define the node data
var nodearray = [
{ // this is the information needed for the headers of the bands
key: "_BANDS",
category: "Bands",
itemArray: [
{ text: "Day 0" },
{ text: "Day 1" },
{ text: "Day 2" },
{ text: "Day 3" },
{ text: "Day 4" },
{ text: "Day 5" }
]
}
];
var linkarray = [
{ from: "A", to: "D" },
{ from: "B", to: "D" },
{ from: "D", to: "E" },
{ from: "D", to: "F" },
{ from: "C", to: "F" }
];
myDiagram.model = $(go.GraphLinksModel,
{ // automatically create node data objects for each "from" or "to" reference
// (set this property before setting the linkDataArray)
archetypeNodeData: {},
nodeDataArray: nodearray,
linkDataArray: linkarray
});
Without having changed any of the templates or the styling, the result is:
Just to make sure it works, I also tried setting HORIZONTAL = false:

Categories

Resources