@@ -261,15 +261,15 @@ async function listAttributeNames(
261261 levelUrl : string ,
262262 options : Partial < ProgressOptions > ,
263263) : Promise < string [ ] > {
264- const attributesUrl = joinBaseUrlAndPath ( levelUrl , "attributes /" ) ;
264+ const attributesUrl = joinBaseUrlAndPath ( levelUrl , "vertex_attributes /" ) ;
265265 const response = await sharedKvStoreContext . kvStoreContext
266266 . list ( attributesUrl , {
267267 responseKeys : "suffix" ,
268268 ...options ,
269269 } )
270270 . catch ( ( ) => undefined ) ;
271271 if ( response === undefined ) return [ ] ;
272- // Each subdirectory under attributes / is one property. Strip the
272+ // Each subdirectory under vertex_attributes / is one property. Strip the
273273 // trailing "/" if present.
274274 return response . directories
275275 . map ( ( d ) => ( d . endsWith ( "/" ) ? d . slice ( 0 , - 1 ) : d ) )
@@ -338,10 +338,11 @@ async function buildPropertySpecsAndDtypes(
338338
339339 for ( const name of orderedNames ) {
340340 let dtype : string | undefined ;
341+ let arrayMeta : any | undefined ;
341342 try {
342- const arrayMeta = await getJsonResource (
343+ arrayMeta = await getJsonResource (
343344 sharedKvStoreContext ,
344- joinBaseUrlAndPath ( levelUrl , `attributes /${ name } /zarr.json` ) ,
345+ joinBaseUrlAndPath ( levelUrl , `vertex_attributes /${ name } /zarr.json` ) ,
345346 `attribute ${ JSON . stringify ( name ) } metadata` ,
346347 options ,
347348 ) ;
@@ -357,13 +358,38 @@ async function buildPropertySpecsAndDtypes(
357358 attributeNames . push ( name ) ;
358359 attributeDtypes . push ( dtype as ZarrVectorsAttributeDtype ) ;
359360
361+ // Dictionary-encoded (categorical/enum) attributes: surface as a
362+ // numeric annotation property with enum_values + enum_labels so
363+ // shaders see category names, not raw codes. The on-disk
364+ // convention is documented in zarr-vectors:
365+ // ``encoding: "dictionary"`` + ``categories: [...]`` lives in the
366+ // attribute array's metadata block.
367+ let enumValues : number [ ] | undefined ;
368+ let enumLabels : string [ ] | undefined ;
369+ if ( arrayMeta ?. attributes ?. encoding === "dictionary" ) {
370+ const categories = arrayMeta . attributes . categories ;
371+ if ( Array . isArray ( categories ) ) {
372+ enumLabels = categories . map ( ( c : unknown ) => String ( c ) ) ;
373+ enumValues = enumLabels . map ( ( _ , i ) => i ) ;
374+ }
375+ }
376+
360377 const hint = declaredByName . get ( name ) ;
361378 if ( hint !== undefined ) {
362- rawPropertyJson . push ( { ...hint , type : hint . type ?? dtype } ) ;
379+ rawPropertyJson . push ( {
380+ ...hint ,
381+ type : hint . type ?? dtype ,
382+ // Hints win for enum metadata; only fill in from the on-disk
383+ // dictionary when the user didn't already specify it.
384+ enum_values : hint . enum_values ?? enumValues ,
385+ enum_labels : hint . enum_labels ?? enumLabels ,
386+ } ) ;
363387 } else {
364388 rawPropertyJson . push ( {
365389 id : name ,
366390 type : ATTR_DTYPE_TO_NG_TYPE [ dtype ] ,
391+ enum_values : enumValues ,
392+ enum_labels : enumLabels ,
367393 } ) ;
368394 }
369395 }
0 commit comments