molstar
Version:
A comprehensive macromolecular library.
146 lines • 6.46 kB
JavaScript
"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