In Spotfire Mods is it possible to check what has updated or changed in the reader?

I have a network chart chart built with d3 using Spotfire Mods. It has multiple properties defined in the mods manifest, as well as the data being passed from Spotfire to the mod.

My reader is defined as:

/**
* Create the read function.
*/
const reader = mod.createReader(
    mod.visualization.data(), 
    mod.windowSize(), 
    mod.property("network_strength"), 
    mod.property("display_labels"),
    mod.property("network_type"),
    mod.property("apply_color"),
);

and my render function looks like:

/**
 * Initiate the read loop
 */
reader.subscribe(render);

/**
 * @param {Spotfire.DataView} dataView
 * @param {Spotfire.Size} windowSize
 * @param {Spotfire.ModProperty<integer>} network_strength
 * @param {Spotfire.ModProperty<boolean>} display_labels
 * @param {Spotfire.ModProperty<string>} network_type
 * @param {Spotfire.ModProperty<string>} apply_color
 */
async function render(dataView, windowSize, network_strength, display_labels, network_type, apply_color) {

However, if there are any changes in either the data, or any of the properties in the reader, the whole network needs to render again as there isn’t a way to know what has changed, and whether the network needs rendered and simulated again.

Is there anyway to determine if the data, or a property has changed so I control what to update? For instance if someone changes only the display_labels, or apply_color property then I could just alter the settings of the existing network chart instead of having to redraw the network.

Thanks for any help!

Answer

You can check a subscribe callback argument for reference equality with its previous value. The reader only fetches changed values from the server and passes them to the subscribe callback. Unchanged values are kept and are equal on reference level.

Here is a small wrapper function around a reader that adds this reference checking functionality through an reader.hasValueChanged(value) method. It keeps references to the previously passed arguments for comparison reasons. If the reference check fails, it means the value is new from the server.

/**
* Wrap a reader and adds an additional method called `hasChanged`.
* It allows you to check whether a passed argument is new or unchanged since the last time the subscribe loop was called.
* @function
* @template A
* @param {A} reader
* @returns {A & {hasValueChanged(value: any):boolean}}
*/
function readerWithChangeChecker(reader) {
    let previousValues = [];
    let currentValues = [];

    function storeValuesForComparison(cb) {
        return function storeValuesForComparison(...values) {
            previousValues = currentValues;
            currentValues = values;
            return cb(...values);
        };
    }

    return {
        ...reader,
        subscribe(cb) {
            reader.subscribe(storeValuesForComparison(cb));
        },
        hasValueChanged(value) {
            return previousValues.indexOf(value) == -1;
        }
    };
}

// Pass an already created reader as the argument to the wrapper function.
let reader = readerWithChangeChecker(
    mod.createReader(
       mod.visualization.data(), 
        mod.windowSize(), 
        mod.property("network_strength"), 
        mod.property("display_labels"),
        mod.property("network_type"),
        mod.property("apply_color")
    )
);

reader.subscribe(async (dataview, size, networkStrength, networkType, applyColor) => {
    console.log("Dataview", reader.hasValueChanged(dataview));
    console.log("size", reader.hasValueChanged(size));
    console.log("network_strength", reader.hasValueChanged(networkStrength));
    console.log("display_labels", reader.hasValueChanged(networkType));
    
    // Use the added method to optimize your render code.
    if(reader.hasValueChanged(applyColor)) {
        console.log("apply_color has changed")    
    }

    await dataview.allRows();
});

See the following resources for more information about readers.