@ -83,7 +83,7 @@ export class BScanClickHandler {
// Add visual highlight by adding a vertical line shape
this . highlightColumn ( plotContainer , columnIndex ) ;
console . log ( ` Column ${ columnIndex } selected. Press 'D' to delete. ` ) ;
console . log ( ` Column ${ columnIndex } selected. Press 'D' to delete, 'P' to preview depth profile . ` ) ;
}
/**
@ -171,10 +171,10 @@ export class BScanClickHandler {
this . showDeleteConfirmation ( this . selectedProcessorId , this . selectedColumn ) ;
}
} else if ( event . key === 'p' || event . key === 'P' ) {
// Show sweep preview modal
// Show column depth profile (A-scan)
if ( this . selectedColumn !== null && this . selectedProcessorId !== null ) {
event . preventDefault ( ) ;
this . showSweepPreview ( this . selectedProcessorId , this . selectedColumn ) ;
this . showColumnProfile ( this . selectedProcessorId , this . selectedColumn ) ;
}
} else if ( event . key === 'Escape' ) {
// Clear selection on Escape
@ -183,7 +183,7 @@ export class BScanClickHandler {
} ;
document . addEventListener ( 'keydown' , this . keyboardListener ) ;
console . log ( 'Keyboard shortcuts: D - delete column, P - preview sweep , Escape - deselect' ) ;
console . log ( 'Keyboard shortcuts: D - delete column, P - preview column depth profile , Escape - deselect' ) ;
}
/**
@ -253,11 +253,11 @@ export class BScanClickHandler {
}
/**
* Show sweep preview modal for selected column
* Show column depth profile (A-scan) modal for selected column
* @param {string} processorId - Processor ID
* @param {number} columnIndex - Column index (1-based)
*/
showSweepPreview ( processorId , columnIndex ) {
showColumnProfile ( processorId , columnIndex ) {
// Get websocket from global window object if not available in instance
const websocket = this . websocket || window . vnaDashboard ? . websocket ;
@ -271,13 +271,13 @@ export class BScanClickHandler {
}
try {
// Request processor state to get sweep data
// Request processor state to get column data
const message = {
type : 'get_processor_state' ,
processor _id : processorId
} ;
console . log ( 'Requesting processor state for sweep preview :' , message ) ;
console . log ( 'Requesting processor state for column profile :' , message ) ;
// Set up one-time listener for the response
const handleResponse = ( event ) => {
@ -288,8 +288,8 @@ export class BScanClickHandler {
// Remove this listener
websocket . ws . removeEventListener ( 'message' , handleResponse ) ;
// Extract sweep data for the selected column
this . displaySweep Modal ( response , columnIndex ) ;
// Extract column data from plot history
this . displayColumn Modal ( response , columnIndex ) ;
}
} catch ( error ) {
console . error ( 'Error parsing processor state response:' , error ) ;
@ -300,61 +300,65 @@ export class BScanClickHandler {
websocket . ws . send ( JSON . stringify ( message ) ) ;
} catch ( error ) {
console . error ( 'Failed to request sweep preview :' , error ) ;
console . error ( 'Failed to request column profile :' , error ) ;
this . notifications ? . show ? . ( {
type : 'error' ,
title : 'Ошибка предпросмотра' ,
message : 'Н е удалось загрузить данные с вип а '
message : 'Н е удалось загрузить данные с толбц а '
} ) ;
}
}
/**
* Display sweep data in modal window
* Display column depth profile data in modal window
* @param {Object} processorState - Processor state from backend
* @param {number} columnIndex - Column index (1-based, same as sweep number )
* @param {number} columnIndex - Column index (1-based)
*/
displaySweep Modal ( processorState , columnIndex ) {
const sweepHistory = processorState . state ? . sweep _history || [ ] ;
displayColumn Modal ( processorState , columnIndex ) {
const plotData = processorState . state ? . plot _data || { } ;
const config = processorState . state ? . config || { } ;
// Convert 1-based column index to 0-based array index
const sweepIndex = columnIndex - 1 ;
// Get plot history data from plot_data
const allTimeData = plotData . all _time _domain _data || [ ] ;
const allDistanceData = plotData . all _distance _data || [ ] ;
if ( sweepIndex < 0 || sweepIndex >= sweepHistory . length ) {
console . log ( 'Plot data available:' , {
columns : allTimeData . length ,
requestedColumn : columnIndex ,
allTimeDataLength : allTimeData . length ,
allDistanceDataLength : allDistanceData . length
} ) ;
// Convert 1-based column index to 0-based array index
const columnArrayIndex = columnIndex - 1 ;
if ( columnArrayIndex < 0 || columnArrayIndex >= allTimeData . length ) {
this . notifications ? . show ? . ( {
type : 'error' ,
title : 'Ошибка' ,
message : ` С вип ${ columnIndex } не найден в истории `
message : ` С толбец ${ columnIndex } не найден в данных (доступно столбцов: ${ allTimeData . length } ) `
} ) ;
return ;
}
const sweep = sweepHistory [ sweep Index] ;
const amplitudeData = allTimeData [ columnArray Index] ;
const depthData = allDistanceData [ columnArrayIndex ] ;
// Use same logic as BScanProcessor: calibrated_data or sweep_data
const hasCalibrated = this . hasCalibratedSweepData ( sweep ) ;
const dataToProcess = hasCalibrated ? sweep . calibrated _points : ( sweep . sweep _points || [ ] ) ;
const referencePoints = sweep . reference _points || [ ] ;
const openAirEnabled = config . open _air || false ;
if ( dataToProcess . length === 0 ) {
if ( ! amplitudeData || amplitudeData . length === 0 || ! depthData || depthData . length === 0 ) {
this . notifications ? . show ? . ( {
type : 'warning' ,
title : 'Нет данных' ,
message : ` С вип ${ columnIndex } не содержит данных для отображения `
message : ` С толбец ${ columnIndex } не содержит данных для отображения `
} ) ;
return ;
}
// Show modal with plot
this . showSweepPlot Modal (
// Show modal with depth profile plot
this . showColumnProfile Modal (
columnIndex ,
dataToProcess ,
referencePoints ,
openAirEnabled ,
hasCalibrated ,
sweep . vna _config
amplitudeData ,
depthData ,
config
) ;
}
@ -443,81 +447,41 @@ export class BScanClickHandler {
}
/**
* Show modal with sweep plot
* @param {number} sweep Number - Sweep number (1-based)
* @param {Array} points - Sweep points (calibrated or raw)
* @param {Array} referencePoints - Reference points for open air subtraction
* @param {boolean} openAirEnabled - Whether to subtract reference
* @param {boolean} hasCalibrated - Whether the data is calibrated (true) or raw (false)
* @param {Object} vnaConfig - VNA configuration
* Show modal with column depth profile (A-scan) plot
* @param {number} column Number - Column number (1-based)
* @param {Array} amplitudeData - Amplitude values
* @param {Array} depthData - Depth values in meters
* @param {Object} config - Processor configuration
*/
showSweepPlot Modal ( sweep Number, points , referencePoints , openAirEnabled , hasCalibrated , vnaC onfig) {
showColumnProfile Modal ( column Number, amplitudeData , depthData , c onfig) {
// Get modal element
const modal = document . getElementById ( 'plotsModal' ) ;
if ( ! modal ) {
console . error ( 'Plots modal not found' ) ;
return ;
}
// Prepare data for Plotly
const frequencies = [ ] ;
const magnitudes = [ ] ;
const phases = [ ] ;
points . forEach ( ( point , index ) => {
const freq = vnaConfig ? . start _freq + ( index / ( points . length - 1 ) ) * ( vnaConfig ? . stop _freq - vnaConfig ? . start _freq ) ;
frequencies . push ( freq / 1e6 ) ; // Convert to MHz
let real = point [ 0 ] || point . real || point . r || 0 ;
let imag = point [ 1 ] || point . imag || point . i || 0 ;
// Apply reference subtraction if enabled (same as BScanProcessor)
if ( openAirEnabled && referencePoints && referencePoints . length > index ) {
const refPoint = referencePoints [ index ] ;
const refReal = refPoint [ 0 ] || refPoint . real || refPoint . r || 0 ;
const refImag = refPoint [ 1 ] || refPoint . imag || refPoint . i || 0 ;
// Subtract reference: complex_data - reference_complex
real = real - refReal ;
imag = imag - refImag ;
}
const magnitude = Math . sqrt ( real * real + imag * imag ) ;
const phase = Math . atan2 ( imag , real ) * ( 180 / Math . PI ) ;
magnitudes . push ( magnitude ) ;
phases . push ( phase ) ;
} ) ;
// Create Plotly traces
// Create Plotly trace - A-scan format (depth on X-axis, amplitude on Y-axis)
const traces = [
{
x : frequencies ,
y : magn itudes ,
x : depthData ,
y : ampl itudeData ,
type : 'scatter' ,
mode : 'lines' ,
name : 'Амплитуда' ,
line : { color : '#3b82f6' , width : 2 }
} ,
{
x : frequencies ,
y : phases ,
type : 'scatter' ,
mode : 'lines' ,
name : 'Фаза' ,
yaxis : 'y2' ,
line : { color : '#f59e0b' , width : 2 }
}
] ;
// Build descriptive title
const data Type = hasCalibrated ? 'откалиброванные' : 'сырые ' ;
const refStatus = openAirEnabled && referencePoints && referencePoints . length > 0 ? 'с вычетом референса' : 'без вычета референса' ;
const titleText = ` С вип ${ sweep Number} ( ${ data Type} , ${ refStatus } ) ` ;
const axis Type = config . axis || 'abs ' ;
const refStatus = config . open _air ? 'с вычетом референса' : 'без вычета референса' ;
const titleText = ` С толбец ${ column Number} - Профиль глубины (${ axis Type} , ${ refStatus } ) ` ;
const layoutOverrides = {
title : { text : titleText , font : { size : 16 , color : '#f1f5f9' } } ,
xaxis : {
title : 'Частота (МГц )' ,
title : 'Глубина (м )' ,
gridcolor : '#334155' ,
zerolinecolor : '#475569' ,
color : '#cbd5e1' ,
@ -525,30 +489,19 @@ export class BScanClickHandler {
} ,
yaxis : {
title : 'Амплитуда' ,
side : 'left' ,
gridcolor : '#334155' ,
zerolinecolor : '#475569' ,
color : '#cbd5e1' ,
fixedrange : false
} ,
yaxis2 : {
title : 'Фаза (°)' ,
overlaying : 'y' ,
side : 'right' ,
gridcolor : '#334155' ,
zerolinecolor : '#475569' ,
color : '#cbd5e1' ,
fixedrange : false
} ,
showlegend : true ,
legend : { x : 0.01 , y : 0.99 } ,
showlegend : false ,
height : 500
} ;
const configOverrides = {
toImageButtonOptions : {
format : 'png' ,
filename : ` sweep -${ sweep Number} -preview ` ,
filename : ` column -${ column Number} -profile ` ,
height : 600 ,
width : 800 ,
scale : 1
@ -577,7 +530,7 @@ export class BScanClickHandler {
<div class="chart-card__title"> ${ titleText } </div>
</div>
<div class="chart-card__content">
<div class="chart-card__plot" id="sweep-preview -plot"></div>
<div class="chart-card__plot" id="column-profile -plot"></div>
</div>
` ;
container . appendChild ( card ) ;
@ -591,7 +544,7 @@ export class BScanClickHandler {
// Create plot after modal is shown
setTimeout ( ( ) => {
const plotElement = document . getElementById ( 'sweep-preview -plot' ) ;
const plotElement = document . getElementById ( 'column-profile -plot' ) ;
if ( plotElement ) {
createPlotlyPlot ( plotElement , traces , layoutOverrides , configOverrides ) ;
} else {
@ -619,10 +572,15 @@ export class BScanClickHandler {
modal . classList . remove ( 'modal--active' ) ;
document . body . style . overflow = '' ;
// Cleanup Plotly plot
const plotElemen t = document . getElementById ( 'sweep-preview -plot' ) ;
if ( plotElemen t ) {
cleanupPlotly ( plotElemen t ) ;
// Cleanup Plotly plot - try both possible plot IDs
const profilePlo t = document . getElementById ( 'column-profile -plot' ) ;
if ( profilePlo t ) {
cleanupPlotly ( profilePlo t ) ;
}
const sweepPlot = document . getElementById ( 'sweep-preview-plot' ) ;
if ( sweepPlot ) {
cleanupPlotly ( sweepPlot ) ;
}
}