I want to arrange some rectangular div components around a regular polygon. Basically one of the long sides of the divs will be coincident with a line segment around the polygon.
In the final code, I'll use .ejs (since the number of sides of the polygon is dynamic, 3-10 sides). In my "quick and dirty" testing I'm doing a triangle in just HTML and CSS to get the math right.
I have a "very close" solution already and am wondering how to get it "exact" and am also wondering why my geometry intuition is so far off.
HTML and CSS:
div {
position: absolute;
left: 200px;
top: 200px;
width: 80px;
height: 40px;
background-color: skyblue;
}
.rotatedA {
transform: translateY(-60px) translateX(-35px) rotate(300deg);
background-color: blue;
}
.rotatedB {
transform: translateY(-60px) translateX(35px) rotate(60deg);
background-color: red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>title</title>
<link rel="stylesheet" href="basic.css">
</head>
<body>
<div>Normal</div>
<div class="rotatedA">Rotated</div>
<div class="rotatedB">Rotated</div>
</body>
</html>
The first attempt I rotated "A" by 60 and "B" by -60 and did a translateY equal to the div height. When that did not work I played around with it.
On this last attempt (close but not perfect since the rotations won't give an integer) it seems like the Y adjustment is 1.5x (item height + cos(60)) but the X adjustment is 1/2 of sin(60) (I don't understand why).
Since my results aren't going to be an integer number of pixels what is the correct way to do this? Also, I don't understand why my geometry is so off (I could understand sin(60) but 1/2(sin(60)) doesn't make sense to me
Here's a mathematical way; the number and dimensions are read by the script, then the divs are arranged accordingly. I also made sure that the wrapper container has the correct dimensions so it can be used with other elements:
function arrange(wrapper) {
wrapper.style.position = "relative";
const rects = Array.from(wrapper.children);
const n = rects.length;
/* dimensions of a rectangle */
const bb = rects[0].getBoundingClientRect();
const a = bb.width;
const h = bb.height;
/* incircle radius of regular polygon */
const r = a * 0.5 / Math.tan(Math.PI / n);
/* radius of outer circle */
const bigR = Math.sqrt((r + h) * (r + h) + a * a / 4);
rects.forEach((rect, i) => {
const angle = i * (360 / n);
if (angle) rect.style.transform = `rotate(${angle}deg)`;
rect.style.position = angle ? "absolute" : "relative";
rect.style.marginBottom = bigR + r + "px";
rect.style.transformOrigin = `${a/2}px ${-r}px`;
rect.style.left = bigR - a / 2 + "px";
rect.style.top = bigR + r + "px";
});
if (window.getComputedStyle(wrapper).display == "inline-block")
wrapper.style.width = 2 * bigR + "px";
}
arrange(document.querySelector('#polygon'));
#polygon {
border: 1px solid black;
display: inline-block;
}
#polygon div {
width: 80px;
height: 20px;
background-color: skyblue;
text-align: center;
padding: 5px;
}
<div id="polygon">
<div>Normal</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
</div>
The basic idea is to
calculate the in-circle's radius of the polygon based on the width of a rectangle
set transform-origin accordingly centered and above the first rectangle
arrange the others by rotating them
(do more calculations so the wrapper element encompasses everything exactly)
Related
Im trying to point the red div towards the corner of the window using transform rotate.
The Yellow is 45deg fixed, just for reference.
The Blue points to the left top corner using the innerHeight and innerWidth as points.
And the Red trys to mimic the Blue by calculating 45 + some offset, it must aways have the same rotation as the Blue but without using innerHeight and innerWidth as points.
This is the closest i got of makeing it work was using this code:
window.onresize = () => calcAngle()
var calcAngle = () => {
console.clear()
var x1 = 0, y1 = 0;
var x2 = window.innerWidth, y2 = window.innerHeight;
var a = (Math.atan2((y2 - x1), (x2 - y1)) * (180 / Math.PI));
document.querySelectorAll(".pointer")[1].style.transform = "translate(50%, -50%) rotate("+a+"deg)"
var of = x2/y2;
var ang = 45;
var calc = ang - (ang*of-ang)
document.querySelectorAll(".pointer")[2].style.transform = "translate(50%, -50%) rotate("+(calc)+"deg)"
console.log(a, calc)
}
calcAngle();
body {
overflow: hidden;
}
.pointer {
width: 200px;
height: 20px;
opacity: .7;
background: blue;
position: absolute;
top: 50%;
right: 50%;
transform: translate(50%, -50%);
clip-path: polygon(100% 0%, 100% 100%, 0% 50%);
}
.pointer:nth-child(1){
background: yellow;
transform: translate(50%, -50%) rotate(45deg);
}
.pointer:nth-child(3){
background: red;
}
<div class="pointer"></div>
<div class="pointer"></div>
<div class="pointer"></div>
Using the code as example, calc must always have the same value of a but using 45 deg as reference.
Of course as you show in your question, the simplest approach is to use the atan(y/x) * 180 / PI to get the entire angle. This is reflected below as refAngle.
Since your condition requires that it be an offset of 45 degrees, this requires more advanced math, using the law of sines in addition to basic trigonometry. We have enough information based on the ratio of width/height of the screen to find the information, but it ends up being a very complex formula. This is reflected below in two steps, first sinOff to get the sine of the offset angle relative to 45 degrees, and then off once we've done the asin and conversion from radians to degrees.
This snippet demonstrates that the two angles agree, no matter how the browser window is resized.
const x = window.innerWidth;
const y = window.innerHeight;
const { sin, atan, asin, sqrt, PI } = Math;
const sinOff = sin(atan(y/x)) / (sqrt(2)*y) * (x-y);
const off = asin(sinOff) * 180 / PI;
const angle = 45 - off;
const refAngle = atan(y/x) * 180 / PI;
console.log(angle, refAngle);
Note, since the formula is so complex, I'm using destructuring to reduce the Math. clutter.
Hi I am using a hashmap that allows me to efficiently detect objects in the given coordinates. However it is working perfectly , the problem lies with using the mouse to gather the position of the mouse within the canvas down to the pixel. I have been using the offsetX and offsetY methods for the event to gather some offset but it seems there is an offset I am unaware of and may have something to do with either:
1.using scaling on the canvas , Note: ive tried to fix this by division of the renderscale, this works with everything else so should be fine here.
mouseoffset is not accounting for parts of the page or is missing pixels at a low level (maybe 20) but divided by the render scale thats massive.
3.I am using a cartesian coordinate system to simplify things for the future , so the game map is in cartesian and may have to do with the problem.
I will not be supplying all the code because it is allot of work to go through it all so i will supply the following :
the html/css canvas code
<html lang="en">
<head>
<meta charset="UTF-8">
<title> Game</title>
</head>
<body onload="jsEngine = new JsEngine(24, 24, .1); " >
<div class ="wrapper">
<canvas id="canvas" width="1920" height="1080"></canvas>
</div>
<style>
.wrapper {
position: relative;
width: auto;
height: 900px;
}
.wrapper canvas {
position: absolute;
left: 90px;
top: 50px;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 90%;
height: 90%;}
.GUI{
top: -315px;
left: -302px;
position: absolute;
width: 300px;
height: 300px;
background-color: cadetblue;
opacity: .5;
word-wrap: break-word;}
img{
image-rendering: optimize-contrast;
}
</style>
<div id = GUI class = "GUI"></div>
<!-- Libraries -->
<script src="../myapi/JSONE.js"></script>
<script src="../myapi/engine/SpacialHash.js"></script>
</body>
</html>
2.the javascript click function
//Click on objects
let onClick = function(event){
let canvas_ctx = document.getElementById("canvas").getContext("2d");
let canvasOffsetX = canvas_ctx.canvas.width/2;
let canvasOffsetY = canvas_ctx.canvas.height/2;
let mousePosX = event.clientX;
let mousePosY = event.clientY;
let mouseX =jsEngine.cameraFocus.x-canvasOffsetX/jsEngine.renderScale+(mousePosX)/jsEngine.renderScale;
let mouseY = jsEngine.cameraFocus.y+(canvasOffsetY)/jsEngine.renderScale+((-mousePosY)/jsEngine.renderScale);
console.log("sum to",mouseX,mouseY);
//My hashMap to place the mouse coordinates on the game map
let clickPosition = hm.find({x:mouseX,y:mouseY,width:1,height:1});
if(clickPosition.length===1){
let gameObject = jsEngine.gameObjects[clickPosition[0].range.id];
//console.log(gameObject.transform.x,gameObject.transform.y,mouseX,mouseY);
let clickBox = {};
let picture = gameObject.texture;
guiCreateClickBox(clickBox,gameObject.id,1200,500,picture);
}else if(clickPosition.length>1) {
for (let i = 0; i < clickPosition.length; i++) {
let gameObject = jsEngine.gameObjects[clickPosition[i].range.id];
if (gameObject instanceof PlayerShip|| gameObject instanceof Bullet)
continue;
let clickBox = {};
let picture = gameObject.texture;
guiCreateClickBox(clickBox,gameObject.id,1200,500,picture);
//console.log(gameObject.transform.x,gameObject.transform.y,mouseX,mouseY)
}
}
};
// Listeners
//Click on objects
document.getElementById("canvas").addEventListener("click", onClick);
the making of the map and scale :Note: this is done via onPreRender
function drawBackground(canvas_ctx, renderScale, imageResource) {
let img = imageResource.mapBackground;
let mapWidth = 1000000;
let mapHeight= 1000000;
let zoom = 1;
mapWidth *= renderScale / zoom;
mapHeight *= renderScale / zoom;
// Render the Background
canvas_ctx.fillStyle = canvas_ctx.createPattern(img, 'repeat');
canvas_ctx.scale(zoom, zoom);
canvas_ctx.fillRect(-mapWidth / 2, - mapHeight / 2, mapWidth, mapHeight);
//if (jsEngine.cameraFocus.x > 1000000) {}
canvas_ctx.scale(1/zoom, 1/zoom);
}
The rendering method used for playership
renderGameObject(gameObject) {
let x = gameObject.transform.x * this.renderScale;
let y = -(gameObject.transform.y * this.renderScale);
let rotation = Math.radians(gameObject.transform.rotation);
let width = gameObject.transform.width;
width *= this.renderScale;
let height = gameObject.texture.height;
height *= this.renderScale;
// Render the gameObject
this.canvas_ctx.translate(x, y);
this.canvas_ctx.rotate(rotation);
this.canvas_ctx.drawImage(gameObject.texture, 0, 0, width / this.renderScale, height / this.renderScale, // Make sure the image is not cropped
-width/2 , // X
-height/2 , // Y
width, height); // width and height
this.canvas_ctx.rotate(-rotation);
this.canvas_ctx.translate(-x, -y);
}
the issue to solve is to make it so that when you click on any given quadrant of the canvas it will return -+ for top left, -- bottom left , -+ topright, +- bottomright, as well as being applied to the render scale which at the moment is .1 so just divide your mouse and canvas coords like shown above and you should be able to get the same results.
Things to keep in mind :
the jsEngine.cameraFocus is set to the playerships x and y coordinates(which are set to the 0,0 posiiton on the map) (which are also in the middle of the ship)
the top left of the canvas is still 0,0 and ++ is still toward the bottom right so theoretically minusing half the canvas width/height then adding the offsets X and Y. this should be working but at my map coordinate -4000,-4000 i get ~-3620,-3295 and at +4000,+4000 I get 3500,3500. (The reason why the canvas 0,0 is not where the ship is , is to make the ship in the middle of the screen)
If you have questions about anything based on code that needs to be supplied please ask via comment . Please note if you have problems with the format of the code supplied I have nothing to say about it . all I need is the click function working on the canvas model i set up in cartesian format.
ps: jQuery is not a solution its a problem please use vanilla js.
I found out why it was off , my canvas has a offset of 90 px and 50 px as well as the main problem that the canvas is only 90% of its origonal size (also in css). If anyone can give me help for how to adjust to these issues please reply in comment . until then I beleieve I have solved my own issue .
I'm trying to make the shooter0 rotate towards the character (which will be moving around constantly). I tried to using the atan() and then converting that to an angle but the shooter0 won't rotate.
var shooter0 = document.getElementById('shooter0');
var character = document.getElementById('character');
var tracker0 = shooter0.getContext('2d');
// The cordinates for the character and shooter0
var characterLeft = 530;
var characterTop = 180;
var shooter0Left = 960;
var shooter0Top = 470;
while (characterLeft >= 700){
setInterval(startShooter, 1000);
}
function startShooter(){
//Getting all the variable to be able to calculate the angle of the hypotenuse
var dX = characterLeft - tracker0Left;
var dY = characterTop - tracker0Top;
var arcTan = Math.atan(dX/dY)* 180/Math.PI;
var cx = shooter0.width/2;
var cy = shooter0.height/2;
tracker0.save();
tracker0.translate(cx, cy); // pivot point
//rotating the square towards the character
tracker0.rotate(arcTan * Math.PI/180);
//Drawing the square
tracker0.fillRect(400, 300, 100, 100);
tracker0.restore();
}
HTML:
<canvas id="character" height="50px;" width="50px;"></canvas>
<canvas id="shooter0" height="100px;" width="100px;"></canvas>
And CSS:
#character{
position: absolute;
top: 180px;
left: 530px;
border: 3px solid black;
background-color: orange;
}
#shooter0{
position: absolute;
left: 960px;
top: 470px;
border: 2px solid black;
background-color: #B40404;
}
Sorry if you find the code rather messy. Here's a fiddle with all of my code if you find that useful. https://jsfiddle.net/Snubben/tc0j4psz/3/
Please don't use any JQuery.
I couldn't get your fiddle to work for some reason, so I created a little example.
The thing I notice in your code:
var arcTan = Math.atan(dX/dY)* 180/Math.PI; : Math.atan returns an angle in radians. And you convert it to degrees by 180/Math.PI
Only to convert it back to radians here again:
tracker0.rotate(arcTan * Math.PI/180);
Then, for calculating angles (in radians), I think Math.atan2 is the most easy to use: Math.atan2 - MDN
The usage of Math.atan2 to calculate an angle between two points is:
Math.atan2(point2.y - point1.y, point2.x - point1.x)
With that information, I think you can get very far.
demo fiddle
I would like the mouse to align with the top of the div and the div should rotate when the mouse moves with the top part of the div aligned with the mouse. I want to use atan2. It should look something like this.
Javascript:
$(function() {
var stick = $(".stick"), sHeight = stick.outerHeight(),
sWidth = stick.outerWidth(), atan,
sTop = stick.offset().top, sLeft = stick.offset().left;
$(document).on("mousemove", function(e) {
// console.log(e.pageX, " ", e.pageY)
atan = Math.atan2(e.pageY - sTop , e.pageX - sLeft )
console.log(atan)
stick.css({"transform" : "rotate(" + atan + "rad)"} )
})
})
css:
.wrapper{
width: 500px;
height: 400px;
position: relative;
border:1px solid green;
}
.stick{
width: 3px;
height: 200px;
position: absolute;
left: 50%;
top: 50%;
background: green;
transform: rotate(90deg);
}
HTML:
<div class="wrapper">
<div class="stick"></div>
</div>
I made something that works here.
It seems you're not centring properly - you need to take into account the width of the div and the centre point of the container
div.$(function(){
var stick = $(".stick"), sHeight = stick.outerHeight(),
sWidth = stick.outerWidth(), atan,
sTop = stick.offset().top, sLeft = stick.offset().left;
$(document).on("mousemove", function(e){
atan = Math.atan2(e.pageY - 200 , e.pageX - 250) + Math.PI/2;
console.log(atan);
stick.css({"transform" : "rotate(" + atan + "rad)"} );
});
});
(I also removed the rotation in the css, and positioned the stick in the centre.)
The problem is that the angle computation phi=atan2(y,x) assumes a "normal" Cartesian coordinate system where the y axis points upwards. In screen coordinates, it points downwards. Thus you should use phi=atan2(-y,x).
But to be sure you should try out and report where the bar points to when using rotations 0deg and 90deg. With your code the rotation center is the middle of the bar, so orientation is difficult to determine, but it seems that 0deg points upwards and angles are applied clockwise. Thus for the inner coordinate system, where 0° is the X axis and 90° the Y axis, X = centery-y and Y=x-centerx, thus
angle = atan2(x-centerx, centery-y)
So i need to create a basic bar graph from scratch using javascript, i tried setting some things such as, I attempted to start trying to define some values but it all went wrong.
The task is to create a program that will accept some of this data, input by the user, and to produce a graph that is suitably formatted.
The output could be made to look like this:
Here is the data that is provided
How would I simply even attempt this from scratch?
Given all the complexities of charting, I think most people just use one of the many open source charting libraries. SparkLines is just one example.
Yet, sometimes you just want a simple chart without adding a library. And a bar chart is one of the easiest to build. The code snippet below is very basic, but it is enough to get OP started with the homework assignment.
Run code snippet to view:
<html>
<body>
<style type="text/css">
#chart {background-color: lightyellow; position: relative; height:200px; width: 200px; border: 1px black solid; display: table-cell; vertical-align: bottom; font-size: 10px; }
.bar {position: absolute; bottom: 0; display:inline-block; width: 10px; margin: 2px; background-color: lightpink;}
</style>
<div id="chart"></div>
<script type="text/javascript">
var i, max, min, h, html='', data = [34.7,68.9,65.1,130.2,208.6,172.8,155.0,168.6,134.4,52.7,94.5,41.5];
max = min = data[0];
for(i=0; i<data.length; i++) {
if (max < data[i]) max = data[i];
if (min > data[i]) min = data[i];
}
for(i=0; i< data.length; i++) {
h = Math.round( 100 * ((data[i] - min) / max));
html += '<div class="bar" style="height:' + h + '%; left:' + (12 * i) + 'px">' + data[i] + '</div>';
}
document.getElementById('chart').innerHTML = html;
</script>
</body>
</html>
Group the values on the X axis so theres the number of groups left that you want on the X axis
Calculate the average of Y for each group
Get the maximum value of all your groups; say it's 320
Take a height in pixels that you want your graph to have; say 200
Calculate the height in pixels that each group has with the rule of three:
320 = 200
pixel-y = group-y
Paint the respective group on your graph
For example, if a group has a Y value of 120:
320 = 200
pixel-y = 120
pixel-y = (320 * 120) / 200
pixel-y = 192