Here is the complete code, it takes an mzML file and displays a total ion chromatogram then when a point is clicked on the line graph the components of that point are meant to be displayed below in a new subplot. I have also included some sample data printed from the array, this seems to plot fine when i replace scan_mz[pointNumber] and scan_intensities[pointNumber]. Thanks.
[array([ 41. , 42. , 43. , 43.9, 45.1, 55. , 58.8, 73.1, 74. ,
75. , 75.9, 77.1, 96. , 100.1, 102.9, 116.9, 132.9, 135.1,
145.9, 147. , 148.1, 161. , 163. , 190.9, 191.9, 193. , 207. ,
208. , 209. , 253. , 281.2, 282. , 283.1, 329. , 355. , 405. ,
429. ], dtype=float32)]
[array([ 267., 206., 292., 723., 388., 211., 236., 1635., 294.,
1084., 331., 200., 226., 165., 194., 274., 403., 250.,
271., 395., 200., 183., 157., 465., 246., 237., 3329.,
758., 561., 403., 1187., 317., 323., 173., 217., 216.,
192.], dtype=float32)]
import dash
from dash import html, dcc
import plotly.express as px
from pyteomics import mzml
import pandas as pd
from dash.dependencies import Input, Output, State
import base64
from plotly.subplots import make_subplots
#import plotly.graph_objects as go
app = dash.Dash(__name__)
upload_component = dcc.Upload(
id="upload-data",
children=html.Div(["Drag and drop or select a mzML file"]),
style={
"width": "50%",
"height": "60px",
"lineHeight": "60px",
"borderWidth": "1px",
"borderStyle": "dashed",
"borderRadius": "5px",
"textAlign": "center",
},
multiple=False
)
graph_component = dcc.Graph(id="graph", style={"height": "400px"})
fig = make_subplots(rows=2, cols=1)
pointNumber = 0
scan_mz = []
scan_intensities = []
debug_component = html.Div(
id="output-container"
)
app.layout = html.Div(
[
upload_component,
debug_component,
graph_component,
]
)
@app.callback(
Output(component_id="graph", component_property="figure"),
Input(component_id="upload-data", component_property="contents"),
)
def process_mzml_and_plot(uploaded_file):
if uploaded_file is not None:
content_type, content_string = uploaded_file.split(",")
decoded = base64.b64decode(content_string)
with open("temp.mzml", "wb") as f:
f.write(decoded)
try:
with mzml.read("temp.mzml") as reader:
all_scan_times = []
all_intensities = []
for scan in reader:
scan_time_minutesStr = scan["scanList"]["scan"][0]["scan start time"]
intensity_array = scan["intensity array"]
mz = scan["m/z array"]
scan_time_minutes = round(float(scan_time_minutesStr), 5)
scan_intensities.append([intensity_array])
scan_mz.append([mz])
intensity = sum(intensity_array)
all_scan_times.append(scan_time_minutes)
all_intensities.append(intensity)
df = pd.DataFrame({"time": all_scan_times, "intensity": all_intensities})
fig.add_trace(px.line(df, x="time", y="intensity", title="TIC").data[0], row=1, col=1)
fig.update_xaxes(showspikes=True, spikemode="across")
fig.update_traces(hovertemplate=None, hoverinfo="none")
fig.update_layout(hovermode="x unified", spikedistance=-1, height=800)
return fig
except Exception as e:
print(f"Failed to process mzML file: {e}")
return px.line(title="Failed to process mzML file")
else:
return dash.no_update
@app.callback(
Output("graph", "figure", allow_duplicate=True),
[Input("graph", "clickData")],
[State("graph", "figure")],
prevent_initial_call=True,
)
def handle_graph_click(clickData, figure):
if clickData is not None:
pointNumber = clickData['points'][0]['pointNumber']
print(scan_mz[pointNumber])
print(scan_intensities[pointNumber])
ms_df = pd.DataFrame({'mz':scan_mz[pointNumber], 'intensity':scan_intensities[pointNumber]})
fig.add_trace(px.scatter(ms_df, x='mz', y='intensity').data[0], row=2, col=1)
return fig
if __name__ == "__main__":
app.run_server(debug=True)