Xmrit

by Commoncog

Integrating with Xmrit

The easiest way to integrate with Xmrit is via Xmrit’s share link. A Xmrit’s share link contains all the needed data to render a XmR chart in the link itself, making it easily shareable, embeddable and portable.

Sharelink Specification

Note: that you will want to attach the sharelink to xmrit.com/t/ — though attaching it to xmrit.com will work as well; you will simply get a redirect.

The specification of the share links are as follows:

This version does not make the sharelink v0 specification obsolete.

Sharelink v0 focuses on storing data inside a URL fragment. While this requires very little setup, we realise there are usecases where this approach is not suitable — for instance, if you want to graph a year’s worth of weekly data. Sharelink v1 aims to address such usecases.

Sharelink v1 makes use of two hash parameters:

  • v: This is the version parameter. It should be the string “1”.
  • d: This is the data endpoint. Xmrit expects this URL to return JSON data. The endpoint should also allow CORS.

An example of sharelink v1 is

https://xmrit.com/t/#v=1&d=https://benclmnt-xmritRemoteDataExample.web.val.run/

You may visit the source endpoint https://benclmnt-xmritRemoteDataExample.web.val.run/ to see the backing JSON data behind this example.

The JSON data definition is the same as the object you passed to the generateShareLink function for sharelink v0. Only xdata is required.

{
    xLabel?: string,
    yLabel?: string,
    xdata: {
        x: string, // date in yyyy-mm-dd
        value: number
    }[],
    dividerLines?: {
        id: string,
        x: number // date in unix milliseconds
    }[],
    lockedLimits?: {
        avgX: number,
        avgMovement: number,
        UNPL: number, // upper natural process limit
        LNPL: number, // lower natural process limit
        URL: number, // upper range limit
    },
    lockedLimitStatus?: number,
}

Sharelink v0 makes use of four hash parameters:

  • v: This is the version parameter. It should be the string “0”.
  • d: This is the data parameter. It is made out of two parts separated by a dot ("."). The part before the dot is a lz77 compressed string of a CSV (comma-separated values) string. The first two values of the CSV are the two axis labels (xLabel and yLabel). If these axis labels contain comma (,), we will replace it with semicolon (;). The rest of the CSV values are the date columns. If the date contains comma (,), we will replace it with semicolon (;). The part after the dot is a base64-no-padding-url-encoded float32 array of the value columns. base64-no-padding-url-encoded means that you remove all equal signs from the end of the string, replace plus (+) with dash (-) and replace forward slash (-) with underscore (/).
  • l: This is the locked limit parameters. It is a base64-no-padding-url-encoded float32 array of the following values: avgX, avgMovement, LNPL, UNPL, URL, lockedLimitStatus. All values except lockedLimitStatus accept floating point numbers. Values for lockedLimitStatus are 0 for unlocked and 1 for locked.
  • s: This is the dividers (separators) parameters. It is a base64-no-padding-url-encoded float32 array of dividers’ x values. X values are dates represented as Unix epoch timestamp in milliseconds.

An example of encoding a share link in Javascript is given in the generateShareLink function below:

import lz77 from "lz77";

/**
 * Sharelink v0:
 * - v: 0
 * - d: lz77.compress(xLabel,yLabel,date-cols...).base64(float32array of value-cols).
 * - l: float32array of [avgX, avgMovement, LNPL, UNPL, URL, lockedLimitStatus]
 * - s: float32array of dividers x values (unix timestamp in milliseconds)
 * @returns 
 */
function generateShareLink(s: {
    xLabel?: string,
    yLabel?: string,
    xdata: {
        x: string, // date in yyyy-mm-dd
        value: number
    }[],
    dividerLines?: {
        id: string,
        x: number // date in unix milliseconds
    }[],
    lockedLimits?: {
        avgX: number,
        avgMovement: number,
        UNPL: number, // upper natural process limit
        LNPL: number, // lower natural process limit
        URL: number, // upper range limit
    },
    lockedLimitStatus?: number,
}): string {
    // Search parameters can also be an object
    const paramsObj = {
        v: "0" // version
    };
    let validXdata = s.xdata.filter(dv => dv.x || dv.value)
    // basically first 2 are labels, followed by date-column, followed by value-column.
    // date-column are compressed using lz77
    // value-column are encoded as bytearray and converted into base64 string
    const dateText = `${s.xLabel.replace(",", ";")},${s.yLabel.replace(",", ";")},` +
        validXdata.map((d) => {
            if (d.x) {
                return d.x.replace(",", ";")
            } else {
                return ''
            }
        }).join(",")
    const valueText = validXdata.map((d) => d.value)
    paramsObj['d'] = btoaUrlSafe(lz77.compress(dateText)) + '.' +
        encodeNumberArrayString(valueText)
    if (s.dividerLines) {
        const dividers = encodeNumberArrayString(s.dividerLines.map(dl => dl.x))
        if (dividers.length > 0) {
            paramsObj['s'] = dividers
        }
    }
    if (s.lockedLimits && (s.lockedLimitStatus & 1) == 1) {
        paramsObj['l'] = encodeNumberArrayString([
            s.lockedLimits.avgX,
            s.lockedLimits.avgMovement,
            s.lockedLimits.LNPL,
            s.lockedLimits.UNPL,
            s.lockedLimits.URL,
            s.lockedLimitStatus
        ])
    }
    const searchParams = new URLSearchParams(paramsObj);
    const fullPath = `https://xmrit.com/t/#${searchParams.toString()}`
    return fullPath
}

function encodeNumberArrayString(input: number[]) {
    const buffer = new ArrayBuffer(input.length * 4);
    const view = new DataView(buffer);
    input.forEach((i, idx) => {
        view.setFloat32(idx * 4, i)
    })
    return btoaUrlSafe(
        new Uint8Array(buffer)
            .reduce((data, byte) => data + String.fromCharCode(byte), '')
    )
}

function btoaUrlSafe(s: string) {
    return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
}
Last Updated: 25 Apr 2024