Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Create interactive HTML plots with plotly and bokeh for exploratory data analysis and web-based sharing of omics visualizations. Use when building zoomable, hoverable plots for data exploration or web dashboards.
.claude/skills/bio-data-visualization-interactive-visualization/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 16% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 40% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 37% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 71% | 0% |
<!--
#
#
-->
pythonimport plotly.express as px import plotly.graph_objects as go import pandas as pd # Scatter plot fig = px.scatter(df, x='PC1', y='PC2', color='condition', hover_data=['sample'], title='PCA Plot') fig.write_html('pca_interactive.html') fig.show()
pythonimport plotly.express as px df['neg_log_pval'] = -np.log10(df['pvalue']) df['significant'] = (df['padj'] < 0.05) & (abs(df['log2FoldChange']) > 1) fig = px.scatter(df, x='log2FoldChange', y='neg_log_pval', color='significant', hover_name='gene', hover_data=['baseMean', 'padj'], color_discrete_map={True: 'red', False: 'grey'}, title='Interactive Volcano Plot') fig.add_hline(y=-np.log10(0.05), line_dash='dash', line_color='grey') fig.add_vline(x=-1, line_dash='dash', line_color='grey') fig.add_vline(x=1, line_dash='dash', line_color='grey') fig.update_layout(xaxis_title='Log2 Fold Change', yaxis_title='-Log10 P-value') fig.write_html('volcano_interactive.html')
pythonimport plotly.express as px fig = px.imshow(df, color_continuous_scale='RdBu_r', aspect='auto', labels=dict(x='Samples', y='Genes', color='Expression')) fig.update_xaxes(tickangle=45) fig.write_html('heatmap_interactive.html')
pythonfrom plotly.subplots import make_subplots import plotly.graph_objects as go fig = make_subplots(rows=1, cols=2, subplot_titles=('PCA', 'Volcano')) fig.add_trace(go.Scatter(x=df['PC1'], y=df['PC2'], mode='markers', marker=dict(color=df['condition'].map({'Control': 'blue', 'Treatment': 'red'})), text=df['sample'], name='PCA'), row=1, col=1) fig.add_trace(go.Scatter(x=de['log2FC'], y=-np.log10(de['pvalue']), mode='markers', marker=dict(color=de['significant'].map({True: 'red', False: 'grey'})), text=de['gene'], name='Volcano'), row=1, col=2) fig.update_layout(height=500, width=1000, showlegend=False) fig.write_html('combined_interactive.html')
rlibrary(plotly) # From ggplot2 p <- ggplot(df, aes(PC1, PC2, color = condition, text = sample)) + geom_point() ggplotly(p) # Native plotly plot_ly(df, x = ~PC1, y = ~PC2, color = ~condition, text = ~sample, type = 'scatter', mode = 'markers') %>% layout(title = 'PCA Plot')
rlibrary(plotly) de_results$text <- paste0('Gene: ', de_results$gene, '<br>', 'baseMean: ', round(de_results$baseMean, 2), '<br>', 'log2FC: ', round(de_results$log2FoldChange, 2), '<br>', 'padj: ', formatC(de_results$padj, format = 'e', digits = 2)) plot_ly(de_results, x = ~log10(baseMean), y = ~log2FoldChange, color = ~(padj < 0.05), colors = c('grey', 'red'), text = ~text, hoverinfo = 'text', type = 'scatter', mode = 'markers', marker = list(size = 5, opacity = 0.6)) %>% layout(title = 'MA Plot', xaxis = list(title = 'Log10 Mean Expression'), yaxis = list(title = 'Log2 Fold Change'))
pythonimport plotly.express as px from plotly.subplots import make_subplots fig = px.scatter_matrix(df, dimensions=['PC1', 'PC2', 'PC3'], color='condition') fig.write_html('scatter_matrix.html')
pythonfrom bokeh.plotting import figure, output_file, save from bokeh.models import ColumnDataSource, HoverTool output_file('pca_bokeh.html') source = ColumnDataSource(df) p = figure(title='PCA Plot', x_axis_label='PC1', y_axis_label='PC2', tools='pan,wheel_zoom,box_zoom,reset,hover,save') p.circle('PC1', 'PC2', source=source, size=10, alpha=0.6, color='color', legend_field='condition') hover = p.select(dict(type=HoverTool)) hover.tooltips = [('Sample', '@sample'), ('Condition', '@condition')] save(p)
pythonfrom bokeh.layouts import column from bokeh.models import Select from bokeh.io import curdoc select = Select(title='Color by:', value='condition', options=['condition', 'batch', 'cluster']) def update(attr, old, new): p.circle.glyph.fill_color = new select.on_change('value', update) curdoc().add_root(column(select, p))
python# plotly fig.write_html('plot.html') fig.write_json('plot.json') # bokeh from bokeh.io import save, export_png save(p, filename='plot.html') export_png(p, filename='plot.png') # requires selenium
python# plotly - works automatically in Jupyter fig.show() # bokeh from bokeh.io import output_notebook, show output_notebook() show(p)
<!-- AUTHOR_SIGNATURE: 9a7f3c2e-MD-BABU-MIA-2026-MSSM-SECURE -->
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 18,779 | 9,285 | -51% | 1 | 1 | 0% | 3,480 | 3,850 | +11% | 0 | 0 | — |
case-02 | fail→pass | 12,180 | 7,293 | -40% | 1 | 1 | 0% | 2,870 | 3,340 | +16% | 0 | 0 | — |
case-08 | pass→pass | 3,559 | 3,188 | -10% | 1 | 1 | 0% | 622 | 2,267 | +264% | 0 | 0 | — |
case-09 | pass→pass | 11,005 | 6,603 | -40% | 1 | 1 | 0% | 2,172 | 2,954 | +36% | 0 | 0 | — |
case-10 | pass→pass | 6,472 | 3,940 | -39% | 1 | 1 | 0% | 1,147 | 2,378 | +107% | 0 | 0 | — |
case-11 | fail→fail | 11,590 | 7,529 | -35% | 1 | 1 | 0% | 2,321 | 3,157 | +36% | 0 | 0 | — |
case-12 | fail→pass | 13,369 | 10,143 | -24% | 1 | 1 | 0% | 2,711 | 3,793 | +40% | 0 | 0 | — |
case-22 | pass→pass | 11,121 | 10,246 | -8% | 1 | 1 | 0% | 2,470 | 3,939 | +59% | 0 | 0 | — |
case-03 | fail→fail | 17,638 | 14,256 | -19% | 1 | 1 | 0% | 4,085 | 4,967 | +22% | 0 | 0 | — |
case-04 | pass→pass | 8,973 | 5,211 | -42% | 1 | 1 | 0% | 1,656 | 2,646 | +60% | 0 | 0 | — |
case-05 | pass→pass | 17,549 | 12,045 | -31% | 1 | 1 | 0% | 2,591 | 4,339 | +67% | 0 | 0 | — |
case-06 | pass→pass | 9,716 | 5,637 | -42% | 1 | 1 | 0% | 1,823 | 2,830 | +55% | 0 | 0 | — |
case-07 | fail→fail | 18,325 | 16,076 | -12% | 1 | 1 | 0% | 3,546 | 5,127 | +45% | 0 | 0 | — |
case-13 | pass→pass | 9,415 | 6,094 | -35% | 1 | 1 | 0% | 1,775 | 2,928 | +65% | 0 | 0 | — |
case-14 | pass→pass | 9,655 | 8,364 | -13% | 1 | 1 | 0% | 1,908 | 3,351 | +76% | 0 | 0 | — |
case-15 | fail→fail | 14,280 | 7,473 | -48% | 1 | 1 | 0% | 3,013 | 3,167 | +5% | 0 | 0 | — |
case-16 | pass→pass | 5,327 | 1,886 | -65% | 1 | 1 | 0% | 1,037 | 1,987 | +92% | 0 | 0 | — |
case-17 | fail→pass | 10,045 | 4,946 | -51% | 1 | 1 | 0% | 2,049 | 2,811 | +37% | 0 | 0 | — |
case-18 | fail→pass | 8,887 | 4,860 | -45% | 1 | 1 | 0% | 1,677 | 2,679 | +60% | 0 | 0 | — |
case-19 | fail→pass | 7,017 | 3,272 | -53% | 1 | 1 | 0% | 1,400 | 2,395 | +71% | 0 | 0 | — |
case-20 | pass→pass | 8,125 | 5,728 | -30% | 1 | 1 | 0% | 1,568 | 2,842 | +81% | 0 | 0 | — |
case-21 | pass→pass | 14,702 | 8,339 | -43% | 1 | 1 | 0% | 3,152 | 3,589 | +14% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/26/2026 | +18% |
Other measured skills in the registry, with their headline benchmark lift.