ericman314 / UnitMath

JavaScript library for unit conversion and arithmetic
Apache License 2.0
31 stars 7 forks source link

Add a `getMatchingUnits` method #62

Open TheOneTheOnlyJJ opened 7 months ago

TheOneTheOnlyJJ commented 7 months ago

Querying quantities: I do not think it will be possible to "filter on all related units" using the quantity value. Defining this value tells the API to create a new base unit, like length or time. So when defining a unit such as mph, there will not be a quantity field. Instead of quantity: 'SPEED', it will have the property dimension: { LENGTH: 1, TIME: -1 }, which it determines from the definition: "mph": "1 mi / hr".

Having a quantity property for each unit was very appealing but I realized it was probably unnecessary. This is because there is already an equalsQuantity method that compares the quantity of one unit with another, by comparing their dimension objects. In theory you could create your own reference object of units to compare against:

const refQuantities = {
  MASS: 'kg',
  SPEED: 'm/s',
  ...
];

let myUnit = unit('4 mile/hour');
myUnit.equalsQuantity(refQuantities.SPEED); // true

I realize it's not quite as convenient as writing myUnit.quantity, but I really want to slim down the library to only the essential parts. I wanted to make it super easy to define your own units without requiring tons of boilerplate definitions. Perhaps eventually we could add a built-in feature to spit out "SPEED" automatically, but I don't want the inner workings of the library to be dependent on this feature, as it would require users to specify these quantities for each custom unit they add. (We could also add a getMatchingUnits method which returns an array of all the defined units which match the given unit. This could be used to populate a drop-down selector for a unit conversion tool, for example.)

Originally posted by @ericman314 in https://github.com/ericman314/UnitMath/issues/34#issuecomment-922615701

Exactly as stated in the above reply, a new getMatchingUnits method would be of great use to developers. It would make developing front end unit manipulation components a lot easier.

With the new method, a personal use case where it would be beneficial involves simplifying the design of a data grid. Developers could populate a column with units of a specific quantity. The system would then allow the end user to select the display units from a custom component located in the column header. This component would include a dropdown menu with valid matching units for the given quantity.

It would also be useful for the library to provide a default reference quantities refQuantities object, populated with the most common physical quantities, such as the mentioned "MASS", "SPEED", but also uncompounded powers of base quantities such as "AREA" and "VOLUME". Developers could of course define their own object (or update the default one through the config function/method of the module) for any custom quantities.

Such an addition would also make it easier to more easily enforce certain quantities when end users input units through making it possible to validate the input against these reference quantities.

ericman314 commented 7 months ago

getMatchingUnits

Yes. We should probably make something like getMatchingUnits available in the API. The easiest way to do this would be to iterate through the list of defined units (both built-ins and any you've added) and filtering using equalsQuantity().

Do you think we would need to return compound units like m/s for getMatchingUnits("mph")? The only way the library would "know" about "m/s" was if it was defined in systems. So getMatchingUnits(unit) might need to know about systems and what system we're currently in, or possibly infer the system from unit. What are your thoughts on that?

quantities

In v1, we got rid of the "quantities" concept (MASS, SPEED, POWER, etc.) The reason for that was to simplify the library, and so that users wouldn't have to specify a quantity when adding a custom unit which was not derived from already existing units. But I agree that having some way to say, "give me everything that has units of POWER", or, "what kind of unit is kg/m^2 s^2 A" would be pretty useful. We could start with a pre-defined built in object that just lists the quantities and their equivalent units. For example:

{
  "ENERGY": "J",
  "MASS": "kg",
  "POWER": "W",
  "SPEED": "m/s",
  "TORQUE": "N m",
}

and so on. This way, the configuration should be super simple to extend. Note that because ENERGY and TORQUE are equivalent (J = N m), some units might have multiple matching quantities.

The real win with doing quantities this way is that it extends the base functionality of the library; the library is not dependent on it. So this should be good for maintainability in the long run.

TheOneTheOnlyJJ commented 7 months ago

getMatchingUnits

Yes. We should probably make something like getMatchingUnits available in the API. The easiest way to do this would be to iterate through the list of defined units (both built-ins and any you've added) and filtering using equalsQuantity().

Yes, this is the way I was thinking of achieving this functionality without having this function available.

Do you think we would need to return compound units like m/s for getMatchingUnits("mph")? The only way the library would "know" about "m/s" was if it was defined in systems. So getMatchingUnits(unit) might need to know about systems and what system we're currently in, or possibly infer the system from unit. What are your thoughts on that?

While I do not know how hard this would be to implement, it would of course be convenient to have the function cover as many possible inputs as possible. If it can be done, I would propose having getMatchingUnits try to infer the current system from the given input unit, but also allow the function to be called with a custom configuration object, in which the system to use would be specified (just like simplify currently does).

Going further into potential parametrisation, it would also be handy to specify prefixGroups through the config object. I have not yet thought out how such an API would ideally look, as there is a great deal of customisability the developer has over the library, so just includeShortPrefixGroup and includeLongPrefixGroup booleans that default to true would not cover potential custom prefix groups. It could control whether the function returns all prefixed units derived from the base unit, or only the short ones, or only the long ones (or only the custom ones from custom prefix groups). Maybe providing an array of prefixes to apply to the base units (where applicable) in the output could also be supported. Maybe having an includePrefixGroups option that maps to either a boolean or a list of strings (the currently defined prefix groups) would work? If true, the result would include all the prefix groups, if false, it would not include any and if it's given as a list of strings it would only include the prefix groups specified in the list by name.

quantities

In v1, we got rid of the "quantities" concept (MASS, SPEED, POWER, etc.) The reason for that was to simplify the library, and so that users wouldn't have to specify a quantity when adding a custom unit which was not derived from already existing units. But I agree that having some way to say, "give me everything that has units of POWER", or, "what kind of unit is kg/m^2 s^2 A" would be pretty useful. We could start with a pre-defined built in object that just lists the quantities and their equivalent units. For example:

{
  "ENERGY": "J",
  "MASS": "kg",
  "POWER": "W",
  "SPEED": "m/s",
  "TORQUE": "N m",
}

and so on. This way, the configuration should be super simple to extend. Note that because ENERGY and TORQUE are equivalent (J = N m), some units might have multiple matching quantities.

While this may not be what most users expect, as long as it's physically consistent, I do not see a problem with this behaviour.

The real win with doing quantities this way is that it extends the base functionality of the library; the library is not dependent on it. So this should be good for maintainability in the long run.

My personal use case concerning these quantities object requires an accessible way to limit what kind of units users can input into a frontend. I want to make sure that the data input form only validates units of a specific strict quantity (like MASS, SPEED, ACCELERATION, etc.). When displaying all the inputted data from many users, I only know the quantity they posses. They may be given in different multiples and submultiples, aliases and systems. Thus, I want to have the getMatchingUnits function available so that I can easily change in what unit I view the values for consistent data analysis. I want to be able to, for example, have all the MASS units be displayed as kg. I then may want to view (or export, print, whatever action I need done) all the values displayed as oz, or any other unit that conforms to the MASS quantity. Given this, being able to directly call getMathcingUnits with the reference quantity as argument (like

getMatchingUnits(refQuantities.MASS, { system: 'si', prefixGroups: ["LONG"] })

to get all SI units that express MASS, including their entire long prefixGroup) would be very elegant in aiding the design of the frontend unit selection components.

TheOneTheOnlyJJ commented 6 months ago

Any update on this?