UNPKG

molstar

Version:

A comprehensive macromolecular library.

146 lines 6.46 kB
"use strict"; /** * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * * adapted from https://github.com/internalfx/distinct-colors (ISC License Copyright (c) 2015, InternalFX Inc.) * which is heavily inspired by http://tools.medialab.sciences-po.fr/iwanthue/ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.distinctColors = exports.DistinctColorsParams = void 0; var tslib_1 = require("tslib"); var lab_1 = require("./spaces/lab"); var hcl_1 = require("./spaces/hcl"); var object_1 = require("../../mol-util/object"); var mol_util_1 = require("../../mol-util"); var array_1 = require("../../mol-util/array"); var param_definition_1 = require("../../mol-util/param-definition"); exports.DistinctColorsParams = { hue: param_definition_1.ParamDefinition.Interval([1, 360], { min: 0, max: 360, step: 1 }), chroma: param_definition_1.ParamDefinition.Interval([40, 70], { min: 0, max: 100, step: 1 }), luminance: param_definition_1.ParamDefinition.Interval([15, 85], { min: 0, max: 100, step: 1 }), clusteringStepCount: param_definition_1.ParamDefinition.Numeric(50, { min: 10, max: 200, step: 1 }, { isHidden: true }), minSampleCount: param_definition_1.ParamDefinition.Numeric(800, { min: 100, max: 5000, step: 100 }, { isHidden: true }) }; function distance(colorA, colorB) { return Math.sqrt(Math.pow(Math.abs(colorA[0] - colorB[0]), 2) + Math.pow(Math.abs(colorA[1] - colorB[1]), 2) + Math.pow(Math.abs(colorA[2] - colorB[2]), 2)); } var LabTolerance = 2; var tmpCheckColorHcl = [0, 0, 0]; var tmpCheckColorLab = [0, 0, 0]; function checkColor(lab, props) { lab_1.Lab.toHcl(tmpCheckColorHcl, lab); // roundtrip to RGB for conversion tolerance testing lab_1.Lab.fromColor(tmpCheckColorLab, lab_1.Lab.toColor(lab)); return (tmpCheckColorHcl[0] >= props.hue[0] && tmpCheckColorHcl[0] <= props.hue[1] && tmpCheckColorHcl[1] >= props.chroma[0] && tmpCheckColorHcl[1] <= props.chroma[1] && tmpCheckColorHcl[2] >= props.luminance[0] && tmpCheckColorHcl[2] <= props.luminance[1] && tmpCheckColorLab[0] >= (lab[0] - LabTolerance) && tmpCheckColorLab[0] <= (lab[0] + LabTolerance) && tmpCheckColorLab[1] >= (lab[1] - LabTolerance) && tmpCheckColorLab[1] <= (lab[1] + LabTolerance) && tmpCheckColorLab[2] >= (lab[2] - LabTolerance) && tmpCheckColorLab[2] <= (lab[2] + LabTolerance)); } function sortByContrast(colors) { var unsortedColors = colors.slice(0); var sortedColors = [unsortedColors.shift()]; while (unsortedColors.length > 0) { var lastColor = sortedColors[sortedColors.length - 1]; var nearest = 0; var maxDist = Number.MIN_SAFE_INTEGER; for (var i = 0; i < unsortedColors.length; ++i) { var dist = distance(lastColor, unsortedColors[i]); if (dist > maxDist) { maxDist = dist; nearest = i; } } sortedColors.push(unsortedColors.splice(nearest, 1)[0]); } return sortedColors; } function getSamples(count, p) { var samples = new Map(); var rangeDivider = Math.cbrt(count) * 1.001; var hStep = (p.hue[1] - p.hue[0]) / rangeDivider; var cStep = (p.chroma[1] - p.chroma[0]) / rangeDivider; var lStep = (p.luminance[1] - p.luminance[0]) / rangeDivider; for (var h = p.hue[0]; h <= p.hue[1]; h += hStep) { for (var c = p.chroma[0]; c <= p.chroma[1]; c += cStep) { for (var l = p.luminance[0]; l <= p.luminance[1]; l += lStep) { var lab = lab_1.Lab.fromHcl((0, lab_1.Lab)(), hcl_1.Hcl.create(h, c, l)); if (checkColor(lab, p)) samples.set(lab.toString(), lab); } } } return Array.from(samples.values()); } /** * Create a list of visually distinct colors */ function distinctColors(count, props) { if (props === void 0) { props = {}; } var p = (0, tslib_1.__assign)((0, tslib_1.__assign)({}, param_definition_1.ParamDefinition.getDefaultValues(exports.DistinctColorsParams)), props); if (count <= 0) return []; var samples = getSamples(Math.max(p.minSampleCount, count * 5), p); if (samples.length < count) { throw new Error('Not enough samples to generate distinct colors, increase sample count.'); } var colors = []; var zonesProto = []; var sliceSize = Math.floor(samples.length / count); for (var i = 0; i < samples.length; i += sliceSize) { colors.push(samples[i]); zonesProto.push([]); if (colors.length >= count) break; } for (var step = 1; step <= p.clusteringStepCount; ++step) { var zones = (0, object_1.deepClone)(zonesProto); // Find closest color for each sample for (var i = 0; i < samples.length; ++i) { var minDist = Number.MAX_SAFE_INTEGER; var nearest = 0; for (var j = 0; j < colors.length; j++) { var dist = distance(samples[i], colors[j]); if (dist < minDist) { minDist = dist; nearest = j; } } zones[nearest].push(samples[i]); } var lastColors = (0, object_1.deepClone)(colors); for (var i = 0; i < zones.length; ++i) { var zone = zones[i]; var size = zone.length; var Ls = []; var As = []; var Bs = []; for (var _i = 0, zone_1 = zone; _i < zone_1.length; _i++) { var sample = zone_1[_i]; Ls.push(sample[0]); As.push(sample[1]); Bs.push(sample[2]); } var lAvg = (0, array_1.arraySum)(Ls) / size; var aAvg = (0, array_1.arraySum)(As) / size; var bAvg = (0, array_1.arraySum)(Bs) / size; colors[i] = [lAvg, aAvg, bAvg]; } if ((0, mol_util_1.deepEqual)(lastColors, colors)) break; } return sortByContrast(colors).map(function (c) { return lab_1.Lab.toColor(c); }); } exports.distinctColors = distinctColors; //# sourceMappingURL=distinct.js.map