Convert Milliseconds to Duration to String? - string

I have a min and max parameter which each require a double.
min: 0,
max: _duration!.inMilliseconds
These parameters are then converted to strings for use as dynamic text on top of the thumb ball of a slider.
But for the max value I'd like to use the actual duration instead;
IE: 1:34:30 instead of 94,500.
The final product required is a String.
So how do I get the Duration to a string while preserving the Duration formatting?
Keep in mind it is passed initially as a double.
Here's the full class for the thumb ball;
class CustomSliderThumbCircle extends SliderComponentShape {
final double thumbRadius;
final int min;
final int max;
const CustomSliderThumbCircle({
required this.thumbRadius,
this.min = 0,
this.max = 10,
});
#override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return Size.fromRadius(thumbRadius);
}
#override
void paint(
PaintingContext context,
Offset center, {
Animation<double>? activationAnimation,
Animation<double>? enableAnimation,
bool? isDiscrete,
TextPainter? labelPainter,
RenderBox? parentBox,
SliderThemeData? sliderTheme,
TextDirection? textDirection,
double? value,
double? textScaleFactor,
Size? sizeWithOverflow,
}) {
final Canvas canvas = context.canvas;
final paint = Paint()
..color = Colors.white //Thumb Background Color
..style = PaintingStyle.fill;
TextSpan span = new TextSpan(
style: new TextStyle(
fontSize: thumbRadius,
//fontWeight: FontWeight.w700,
color: Colors.white, //Text Color of Value on Thumb
),
text: getValue(value!),
);
TextPainter tp = new TextPainter(
text: span,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr);
tp.layout();
Offset textCenter =
Offset(center.dx - (tp.width / 2), center.dy - (tp.height / 2));
final rect = Rect.fromCircle(center: center, radius: thumbRadius);
final rrect = RRect.fromRectAndRadius(
Rect.fromPoints(
Offset(rect.left - 10, rect.top),
Offset(rect.right + 10, rect.bottom),
),
Radius.elliptical(20, 100),
);
final fillPaint = Paint()
..color = sliderTheme!.activeTrackColor!
..style = PaintingStyle.fill;
final borderPaint = Paint()
..color = Colors.black
..strokeWidth = 2.8
..style = PaintingStyle.stroke;
canvas.drawRRect(rrect, fillPaint);
canvas.drawRRect(rrect, borderPaint);
tp.paint(canvas, textCenter);
}
String getValue(double value) {
return (min + (max - min) * value).round().toString();
}
}

If CustomSliderThumbCircle is your own class, I would change it to take a callback function to format the value to a String (falling back to just .toString() if no callback is specified), something like:
class CustomSliderThumbCircle extends SliderComponentShape {
final String Function(int value)? formatValue;
...
const CustomSliderThumbCircle({
required this.thumbRadius,
this.min = 0,
this.max = 10,
this.formatValue,
});
String getValue(double value) {
var clamped = (min + (max - min) * value).round();
return (formatValue == null) ? clamped.toString() : formatValue!(clamped);
}
}
and then your caller could pass a callback:
CustomSliderThumbCircle(
thumbRadius: ...,
...,
formatValue: (value) => Duration(milliseconds: value).toString(),
)
This also would allow you to use a function that formats Durations as, say, 1m34.5s instead of as 0:01:34.500000. I generally dislike representing durations with colons because it can be ambiguous if fractional seconds are omitted. For example, using a different function to format Durations:
CustomSliderThumbCircle(
thumbRadius: ...,
...,
formatValue: (value) => prettyDuration(Duration(milliseconds: value)),
)
If CustomSliderThumbCircle is not under your control, then you could extend it with your own class that overrides getValue.

I doubt this is better than jamesdlin's solution but I don't know how to format it the way I'd like using the call back. Plus I realised I only need seconds and milliseconds as the audio is limited to 60 seconds anyway. So here's what I did;
String getValue(double value) {
double seconds = value * (max/1000);
String secstring = secs.truncate().toString();
var milliseconds = value * (max.remainder(1000));
String millistring = milliseconds.truncate().toString();
String stringDuration = secstring + ':' + millistring;
return stringDuration ;
}

Related

Google slides - AUTOFIT_TEXT alternative - calculate based on dimensions of element

