vitessce
Version:
Vitessce app and React component library
126 lines (116 loc) • 4.74 kB
JavaScript
import {
useState, useEffect, useRef,
} from 'react';
import { getSourceAndLoaderFromFileType } from '../loaders/types';
import { getFileTypeDataTypeMapping } from './plugins';
/**
* Return the bottom coordinate of the layout.
* https://github.com/STRML/react-grid-layout/blob/20dac73f91274526034c00968b5bedb9c2ed36b9/lib/utils.js#L82
* @param {array} layout react-grid-layout layout array.
* @returns {number} Bottom coordinate.
*/
function getNumRows(layout) {
let max = 0;
let bottomY;
// eslint-disable-next-line no-plusplus
for (let i = 0, len = layout.length; i < len; i++) {
bottomY = layout[i].y + layout[i].h;
if (bottomY > max) max = bottomY;
}
return max;
}
/**
* Compute the row height based on the container height, number of rows,
* and margin/padding. Basically the reverse of the react-grid-layout's
* `.containerHeight()` function.
* https://github.com/STRML/react-grid-layout/blob/83251e5e682abfa3252ff89d4bacf47fdc1f4270/lib/ReactGridLayout.jsx#L223
* @param {number} containerHeight The height of the .vitessce-container element.
* @param {number} numRows The number of rows in the layout.
* @param {number} margin The margin value that will be passed to VitessceGrid.
* @param {number} padding The padding value that will be passed to VitessceGrid.
* @returns {number} The new row height value.
*/
function getRowHeight(containerHeight, numRows, margin, padding) {
const effectiveContainerHeight = containerHeight - 2 * padding - (numRows - 1) * margin;
return effectiveContainerHeight / numRows;
}
export function useRowHeight(config, initialRowHeight, height, margin, padding) {
const [containerHeight, setContainerHeight] = useState(height);
const [rowHeight, setRowHeight] = useState(initialRowHeight);
const containerRef = useRef();
// Detect when the `config` or `containerHeight` variables
// have changed, and update `rowHeight` in response.
useEffect(() => {
const numRows = getNumRows(config.layout);
const newRowHeight = getRowHeight(containerHeight, numRows, margin, padding);
setRowHeight(newRowHeight);
}, [containerHeight, config, margin, padding]);
// Update the `containerHeight` state when the `height` prop has changed.
useEffect(() => {
if (height !== null && height !== undefined) {
setContainerHeight(height);
}
}, [height]);
// If no height prop has been provided, set the `containerHeight`
// using height of the `.vitessce-container` element.
// Check the container element height whenever the window has been
// resized, as it may change if `.vitessce-container` should be
// sized relative to its parent (and by extension, potentially the window).
useEffect(() => {
if (height !== null && height !== undefined) {
// eslint will complain if the return value is inconsistent,
// so return a no-op function.
return () => {};
}
function onWindowResize() {
if (!containerRef.current) return;
const containerRect = containerRef.current.getBoundingClientRect();
setContainerHeight(containerRect.height);
}
window.addEventListener('resize', onWindowResize);
onWindowResize();
return () => {
window.removeEventListener('resize', onWindowResize);
};
}, [containerRef, height]);
return [rowHeight, containerRef];
}
/**
* Create a mapping from dataset ID to loader objects by data type.
* @param {object[]} datasets The datasets array from the view config.
* @param {string} configDescription The top-level description in the
* view config.
* @returns {object} Mapping from dataset ID to data type to loader
* instance.
*/
export function createLoaders(datasets, configDescription) {
const result = {};
const dataSources = {};
const fileTypeDataTypeMapping = getFileTypeDataTypeMapping();
datasets.forEach((dataset) => {
const datasetLoaders = {
name: dataset.name,
description: dataset.description || configDescription,
loaders: {},
};
dataset.files.forEach((file) => {
const {
url,
options,
requestInit,
fileType,
} = file;
const dataType = fileTypeDataTypeMapping[fileType];
const [DataSourceClass, LoaderClass] = getSourceAndLoaderFromFileType(fileType);
// Create _one_ DataSourceClass instance per URL. Derived loaders share this object.
const fileId = url || JSON.stringify(options);
if (!(fileId in dataSources)) {
dataSources[fileId] = new DataSourceClass({ url, requestInit });
}
const loader = new LoaderClass(dataSources[fileId], file);
datasetLoaders.loaders[dataType] = loader;
});
result[dataset.uid] = datasetLoaders;
});
return result;
}