Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Ima Computer
Oct 28, 2007

Stop all the downloading!

Help computer.
I like using Object.entries to convert the object into an array of key-value pairs, doing whatever manipulation I want, then rebuilding the object with Object.fromEntries:
TypeScript code:
const removeManyKeys = <T extends Record<string, unknown>>(thing: T, removeMany: (keyof T)[]) =>
  Object.fromEntries(
    Object.entries(thing).filter(([key]) => !removeMany.includes(key))
  ) as Omit<T, typeof removeMany[number]>;

Adbot
ADBOT LOVES YOU

Doom Mathematic
Sep 2, 2008
Yes, that's what I'd go for too. Or just write the loop. It wouldn't be a very long loop and I think in this case it could be clearer. I actually really dislike the ergonomics of Object.entries and Object.fromEntries.

teen phone cutie
Jun 18, 2012

last year i rewrote something awful from scratch because i hate myself
i honestly always forget those functions exist. I think i've used .entries like once in 6 years developing

fromEntries I just had no idea about. had to look that one up

Mr. Bubbles
Jul 19, 2012
Javascript newb trying to find my way in node-red. I have two arrays of data that I am trying to assimilate.
Array 1 is users
Array 2 is groups

I have a for loop looping through the users group in a prior node and setting the flow variables usercode and userid. It passes the appropriate variables to this function below. This function deals with the second array, groups. Within the groups array, for each user I have a list of groups that user belongs to. (The letters 'aa', 'bb', etc). I am basically trying to take this information and make a master array that looks as follows:

userid, usercode, aa, bb, cc ...
3290, JonesB, 0, 1, 1

where the group numbers are binary either 0 or 1 indicating if that user is a member of the group.

The problem I'm having is that the resulting array looks like this:
userid, usercode, aa, bb, cc ... 3290, JonesB, 0, 1, 1 ...

essentially one dimensional without a new line / new row for each new user. This seems like a simple issue but my google fu is failing me. Any guidance here?

Thanks

code:
masterarray = flow.get("masterarray")

counter = flow.get('counter');
usercode = flow.get("usercode");


if(counter==1)
{
    masterarray[].push("aa","bb","cc","dd","ee","ff","gg","hh"); //start with headers in array
}

groups = msg.payload.groups


for (var i = groups.length -1; i>=0; i--) {
    node.warn("i: "+i)
    if (groups[i].groupId == "123") {
        node.warn("yes");
        aa = 1;
    }
    else if (groups[i].groupId == "234") {
        bb = 1;
    }
    else if (groups[i].groupId == "345") {
        cc = 1;
    }
    else if (groups[i].groupId == "456") {
        dd = 1;
    }
    else if (groups[i].groupId == "567") {
        ee = 1;
    }
    else if (groups[i].groupId == "678") {
        ff = 1;
    }
    else if (groups[i].groupId == "789") {
        gg = 1;
    }
}


masterarray.push(flow.get("usercode"),aa,bb,cc,dd,ee,ff,gg);

return msg;

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Mr. Bubbles posted:

code:
masterarray.push(flow.get("usercode"),aa,bb,cc,dd,ee,ff,gg);
You want
code:
masterarray.push([flow.get("usercode"),aa,bb,cc,dd,ee,ff,gg]);
You're pushing 8 things onto the array, where you want to be pushing one array of things onto the array, if I understand you right. The square brackets create an array as a single object.

Mr. Bubbles
Jul 19, 2012

roomforthetuna posted:

You want
code:
masterarray.push([flow.get("usercode"),aa,bb,cc,dd,ee,ff,gg]);
You're pushing 8 things onto the array, where you want to be pushing one array of things onto the array, if I understand you right. The square brackets create an array as a single object.

That did it, thanks! I knew it was something simple..

Sab669
Sep 24, 2009

