@glossarist/concept-browser 0.7.85 → 0.7.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/scripts/build-edges.js +42 -26
- package/scripts/generate-data.mjs +45 -5
- package/scripts/migrate-v1-to-v3.d.mts +6 -0
- package/scripts/migrate-v1-to-v3.mjs +176 -0
- package/src/__tests__/components/PartitiveRelationDiagram.test.ts +92 -136
- package/src/__tests__/composables/use-concept-edges-v2.test.ts +115 -87
- package/src/__tests__/partitive-relation-styling.test.ts +12 -54
- package/src/__tests__/partitive-relation.test.ts +42 -90
- package/src/__tests__/scripts/migrate-v1-to-v3.d.mts +8 -0
- package/src/__tests__/scripts/migrate-v1-to-v3.test.ts +115 -0
- package/src/__tests__/utils/partitive-multiplicity.test.ts +113 -0
- package/src/adapters/model-bridge.ts +15 -19
- package/src/adapters/non-verbal/glossarist-augment.d.ts +34 -18
- package/src/adapters/types.ts +2 -8
- package/src/components/PartitiveRelationDiagram.vue +88 -76
- package/src/components/PartitiveRelationList.vue +26 -34
- package/src/components/RelationSphere.vue +57 -72
- package/src/composables/use-concept-edges.ts +21 -21
- package/src/utils/partitive-multiplicity.ts +106 -0
- package/src/utils/partitive-relation-styling.ts +29 -38
- package/src/views/ConceptView.vue +7 -13
- package/src/__tests__/model-bridge-partitive.test.ts +0 -146
|
@@ -26,6 +26,7 @@ import { useI18n } from '../i18n';
|
|
|
26
26
|
|
|
27
27
|
const { t, locale } = useI18n();
|
|
28
28
|
import type { Concept, Manifest, GraphEdge, PartitiveRelationWire } from '../adapters/types';
|
|
29
|
+
import { rakeStrokeStyle, type PartitiveMultiplicity } from '../utils/partitive-multiplicity';
|
|
29
30
|
import { conceptUri } from '../adapters/model-bridge';
|
|
30
31
|
|
|
31
32
|
const props = defineProps<{
|
|
@@ -760,41 +761,33 @@ function drawEdges(cx: number, cy: number) {
|
|
|
760
761
|
}
|
|
761
762
|
|
|
762
763
|
/**
|
|
763
|
-
* Render PartitiveRelations as ISO 704
|
|
764
|
+
* Render PartitiveRelations as ISO 704:2022 rake brackets in the sphere.
|
|
764
765
|
*
|
|
765
|
-
* Each relation renders as a
|
|
766
|
+
* Each relation renders as a right-angle bracket with stem + spine + drops
|
|
767
|
+
* (geometry described in detail above). Stem and spine are the structural
|
|
768
|
+
* frame — always single solid at FRAME_STROKE_WIDTH.
|
|
766
769
|
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
* │ ← stem (along stem-axis)
|
|
770
|
-
* │
|
|
771
|
-
* ┌──────────●──────────┐ ← spine (perpendicular to stem-axis; right angle at junction)
|
|
772
|
-
* │ │ │
|
|
773
|
-
* │ │ │ ← drops (parallel to stem-axis; right angle at each tooth)
|
|
774
|
-
* [p1] [p2] [p3]
|
|
770
|
+
* Per-member multiplicity + is_delimiting is encoded on each DROP via
|
|
771
|
+
* rakeStrokeStyle() (see utils/partitive-multiplicity.ts):
|
|
775
772
|
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
773
|
+
* multiplicity is_delimiting drop visual
|
|
774
|
+
* ------------------- -------------- --------------------------------
|
|
775
|
+
* compulsory false 1 solid @ 1.5px
|
|
776
|
+
* compulsory true 1 solid @ 4.5px (3× bold)
|
|
777
|
+
* optional false 1 dashed @ 1.5px
|
|
778
|
+
* optional true 1 dashed @ 4.5px
|
|
779
|
+
* compulsory_multiple false 2 solid @ 1.5px
|
|
780
|
+
* compulsory_multiple true 2 solid @ 4.5px
|
|
781
|
+
* optional_multiple false 2 dashed @ 1.5px
|
|
782
|
+
* optional_multiple true 2 dashed @ 4.5px
|
|
783
|
+
* compulsory_at_least_one false 1 solid + 1 dashed @ 1.5px
|
|
784
|
+
* compulsory_at_least_one true 1 solid + 1 dashed @ 4.5px
|
|
783
785
|
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
* ISO 704 line conventions:
|
|
788
|
-
* - default : single solid line per segment
|
|
789
|
-
* - plurality.isShared && !isUncertain : TWO parallel solid lines (close-set)
|
|
790
|
-
* - plurality.isShared && isUncertain : one solid + one dashed line
|
|
791
|
-
* - completeness === 'partial' : spine extends past last tooth
|
|
792
|
-
* (continued backline — "more exist")
|
|
793
|
-
*
|
|
794
|
-
* Per-member certainty 'possible' → that drop is dashed + reduced opacity
|
|
795
|
-
* (stem and spine keep relation style).
|
|
786
|
+
* completeness === 'partial' : spine extends past last tooth (continued
|
|
787
|
+
* backline — "more exist but not shown")
|
|
796
788
|
*
|
|
797
789
|
* Diamond marker at the comprehensive end signals the hyperedge origin.
|
|
790
|
+
* Junction marker at the spine midpoint signals the right-angle corner.
|
|
798
791
|
*/
|
|
799
792
|
function drawRakeBundles(
|
|
800
793
|
svg: SVGSVGElement,
|
|
@@ -806,6 +799,7 @@ function drawRakeBundles(
|
|
|
806
799
|
const color = uiStore.isDark ? '#2dd4bf' : '#0d9488';
|
|
807
800
|
const DOUBLE_GAP = 4;
|
|
808
801
|
const DASH = '4 3';
|
|
802
|
+
const FRAME_STROKE_WIDTH = 1.4;
|
|
809
803
|
const SPINE_FRACTION = 0.55; // spine sits 55% from comp toward centroid
|
|
810
804
|
const PARTIAL_TAIL = 28; // px extension for completeness: 'partial'
|
|
811
805
|
|
|
@@ -824,7 +818,7 @@ function drawRakeBundles(
|
|
|
824
818
|
const p = pos.get(nid);
|
|
825
819
|
return p ? { member, pos: p } : null;
|
|
826
820
|
})
|
|
827
|
-
.filter((x): x is { member: { uri: string;
|
|
821
|
+
.filter((x): x is { member: { uri: string; multiplicity: PartitiveMultiplicity; isDelimiting: boolean }; pos: {x: number; y: number} } => x !== null);
|
|
828
822
|
|
|
829
823
|
if (memberPositions.length === 0) continue;
|
|
830
824
|
|
|
@@ -850,7 +844,14 @@ function drawRakeBundles(
|
|
|
850
844
|
const along = vx * spineDirX + vy * spineDirY;
|
|
851
845
|
const toothX = spineCx + spineDirX * along;
|
|
852
846
|
const toothY = spineCy + spineDirY * along;
|
|
853
|
-
return {
|
|
847
|
+
return {
|
|
848
|
+
member,
|
|
849
|
+
partitivePos: pPos,
|
|
850
|
+
toothX,
|
|
851
|
+
toothY,
|
|
852
|
+
along,
|
|
853
|
+
style: rakeStrokeStyle(member.multiplicity, member.isDelimiting),
|
|
854
|
+
};
|
|
854
855
|
});
|
|
855
856
|
|
|
856
857
|
// 4. Spine extents (leftmost to rightmost tooth along spine-axis)
|
|
@@ -867,23 +868,22 @@ function drawRakeBundles(
|
|
|
867
868
|
const spineEndX = spineCx + spineDirX * maxAlong;
|
|
868
869
|
const spineEndY = spineCy + spineDirY * maxAlong;
|
|
869
870
|
|
|
870
|
-
const isShared = !!rel.plurality?.isShared;
|
|
871
|
-
const isUncertain = !!rel.plurality?.isUncertain;
|
|
872
|
-
|
|
873
871
|
/**
|
|
874
|
-
* Draw a single straight segment
|
|
875
|
-
*
|
|
872
|
+
* Draw a single straight segment.
|
|
873
|
+
*
|
|
874
|
+
* Stem + spine use FRAME_STROKE_WIDTH (single solid frame).
|
|
875
|
+
* Drops use the per-member multiplicity + delimiting style from
|
|
876
|
+
* rakeStrokeStyle().
|
|
876
877
|
*/
|
|
877
878
|
const drawSegment = (
|
|
878
879
|
ax: number, ay: number, bx: number, by: number,
|
|
879
|
-
opts: {
|
|
880
|
+
opts: { width: number; dashed?: boolean; offset?: number; opacity?: number },
|
|
880
881
|
) => {
|
|
882
|
+
const opacity = opts.opacity ?? 0.85;
|
|
881
883
|
const offset = opts.offset ?? 0;
|
|
882
884
|
const dashed = opts.dashed ?? false;
|
|
883
|
-
const opacity = opts.opacity ?? 0.85;
|
|
884
885
|
const dx = bx - ax, dy = by - ay;
|
|
885
886
|
const len = Math.hypot(dx, dy) || 1;
|
|
886
|
-
// Perpendicular to THIS segment (not the spine-axis) for parallel offset
|
|
887
887
|
const px = -dy / len, py = dx / len;
|
|
888
888
|
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
889
889
|
line.setAttribute('class', 'rake-seg');
|
|
@@ -892,50 +892,35 @@ function drawRakeBundles(
|
|
|
892
892
|
line.setAttribute('x2', String(bx + px * offset));
|
|
893
893
|
line.setAttribute('y2', String(by + py * offset));
|
|
894
894
|
line.setAttribute('stroke', color);
|
|
895
|
-
line.setAttribute('stroke-width',
|
|
895
|
+
line.setAttribute('stroke-width', String(opts.width));
|
|
896
896
|
line.setAttribute('opacity', String(opacity));
|
|
897
|
-
line.setAttribute('stroke-linecap', 'square');
|
|
897
|
+
line.setAttribute('stroke-linecap', 'square');
|
|
898
898
|
if (dashed) line.setAttribute('stroke-dasharray', DASH);
|
|
899
899
|
svg.appendChild(line);
|
|
900
900
|
};
|
|
901
901
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
* Single solid (default), double solid (isShared),
|
|
905
|
-
* or solid + dashed (isShared + isUncertain).
|
|
906
|
-
*/
|
|
907
|
-
const drawSegmentWithPlurality = (
|
|
908
|
-
ax: number, ay: number, bx: number, by: number,
|
|
909
|
-
opts: { opacity?: number; perMemberPossible?: boolean } = {},
|
|
910
|
-
) => {
|
|
911
|
-
const opacity = opts.opacity ?? 0.85;
|
|
912
|
-
if (!isShared) {
|
|
913
|
-
drawSegment(ax, ay, bx, by, { opacity, dashed: opts.perMemberPossible });
|
|
914
|
-
} else {
|
|
915
|
-
drawSegment(ax, ay, bx, by, { offset: -DOUBLE_GAP / 2, opacity });
|
|
916
|
-
drawSegment(ax, ay, bx, by, {
|
|
917
|
-
offset: DOUBLE_GAP / 2,
|
|
918
|
-
opacity,
|
|
919
|
-
dashed: isUncertain || opts.perMemberPossible,
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
|
|
924
|
-
// 5. Stem: comp → spine center (along stem-axis)
|
|
925
|
-
drawSegmentWithPlurality(compPos.x, compPos.y, spineCx, spineCy);
|
|
902
|
+
// 5. Stem: comp → spine center (frame — single solid)
|
|
903
|
+
drawSegment(compPos.x, compPos.y, spineCx, spineCy, { width: FRAME_STROKE_WIDTH });
|
|
926
904
|
|
|
927
|
-
// 6. Spine: leftmost tooth → rightmost tooth (
|
|
928
|
-
// (skip if only one tooth — no horizontal extent)
|
|
905
|
+
// 6. Spine: leftmost tooth → rightmost tooth (frame — single solid)
|
|
929
906
|
if (Math.abs(maxAlong - minAlong) > 1) {
|
|
930
|
-
|
|
907
|
+
drawSegment(spineStartX, spineStartY, spineEndX, spineEndY, { width: FRAME_STROKE_WIDTH });
|
|
931
908
|
}
|
|
932
909
|
|
|
933
|
-
// 7. Drops: tooth → partitive (
|
|
910
|
+
// 7. Drops: tooth → partitive (per-member multiplicity + delimiting)
|
|
911
|
+
// Draw 1 or 2 parallel lines per the style.
|
|
934
912
|
for (const tooth of teeth) {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
913
|
+
drawSegment(tooth.toothX, tooth.toothY, tooth.partitivePos.x, tooth.partitivePos.y, {
|
|
914
|
+
width: tooth.style.strokeWidth,
|
|
915
|
+
dashed: tooth.style.primaryDashed,
|
|
938
916
|
});
|
|
917
|
+
if (tooth.style.lineCount === 2) {
|
|
918
|
+
drawSegment(tooth.toothX, tooth.toothY, tooth.partitivePos.x, tooth.partitivePos.y, {
|
|
919
|
+
width: tooth.style.strokeWidth,
|
|
920
|
+
dashed: tooth.style.secondaryDashed,
|
|
921
|
+
offset: DOUBLE_GAP,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
939
924
|
}
|
|
940
925
|
|
|
941
926
|
// 8. Junction marker at spine center (the right-angle corner)
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { computed, type ComputedRef } from 'vue';
|
|
2
2
|
import type { Router } from 'vue-router';
|
|
3
|
+
// glossarist 0.4.24's top-level d.ts is incomplete for v3 model fields
|
|
4
|
+
// (multiplicity + is_delimiting). Import the v3-augmented types from
|
|
5
|
+
// glossarist/models (extended by src/adapters/non-verbal/glossarist-augment.d.ts).
|
|
6
|
+
import type { Concept, RelatedConcept, ConceptRef } from 'glossarist';
|
|
3
7
|
import type {
|
|
4
|
-
Concept,
|
|
5
|
-
RelatedConcept,
|
|
6
8
|
PartitiveRelation as GlsPartitiveRelation,
|
|
7
9
|
PartitiveMember as GlsPartitiveMember,
|
|
8
|
-
|
|
9
|
-
ConceptRef,
|
|
10
|
-
} from 'glossarist';
|
|
10
|
+
} from 'glossarist/models';
|
|
11
11
|
import type {
|
|
12
12
|
Manifest,
|
|
13
13
|
GraphEdge,
|
|
14
14
|
PartitiveRelationWire,
|
|
15
15
|
PartitiveMemberWire,
|
|
16
|
-
TypeSharedPluralityWire,
|
|
17
16
|
} from '../adapters/types';
|
|
18
17
|
import { getFactory } from '../adapters/factory';
|
|
19
18
|
import { conceptUri } from '../adapters/model-bridge';
|
|
@@ -24,6 +23,7 @@ import { categorizeRelationship, relationshipLabel, INVERSE_RELATIONSHIPS } from
|
|
|
24
23
|
import { langLabel } from '../utils/lang';
|
|
25
24
|
import { escapeAttr } from '../utils/escape';
|
|
26
25
|
import { useI18n } from '../i18n';
|
|
26
|
+
import type { PartitiveMultiplicity } from '../utils/partitive-multiplicity';
|
|
27
27
|
|
|
28
28
|
export interface EdgeDisplay {
|
|
29
29
|
uri: string;
|
|
@@ -190,7 +190,11 @@ export function useConceptEdges(
|
|
|
190
190
|
.map((m: GlsPartitiveMember): PartitiveMemberWire | null => {
|
|
191
191
|
const uri = resolveConceptRefUri(m.ref);
|
|
192
192
|
if (!uri || uri === source) return null;
|
|
193
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
uri,
|
|
195
|
+
multiplicity: readMultiplicity(m),
|
|
196
|
+
isDelimiting: readIsDelimiting(m),
|
|
197
|
+
};
|
|
194
198
|
})
|
|
195
199
|
.filter((m: PartitiveMemberWire | null): m is PartitiveMemberWire => m !== null);
|
|
196
200
|
if (partitives.length === 0) return null;
|
|
@@ -200,7 +204,6 @@ export function useConceptEdges(
|
|
|
200
204
|
comprehensive,
|
|
201
205
|
partitives,
|
|
202
206
|
completeness: rel.completeness,
|
|
203
|
-
plurality: projectPlurality(rel.plurality),
|
|
204
207
|
criterion: rel.criterion ?? undefined,
|
|
205
208
|
register: registerId.value,
|
|
206
209
|
};
|
|
@@ -208,24 +211,21 @@ export function useConceptEdges(
|
|
|
208
211
|
.filter((r: PartitiveRelationWire | null): r is PartitiveRelationWire => r !== null);
|
|
209
212
|
});
|
|
210
213
|
|
|
211
|
-
function projectPlurality(p: GlsTypeSharedPlurality | null): TypeSharedPluralityWire | null {
|
|
212
|
-
if (!p) return null;
|
|
213
|
-
const sharedTypeRef: ConceptRef | null = p.sharedType;
|
|
214
|
-
const sharedType = sharedTypeRef
|
|
215
|
-
? [sharedTypeRef.source, sharedTypeRef.id].filter(Boolean).join(':') || null
|
|
216
|
-
: null;
|
|
217
|
-
return {
|
|
218
|
-
isShared: p.isShared,
|
|
219
|
-
isUncertain: p.isUncertain,
|
|
220
|
-
sharedType,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
214
|
function resolveConceptRefUri(ref: ConceptRef | null): string | null {
|
|
225
215
|
if (!ref) return null;
|
|
226
216
|
return resolveRefUri({ source: ref.source ?? null, id: ref.id ?? null });
|
|
227
217
|
}
|
|
228
218
|
|
|
219
|
+
// glossarist 0.4.24 carries multiplicity + is_delimiting natively on
|
|
220
|
+
// PartitiveMember. Read them directly — no migration adapter needed.
|
|
221
|
+
function readMultiplicity(m: GlsPartitiveMember): PartitiveMultiplicity {
|
|
222
|
+
return m.multiplicity;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function readIsDelimiting(m: GlsPartitiveMember): boolean {
|
|
226
|
+
return m.is_delimiting;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
229
|
function resolveRefUri(ref: { source: string | null; id: string | null } | null): string | null {
|
|
230
230
|
const target = resolveRelatedRef(ref);
|
|
231
231
|
if (!target) return null;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO 704:2022 §5.5.4.2 — partitive multiplicity + delimiting model.
|
|
3
|
+
*
|
|
4
|
+
* Multiplicity (5 values) encodes part-count semantics:
|
|
5
|
+
* - compulsory : exactly one, required
|
|
6
|
+
* - optional : zero or one
|
|
7
|
+
* - compulsory_multiple : two or more, required
|
|
8
|
+
* - optional_multiple : zero or more
|
|
9
|
+
* - compulsory_at_least_one : one or more (required, count unknown)
|
|
10
|
+
*
|
|
11
|
+
* Delimiting (boolean) is orthogonal — a delimiting part behaves like a
|
|
12
|
+
* delimiting characteristic in generic relations: it distinguishes the
|
|
13
|
+
* comprehensive concept from coordinate concepts. Visual encoding:
|
|
14
|
+
* 3× stroke width (4.5 px vs 1.5 px).
|
|
15
|
+
*
|
|
16
|
+
* Adding a new multiplicity value = add one entry to PARTITIVE_MULTIPLICITY.
|
|
17
|
+
* Renderers consume RakeStrokeStyle via rakeStrokeStyle(); never inspect
|
|
18
|
+
* the raw multiplicity string. OCP + MECE + DRY.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export const PARTITIVE_MULTIPLICITY = {
|
|
22
|
+
compulsory: { lines: 1, pattern: 'solid', label: 'Compulsory' },
|
|
23
|
+
optional: { lines: 1, pattern: 'dashed', label: 'Optional' },
|
|
24
|
+
compulsory_multiple: { lines: 2, pattern: 'solid', label: 'Compulsory multiple' },
|
|
25
|
+
optional_multiple: { lines: 2, pattern: 'dashed', label: 'Optional multiple' },
|
|
26
|
+
compulsory_at_least_one: { lines: 2, pattern: 'mixed', label: 'At least one' },
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export type PartitiveMultiplicity = keyof typeof PARTITIVE_MULTIPLICITY;
|
|
30
|
+
|
|
31
|
+
export const PARTITIVE_MULTIPLICITY_VALUES: readonly PartitiveMultiplicity[] =
|
|
32
|
+
Object.keys(PARTITIVE_MULTIPLICITY) as PartitiveMultiplicity[];
|
|
33
|
+
|
|
34
|
+
export function isPartitiveMultiplicity(value: unknown): value is PartitiveMultiplicity {
|
|
35
|
+
return typeof value === 'string' && value in PARTITIVE_MULTIPLICITY;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type LinePattern = 'solid' | 'dashed' | 'mixed';
|
|
39
|
+
|
|
40
|
+
export interface MultiplicityDefinition {
|
|
41
|
+
/** Number of parallel lines used to render the multiplicity. */
|
|
42
|
+
readonly lines: 1 | 2;
|
|
43
|
+
/** Dash pattern: solid, dashed, or mixed (one solid + one dashed). */
|
|
44
|
+
readonly pattern: LinePattern;
|
|
45
|
+
/** Human-readable label for legends and badges. */
|
|
46
|
+
readonly label: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function multiplicityDefinition(m: PartitiveMultiplicity): MultiplicityDefinition {
|
|
50
|
+
return PARTITIVE_MULTIPLICITY[m];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── RakeStrokeStyle — the render-ready form consumed by all renderers ────
|
|
54
|
+
|
|
55
|
+
export interface RakeStrokeStyle {
|
|
56
|
+
/** 1 or 2 parallel lines. */
|
|
57
|
+
readonly lineCount: 1 | 2;
|
|
58
|
+
/** Primary (always-present) line dash. */
|
|
59
|
+
readonly primaryDashed: boolean;
|
|
60
|
+
/** Secondary line dash (only meaningful when lineCount === 2). */
|
|
61
|
+
readonly secondaryDashed: boolean;
|
|
62
|
+
/** Stroke width in px. 1.5 normal, 4.5 (3×) when is_delimiting. */
|
|
63
|
+
readonly strokeWidth: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const NORMAL_STROKE_WIDTH = 1.5;
|
|
67
|
+
export const DELIMITING_STROKE_WIDTH = 4.5; // 3× normal per ISO 704
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Compute the render-ready stroke style for a partitive member.
|
|
71
|
+
*
|
|
72
|
+
* Pure function — same inputs always yield the same RakeStrokeStyle.
|
|
73
|
+
* Consumed by both the sidebar rake diagram (PartitiveRelationDiagram)
|
|
74
|
+
* and the sphere rake bundles (RelationSphere.drawRakeBundles).
|
|
75
|
+
*/
|
|
76
|
+
export function rakeStrokeStyle(
|
|
77
|
+
multiplicity: PartitiveMultiplicity,
|
|
78
|
+
isDelimiting: boolean,
|
|
79
|
+
): RakeStrokeStyle {
|
|
80
|
+
const def = PARTITIVE_MULTIPLICITY[multiplicity];
|
|
81
|
+
return {
|
|
82
|
+
lineCount: def.lines,
|
|
83
|
+
primaryDashed: def.pattern === 'dashed',
|
|
84
|
+
secondaryDashed: def.pattern === 'dashed' || def.pattern === 'mixed',
|
|
85
|
+
strokeWidth: isDelimiting ? DELIMITING_STROKE_WIDTH : NORMAL_STROKE_WIDTH,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Migration from glossarist 0.4.20 certainty → ISO 704 multiplicity ────
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* glossarist 0.4.20 carries per-member `certainty: 'confirmed' | 'possible'`
|
|
93
|
+
* (a v0.4 abstraction that conflated "this member exists" with "we're sure
|
|
94
|
+
* about it"). ISO 704:2022 replaces that with the multiplicity enum.
|
|
95
|
+
*
|
|
96
|
+
* Until glossarist publishes native multiplicity support, this helper maps
|
|
97
|
+
* the legacy field to the closest ISO 704 multiplicity:
|
|
98
|
+
* - confirmed → compulsory
|
|
99
|
+
* - possible → optional
|
|
100
|
+
*
|
|
101
|
+
* Once glossarist publishes multiplicity natively, the bridge can read
|
|
102
|
+
* it directly and this helper can be deleted.
|
|
103
|
+
*/
|
|
104
|
+
export function multiplicityFromCertainty(certainty: 'confirmed' | 'possible' | null | undefined): PartitiveMultiplicity {
|
|
105
|
+
return certainty === 'possible' ? 'optional' : 'compulsory';
|
|
106
|
+
}
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Partitive relation styling — colors for one-to-many
|
|
2
|
+
* Partitive relation styling — colors and labels for one-to-many
|
|
3
|
+
* partitive decompositions.
|
|
3
4
|
*
|
|
4
5
|
* Independent of relationship-categories.ts (which handles binary edges).
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - plurality (type-shared, uncertain) adds accent badges
|
|
6
|
+
* The visual encoding for PartitiveRelation comes from two orthogonal
|
|
7
|
+
* axes per ISO 704:2022:
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* 1. completeness (complete / partial) — relation-level metadata,
|
|
10
|
+
* affects overall opacity. Partial relations have a continued
|
|
11
|
+
* backline ("more exist but aren't shown").
|
|
12
|
+
* 2. multiplicity + is_delimiting (per-member) — see
|
|
13
|
+
* partitive-multiplicity.ts for the registry + rakeStrokeStyle().
|
|
14
|
+
*
|
|
15
|
+
* This module owns:
|
|
16
|
+
* - relation-level color/badge (completeness-driven)
|
|
17
|
+
* - human-readable labels for badges
|
|
18
|
+
*
|
|
19
|
+
* It does NOT encode multiplicity visuals — those live in
|
|
20
|
+
* partitive-multiplicity.ts (separated so renderers can consume the
|
|
21
|
+
* pure rakeStrokeStyle() helper without pulling badge strings).
|
|
11
22
|
*/
|
|
12
23
|
|
|
13
|
-
import type { TypeSharedPluralityWire } from '../adapters/types';
|
|
14
|
-
|
|
15
24
|
const BASE_LIGHT = '#0d9488';
|
|
16
25
|
const BASE_DARK = '#2dd4bf';
|
|
17
|
-
const ACCENT_SHARED_LIGHT = '#3b82f6';
|
|
18
|
-
const ACCENT_SHARED_DARK = '#60a5fa';
|
|
19
|
-
const ACCENT_UNCERTAIN_LIGHT = '#f59e0b';
|
|
20
|
-
const ACCENT_UNCERTAIN_DARK = '#fbbf24';
|
|
21
26
|
|
|
22
27
|
export interface PartitiveRelationStyle {
|
|
23
28
|
color: string;
|
|
@@ -25,38 +30,24 @@ export interface PartitiveRelationStyle {
|
|
|
25
30
|
opacity: number;
|
|
26
31
|
}
|
|
27
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Relation-level style (color + opacity) driven by completeness.
|
|
35
|
+
*
|
|
36
|
+
* Per-member multiplicity + delimiting rendering is via
|
|
37
|
+
* `rakeStrokeStyle()` from partitive-multiplicity.ts, not here —
|
|
38
|
+
* this helper is for the relation's overall frame + header badges.
|
|
39
|
+
*/
|
|
28
40
|
export function partitiveRelationStyle(
|
|
29
41
|
completeness: 'complete' | 'partial',
|
|
30
|
-
plurality: TypeSharedPluralityWire | null,
|
|
31
42
|
isDark: boolean,
|
|
32
43
|
): PartitiveRelationStyle {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
badgeClass = 'badge-blue';
|
|
39
|
-
if (plurality.isUncertain) {
|
|
40
|
-
badgeClass = 'badge-yellow';
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return { color: base, badgeClass, opacity };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function pluralityColor(
|
|
48
|
-
plurality: TypeSharedPluralityWire,
|
|
49
|
-
isDark: boolean,
|
|
50
|
-
): string {
|
|
51
|
-
if (plurality.isUncertain) return isDark ? ACCENT_UNCERTAIN_DARK : ACCENT_UNCERTAIN_LIGHT;
|
|
52
|
-
if (plurality.isShared) return isDark ? ACCENT_SHARED_DARK : ACCENT_SHARED_LIGHT;
|
|
53
|
-
return isDark ? BASE_DARK : BASE_LIGHT;
|
|
44
|
+
return {
|
|
45
|
+
color: isDark ? BASE_DARK : BASE_LIGHT,
|
|
46
|
+
badgeClass: 'badge-teal',
|
|
47
|
+
opacity: completeness === 'partial' ? 0.6 : 1.0,
|
|
48
|
+
};
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
export function completenessLabel(completeness: 'complete' | 'partial'): string {
|
|
57
52
|
return completeness === 'partial' ? 'Partial' : 'Complete';
|
|
58
53
|
}
|
|
59
|
-
|
|
60
|
-
export function certaintyLabel(certainty: 'confirmed' | 'possible'): string {
|
|
61
|
-
return certainty === 'possible' ? 'Possible' : 'Confirmed';
|
|
62
|
-
}
|
|
@@ -5,6 +5,8 @@ import { useVocabularyStore } from '../stores/vocabulary';
|
|
|
5
5
|
import { conceptUri } from '../adapters/model-bridge';
|
|
6
6
|
import { UriRouter } from '../adapters/UriRouter';
|
|
7
7
|
import type { PartitiveRelationWire, PartitiveMemberWire } from '../adapters/types';
|
|
8
|
+
import type { PartitiveMember } from 'glossarist/models';
|
|
9
|
+
import type { PartitiveMultiplicity } from '../utils/partitive-multiplicity';
|
|
8
10
|
import ConceptDetail from '../components/ConceptDetail.vue';
|
|
9
11
|
import RelationSphere from '../components/RelationSphere.vue';
|
|
10
12
|
import ShortcutsModal from '../components/ShortcutsModal.vue';
|
|
@@ -80,15 +82,14 @@ const partitiveRelationsForSphere = computed<PartitiveRelationWire[]>(() => {
|
|
|
80
82
|
: null;
|
|
81
83
|
if (!compRef.id || !compTarget) continue;
|
|
82
84
|
const partitives: PartitiveMemberWire[] = rel.partitives
|
|
83
|
-
.map(member => {
|
|
85
|
+
.map((member): PartitiveMemberWire | null => {
|
|
84
86
|
const ref = member.ref;
|
|
85
87
|
if (!ref?.id) return null;
|
|
86
|
-
const
|
|
87
|
-
? activeRegisterId.value
|
|
88
|
-
: activeRegisterId.value;
|
|
89
|
-
const uri = UriRouter.buildConceptUri(m.uriBase, targetRegister, ref.id);
|
|
88
|
+
const uri = UriRouter.buildConceptUri(m.uriBase, activeRegisterId.value, ref.id);
|
|
90
89
|
if (uri === sourceUri) return null;
|
|
91
|
-
|
|
90
|
+
const multiplicity: PartitiveMultiplicity = member.multiplicity;
|
|
91
|
+
const isDelimiting = member.isDelimiting;
|
|
92
|
+
return { uri, multiplicity, isDelimiting };
|
|
92
93
|
})
|
|
93
94
|
.filter((x): x is PartitiveMemberWire => x !== null);
|
|
94
95
|
if (partitives.length === 0) continue;
|
|
@@ -97,13 +98,6 @@ const partitiveRelationsForSphere = computed<PartitiveRelationWire[]>(() => {
|
|
|
97
98
|
comprehensive: compTarget,
|
|
98
99
|
partitives,
|
|
99
100
|
completeness: rel.completeness,
|
|
100
|
-
plurality: rel.plurality
|
|
101
|
-
? {
|
|
102
|
-
isShared: rel.plurality.isShared,
|
|
103
|
-
isUncertain: rel.plurality.isUncertain,
|
|
104
|
-
sharedType: null,
|
|
105
|
-
}
|
|
106
|
-
: null,
|
|
107
101
|
register: activeRegisterId.value,
|
|
108
102
|
});
|
|
109
103
|
}
|