194194 background : # f0f8ff ;
195195 color : # 333 ;
196196 }
197+
198+ .metric-checkbox-label {
199+ display : flex;
200+ align-items : center;
201+ gap : 0.5rem ;
202+ cursor : grab;
203+ padding : 0.5rem ;
204+ border-radius : 4px ;
205+ transition : background 0.2s ;
206+ user-select : none;
207+ }
208+
209+ .metric-checkbox-label : hover {
210+ background : # f0f0f0 ;
211+ }
212+
213+ .metric-checkbox-label .dragging {
214+ opacity : 0.5 ;
215+ cursor : grabbing;
216+ }
217+
218+ .metric-checkbox-label .drag-over-left {
219+ background : # e0e0e0 ;
220+ border-left : 3px solid # 007acc ;
221+ }
222+
223+ .metric-checkbox-label .drag-over-right {
224+ background : # e0e0e0 ;
225+ border-right : 3px solid # 007acc ;
226+ }
197227 </ style >
198228</ head >
199229< body >
@@ -222,7 +252,7 @@ <h3>Select Test Files</h3>
222252 </ div >
223253
224254 < div class ="file-selector " id ="metricSelector " style ="display: none; ">
225- < h3 > Select Metrics to Display</ h3 >
255+ < h3 > Select Metrics to Display < span style =" font-size: 0.8em; font-weight: normal; color: #666; " > (drag to reorder) </ span > </ h3 >
226256 < div id ="metricCheckboxes " style ="display: flex; flex-wrap: wrap; gap: 1rem; "> </ div >
227257 </ div >
228258
@@ -240,6 +270,7 @@ <h3>Select Metrics to Display</h3>
240270 // Default metrics to show (in order)
241271 const defaultMetrics = [ 'cpu' , 'qps' , 'write_bytes_per_second' , 'replicas' , 'leases' ] ;
242272 let selectedMetrics = new Set ( ) ;
273+ let metricOrder = [ ] ; // Custom order for metrics
243274
244275 // Clean Zoom State Manager - Option 1: Plugin for input only
245276 const ZoomManager = {
@@ -459,6 +490,23 @@ <h3>Select Metrics to Display</h3>
459490 localStorage . setItem ( 'asimview-selected-metrics' , JSON . stringify ( [ ...selectedMetrics ] ) ) ;
460491 }
461492
493+ // Load metric order from localStorage
494+ function loadMetricOrder ( ) {
495+ const saved = localStorage . getItem ( 'asimview-metric-order' ) ;
496+ if ( saved ) {
497+ try {
498+ metricOrder = JSON . parse ( saved ) ;
499+ } catch ( e ) {
500+ metricOrder = [ ] ;
501+ }
502+ }
503+ }
504+
505+ // Save metric order to localStorage
506+ function saveMetricOrder ( ) {
507+ localStorage . setItem ( 'asimview-metric-order' , JSON . stringify ( metricOrder ) ) ;
508+ }
509+
462510 // Load file selection from localStorage
463511 function loadFileSelection ( ) {
464512 const saved = localStorage . getItem ( 'asimview-selected-files' ) ;
@@ -480,6 +528,7 @@ <h3>Select Metrics to Display</h3>
480528 // Fetch available files on page load
481529 window . addEventListener ( 'DOMContentLoaded' , async ( ) => {
482530 loadMetricPreferences ( ) ;
531+ loadMetricOrder ( ) ;
483532 await fetchAvailableFiles ( ) ;
484533
485534 // Restore saved file selection if any files match
@@ -877,26 +926,19 @@ <h3>Select Metrics to Display</h3>
877926 }
878927 } ) ;
879928
880- // Update metric selector checkboxes
929+ // Update metric selector checkboxes (this also updates metricOrder)
881930 updateMetricSelector ( allMetrics ) ;
882931
883- // Sort metrics according to defaultMetrics order, then alphabetically for others
884- const sortedMetrics = Array . from ( allMetrics ) . sort ( ( a , b ) => {
885- const aIndex = defaultMetrics . indexOf ( a ) ;
886- const bIndex = defaultMetrics . indexOf ( b ) ;
887- if ( aIndex !== - 1 && bIndex !== - 1 ) return aIndex - bIndex ;
888- if ( aIndex !== - 1 ) return - 1 ;
889- if ( bIndex !== - 1 ) return 1 ;
890- return a . localeCompare ( b ) ;
891- } ) ;
932+ // Use metricOrder for rendering charts
933+ const orderedMetrics = metricOrder . filter ( m => allMetrics . has ( m ) ) ;
892934
893935 // Filter to only selected metrics
894- const metricsToRender = sortedMetrics . filter ( m => selectedMetrics . has ( m ) ) ;
936+ const metricsToRender = orderedMetrics . filter ( m => selectedMetrics . has ( m ) ) ;
895937
896938 // Pre-calculate all Y-axis ranges once (for all metrics, not just selected)
897939 const metricRanges = new Map ( ) ;
898940
899- sortedMetrics . forEach ( metricName => {
941+ orderedMetrics . forEach ( metricName => {
900942 let globalMin = Infinity ;
901943 let globalMax = - Infinity ;
902944
@@ -925,7 +967,7 @@ <h3>Select Metrics to Display</h3>
925967 } ) ;
926968
927969 // Now create sections for ALL metrics (we'll hide/show them based on selection)
928- sortedMetrics . forEach ( metricName => {
970+ orderedMetrics . forEach ( metricName => {
929971 const section = document . createElement ( 'div' ) ;
930972 section . className = 'metric-section' ;
931973 section . dataset . metric = metricName ;
@@ -1069,22 +1111,31 @@ <h3>Select Metrics to Display</h3>
10691111 selectorDiv . style . display = 'block' ;
10701112 checkboxContainer . innerHTML = '' ;
10711113
1072- // Sort metrics according to defaultMetrics order, then alphabetically
1073- const sortedMetrics = Array . from ( availableMetrics ) . sort ( ( a , b ) => {
1074- const aIndex = defaultMetrics . indexOf ( a ) ;
1075- const bIndex = defaultMetrics . indexOf ( b ) ;
1076- if ( aIndex !== - 1 && bIndex !== - 1 ) return aIndex - bIndex ;
1077- if ( aIndex !== - 1 ) return - 1 ;
1078- if ( bIndex !== - 1 ) return 1 ;
1079- return a . localeCompare ( b ) ;
1080- } ) ;
1114+ // Update metric order: add new metrics, remove unavailable ones
1115+ const metricsArray = Array . from ( availableMetrics ) ;
1116+ const newMetrics = metricsArray . filter ( m => ! metricOrder . includes ( m ) ) ;
1117+ metricOrder = metricOrder . filter ( m => availableMetrics . has ( m ) ) ;
10811118
1082- sortedMetrics . forEach ( metric => {
1119+ // Add new metrics in default order
1120+ if ( newMetrics . length > 0 ) {
1121+ const sortedNew = newMetrics . sort ( ( a , b ) => {
1122+ const aIndex = defaultMetrics . indexOf ( a ) ;
1123+ const bIndex = defaultMetrics . indexOf ( b ) ;
1124+ if ( aIndex !== - 1 && bIndex !== - 1 ) return aIndex - bIndex ;
1125+ if ( aIndex !== - 1 ) return - 1 ;
1126+ if ( bIndex !== - 1 ) return 1 ;
1127+ return a . localeCompare ( b ) ;
1128+ } ) ;
1129+ metricOrder . push ( ...sortedNew ) ;
1130+ }
1131+
1132+ // Create draggable checkbox labels
1133+ metricOrder . forEach ( ( metric , index ) => {
10831134 const label = document . createElement ( 'label' ) ;
1084- label . style . display = 'flex ' ;
1085- label . style . alignItems = 'center' ;
1086- label . style . gap = '0.5rem' ;
1087- label . style . cursor = 'pointer' ;
1135+ label . className = 'metric-checkbox-label ' ;
1136+ label . draggable = true ;
1137+ label . dataset . metric = metric ;
1138+ label . dataset . index = index ;
10881139
10891140 const checkbox = document . createElement ( 'input' ) ;
10901141 checkbox . type = 'checkbox' ;
@@ -1113,6 +1164,104 @@ <h3>Select Metrics to Display</h3>
11131164 label . appendChild ( checkbox ) ;
11141165 label . appendChild ( text ) ;
11151166 checkboxContainer . appendChild ( label ) ;
1167+
1168+ // Drag and drop handlers
1169+ label . addEventListener ( 'dragstart' , ( e ) => {
1170+ label . classList . add ( 'dragging' ) ;
1171+ e . dataTransfer . effectAllowed = 'move' ;
1172+ e . dataTransfer . setData ( 'text/plain' , metric ) ;
1173+ } ) ;
1174+
1175+ label . addEventListener ( 'dragend' , ( e ) => {
1176+ label . classList . remove ( 'dragging' ) ;
1177+ document . querySelectorAll ( '.metric-checkbox-label' ) . forEach ( l => {
1178+ l . classList . remove ( 'drag-over-left' , 'drag-over-right' ) ;
1179+ } ) ;
1180+ } ) ;
1181+
1182+ label . addEventListener ( 'dragover' , ( e ) => {
1183+ e . preventDefault ( ) ;
1184+ e . dataTransfer . dropEffect = 'move' ;
1185+
1186+ const dragging = document . querySelector ( '.dragging' ) ;
1187+ if ( dragging && dragging !== label ) {
1188+ // Determine which side of the target we're hovering over
1189+ const rect = label . getBoundingClientRect ( ) ;
1190+ const midpoint = rect . left + rect . width / 2 ;
1191+ const isLeftSide = e . clientX < midpoint ;
1192+
1193+ // Remove both classes first
1194+ label . classList . remove ( 'drag-over-left' , 'drag-over-right' ) ;
1195+
1196+ // Add the appropriate class
1197+ if ( isLeftSide ) {
1198+ label . classList . add ( 'drag-over-left' ) ;
1199+ label . dataset . dropSide = 'left' ;
1200+ } else {
1201+ label . classList . add ( 'drag-over-right' ) ;
1202+ label . dataset . dropSide = 'right' ;
1203+ }
1204+ }
1205+ } ) ;
1206+
1207+ label . addEventListener ( 'dragleave' , ( e ) => {
1208+ label . classList . remove ( 'drag-over-left' , 'drag-over-right' ) ;
1209+ delete label . dataset . dropSide ;
1210+ } ) ;
1211+
1212+ label . addEventListener ( 'drop' , ( e ) => {
1213+ e . preventDefault ( ) ;
1214+ const dropSide = label . dataset . dropSide || 'right' ;
1215+ label . classList . remove ( 'drag-over-left' , 'drag-over-right' ) ;
1216+ delete label . dataset . dropSide ;
1217+
1218+ const draggedMetric = e . dataTransfer . getData ( 'text/plain' ) ;
1219+ if ( draggedMetric && draggedMetric !== metric ) {
1220+ // Reorder the metrics
1221+ const oldIndex = metricOrder . indexOf ( draggedMetric ) ;
1222+ const targetIndex = metricOrder . indexOf ( metric ) ;
1223+
1224+ if ( oldIndex !== - 1 && targetIndex !== - 1 ) {
1225+ // Remove the dragged item
1226+ metricOrder . splice ( oldIndex , 1 ) ;
1227+
1228+ // Calculate new insert position
1229+ let insertIndex = metricOrder . indexOf ( metric ) ;
1230+ if ( dropSide === 'right' ) {
1231+ insertIndex += 1 ;
1232+ }
1233+
1234+ // Insert at the new position
1235+ metricOrder . splice ( insertIndex , 0 , draggedMetric ) ;
1236+
1237+ saveMetricOrder ( ) ;
1238+
1239+ // Re-render checkboxes and charts
1240+ updateMetricSelector ( availableMetrics ) ;
1241+ reorderChartSections ( ) ;
1242+ }
1243+ }
1244+ } ) ;
1245+ } ) ;
1246+ }
1247+
1248+ // Reorder chart sections to match metricOrder
1249+ function reorderChartSections ( ) {
1250+ const chartsDiv = document . getElementById ( 'charts' ) ;
1251+ const sections = Array . from ( chartsDiv . querySelectorAll ( '.metric-section' ) ) ;
1252+
1253+ // Sort sections based on metricOrder
1254+ sections . sort ( ( a , b ) => {
1255+ const aMetric = a . dataset . metric ;
1256+ const bMetric = b . dataset . metric ;
1257+ const aIndex = metricOrder . indexOf ( aMetric ) ;
1258+ const bIndex = metricOrder . indexOf ( bMetric ) ;
1259+ return aIndex - bIndex ;
1260+ } ) ;
1261+
1262+ // Re-append in correct order
1263+ sections . forEach ( section => {
1264+ chartsDiv . appendChild ( section ) ;
11161265 } ) ;
11171266 }
11181267
0 commit comments