So this is definitely feels like a dumb question but... My angular app is consuming some API endpoint (C#) that returns a List<Dictionary<string, string>> and I don't really know how to handle this type in Javascript? :downs:

The List contains 2 dictionaries, which I wish to present in 2 columns in a table. One column will be user-editable data, the other column will be some static display coming from another system. The first dictionary contains the editable data (if any), and the second dictionary is the static values.

I have this code:

code:
  userInputValues: string[];
  staticValues: string[];

  ngOnInit(): void {
    this.getArray(this.inputArrayName).subscribe(arr => {
      if (arr){
        this.userInputValues = arr[0];
        this.staticValues = arr[0]; // ***this is the problematic line but I don't know why the "obvious solution" doesn't work***
      }
    });


  getArray(model: string): Observable<[[]]>
  {
    let serviceUrl = this.baseServiceUrl + '/get-array/' + model;

    return this.http
      .get<[[]]>(serviceUrl, this.getHeaders())
      .pipe(catchError(this.handleError<[[]]>('getArray', null)));
  }
And in my component html:

code:
    <tr>
        <td>Terminal Cap Rate</td>
        <td><input type="text" id="txtAtQuote1" name="txtAtQuote1" [(ngModel)]="userInputValues['exit_terminal_cap_rate']" (ngModelChange)="update()"></td>
        <td>{{this.staticValues['exit_terminal_cap_rate']}}</td>
    </tr>
The above code works enough, but there is a problem in my ngOnit at the top, as the comment says. It "should be" this.staticValues = arr[1]; instead of [0], but doing that throws a compile time error: "Tuple type '[[]]' of length '1' has no element at index '1'" which doesn't make sense to me because how could it possibly know the length at compile time?

So essentially the question is, how do I access the second dictionary from the object returned by my API?

Sab669 fucked around with this message at 19:26 on Aug 4, 2022

Obfuscation
Jan 1, 2008
Good luck to you, I know you believe in hell
You are getting a compile time error because [[]] is a nonsensical type that seems to only accept an empty array inside another array? Define return values for your api call, or use 'any' to bypass the type checking

edit: the thing that returns from the api is probably something like [Record<string, string>, Record<string, string>]. The api turns the data into JSON when it sends the response, and JSON has objects instead of dictionaries.

Obfuscation fucked around with this message at 20:19 on Aug 4, 2022

Sab669
Sep 24, 2009

I wasn't really sure how to define just like an array of arrays, or an array of generic objects.

I say generic because that getArray function is actually just living in some service and is going to be used by... probably dozens of different components, returning Lists of a variable number of dictionaries with god knows how many keys depending on whatever the array name passed in is -- so I was trying to avoid actually creating some sort of object with actual properties for me to reference in my component/HTML.


So, yea, changing it to `any` works perfectly :toot: thanks

Lumpy
Apr 26, 2002

La! La! La! Laaaa!



College Slice

Sab669 posted:

I wasn't really sure how to define just like an array of arrays, or an array of generic objects.

I say generic because that getArray function is actually just living in some service and is going to be used by... probably dozens of different components, returning Lists of a variable number of dictionaries with god knows how many keys depending on whatever the array name passed in is -- so I was trying to avoid actually creating some sort of object with actual properties for me to reference in my component/HTML.


So, yea, changing it to `any` works perfectly :toot: thanks

Sounds like a job for generics.

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Sab669 posted:

So, yea, changing it to `any` works perfectly :toot: thanks
You could at least have the type be a list of any so the compiler knows about *some* of the properties.

Sab669
Sep 24, 2009

Good point. I guess I'm just so ingrained in working in more strictly typed languages all my life :v:

Osmosisch
Sep 9, 2007

I shall make everyone look like me! Then when they trick each other, they will say "oh that Coyote, he is the smartest one, he can even trick the great Coyote."



Grimey Drawer

Sab669 posted:

I wasn't really sure how to define just like an array of arrays, or an array of generic objects.


JavaScript code:

T[][]
Should work in a generic context. Your mistake was nesting the brackets.

swax
Aug 21, 2004

Sab669 posted:

So this is definitely feels like a dumb question but... My angular app is consuming some API endpoint (C#) that returns a List<Dictionary<string, string>> and I don't really know how to handle this type in Javascript? :downs:

This is a red flag that the type itself is not very good to return from an API.

First off don't return a List as that's not something that can be extended in the future without having to version the API, it'd be better to return an object with a List property.

Next don't return a Dictionary<string, string>. What is that? Even if it was a poor mans tuple, don't use a tuple because no one would know what the properties are. Write code for readability and maintainability. Not for others, but for yourself. A year from now you will look at this code and have no idea what the <string, string> is. Try this -

code:
public class MyApiResponse
{
    public List<MyWellNamedObject> Values {get; set;}

    public class MyWellNamedObject
    {
        public string MyWellNamedProperty1 {get; set;}
        public string MyWellNamedProperty2  {get; set;}
    }
}

Sab669
Sep 24, 2009

Well. It's hard to give a concise answer to that, and I do agree it felt pretty "smelly" when I was first introduced to this List<Dictionary<>> aspect of our database a month or two ago (and still does :v:). But unfortunately it's way too big of an org for us to just go change poo poo like that and it would have implications for dozens of other applications downstream.

But basically we have this concept of "array fields" which I think was implemented to track groups of data through time, although I don't really know who came up with this structure or why. But for example say we've got these 4 tables:

- Property
- ArrayTypes
- ArrayFields
- ArrayData

One of the "ArrayTypes" is "Expenses".
The "ArrayFields" for "Expenses" might be "Taxes", "Maintenance", "Administrative Fees", "Utilities" and whatever else you can imagine costs a property owner money. Then the ArrayData table would contain the actual values, and the IDs for the Property & ArrayFields. Those Array tables are recycled for all manner of objects, not just properties, and the values aren't always going to be numeric. But either way, this lets us just insert another row into the ArrayFields without having to add another column to a table and then update all of the objects code-side to account for this new field/row.

So we're basically just saying "Give me all the <ArrayFields> of <ArrayType> for <Property>"... Except we're not only getting 1 collection of fields/data, we're getting N collections with historical data & predictive data for the future -- and that is why it's a List of Dictionaries, where the key/value pair is the array field name and the array data. And each dictionary in the list would be the data for that array as it was 2 years ago, 1 year ago, current year, +1, +2 years or whatever.

And lastly... Pretty much all of our applications are more or less being written to be config driven so we almost never need to actually push code changes. So while my HTML example in my original post does hard-code the dictionary keys/object properties, that won't be the case soon -- it'll just be more like "[(ngModel)]="userInputValues[arrayFieldNameFromConfig]". Then when we add a new row to the ArrayFields, all I'd need to do is just update this config file with the input type (date, text, number, label, whatever) and the array field name and the app will automagically handle it all.


I'm not saying it's the best solution for the problem, especially when I'm not even sure "tracking groups of data through time" was the exact problem that resulted in this implementation, but it's what I'm stuck with so :shrug:

Sab669 fucked around with this message at 21:58 on Aug 11, 2022

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Sab669 posted:

The "ArrayFields" for "Expenses" might be "Taxes", "Maintenance", "Administrative Fees", "Utilities" and whatever else you can imagine costs a property owner money. Then the ArrayData table would contain the actual values, and the IDs for the Property & ArrayFields. Those Array tables are recycled for all manner of objects, not just properties, and the values aren't always going to be numeric. But either way, this lets us just insert another row into the ArrayFields without having to add another column to a table and then update all of the objects code-side to account for this new field/row.
I think this sounds like on the Typescript end the right thing to do would be to make it a list of SomeWeirdInterfaceTypeName, or a union of appropriate weird interfaces (ExpensesInterface|SomethingElseInterface) or equivalent Record types, so that the rest of your code can be like row.Taxes, row.Maintenance and be type-checked and autocompleted properly for fields you expect to be there.

Sab669
Sep 24, 2009

But again the thing we're trying to avoid is that if we go and add a new row to the ArrayFields table then we don't want to have to go and update our hardcoded model with a new field and then have to deploy that code. By making it all "config driven" we're just updating some JSON in Azure somewhere and then the app will immediately reflect those changes.

We have dozens of these "array types" with dozens of "array fields" each that my app will have to support, whether I manually create them all or somehow just write some script that generates the files/classes for me is still going to add a lot of stuff that would need to be maintained in the future.

Sab669 fucked around with this message at 01:05 on Aug 12, 2022

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Sab669 posted:

But again the thing we're trying to avoid is that if we go and add a new row to the ArrayFields table then we don't want to have to go and update our hardcoded model with a new field and then have to deploy that code. By making it all "config driven" we're just updating some JSON in Azure somewhere and then the app will immediately reflect those changes.

We have dozens of these "array types" with dozens of "array fields" each that my app will have to support, whether I manually create them all or somehow just write some script that generates the files/classes for me is still going to add a lot of stuff that would need to be maintained in the future.
Luckily it's typescript so you wouldn't have to update poo poo deployment-wise, the deployed code would work just the same as it does with untyped types. The typescript types just acts as hints for when you're writing the code.

But yeah, if your goal is to minimize effort, at the cost of hard to read, easy to write incorrectly, code, just use 'any' or write javascript directly.


vvvv Then yeah, if you're not doing anything with the types anyway and it's not an API anyone else uses either then just use 'any'.

roomforthetuna fucked around with this message at 02:37 on Aug 12, 2022

Sab669
Sep 24, 2009

The thing is though is that the HTML is basically just a giant for loop that does little more than print out a collection of tables and input controls that all automatically get force fed back to the API which does all the validation. So I'm really not writing much, there's nothing to read, there's virtually nothing to write incorrectly. It's an extremely thin client that just looks at that config file to know what the app should look like and how to send/receive some arbitrary collection of data.

If I've been working on this project for 3 months, the majority of that time has just been building out that config file with some small amount of development time bending CKEditor to our will, and getting acquainted with the API we use to get/pass data from our DB. And now dicking around with these "array fields" that are a beast of their own and weren't present in the first 2 sections of the application.

Sab669 fucked around with this message at 02:14 on Aug 12, 2022

Shibawanko
Feb 13, 2013

i need some help with DOM stuff and event handlers. i'm building a minesweeper clone that works fine on initial load but bugs out when i soft-reload the page using a reset button.

on initial load, a function is called with some static values to create a basic minesweeper board, the board consists of <tr> and <td> tags that get assigned numerical id's, like so:
code:
const boardBuilder = (dimx, dimy, mcount) => {
  const board = document.getElementById("minesweeper");
  // clear board
  board.innerHTML = "";

  // insert rows
  for (let i = 0; i < dimy; i += 1) {
    board.insertAdjacentHTML("afterbegin", rblock);
  }

  // insert columns
  const columns = document.querySelectorAll("tr");
  columns.forEach((row) => {
    for (let i = 0; i < dimx; i += 1) {
      row.insertAdjacentHTML("afterbegin", cblock);
    }
  });
this is followed by some other stuff about random mine placements that i don't think matters for this question. at the end of the board building function there's this bit that adds event handlers to each field on the board:

code:
  blocks.forEach((block) => {
    block.addEventListener("click", (event) => {
      const target = event.currentTarget;
      const targetid = Number(target.id);
      const adjacents = adjacentCounter(targetid, dimx, mineindices);
      if (mineindices.includes(targetid)) {
        event.currentTarget.classList.add("mine");
      } else if (adjacents > 0) {
        target.classList.add(`mine-neighbour-${adjacents}`);
      } else {
        target.classList.add("opened");
      }
    });
  });
... which calls adjacentCounter with the click event's target id, the horizontal dimension of the board and the indices of the mines as arguments, this is supposed to find adjacent mines:

code:
const adjacentCounter = (index, x, mindex) => {
  console.log(x);
  const left = document.getElementById(`${index}`).previousSibling;
  const top = document.getElementById(`${index - x}`);
  const right = document.getElementById(`${index}`).nextSibling;
  const bottom = document.getElementById(`${index + x}`); // somehow becomes null after resetting the game, weird
  const minecounter = [];
  console.log(`Top: ${top}, ID: ${(top != null) ? top.id : null}`);
  console.log(`Bottom: ${bottom}, ID: ${(bottom != null) ? bottom.id : null}`);
  minecounter.push((left != null) ? mindex.includes(Number(left.id)) : false);
  minecounter.push((top != null) ? mindex.includes(Number(top.id)) : false);
  minecounter.push((right != null) ? mindex.includes(Number(right.id)) : false);
  minecounter.push((bottom != null) ? mindex.includes(Number(bottom.id)) : false);
  return minecounter.filter(Boolean).length;
};
(does not check for diagonals yet, i will get to that). the weird thing here is that everything works fine on initially loading the page, it's only when boardBuilder() is called again when the page is reloaded that suddenly the "bottom" value in adjacentCounter stops working, although the left/right/top ones still work fine. only bottom becomes null

inspecting the page, the id's get assigned correctly to each field even after reloading so that doesn't seem to be the issue, and all the values passed as arguments to adjacentCounter are fine as well. i deduced that it must be because of the way the DOM loads and fires the event handler, since the rows below the clicked target are embedded in a different parent <tr> tag they somehow don't exist yet when the event handler is assigned (although visually on the page, they definitely DO exist)

stackoverflow just redirected me to this page: https://stackoverflow.com/questions/14028959/why-does-jquery-or-a-dom-method-such-as-getelementbyid-not-find-the-element but i'm a noob and frankly it's difficult to understand which option i should be using (the script tag is already at the bottom of my body so that's not it)

here's the event handler for the reset button, it simply reads the values from some fields on an html form and passes them to boardBuilder():
code:
reset.addEventListener("click", (event) => {
  event.preventDefault();
  const x = document.getElementById("gridx").value;
  const y = document.getElementById("gridy").value;
  const m = document.getElementById("mines").value;
  const xchecker = (x >= 2 && x <= 50);
  const ychecker = (y >= 2 && y <= 50);
  const mchecker = (m > 0 && m < ((x * y) * 0.8));
  if (xchecker && ychecker && mchecker) {
    boardBuilder(x, y, m);
  } else {
    alert("Invalid values!");
    document.getElementById("gridx").value = 5;
    document.getElementById("gridy").value = 5;
    document.getElementById("mines").value = 5;
  }
});

fakemirage
Nov 6, 2012

Can't see the haters.

Shibawanko posted:

only bottom becomes null
I'm thinking x may be a string, resulting in a string concatenation instead of a sum.
JavaScript code:
const index = 5;
const x = "1";
`${index + x}`
=> 51
x is from (what I assume is) an input field (document.getElementById("gridx").value), so it should be a string in the snippets you posted.

A quick way to verify this would be to log the value you're passing to .getElementById()
JavaScript code:
const bottomId = `${index + x}`;
console.log({bottomId});
const bottom = document.getElementById(bottomId);

AlphaKeny1
Feb 17, 2006

Yeah that was my guess too, when you are doing your index-x and index+x you should make sure that they are numbers and not strings. You can console log and even check the typeof, like console.log(typeof x).

Shibawanko
Feb 13, 2013

thanks for the replies, i fixed it now! yeah the problem was that somehow a string was being passed, and resetting the page with forced Number() called on the arguments for the board building function made it work, although i still don't understand why it kept working for the "top" value but not "bottom" when they have basically identical syntax



now i just need to get it to do that thing where it opens up an entire "field" of tiles between mines when you click a free tile

Osmosisch
Sep 9, 2007

I shall make everyone look like me! Then when they trick each other, they will say "oh that Coyote, he is the smartest one, he can even trick the great Coyote."



Grimey Drawer

Shibawanko posted:

i still don't understand why it kept working for the "top" value but not "bottom" when they have basically identical syntax
The reason is that the + operator also does string concatenation while the - operator does nothing to strings, and therefore doesn't give javascript the option to even try that.

In programming, "basically identical" !== "identical" :P

Shibawanko
Feb 13, 2013

Osmosisch posted:

The reason is that the + operator also does string concatenation while the - operator does nothing to strings, and therefore doesn't give javascript the option to even try that.

In programming, "basically identical" !== "identical" :P

so when it sees - and fails to cocatenate, it resorts to treating them like integers by default and therefore it still works?

Osmosisch
Sep 9, 2007

I shall make everyone look like me! Then when they trick each other, they will say "oh that Coyote, he is the smartest one, he can even trick the great Coyote."



Grimey Drawer

Shibawanko posted:

so when it sees - and fails to cocatenate, it resorts to treating them like integers by default and therefore it still works?

What would the semantics of
code:
"foo" - "bar"
even be? The - operator is purely numerical, and the expression I wrote will resolve to NaN. It's just that JS has a "keep on trucking" mentality designed to make life superficially easier but actually massively more difficult in practice.

You can try all this out really easily in your browser console if you're curious.

Or you can read big ol' articles like this one and try to cram it into your head.

The basic lesson is to switch to TypeScript if you want your compiler to stop you from doing this kind of thing, or get really handy with debug logging and breakpoints to see what's messing you up.

Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense

Osmosisch posted:

It's just that JS has a "keep on trucking" mentality designed to make life superficially easier but actually massively more difficult in practice.

It's because javascript is still backwards compatible to the days when there were no js compilers and the rest. It's suffering from the same problems PHP did, they should get on with it and break a whole bunch of javascript code.

MrMoo
Sep 14, 2000

ES6M was an opportunity to reset some things, and the only thing achieved was "use strict" . Perfect time to drop the automatic semicolon insertion 🤷🏻‍♀️

MrMoo fucked around with this message at 18:13 on Aug 22, 2022

Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense
In fact lets just make typescript the official language, starting tomorrow everything written in javascript no longer works. Job done.

camoseven
Dec 30, 2005

RODOLPHONE RINGIN'

Nolgthorn posted:

In fact lets just make typescript the official language, starting tomorrow everything written in javascript no longer works. Job done.

I second this motion

gbut
Mar 28, 2008

😤I put the UN🇺🇳 in 🎊FUN🎉


Can't wait for the WASM promised land where I can use any language in the frontend.

Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense
Given a distance from `0, 0` represented by `x` and `y` coordinates, and given an aspect ratio represented by a number calculated by `width / height`. How do I make a return value for `x` and `y` match that aspect ratio.

Ideally, if I have a ratio calculated by `width 100` `height 50`, the returned `y` would be half the value of `x`.

TypeScript code:
function arDist(x: number, y: number, ar: number): { x: number, y: number } {
    if (Math.abs(x) > Math.abs(y)) {
        return { x: y * ar, y };
    } else {
        return { x, y: x * ar };
    }
}
I think where I'm having a problem is figuring out whether to return `x` as a calculation of the adjusted `y` or `y` as a calculation of the adjusted `x`. I can't use the solution in the example above, because it doesn't make any sense.

Why would I care if `x` or `y` was bigger? I should care whether `x` or `y` is closer to the desired output or something like that. But something like the following doesn't make any sense either, it's the same thing.

TypeScript code:
if (Math.abs(x * ar) > Math.abs(y * ar)) {
Should it be something like?

TypeScript code:
if ((Math.abs(x * ar) - (Math.abs(x)) > (Math.abs(y * ar) - Math.abs(y))) {
Feel like I'm just comparing numbers that have been multiplied by the same thing.

Edit:

Actually none of what I'm doing makes sense. Why would multiplying `y` by `ar` give me the correct value for `x` if at the same time multiplying `x` by `ar` would also give me the correct value for `y`.

Ok this whole question is bad.

Nolgthorn fucked around with this message at 14:50 on Aug 23, 2022

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Nolgthorn posted:

Given a distance from `0, 0` represented by `x` and `y` coordinates, and given an aspect ratio represented by a number calculated by `width / height`. How do I make a return value for `x` and `y` match that aspect ratio.
You haven't defined your question sufficiently, seems to be the problem you're having.

There are an infinite number of answers that match this specification, in as much as it makes sense at all, which I'm not sure it does (a distance can't really be aspect-ratio'd, a rectangle can).

If you want to keep the 'direction' of your distance, the rest of the question you're missing is what constrains the length (do you want it to be as large as it can be within the limits defined by the "aspect ratio"?)

If you want to keep the 'length' of your distance, the rest of the question is what constrains the direction (do you want it to be as close to the original direction as possible while touching one of the bounds from the "aspect ratio"?)

From your example it seems like you don't want to keep either of those properties of the distance, you actually have x and y representing more like the width and height of a rectangle, and you want that rectangle to have the given aspect ratio. In that case what you're missing is the target constraints.

If there *are no* constraints, you can just do y = x * aspect_ratio, unconditionally, or x = y / aspect ratio, unconditionally. (Or maybe the other way around, I can't be bothered to figure out which direction your aspect ratio is in.)

Edit: if you want to always shrink it or always grow it to the aspect ratio then the condition you need is if (aspect_ratio > x/y) to decide which of the operations above to apply. If you want to try to evenly distribute the change between x and y then it would need something more complicated.

roomforthetuna fucked around with this message at 14:58 on Aug 23, 2022

Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense
After I posted it I realized how bad the question was and I didn't want to keep editing.

Properties `x` and `y` represent the distance the mouse has moved from `0, 0` (where the mouse button was pressed down) in a dragging scenario. We are dragging the corner of an element, so that it will be resized. However the resized element maintains it's aspect ratio.

Therefore the `x` and `y` coordinates we get back from this method should be as close to where the cursor is as possible while still maintaining the element's aspect ratio.

minato
Jun 7, 2004

cutty cain't hang, say 7-up.
Taco Defender
Let's say that your window's top left is at 0,0, and it's current bottom right is at WinX,WinY. The user clicks a point MouseX,MouseY, and you want the window's bottom right to get as close as possible to that new point, but maintain the same aspect ratio.

Imagine a straight line drawn through 0,0 -> WinX, WinY and continuing forever. MouseX,MouseY is going to fall on either side of the line (or exactly on it). So to cover these 3 scenarios:

1) Exactly on the line: The new WinX,WinY will be the same as MouseX,MouseY. The aspect ratio didn't change at all.
2) Above the line: NewWinX = MouseX. NewWinY = MouseX / AspectRatio
3) Below the line: NewWinY = MouseY. NewWinX = MouseY * AspectRatio

So how can you tell whether MouseX,MouseY is above or below the line? Do a dot product of the vectors (0,0 -> MouseX,MouseY) and (0,0 -> WinX,WinY). If you get a negative number, it's on one side of the line, if you get a positive number it's on the other side, and if it's 0 then it's on the line.

(Scenario 1 doesn't need to be handled separately, it can be handled by scenario 2 or 3 and you'll get the same result)

Edit: dot product, not cross product.

So the algorithm looks like:

code:
float dot_product = MouseX * WinX + MouseY * WinY;
if dot_product < 0:
   newWinX = MouseX;
   newWinY = MouseX / (WinX/WinY);
else
   newWinX = MouseY * (WinX/WinY);
   newWinY = MouseY;
(You might have to flip that "<" in the if statement)

minato fucked around with this message at 17:12 on Aug 23, 2022

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Nolgthorn posted:

After I posted it I realized how bad the question was and I didn't want to keep editing.

Properties `x` and `y` represent the distance the mouse has moved from `0, 0` (where the mouse button was pressed down) in a dragging scenario. We are dragging the corner of an element, so that it will be resized. However the resized element maintains it's aspect ratio.

Therefore the `x` and `y` coordinates we get back from this method should be as close to where the cursor is as possible while still maintaining the element's aspect ratio.
I think the part of the question that makes this confusing is that you haven't decided what you want to do in the situation where someone grabs the top right corner, then moves the mouse down and to the right. Does that mean grow the rectangle or shrink the rectangle? In your description it would actually do nothing, if you're trying to keep the corner 'close', if they go that way at just the right angle.

What I think you actually want here is for one of the edges of the rectangle to always intersect the mouse position, i.e. in the case of going down and right from the top right, the rectangle has to grow, because that way its right edge can be where the cursor is, versus if it shrunk then it would move away from the cursor.

With that insight you might be able to do the math part yourself.

Edit: you might still also need to decide what the expected behavior is if you grab the top right corner and drag it down and to the right so far that the cursor is below the bottom edge of the original rectangle. If you can't flip your original thing then it's literally impossible to satisfy the "cursor on an edge" rule in that situation.

roomforthetuna fucked around with this message at 02:57 on Aug 24, 2022

Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense
Sorry for the delay, other priorities happened. I'm working on this problem again.



The codebase has it where the corner the mouse down event happens is 0,0 and I've written this small function that basically sets an "origin" at the opposite corner. I know origin likely isn't the correct term.

TypeScript code:
// origin relative to start position
function calcOrigin(width: number, height: number, { hPos, vPos }: TResizeHandle): TPosition {
    let x = width;
    let y = height;

    if (hPos === H_POS.CENTER) x = 0;
    if (hPos === H_POS.RIGHT) x = -x;
    if (vPos === V_POS.MIDDLE) y = 0;
    if (vPos === V_POS.BOTTOM) y = -y;

    return { x, y };
}
Then I'm finding the dot product where dist is the amount the mouse moved. I know this is effectively pointing in the opposite direction of what you said but my assumption is it doesn't matter. The line goes in both directions an infinite distance.

TypeScript code:
// calculate dot product
function calcDotProduct(origin: TPosition, dist: TPosition): number {
    return dist.x * origin.x + dist.y * origin.y;
}
And finally I'm using that to figure out the new relative dimensions of the thing while dragging. Where dist is how far the mouse has moved and ar is the desired aspect ratio, which are both correct.

TypeScript code:
// offset from position with aspect ratio
function calcOffset(dist: TPosition, ar: number, dotProduct?: number): TPosition {
    let x = dist.x;
    let y = dist.y;

    // free transform
    if (dotProduct === undefined) return { x, y };

    // aspect ratio
    if (dotProduct < 0) {
        x = y * ar;
    } else {
        y = x / ar;
    }

    return { x, y };
}
This sort of works.

But I can't figure out why instead of the `dotProduct < 0` part working along the line I want, it seems to switch at a complete perpendicular. So, 0,0 is the center of the line and it extends up and to the right, down and to the left. Where I expect it to extend up and to the left, down and to the right.

This is the same on all dragging handles, the line is perpendicular to what I expect.

So what happens is I drag the thing bigger and the mouse position changes the height, I drag the thing smaller and the mouse position changes the width.

e:
I've verified that hPos and vPos in my calcOrigin function are correct.
I've verified that x and y returned from calcOrigin are as expected. So there must be an issue related to my math, the dotProduct is wrong.

Nolgthorn fucked around with this message at 07:12 on Sep 7, 2022

Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense
I found the solution it is the following:

TypeScript code:
// calculate dot product
function calcDotProduct(origin: TPosition, dist: TPosition): number {
    return dist.x * origin.y - dist.y * origin.x;
}
This is apparently called an "outer product" or something like that. It works and has solved my issue, thanks everyone for getting me that far.

Combat Pretzel
Jun 23, 2004

No, seriously... what kurds?!
So in TypeScript, generics are a compile-time thing. Since that's so, why can't I instance generic types? Kinda this sort of stuff.

TypeScript code:
class Foo<T extends SomeBaseClass>{
    someFunc()
    {
        const bar = new T();
        ...
    }
}
If it's about enforcing some constructor notation, you'd figure that could be arranged by some special notation around <T>, but no bueno.

Combat Pretzel fucked around with this message at 15:47 on Sep 7, 2022

Adbot
ADBOT LOVES YOU

Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense
Confused about what it is. If you want your class to extend another class you should define another class and extend it. Then typescript would surely figure out what you were doing.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply