import { select } from "d3-selection";
import {
DEFAULT_MIN_RADIUS_FOR_DOTPLOT,
TOOLTIP_IDENTIFIER,
} from "./constants";
/**
* Check if a given variable is an object and not an array.
*
* @param {any} object - The variable to check.
* @returns {boolean} - Returns true if the variable is an object, and not an array.
*/
export function isObject(object) {
return typeof object === "object" && Array.isArray(object) === false;
}
/**
* Get the minimum and maximum values from an array.
*
* @param {Array<number>} arr - An array of numbers.
* @returns {Array<number>} - An array containing the minimum and maximum values, in that order.
*/
export const getMinMax = (arr) => {
var max = -Number.MAX_VALUE,
min = Number.MAX_VALUE;
arr.forEach(function (x) {
if (max < x) max = x;
if (min > x) min = x;
});
return [min, max];
};
/**
* Parses an object of margins and returns an object with top, bottom, left, and right margins as integers.
*
* @param {Object} margins - An object with potential margin properties.
* @returns {Object} - An object with top, bottom, left, and right margins as integers.
*/
export const parseMargins = (margins) => {
const parsedMargins = {
top: 0,
bottom: 0,
left: 0,
right: 0,
};
for (let key in margins) {
if (margins.hasOwnProperty(key)) {
const value = margins[key];
const parsedValue = parseInt(value, 10);
if (!isNaN(parsedValue)) {
parsedMargins[key] = parsedValue;
}
}
}
return parsedMargins;
};
/**
* Measure the width of a text string for a given font size using SVG.
*
* @param {string} text - The text to measure.
* @param {string} fontSize - The font size to use for the measurement, e.g., '16px'.
* @returns {number} - The width of the text in pixels.
*/
export const getTextWidth = (text, fontSize = "16px") => {
// Create a temporary SVG to measure the text width
const svg = select("body").append("svg");
const textNode = svg.append("text").style("font-size", fontSize).text(text);
const width = textNode.node().getBBox().width;
svg.remove();
return width;
};
/**
* Create a tooltip on a specified container at the given position.
*
* @param {HTMLElement} container - The container element.
* @param {string} text - The text for the tooltip.
* @param {number} posX - The x-coordinate for the tooltip.
* @param {number} posY - The y-coordinate for the tooltip.
*/
export const createTooltip = (container, text, posX, posY) => {
let tooltip = select(container)
.append("div")
.attr("id", TOOLTIP_IDENTIFIER)
.style("position", "absolute")
.style("background", "#f9f9f9")
.style("padding", "8px")
.style("border", "1px solid #ccc")
.style("border-radius", "6px")
.style("z-index", "1000")
.style("visibility", "hidden");
tooltip
.style("visibility", "visible")
.text(text)
.style("left", posX + 10 + "px")
.style("top", posY - 10 + "px");
};
/**
* Remove a tooltip from the specified container.
*
* @param {HTMLElement} container - The container from which to remove the tooltip.
*/
export const removeTooltip = (container) => {
const tooltip = select(container).select(`#${TOOLTIP_IDENTIFIER}`);
if (tooltip) {
tooltip.remove();
}
};
export const getMaxRadiusForDotplot = (xlen, ylen, padding) => {
return Math.max(
Math.min(198 / (xlen + 1), 198 / (ylen + 1)) - padding,
DEFAULT_MIN_RADIUS_FOR_DOTPLOT
);
};
export const getScaledRadiusForDotplot = (
radius,
maxRadiusScaled,
minRadiusOriginal,
maxRadiusOriginal,
defaultMinRadius = DEFAULT_MIN_RADIUS_FOR_DOTPLOT
) => {
return (
defaultMinRadius +
(maxRadiusScaled - defaultMinRadius) *
((radius - minRadiusOriginal) / (maxRadiusOriginal - minRadiusOriginal))
);
};
/**
* A function to map over both regular JavaScript arrays and typed arrays.
*
* @param {Array|TypedArray} array - The input array or typed array.
* @param {Function} callback - A function that produces an element of the new array,
* taking three arguments:
* currentValue - The current element being processed in the array.
* index - The index of the current element being processed in the array.
* array - The array map was called upon.
* @returns {Array|TypedArray} - A new array or typed array with each element being the result
* of the callback function.
* @throws {Error} - Throws an error if the input is neither a regular array nor a typed array.
*/
export const mapArrayOrTypedArray = (array, callback) => {
// Check if the input is a regular JavaScript array.
if (Array.isArray(array)) {
return array.map(callback);
}
// Check if the input is a typed array.
else if (
array instanceof Int8Array ||
array instanceof Uint8Array ||
array instanceof Uint8ClampedArray ||
array instanceof Int16Array ||
array instanceof Uint16Array ||
array instanceof Int32Array ||
array instanceof Uint32Array ||
array instanceof Float32Array ||
array instanceof Float64Array
) {
// Create a new typed array of the same type and size as the input.
let result = new array.constructor(array.length);
// Use forEach to emulate the map functionality for typed arrays.
array.forEach((value, index) => {
result[index] = callback(value, index);
});
return result;
}
// Handle the case where the input is neither a regular array nor a typed array.
else {
throw new Error("Input is neither a normal array nor a typed array.");
}
};