Skip to content

Commit beb1b8b

Browse files
committed
Improve named graph color assignment
1 parent 97a040f commit beb1b8b

3 files changed

Lines changed: 101 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
### Fixed
1212

1313
- Graph view layout no longer resets when enabling or disabling custom rules.
14+
- Improved named graph color distinction and stability.
1415

1516
## Version 5.0.0 - 2026-03-13
1617

packaging/flatpak/appdata/fr.inria.corese.CoreseGui.appdata.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,16 @@
148148
<li>Queries can now run without local data being loaded.</li>
149149
<li>Graph view layout no longer resets when enabling or disabling custom
150150
rules.</li>
151+
<li>Improved named graph color distinction and stability.</li>
151152

152153
<li xml:lang="fr">Les modèles de requêtes SPARQL prennent désormais en charge
153154
<code>SERVICE</code> avec une URL d'endpoint.</li>
154155
<li xml:lang="fr">Les requêtes peuvent désormais être exécutées sans données
155156
locales chargées.</li>
156157
<li xml:lang="fr">La disposition de la vue graphe n'est plus réinitialisée
157158
lors de l'activation ou de la désactivation de règles personnalisées.</li>
159+
<li xml:lang="fr">Amélioration de la distinction et de la stabilité des
160+
couleurs des graphes nommés.</li>
158161
</ul>
159162
</description>
160163
</release>

src/main/resources/graph-viewer/js/kg-graph.js

Lines changed: 97 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,58 @@ const GRAPH_DEFAULTS = Object.freeze({
2121
});
2222

2323
const GRAPH_PRESET_COLORS = Object.freeze({
24+
"kg:entailment": "#5C677D",
25+
"http://ns.inria.fr/corese/kgram/entailment": "#5C677D",
2426
"urn:corese:inference:rdfs": "#7BC8A4",
2527
"urn:corese:inference:owlrl": "#F6B26B",
2628
"urn:corese:inference:owlrl-lite": "#9A86E8",
2729
"urn:corese:inference:owlrl-ext": "#F08CA0"
2830
});
2931