The google slides API documentation says on shapes/text elements it can accept "AUTOFIT_TEXT" as an autofit type, however this simply doesn't work and the API throws an error saying "autofitType can only be set to NONE", see here
My alternate pathway solution here is to attempt to come up with a method that can recalculate or estimate the font-size needed for the width of the element in question.
How would I go about doing this?
The font size could be anything it doesn't matter, I just need the text I'm replacing in the element to NOT wrap.
Simply just duplicating the slide with the API resets all the auto fit options already set in the google slide template.
Let's say the string length is "Hi-dilly-ho, neighborinos!", which has a length of 26, and let's say the width of the element is say 200PX. I'd need to come up with a method to fit the text on one line!
There could simply be a magic number I come up with here and multiply that by the length of the string which may work, but i was wondering if there's a nicer way with nodejs that anyone can think of
As i couldn't find a good example of this anywhere, i wrote a method to re-calculate the font-size based on the dimensions of the input element from the Google Presentation.
This workaround calculates the size of the element (width and height) and using node canvas, i created a text element and measure the size of the text which works for multiline text as well.
This is NOT as smart as the default autofit inside gslides, as it bases the amount of chars that can fit of the W key as it's the widest key usually in font faces.
There's a few helpers i've created here which are used by the main function:
// An English Metric Unit (EMU) is defined as 1/360,000 of a centimeter and thus there are 914,400 EMUs per inch, and 12,700 EMUs per point.
export const convertEMUToPT = (emu: number): number => emu / 12700;
// convert pixles to PT, there is 0.75pt to a px
export const covertPXtoPT = (px: number): number => px * 0.75;
// convert PT to PX, there is 0.75pt to a px
export const convertPTtoPX = (px: number): number => px / 0.75;
// this is a very simple example of what i have, obviously you'll need error handling if those values don't exist
// The below will return the dimensions in EMU, to convert to PT divide the EMU value by `12700`
export function getElementSize(element: slides_v1.Schema$PageElement) {
const width = element?.size?.width?.magnitude * element.transform?.scaleX;
const height = element?.size?.height?.magnitude * element.transform?.scaleY;
return { width, height };
}
/**
* #name findByKey
* #description This was introduced as the fontWeight key for example could be on a mixture of elements, and we
* want to find them whereever they may be on the element so we can average out the values
* #function
* #param obj - any object to search
* #param kee - representing the needle to search
* #returns any - returns the value by the key if found
*/
export const findByKey = (obj: any, kee: string): any | undefined => {
if (kee in obj) {
return obj[kee];
}
for (const n of Object.values(obj).filter(Boolean).filter(v => typeof v === 'object')) {
const found = findByKey(n, kee);
if (typeof found !== 'undefined') {
return found;
}
}
};
/**
* #name splitter
* #description Based on the maximum allowed characters on a single line, we split the lines
* based on this value so we can calculate multi line text wrapping and adjust the font size
* continually within a while loop
* #function
* #param str - the input string
* #param l - the length of each "line" of text
* #returns string[] - an array of strings representing each new line of text
*/
export function splitter(str: string, l: number): string[] {
const strs = [];
while (str.length > l) {
let pos = str.substring(0, l).lastIndexOf(' ');
pos = pos <= 0 ? l : pos;
strs.push(str.substring(0, pos));
let i = str.indexOf(' ', pos) + 1;
if (i < pos || i > pos + l)
i = pos;
str = str.substring(i);
}
strs.push(str);
return strs;
}
import { createCanvas } from 'canvas';
export function calculateFontSize(element: slides_v1.Schema$PageElement, text: string): number {
// get the dimensions of the element
const size = getElementSize(element, false);
// create a canvas with the same size as the element, this most likely does not matter as we're only measureing a fake
// representation of the text with ctx.measureText
const canvas = createCanvas(convertPTtoPX(size.width), convertPTtoPX(size.height));
const ctx = canvas.getContext('2d');
// try to extract all the font-sizes
const fontSizes = element.shape?.text?.textElements?.map(textElement => textElement.textRun?.style?.fontSize?.magnitude).filter((a): a is number => isNumber(a)) ?? [];
// try to extract all the font-weights
const fontWeights = element.shape?.text?.textElements?.map(textElement => textElement.textRun?.style?.weightedFontFamily?.weight).filter((a): a is number => isNumber(a)) ?? [];
// fallback to arial if not found, if there's more than one fontFamily used in a single element, we just pick the first one, no way i can think of
// to be smart here and not really necessary to create multiple strings with different fonts and calculate those, this seems to work fine
const fontFamily = findByKey(element, 'fontFamily') ?? 'Arial';
// calulate the average as there can be different fonts with different weights within a single text element
const averageFontWeight = fontWeights.reduce((a, n) => a + n, 0) / fontWeights.length;
const averageFontSize = fontSizes.reduce((a, n) => a + n, 0) / fontSizes.length;
// if the average font-weight is not a number, usae the default
const fontWeight = isNaN(averageFontWeight) ? DEFAULT_FONT_WEIGHT : averageFontWeight;
// use the average fontSize if available, else start at an arbitrary default
let fontSize = isNaN(averageFontSize) ? DEFAULT_FONT_SIZE : averageFontSize;
// if the input value is an empty string, don't bother with any calculations
if (text.length === 0) {
return fontSize;
}
// create the initial font value, this is overwritten during the while loop
ctx.font = `${fontWeight} ${fontSize}pt ${fontFamily}`;
// max chars we will fit horizontally based on the char width of W
const getCharWidth = (): number => convertPTtoPX(size.width) / ctx.measureText('W').width;
// used for the while loop, to continually resize the shape and multiline text, until it fits within the bounds
// of the element
const isOutsideBounds = (): boolean => {
// based on the maximum amount of chars available in the horizontal axis for this font size
// we split onto a new line to get the width/height correctly
const multiLineString = splitter(text, getCharWidth()).join('\n');
// get the measurements of the current multiline string
const metrics = ctx.measureText(multiLineString);
// get the width in PT
const width = covertPXtoPT(metrics.width);
// the emAcent/Decent values do exist, it's the types that are wrong from canvas
// #ts-expect-error
const emAcent = metrics.emHeightAscent as number;
// #ts-expect-error
const emDecent = metrics.emHeightDescent as number;
const height = covertPXtoPT(emAcent + emDecent);
return width > size.width || height > size.height;
};
// continually loop over until the size of the text element is less than the intiial size of the element in gslides
while (isOutsideBounds()) {
// decrease by 0.1 incrememnts until it fits within the width
fontSize = fontSize - 0.1;
// update the ctx with the new font style (shrinking the font size)
ctx.font = `${fontWeight} ${fontSize}pt ${fontFamily}`;
}
// returns the font size
return fontSize;
}
Then to use for a request to update the text after the text is inserted/replaced:
{
updateTextStyle: {
objectId: pageElement.objectId,
fields: 'fontSize',
style: {
fontSize: {
magnitude: calculateFontSize(pageElement, STRING_VALUE_HERE),
unit: 'PT'
}
}
}
}

