diff --git a/common/util.js b/common/util.js index a9fd9d6c0c939b5bb05ef281543f74d56b105a29..6b6df94eb08068247b17e3c9865f055752f3743a 100644 --- a/common/util.js +++ b/common/util.js @@ -47,14 +47,13 @@ * * https://stackoverflow.com/a/16348977/6059235 */ - exports.intToRgb = int => { - if (int >= 65500) { - return [255, 255, 255] - } - const str = String(int * 65535) + exports.strToRgb = str => { + if (typeof str !== 'string') throw new Error(`strToRgb input must be typeof string !`) + let hash = 0 - for (let i = 0; i < str.length; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); + // run at least 2 cycles, or else, len 1 string does not get hashed well + for (let i = 0; i < str.length || i < 5; i++) { + hash = str.charCodeAt(i % str.length) + ((hash << 5) - hash); } const returnV = [] for (let i = 0; i < 3; i++) { diff --git a/common/util.spec.js b/common/util.spec.js index 4597a54db648521dd029fb002f22ef56436d3b51..a66f12a39bd1307fbc17573f078937e37d742350 100644 --- a/common/util.spec.js +++ b/common/util.spec.js @@ -1,4 +1,4 @@ -import { getIdFromFullId } from './util' +import { getIdFromFullId, strToRgb } from './util' describe('common/util.js', () => { describe('getIdFromFullId', () => { @@ -17,4 +17,63 @@ describe('common/util.js', () => { expect(getIdFromFullId(fullId)).toBe(`minds/core/parcellationregion/v1.0.0/a844d80f-1d94-41a0-901a-14ae257519db`) }) }) + + describe('strToRgb', () => { + const str1 = 'hello world' + const str2 = 'foo bar' + const str3 = 'a' + const str4 = 'b' + const strArr = [ + str1, + str2, + str3, + str4, + ] + it('should return rgb', () => { + const outs = strArr.map(strToRgb) + for (const out of outs) { + expect( + out instanceof Array + ).toBeTruthy() + + expect(out.length).toEqual(3) + + for (const n of out) { + expect(n).toBeGreaterThanOrEqual(0) + expect(n).toBeLessThanOrEqual(255) + } + } + + }) + + it('rgb returned should be disinct', () => { + + const outs = strArr.map(strToRgb) + for (let i = 0; i < outs.length; i++) { + const compareA = outs[i] + for (let j = i + 1; j < outs.length; j++) { + const compareB = outs[j] + // compare all generated rgb, expect at least 1 of rgb to be of greater than 5 units out + expect( + compareA.some((n, idx) => Math.abs( n - compareB[idx] ) > 5) + ).toBeTruthy() + } + } + }) + + it ('should throw if not providing stirng', () => { + expect(() => { + strToRgb(12) + }).toThrow() + + expect(() => { + strToRgb(['hello world']) + }).toThrow() + + expect(() => { + strToRgb({foo: 'baz'}) + }).toThrow() + }) + + }) }) diff --git a/docs/releases/v2.4.0.md b/docs/releases/v2.4.0.md index 6b666247cb284cbf07dc838c64322ccff4c2e187..feec3a1ee6c3b69fc8947a44a436e4183418c16c 100644 --- a/docs/releases/v2.4.0.md +++ b/docs/releases/v2.4.0.md @@ -3,6 +3,7 @@ ## Bugfixes - fixes UI issue, where chips wraps on smaller screen (#740) +- regions with the same labelIndex without colour will no longer have the same colour (#750) ## New features diff --git a/src/atlasComponents/parcellationRegion/region.base.ts b/src/atlasComponents/parcellationRegion/region.base.ts index 56742360d9194d228b1eda8c529373d48a190fe6..d85da3b272cf525a2bcf54e7feae1a903d3c5935 100644 --- a/src/atlasComponents/parcellationRegion/region.base.ts +++ b/src/atlasComponents/parcellationRegion/region.base.ts @@ -7,7 +7,7 @@ import { ARIA_LABELS } from 'common/constants' import { flattenRegions, getIdFromFullId, rgbToHsl } from 'common/util' import { viewerStateSetConnectivityRegion, viewerStateNavigateToRegion, viewerStateToggleRegionSelect, viewerStateNewViewer, isNewerThan } from "src/services/state/viewerState.store.helper"; import { viewerStateFetchedTemplatesSelector, viewerStateGetSelectedAtlas, viewerStateSelectedTemplateFullInfoSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"; -import { intToRgb, verifyPositionArg, getRegionHemisphere } from 'common/util' +import { strToRgb, verifyPositionArg, getRegionHemisphere } from 'common/util' export class RegionBase { @@ -36,7 +36,11 @@ export class RegionBase { this.position = val && val.position if (!this._region) return - const rgb = this._region.rgb || (this._region.labelIndex && intToRgb(Number(this._region.labelIndex))) || [255, 200, 200] + let rgb = this._region.rgb + rgb = rgb || this._region.labelIndex > 65500 ? [255, 255, 255] : null + rgb = rgb || strToRgb(`${this._region.ngId || this._region.name}${this._region.labelIndex}`) + rgb = rgb || [255, 200, 200] + this.rgbString = `rgb(${rgb.join(',')})` const [_h, _s, l] = rgbToHsl(...rgb) this.rgbDarkmode = l < 0.4 diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts index 57f4f526ff7651ce098ed165f73e7c835b4a7f2d..dc8112ccf8053657283dde99b665a2eab8506e60 100644 --- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts @@ -10,7 +10,7 @@ import { getExportNehuba, getViewer, setNehubaViewer } from "src/util/fn"; import '!!file-loader?context=third_party&name=main.bundle.js!export-nehuba/dist/min/main.bundle.js' import '!!file-loader?context=third_party&name=chunk_worker.bundle.js!export-nehuba/dist/min/chunk_worker.bundle.js' import { scanSliceViewRenderFn } from "../util"; -import { intToRgb as intToColour, deserialiseParcRegionId } from 'common/util' +import { strToRgb, deserialiseParcRegionId } from 'common/util' const NG_LANDMARK_LAYER_NAME = 'spatial landmark layer' const NG_USER_LANDMARK_LAYER_NAME = 'user landmark layer' @@ -944,7 +944,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { return [val, {}] }), ], - ).map((val: [number, any]) => ([val[0], this.getRgb(val[0], val[1].rgb)])) as any), + ).map((val: [number, any]) => ([val[0], this.getRgb(val[0], { ngId, rgb: val[1].rgb})])) as any), ] }) as Array<[string, Map<number, {red: number, green: number, blue: number}>]> @@ -987,9 +987,17 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { }) } - private getRgb(labelIndex: number, rgb?: number[]): {red: number, green: number, blue: number} { + private getRgb(labelIndex: number, region: { rgb: number[], ngId: string }): {red: number, green: number, blue: number} { + const { rgb, ngId } = region if (typeof rgb === 'undefined' || rgb === null) { - const arr = intToColour(Number(labelIndex)) + if (labelIndex > 65500) { + return { + red: 255, + green: 255, + blue: 255 + } + } + const arr = strToRgb(`${ngId}${labelIndex}`) return { red : arr[0], green: arr[1],