UNPKG

model-navigator-standalone

Version:

Standalone MDF graph model visualizer

473 lines (461 loc) 15 kB
import "core-js/modules/es.array.reduce.js"; import "core-js/modules/es.array.sort.js"; import "core-js/modules/es.json.stringify.js"; import "core-js/modules/es.number.to-fixed.js"; import "core-js/modules/es.object.from-entries.js"; import "core-js/modules/es.promise.js"; import "core-js/modules/es.regexp.exec.js"; import "core-js/modules/es.string.replace.js"; import "core-js/modules/es.string.starts-with.js"; import "core-js/modules/es.string.trim.js"; import "core-js/modules/esnext.iterator.constructor.js"; import "core-js/modules/esnext.iterator.filter.js"; import "core-js/modules/esnext.iterator.for-each.js"; import "core-js/modules/esnext.iterator.map.js"; import "core-js/modules/esnext.iterator.reduce.js"; import "core-js/modules/web.dom-collections.iterator.js"; import * as d3 from 'd3-scale'; import JSZip from 'jszip'; import { saveAs } from 'file-saver'; import { fileManifestDownload } from '../../../config/file-manifest-config'; import axios from 'axios'; const submissionApiPath = 'FIXME-submissionApiPath'; export const humanFileSize = size => { if (typeof size !== 'number') { return ''; } const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); const sizeStr = (size / 1024 ** i).toFixed(2) * 1; const suffix = ['B', 'KB', 'MB', 'GB', 'TB'][i]; return "".concat(sizeStr, " ").concat(suffix); }; export const getSubmitPath = project => { const path = project.split('-'); const programName = path[0]; const projectCode = path.slice(1).join('-'); return "".concat(submissionApiPath, "/").concat(programName, "/").concat(projectCode); }; export const jsonToString = function jsonToString(data) { let schema = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const replacer = (key, value) => { if (value === null) { return undefined; } if (schema[key] === 'number') { const castedValue = Number(value); if (Number.isNaN(castedValue)) { return value; } return castedValue; } return value; }; return JSON.stringify(data, replacer, ' '); }; export const predictFileType = (dirtyData, fileType) => { const predictType = fileType; const jsonType = 'application/json'; const tsvType = 'text/tab-separated-values'; const data = dirtyData.trim(); if (data.indexOf('{') !== -1 || data.indexOf('}') !== -1) { return jsonType; } if (data.indexOf('\t') !== -1) { return tsvType; } return predictType; }; /** * Little wrapper around setinterval with a guard to prevent an async function * from being invoked multiple times. * * @param {()=>Promise} lambda callback should return a Promise * @param {int} timeoutMs passed through to setinterval * @return the setinterval id (can be passed to clearinterval) */ export async function asyncSetInterval(lambda, timeoutMs) { let isRunningGuard = false; return setInterval(() => { if (!isRunningGuard) { isRunningGuard = true; lambda().then(() => { isRunningGuard = false; }); } }, timeoutMs); } // export const getCategoryColor = (category) => { // const colorMap = { // clinical: '#05B8EE', // biospecimen: '#27AE60', // data_file: '#7EC500', // metadata_file: '#F4B940', // analysis: '#FF7ABC', // administrative: '#AD91FF', // notation: '#E74C3C', // index_file: '#26D9B1', // clinical_assessment: '#3283C8', // medical_history: '#05B8EE', // satellite: d3.schemeCategory20[11], // radar: d3.schemeCategory20[16], // stream_gauge: d3.schemeCategory20[19], // weather_station: d3.schemeCategory20[10], // data_observations: d3.schemeCategory20[3], // experimental_methods: d3.schemeCategory20[4], // Imaging: d3.schemeCategory20[5], // study_administration: d3.schemeCategory20[6], // subject_characteristics: d3.schemeCategory20[7], // }; // const defaultColor = '#9B9B9B'; // return colorMap[category] ? colorMap[category] : defaultColor; // }; // export function legendCreator(legendGroup, nodes, legendWidth) { // // Find all unique categories // const uniqueCategoriesList = nodes.reduce((acc, elem) => { // if (acc.indexOf(elem.category) === -1) { // acc.push(elem.category); // } // return acc; // }, []); // uniqueCategoriesList.sort((aIn, bIn) => { // const a = aIn.toLowerCase(); // const b = bIn.toLowerCase(); // if (a < b) { // return -1; // } if (a > b) { // return 1; // } // return 0; // }); // const legendFontSize = '0.9em'; // // Make Legend // legendGroup.selectAll('text') // .data(uniqueCategoriesList) // .enter().append('text') // .attr('x', legendWidth / 2) // .attr('y', (d, i) => `${1.5 * (2.5 + i)}em`) // .attr('text-anchor', 'middle') // .attr('fill', (d) => getCategoryColor(d)) // .style('font-size', legendFontSize) // .text((d) => d); // legendGroup.append('text') // .attr('x', legendWidth / 2) // .attr('y', `${2}em`) // .attr('text-anchor', 'middle') // .text('Categories') // .style('font-size', legendFontSize) // .style('text-decoration', 'underline'); // } // export function addArrows(graphSvg) { // graphSvg.append('svg:defs') // .append('svg:marker') // .attr('id', 'end-arrow') // .attr('viewBox', '0 -5 10 10') // .attr('fill', 'darkgray') // .attr('refX', 0) // .attr('refY', 0) // .attr('markerWidth', 6) // .attr('markerHeight', 6) // .attr('orient', 'auto') // .append('svg:path') // .attr('d', 'M0,-5L10,0L0,5'); // } // export function addLinks(graphSvg, edges) { // return graphSvg.append('g') // .selectAll('path') // .data(edges) // .enter() // .append('path') // .attr('stroke-width', 2) // .attr('marker-mid', 'url(#end-arrow)') // .attr('stroke', 'darkgray') // .attr('fill', 'none'); // } /** * Compute SVG coordinates fx, fy for each node in nodes. * Decorate each node with .fx and .fy property as side effect. * * @param {Array<Node>} nodes each decorated with a position [width,height] in [0,1] * @param {*} graphWidth * @param {*} graphHeight */ export function calculatePosition(nodes, graphWidth, graphHeight) { // Calculate the appropriate position of each node on the graph const fyVals = []; const retNodes = nodes; for (let i = 0; i < nodes.length; i += 1) { retNodes[i].fx = retNodes[i].position[0] * graphWidth; retNodes[i].fy = retNodes[i].position[1] * graphHeight; if (fyVals.indexOf(retNodes[i].fy) === -1) { fyVals.push(retNodes[i].fy); } } return { retNodes, fyValsLength: fyVals.length }; } /** * Type agnostic compare thunk for Array.sort * @param {*} a * @param {*} b */ export function sortCompare(a, b) { if (a === b) { return 0; } return a < b ? -1 : 1; } export function computeLastPageSizes(filesMap, pageSize) { return Object.keys(filesMap).reduce((d, key) => { const result = d; result[key] = filesMap[key].length % pageSize; return result; }, {}); } export function capitalizeFirstLetter(str) { const res = str.replace(/_/gi, ' '); return res.replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); } /** * Avoid importing underscore just for this ... export for testing * @method intersection * @param aList {Array<String>} * @param bList {Array<String>} * @return list of intersecting elements */ export function intersection(aList, bList) { const key2Count = aList.concat(bList).reduce((db, it) => { const res = db; if (res[it]) { res[it] += 1; } else { res[it] = 1; } return res; }, {}); return Object.entries(key2Count).filter(kv => kv[1] > 1).map(_ref => { let [k] = _ref; return k; }); } export function minus(aList, bList) { const key2Count = aList.concat(bList).concat(aList).reduce((db, it) => { const res = db; if (res[it]) { res[it] += 1; } else { res[it] = 1; } return res; }, {}); return Object.entries(key2Count).filter(kv => kv[1] === 2).map(_ref2 => { let [k] = _ref2; return k; }); } export const parseParamWidth = width => typeof width === 'number' ? "".concat(width, "px") : width; export const isPageFullScreen = pathname => !!(pathname && (pathname.toLowerCase() === '/dd' || pathname.toLowerCase().startsWith('/dd/') || pathname.toLowerCase() === '/cohort-tools' || pathname.toLowerCase().startsWith('/cohort-tools/'))); export const isFooterHidden = pathname => !!(pathname && (pathname.toLowerCase() === '/dd' || pathname.toLowerCase().startsWith('/dd/'))); export function createFileName(fileName, filePreFix) { const date = new Date(); const yyyy = date.getFullYear(); let dd = date.getDate(); let mm = date.getMonth() + 1; if (dd < 10) { dd = "0".concat(dd); } if (mm < 10) { mm = "0".concat(mm); } const todaysDate = "".concat(yyyy, "-").concat(mm, "-").concat(dd); let hours = date.getHours(); let minutes = date.getMinutes(); let seconds = date.getSeconds(); if (hours < 10) { hours = "0".concat(hours); } if (minutes < 10) { minutes = "0".concat(minutes); } if (seconds < 10) { seconds = "0".concat(seconds); } return filePreFix ? "".concat(filePreFix).concat(fileName, " ").concat(todaysDate, " ").concat(hours, "-").concat(minutes, "-").concat(seconds) : "".concat(fileName, " ").concat(todaysDate, " ").concat(hours, "-").concat(minutes, "-").concat(seconds); } export const tsvMiddleware = node => { let line = 'type'; const { links } = node; if (links && links.length) { links.forEach(c => { if (c.targetId && String(c.generatedType).toLowerCase() !== 'loader-generated') { line += '\t'.concat(" ", c.target_type, ".").concat(c.targetId); } }); } return line; }; export const convertToTSV = node => { let line = tsvMiddleware(node); Object.keys(node.properties).forEach(key => { line += '\t'.concat("".concat(key)); }); const text = "".concat(line, "\r\n").concat(node.title); return text; }; export const isFileManifest = node => node.id === 'file'; export const generateFileManifest = node => { let line = tsvMiddleware(node); const arr = Object.entries(node.properties); const mergedArr = arr.concat(fileManifestDownload); mergedArr.forEach(_ref3 => { let [key, value] = _ref3; if (value.src !== 'Loader-derived') { line += '\t'.concat("".concat(key)); } }); const text = "".concat(line, "\r\n").concat(node.title); return text; }; export const generateVocabFullDownload = function generateVocabFullDownload(fullDictionary, format) { let prefix = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ""; const c2nl = category2NodeList(fullDictionary); const enumArr = []; const zip = new JSZip(); Object.keys(c2nl).forEach(category => { const nodes = c2nl[category]; nodes.forEach(_ref4 => { let { title, properties } = _ref4; const propertyKeyList = Object.keys(properties); propertyKeyList.forEach(propertyKey => { const property = properties[propertyKey]; if (property.enum) { enumArr.push({ title, enums: property.enum, propertyKey }); } }); }); }); const zipFileName = createFileName(prefix + 'Controlled_Vocabularies', ''); const getFileName = (title, propertyKey, format) => "".concat(createFileName("".concat(title, "-").concat(propertyKey), prefix + 'Controlled_Vocabulary-'), ".").concat(format); switch (format) { case 'TSV': { const vocabTSVArr = enumArr.map(_ref5 => { let { enums, title, propertyKey } = _ref5; let content = ''; if (enums && enums.length) { enums.forEach((item, index) => { content += index === 0 ? item : '\n'.concat(item); }); } return { content, title, propertyKey }; }); vocabTSVArr.forEach(_ref6 => { let { title, propertyKey, content } = _ref6; return zip.file(getFileName(title, propertyKey, 'tsv'), content); }); zip.generateAsync({ type: 'blob' }).then(thisContent => { saveAs(thisContent, zipFileName); }); } break; // eslint-disable-next-line no-lone-blocks case 'JSON': { enumArr.forEach(_ref7 => { let { title, enums, propertyKey } = _ref7; return zip.file(getFileName(title, propertyKey, 'json'), JSON.stringify(enums)); }); zip.generateAsync({ type: 'blob' }).then(thisContent => { saveAs(thisContent, zipFileName); }); } break; default: break; } }; export const generateLoadingExample = async function generateLoadingExample() { let configUrl = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "https://raw.githubusercontent.com/CBIIT/icdc-data-loading-example-sets/main/config.json"; const zip = new JSZip(); // fetch config const { loadingExamples, title } = await (await axios.get(configUrl)).data; try { const titleArr = Object.keys(loadingExamples); const res = await Promise.all(Object.values(loadingExamples).map(example => axios.get(example))); const data = res.map((res, index) => ({ title: titleArr[index], content: res.data, format: titleArr[index].split('.')[1] })); data.forEach(_ref8 => { let { title, content, format } = _ref8; zip.file("".concat(createFileName(title), ".").concat(format), content); }); zip.generateAsync({ type: 'blob' }).then(thisContent => { saveAs(thisContent, createFileName(title)); }); } catch (_unused) { throw Error("Failed to fetch example files"); } }; export const downloadLoadingExample = async function downloadLoadingExample() { let zipUrl = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; window.open(zipUrl, '_blank'); }; export function category2NodeList(model) { return Object.fromEntries(model.tag_kvs('Category').map(_ref9 => { let [key, value] = _ref9; let cat = value; return [cat.toLowerCase(), model.tagged_items('Category', cat).filter(item => item._kind === 'Node')]; })); } /** cluster props according to the category for PDF download */ export function sortByCategory(c2nl, model) { const keys = Object.keys(c2nl); return model.nodes().sort((a, b) => keys.indexOf(a.tags('Category') - keys.indexOf(b.tags('Category')))); } export function getNodePropertyCount(model) { return { nodesCount: model.nodes().length, propertiesCount: model.props().length }; }