@@ -15,6 +15,8 @@ class ColorScalePicker {
1515 this . elementType = "nodes" ;
1616 this . currentProperty = null ;
1717 this . dom = { } ;
18+ this . metricValuePrefix = "__metric__:" ;
19+ this . activeMetricSource = null ;
1820
1921 this . cache = cache ;
2022 }
@@ -146,32 +148,95 @@ class ColorScalePicker {
146148 } ) ;
147149 } ) ;
148150
151+ const metricOptions = this . elementType === 'nodes'
152+ ? this . cache . metrics . getMetricScaleOptions ( )
153+ : [ ] ;
154+
149155 dropdown . innerHTML = '<option value="">Select property</option>' ;
150- Array . from ( available ) . sort ( ) . forEach ( prop => {
151- const opt = document . createElement ( 'option' ) ;
152- opt . value = prop ;
153- opt . textContent = prop ;
154- dropdown . appendChild ( opt ) ;
155- } ) ;
156+ const propertyOptions = Array . from ( available ) . sort ( ) ;
157+ if ( propertyOptions . length > 0 ) {
158+ const dataGroup = document . createElement ( 'optgroup' ) ;
159+ dataGroup . label = 'Data Properties' ;
160+ propertyOptions . forEach ( prop => {
161+ const opt = document . createElement ( 'option' ) ;
162+ opt . value = prop ;
163+ opt . textContent = prop ;
164+ dataGroup . appendChild ( opt ) ;
165+ } ) ;
166+ dropdown . appendChild ( dataGroup ) ;
167+ }
168+
169+ if ( metricOptions . length > 0 ) {
170+ const metricGroup = document . createElement ( 'optgroup' ) ;
171+ metricGroup . label = 'Network Metrics' ;
172+ metricOptions . forEach ( metric => {
173+ const opt = document . createElement ( 'option' ) ;
174+ opt . value = `${ this . metricValuePrefix } ${ metric . id } ` ;
175+ opt . textContent = `${ metric . label } (${ metric . valueLabel } )${ metric . cached ? '' : ' (calculate)' } ` ;
176+ metricGroup . appendChild ( opt ) ;
177+ } ) ;
178+ dropdown . appendChild ( metricGroup ) ;
179+ }
156180
157- dropdown . onchange = ( ) => this . selectProperty ( dropdown . value , filters . get ( dropdown . value ) ) ;
181+ dropdown . onchange = async ( ) => {
182+ const value = dropdown . value ;
183+ const metricSource = this . getMetricSource ( value ) ;
184+ if ( metricSource ) {
185+ await this . selectProperty ( value , { isCategory : false } , metricSource ) ;
186+ } else {
187+ await this . selectProperty ( value , filters . get ( value ) ) ;
188+ }
189+ } ;
158190 }
159191
160- selectProperty ( property , filterObj ) {
192+ async selectProperty ( property , filterObj , metricSource = null ) {
161193 if ( ! property ) return ;
162194
163195 const selectedElements = this . elementType === 'nodes' ? this . cache . selectedNodes : this . cache . selectedEdges ;
164196 const elementRef = this . elementType === 'nodes' ? this . cache . nodeRef : this . cache . edgeRef ;
197+ if ( property . startsWith ( this . metricValuePrefix ) && ! metricSource ) {
198+ const metricId = property . slice ( this . metricValuePrefix . length ) ;
199+ metricSource = await this . cache . metrics . ensureMetricValues ( metricId ) ;
200+ filterObj = { isCategory : false } ;
201+ }
202+ if ( property . startsWith ( this . metricValuePrefix ) && ! metricSource ) {
203+ this . cache . ui . warning ( 'Metric values not available yet. Calculate the metric first.' ) ;
204+ return ;
205+ }
206+ if ( ! filterObj ) {
207+ this . cache . ui . warning ( 'No values found for selected property' ) ;
208+ return ;
209+ }
210+ this . activeMetricSource = metricSource ;
165211
212+ const values = [ ] ;
166213 const elementsWithProperty = Array . from ( selectedElements )
167214 . filter ( id => {
215+ if ( metricSource ) {
216+ const value = metricSource . values . get ( id ) ;
217+ if ( value !== undefined ) {
218+ values . push ( value ) ;
219+ return true ;
220+ }
221+ return false ;
222+ }
168223 const element = elementRef . get ( id ) ;
169- return element ?. featureValues . has ( property ) ;
224+ const value = element ?. featureValues . get ( property ) ;
225+ if ( value !== undefined ) {
226+ values . push ( value ) ;
227+ return true ;
228+ }
229+ return false ;
170230 } ) ;
171231
172232 const totalElements = selectedElements . length ;
173233 const elementsWithPropertyCount = elementsWithProperty . length ;
174234
235+ if ( ! filterObj . isCategory && values . length === 0 ) {
236+ this . cache . ui . warning ( 'No numeric values found for selected property' ) ;
237+ return ;
238+ }
239+
175240 const existingCounter = this . element . querySelector ( '.picker-property-counter' ) ;
176241 if ( existingCounter ) {
177242 existingCounter . remove ( ) ;
@@ -191,9 +256,9 @@ class ColorScalePicker {
191256 const elementTypeLabel = this . elementType === 'nodes' ? 'nodes' : 'edges' ;
192257
193258 // Extract property label after last "::"
194- const propertyDisplayName = property . includes ( '::' )
195- ? property . split ( '::' ) . pop ( )
196- : property ;
259+ const propertyDisplayName = metricSource
260+ ? ` ${ metricSource . label } ( ${ metricSource . valueLabel } )`
261+ : ( property . includes ( '::' ) ? property . split ( '::' ) . pop ( ) : property ) ;
197262
198263 // Get the current property being styled (like "Node Fill Color")
199264 const targetProperty = this . currentProperty || 'color' ;
@@ -205,10 +270,6 @@ class ColorScalePicker {
205270
206271 // Add property range for continuous (non-category) properties
207272 if ( ! filterObj . isCategory ) {
208- const values = Array . from ( elementsWithProperty )
209- . map ( id => elementRef . get ( id ) ?. featureValues . get ( property ) )
210- . filter ( v => v !== undefined ) ;
211-
212273 const minVal = Math . min ( ...values ) ;
213274 const maxVal = Math . max ( ...values ) ;
214275
@@ -282,10 +343,13 @@ class ColorScalePicker {
282343 initializeGradient ( property ) {
283344 const selectedElements = this . elementType === 'nodes' ? this . cache . selectedNodes : this . cache . selectedEdges ;
284345 const elementRef = this . elementType === 'nodes' ? this . cache . nodeRef : this . cache . edgeRef ;
285-
286- const values = Array . from ( selectedElements )
287- . map ( id => elementRef . get ( id ) ?. featureValues . get ( property ) )
288- . filter ( v => v !== undefined ) ;
346+ const values = this . activeMetricSource
347+ ? Array . from ( selectedElements )
348+ . map ( id => this . activeMetricSource . values . get ( id ) )
349+ . filter ( v => v !== undefined )
350+ : Array . from ( selectedElements )
351+ . map ( id => elementRef . get ( id ) ?. featureValues . get ( property ) )
352+ . filter ( v => v !== undefined ) ;
289353
290354 this . minValue = Math . min ( ...values ) ;
291355 this . maxValue = Math . max ( ...values ) ;
@@ -434,7 +498,10 @@ class ColorScalePicker {
434498 const selectedElements = this . elementType === 'nodes' ? this . cache . selectedNodes : this . cache . selectedEdges ;
435499 const elementRef = this . elementType === 'nodes' ? this . cache . nodeRef : this . cache . edgeRef ;
436500
437- const filterObj = this . cache . data . layouts [ this . cache . data . selectedLayout ] . filters . get ( dropdown . value ) ;
501+ const metricSource = this . getMetricSource ( dropdown . value ) ;
502+ const filterObj = metricSource
503+ ? { isCategory : false }
504+ : this . cache . data . layouts [ this . cache . data . selectedLayout ] . filters . get ( dropdown . value ) ;
438505 const isCategory = filterObj ?. isCategory ;
439506
440507 if ( isCategory ) {
@@ -455,7 +522,9 @@ class ColorScalePicker {
455522 } else {
456523 Array . from ( selectedElements ) . forEach ( elementId => {
457524 const element = elementRef . get ( elementId ) ;
458- const value = element ?. featureValues . get ( dropdown . value ) ;
525+ const value = metricSource
526+ ? metricSource . values . get ( elementId )
527+ : element ?. featureValues . get ( dropdown . value ) ;
459528
460529 if ( value !== undefined ) {
461530 const normalizedValue = ( ( value - this . minValue ) / ( this . maxValue - this . minValue ) ) * 100 ;
@@ -531,6 +600,12 @@ class ColorScalePicker {
531600 this . element ?. remove ( ) ;
532601 this . element = null ;
533602 }
603+
604+ getMetricSource ( property ) {
605+ if ( ! property || ! property . startsWith ( this . metricValuePrefix ) ) return null ;
606+ const metricId = property . slice ( this . metricValuePrefix . length ) ;
607+ return this . cache . metrics . getMetricScaleValues ( metricId ) ;
608+ }
534609}
535610
536611function replaceColorScale ( obj , elemID , colorMap ) {
@@ -557,4 +632,4 @@ function replaceColorScale(obj, elemID, colorMap) {
557632 return obj ;
558633}
559634
560- export { ColorScalePicker , replaceColorScale } ;
635+ export { ColorScalePicker , replaceColorScale } ;
0 commit comments