Fixing data that contains numeric overflow

I have been given a dataset that I have identified contains records with a numeric overflow eg -214012688. Is there a way for me to fix this?
Here's a sample of data I have
2142936357
2143000225
2142936544
2142974737
2142935734
2143051458
-2143303677
-2142887448
2142866111
2142864212
2143020049
2143009445
2143300604
2143121125
-2142802104
2142801451
The dataset is not sorted so the numbers aren't in order. when I asked my client why there's negative numbers its because it went over the max integer amount.
If any of your values have become positive after rolling over, there is no way to fix it, but if that's not the case, you can write a custom function to get the absolute value of the difference between each negative value and the signed minimum, then add that to the signed maximum. Here is a JavaScript function for it:
function signedRolloverToUnsigned (x, signedMin, signedMax) {
if (x < 0) {
x = Math.abs((signedMin - 1) - x) + signedMax;
}
return x;
}
And here is the custom Google sheet function you'd need if you just want to copy + paste and use it in your document:
/**
* Converts signed rollover (negative) values to their equivalent unsigned positive values
*
* #param {number} value The value to convert.
* #return The unsigned representation of the input value.
* #customfunction
*/
function signedRolloverToUnsigned (value) {
if (value < 0) {
value = Math.abs((-2147483648 - 1) - value) + 2147483647;
}
return value;
}
Here is a working example using the data you provided:
'use strict';
function signedRolloverToUnsigned (x, signedMin, signedMax) {
if (x < 0) {
x = Math.abs((signedMin - 1) - x) + signedMax;
}
return x;
}
const text = `2142936357
2143000225
2142936544
2142974737
2142935734
2143051458
-2143303677
-2142887448
2142866111
2142864212
2143020049
2143009445
2143300604
2143121125
-2142802104
2142801451`;
const arrSource = text.split('\n').map((x) => {
return parseInt(x);
});
const signedRange = [-2147483648, 2147483647];
const arrFixed = arrSource.map((x) => signedRolloverToUnsigned(x, signedRange[0], signedRange[1]));
//---Ignore: For displaying output --->
document.body.style = 'background-color: #eeeeee; padding: 1rem;';
const pre = document.body.appendChild(document.createElement('pre'));
const samp = pre.appendChild(document.createElement('samp'));
samp.textContent = arrFixed.join('\n');
const styles = {
'box-sizing': 'border-box',
'background-color': 'white',
'border-radius': '0.5rem',
'display': 'block',
'padding': '1rem',
};
for (const [prop, value] of Object.entries(styles)) {
samp.style.setProperty(prop, value);
}

Arduino+processing and string information in an "if" statment