30-
const GRAPH_COLOR_GENERATION = Object.freeze({
31-
hueSlotCount: 30,
32-
hueStride: 11,
33-
hueRetryStep: 7,
34-
minDistanceFromPresetHue: 18,
35-
saturationLevels: [42, 48, 54],
36-
lightnessLevels: [64, 69, 74]
32+
// Ordered palette for user named graphs.
33+
//
34+
// The first entries are deliberately spaced out for the common case where only
35+
// a few named graphs are visible. Preset system graphs do not consume this
36+
// palette, so user graph colors stay stable and predictable across reloads and
37+
// later rule activations.
38+
const GRAPH_NAMED_GRAPH_PALETTE = Object.freeze([
39+
"#0077BB",
40+
"#CC3311",
41+
"#228833",
42+
"#EE7733",
43+
"#33BBEE",
44+
"#6A3D9A",
45+
"#999933",
46+
"#8C564B",
47+
"#AA3377",
48+
"#332288",
49+
"#009988",
50+
"#B7791F",
51+
"#0B63A5",
52+
"#9E2A2B",
53+
"#0F766E",
54+
"#A61E4D",
55+
"#3B5BDB",
56+
"#15803D",
57+
"#17BECF",
58+
"#A16207",
59+
"#9467BD",
60+
"#CC6677",
61+
"#4B5563",
62+
"#44AA99",
63+
"#7C3AED",
64+
"#BCBD22",
65+
"#117733",
66+
"#882255"
67+
]);
68+
69+
const GRAPH_COLOR_FALLBACK = Object.freeze({
70+
fallbackHueSlotCount: 48,
71+
fallbackHueStride: 17,
72+
fallbackHueRetryStep: 11,
73+
minDistanceFromPresetHue: 14,
74+
saturationLevels: [70, 60, 82],
75+
lightnessLevels: [50, 60, 44]
3776
});
3877

3978
/**
@@ -143,7 +182,6 @@ class KGGraphVis extends HTMLElement {
143182
this.graphColorMap = new Map();
144183
this.graphContextPrefixes = new Map();
145184
this.defaultGraphColor = GRAPH_DEFAULTS.defaultGraphColor;
146-
this.reservedGraphHues = [];
147185

148186
// Performance optimization
149187
this.TICK_THROTTLE_SMALL = 16;
@@ -708,13 +746,6 @@ class KGGraphVis extends HTMLElement {
708746
return gid;
709747
}
710748

711-
resolveReservedGraphHues() {
712-
return Object.values(GRAPH_PRESET_COLORS)
713-
.map(color => this.hexToHsl(color))
714-
.filter(hsl => Number.isFinite(hsl.h) && hsl.s > 0)
715-
.map(hsl => hsl.h);
716-
}
717-
718749
stableHash(input) {
719750
const value = String(input ?? "");
720751
let hash = 2166136261;
@@ -730,26 +761,41 @@ class KGGraphVis extends HTMLElement {
730761
return raw > 180 ? 360 - raw : raw;
731762
}
732763

733-
isHueTooCloseToReserved(candidateHue, minDistance = GRAPH_COLOR_GENERATION.minDistanceFromPresetHue) {
734-
if (!Array.isArray(this.reservedGraphHues) || this.reservedGraphHues.length === 0) {
735-
return false;
764+
isHueTooCloseToReserved(candidateHue, minDistance = GRAPH_COLOR_FALLBACK.minDistanceFromPresetHue) {
765+
return Object.values(GRAPH_PRESET_COLORS).some(colorHex => {
766+
const hsl = this.hexToHsl(colorHex);
767+
return Number.isFinite(hsl.h)
768+
&& hsl.s > 0
769+
&& this.hueDistance(candidateHue, hsl.h) < minDistance;
770+
});
771+
}
772+
773+
pickPaletteGraphColor() {
774+
const usedHexes = new Set(
775+
Array.from(this.graphColorMap.values(), colorHex => String(colorHex ?? "").trim().toUpperCase())
776+
);
777+
778+
for (const colorHex of GRAPH_NAMED_GRAPH_PALETTE) {
779+
if (!usedHexes.has(colorHex)) {
780+
return colorHex;
781+
}
736782
}
737-
return this.reservedGraphHues.some(reservedHue => this.hueDistance(candidateHue, reservedHue) < minDistance);
783+
return null;
738784
}
739785

740786
buildStableGraphColor(graphId) {
741787
const hueHash = this.stableHash(`${graphId}|h`);
742788
const saturationHash = this.stableHash(`${graphId}|s`);
743789
const lightnessHash = this.stableHash(`${graphId}|l`);
744790

745-
const config = GRAPH_COLOR_GENERATION;
746-
const hueStep = 360 / config.hueSlotCount;
747-
const baseHueIndex = (hueHash * config.hueStride) % config.hueSlotCount;
791+
const config = GRAPH_COLOR_FALLBACK;
792+
const hueStep = 360 / config.fallbackHueSlotCount;
793+
const baseHueIndex = (hueHash * config.fallbackHueStride) % config.fallbackHueSlotCount;
748794
const saturation = config.saturationLevels[saturationHash % config.saturationLevels.length];
749795
const lightness = config.lightnessLevels[lightnessHash % config.lightnessLevels.length];
750796

751-
for (let attempt = 0; attempt < config.hueSlotCount; attempt += 1) {
752-
const candidateIndex = (baseHueIndex + attempt * config.hueRetryStep) % config.hueSlotCount;
797+
for (let attempt = 0; attempt < config.fallbackHueSlotCount; attempt += 1) {
798+
const candidateIndex = (baseHueIndex + attempt * config.fallbackHueRetryStep) % config.fallbackHueSlotCount;
753799
const candidateHue = candidateIndex * hueStep;
754800
if (!this.isHueTooCloseToReserved(candidateHue)) {
755801
return this.hslToHex(candidateHue, saturation, lightness);
@@ -764,7 +810,7 @@ class KGGraphVis extends HTMLElement {
764810
}
765811

766812
/**
767-
* Generate a deterministic color for each named graph.
813+
* Returns the stable session color for a graph.
768814
* @param {string} graphId - Graph identifier.
769815
* @returns {string} Hex color code.
770816
*/
@@ -775,17 +821,14 @@ class KGGraphVis extends HTMLElement {
775821
}
776822
const graphColorKey = this.resolveGraphColorKey(gid);
777823

778-
if (this.reservedGraphHues.length === 0) {
779-
this.reservedGraphHues = this.resolveReservedGraphHues();
780-
}
781-
782824
if (!this.graphColorMap.has(graphColorKey)) {
783825
const presetColor = this.resolvePresetGraphColor(graphColorKey);
784826
if (presetColor) {
785827
this.graphColorMap.set(graphColorKey, presetColor);
786828
return presetColor;
787829
}
788-
this.graphColorMap.set(graphColorKey, this.buildStableGraphColor(graphColorKey));
830+
const assignedColor = this.pickPaletteGraphColor() ?? this.buildStableGraphColor(graphColorKey);
831+
this.graphColorMap.set(graphColorKey, assignedColor);
789832
}
790833
return this.graphColorMap.get(graphColorKey);
791834
}
@@ -2916,7 +2959,28 @@ class KGGraphVis extends HTMLElement {
29162959
safeSummary.linkCount = Number.isFinite(summary.linkCount)
29172960
? Math.max(0, Math.floor(summary.linkCount))
29182961
: 0;
2919-
safeSummary.namedGraphs = Array.isArray(summary.namedGraphs) ? summary.namedGraphs : [];
2962+
safeSummary.namedGraphs = Array.isArray(summary.namedGraphs)
2963+
? summary.namedGraphs
2964+
.map(namedGraph => {
2965+
const id = this.normalizeGraphId(namedGraph?.id);
2966+
if (id === GRAPH_DEFAULTS.defaultGraphId) {
2967+
return null;
2968+
}
2969+
return {
2970+
id,
2971+
nodeCount: Number.isFinite(namedGraph?.nodeCount)
2972+
? Math.max(0, Math.floor(namedGraph.nodeCount))
2973+
: 0,
2974+
linkCount: Number.isFinite(namedGraph?.linkCount)
2975+
? Math.max(0, Math.floor(namedGraph.linkCount))
2976+
: 0,
2977+
color: typeof namedGraph?.color === "string" && namedGraph.color
2978+
? namedGraph.color
2979+
: this.getGraphColor(id)
2980+
};
2981+
})
2982+
.filter(namedGraph => namedGraph !== null)
2983+
: [];
29202984
const counts = summary.componentCounts ?? {};
29212985
safeSummary.componentCounts.resource = Number.isFinite(counts.resource) ? Math.max(0, Math.floor(counts.resource)) : 0;
29222986
safeSummary.componentCounts.literal = Number.isFinite(counts.literal) ? Math.max(0, Math.floor(counts.literal)) : 0;
@@ -2988,11 +3052,11 @@ class KGGraphVis extends HTMLElement {
29883052
});
29893053

29903054
summary.namedGraphs = [...namedGraphStats.values()]
3055+
.sort((left, right) => right.linkCount - left.linkCount || left.id.localeCompare(right.id))
29913056
.map(stat => ({
29923057
...stat,
29933058
color: this.getGraphColor(stat.id)
2994-
}))
2995-
.sort((left, right) => right.linkCount - left.linkCount || left.id.localeCompare(right.id));
3059+
}));
29963060
return summary;
29973061
}
29983062

0 commit comments

Comments
 (0)