I am using an arduino to read out information from a scale into Processing.
Now I want certain things to happen depending on the weight registered. The weight is read out in a String. I have found out that >= does not work for strings. I have tried if (val >= str ("20.00")) but that doesnt work either.
I have also tried to convert it into a float using float scale = Float.parseFloat(val); But that doesnt work either. Does anyone have an idea. That would be great!
PImage pictureday;
import processing.serial.*;
import cc.arduino.*;
import org.firmata.*;
Serial myPort;
String val;
color textcolor = color(0, 50, 50, 240);
color textcolor2 = color(255, 0, 0);
void setup()
{
fullScreen();
background (189, 215, 239);
String portName = Serial.list()[0];
myPort = new Serial(this, portName, 9600);
}
void draw()
{
if ( myPort.available() > 0)
{ // If data is available,
val = myPort.readStringUntil('\n');
}
if (scale >= 20.00)
{
image(pictureday, 0, 0);
textSize(50);
fill(textcolor);
text(val, 900, 360);
text("KG ", 1030, 360);
println (val);
}
}
Like you've discovered, you can't compare Strings like that. Processing isn't smart enough to know the String contains a number.
Instead, use the float() function:
String val = "123.456";
float f = float(val);
if(f > 100){
println("here!");
}

How to know if an 3DObject is Looked at in VR

I am using RajawaliVR library.
I have added a plane and applied texture to it. Know I want to know when my object is being looked at so that I can trigger some even. Is there anything in RajawaliVR or google cardboad that can help me achieve this.
Material cruiserMaterial = new Material();
cruiserMaterial.setDiffuseMethod(new DiffuseMethod.Lambert());
cruiserMaterial.setColorInfluence(0);
cruiserMaterial.enableLighting(true);
try {
cruiserMaterial.addTexture(new Texture("spaceCruiserTex",
R.drawable.image2));
} catch (TextureException e) {
e.printStackTrace();
}
Object3D leftPlane = new Plane(10f, 10f, 1, 1, 1);
leftPlane.setMaterial(cruiserMaterial);
leftPlane.setRotZ(90);
Object3D container = new Object3D();
container.addChild(leftPlane);
container.setRotX(90);
container.setRotY(90);
container.setRotZ(90);
container.setZ(-20);
getCurrentScene().addChild(container);
Just put this in your renderers main loop (OnDrawFrame), iterate a list with objects and pass the object as parameter. The method will return true if you are currently looking at an object.
private static final float YAW_LIMIT = 0.12f;
private static final float PITCH_LIMIT = 0.12f;
/**
* Check if user is looking at object by calculating where the object is in eye-space.
*
* #return true if the user is looking at the object.
*/
private boolean isLookingAtObject(WorldObject object) {
float[] initVec = { 0, 0, 0, 1.0f };
float[] objPositionVec = new float[4];
// Convert object space to camera space. Use the headView from onNewFrame.
Matrix.multiplyMM(mModelView, 0, this.getHeadViewMatrix(), 0, object.getModel().getModelMatrix().getFloatValues(), 0);
Matrix.multiplyMV(objPositionVec, 0, mModelView, 0, initVec, 0);
float pitch = (float) Math.atan2(objPositionVec[1], -objPositionVec[2]);
float yaw = (float) Math.atan2(objPositionVec[0], -objPositionVec[2]);
return Math.abs(pitch) < PITCH_LIMIT && Math.abs(yaw) < YAW_LIMIT;
}

How to make it two seconds for Time.time?

var targetscript : Diamond;
var red : Color;
var orange : Color;
function Start () {
gameObject.camera.backgroundColor = red;
}
function Update () {
if (targetscript.score > 4) {
gameObject.camera.backgroundColor = Color.Lerp(red, orange, Time.time);
}
}
So right now, if the score is larger than 4 then it would change the camera background color to orange with lerp. But its too quick. I read on the internet that Time.time is 1 second. What is the alternative way but for 2 or 3 seconds? I tried this : http://answers.unity3d.com/questions/328891/controlling-duration-of-colorlerp-in-seconds.html I tried the code for the voted answer but it didn't work. It still lerps through quickly. Does anyone have any ideas? Thanks
Time.time gives you the time in seconds since the start of the game. What you want is Time.deltaTime which is the time difference between each frames.
var targetscript : Diamond;
var red : Color;
var orange : Color;
var t : float = 0;
var duration : float = 3.0; //however long you want it to be
function Start () {
gameObject.camera.backgroundColor = red;
}
function Update () {
if (targetscript.score > 4) {
gameObject.camera.backgroundColor = Color.Lerp(red, orange, t);
t += Time.deltaTime/duration;
}
}
Color.Lerp(Color a, Color b, float t), so:
-> t = 0, the Color result is a
-> t = 0.5, the Color result is half a and half b
-> t = 1.0, the Color result is b
Based on that definition, when Time.time return value >= 1.0 the Lerp will be completed. That's why your code finish in 1 seconds.
so, you can change the update function like this:
var duration : float = 3.0; //however long you want it to be
function Update ()
{
if (targetscript.score > 4)
{
gameObject.camera.backgroundColor = Color.Lerp(red, orange, Time.time / duration);
}
}
This will make the Lerp completes exactly as the duration. because Time.time / duration >= 1 when Time.time >= duration.

Resources