| Crates.io | flow-plots |
| lib.rs | flow-plots |
| version | 0.2.0 |
| created_at | 2026-01-08 17:57:28.973176+00 |
| updated_at | 2026-01-21 19:12:46.664063+00 |
| description | Package for drawing and interacting with plots in flow cytometry data |
| homepage | |
| repository | https://github.com/jrmoynihan/flow/flow-plots |
| max_upload_size | |
| id | 2030801 |
| size | 267,531 |
A library for creating visualizations of flow cytometry data.
This library provides a flexible, extensible API for creating different types of plots from flow cytometry data. The architecture is designed to be easily extended with new plot types while maintaining clean separation of concerns.
Plot traituse flow_plots::{DensityPlot, DensityPlotOptions};
use flow_plots::render::RenderConfig;
let plot = DensityPlot::new();
let options = DensityPlotOptions::new()
.width(800)
.height(600)
.title("My Density Plot")
.build()?;
let data: Vec<(f32, f32)> = vec![(100.0, 200.0), (150.0, 250.0)];
let mut render_config = RenderConfig::default();
let bytes = plot.render(data, &options, &mut render_config)?;
This example shows the complete workflow from opening an FCS file to generating a plot:
use flow_plots::{DensityPlot, helpers};
use flow_plots::render::RenderConfig;
use flow_fcs::Fcs;
// Step 1: Open the FCS file from a file path
let fcs = Fcs::open("path/to/your/file.fcs")?;
// Step 2: Select parameters for the x and y axes
// You can find parameters by their channel name (e.g., "FSC-A", "SSC-A", "FL1-A")
let x_parameter = fcs.find_parameter("FSC-A")?;
let y_parameter = fcs.find_parameter("SSC-A")?;
// Step 3: Use the helper function to create plot options with sensible defaults
// This analyzes the FCS file and parameters to determine appropriate ranges and transforms
let mut builder = helpers::density_options_from_fcs(
&fcs,
x_parameter,
y_parameter,
)?;
// Step 4: Customize the options further if needed
let options = builder
.width(800)
.height(600)
.build()?;
// Step 5: Extract the data for plotting
// The helper function uses the parameter's transform to calculate appropriate ranges,
// so we should use the same data (raw or transformed) for plotting
let data: Vec<(f32, f32)> = fcs.get_xy_pairs("FSC-A", "SSC-A")?;
// Step 6: Create and render the plot
let plot = DensityPlot::new();
let mut render_config = RenderConfig::default();
let bytes = plot.render(data, &options, &mut render_config)?;
// Step 7: Use the bytes (JPEG-encoded image) as needed
// e.g., save to file, send over network, display in UI, etc.
Key Points:
Opening FCS files: Fcs::open(path) opens and parses an FCS file from a file path. The path must have a .fcs extension. This function:
Fcs struct ready for useFinding parameters: fcs.find_parameter(channel_name) finds a parameter by its channel name (e.g., "FSC-A", "SSC-A", "FL1-A"). Returns a Result<&Parameter> - an error if the parameter doesn't exist. To list all available parameters, use fcs.get_parameter_names_from_dataframe() which returns a Vec<String> of all channel names.
Automatic option configuration: helpers::density_options_from_fcs() analyzes the FCS file and parameters to automatically:
TransformType (typically Arcsinh) before calculating percentile bounds, ensuring the plot range reflects the transformed data scale$FIL keyword for the plot titleExtracting data: fcs.get_xy_pairs(x_param, y_param) extracts (x, y) coordinate pairs for plotting. This returns raw (untransformed) values, which is appropriate since the plot options handle transformation during rendering and axis labeling.
Error Handling:
All operations return Result types. Here's a more complete example with error handling:
use anyhow::Result;
fn create_plot_from_file(path: &str) -> Result<Vec<u8>> {
// Open the file - returns error if file doesn't exist or is invalid
let fcs = Fcs::open(path)?;
// Find parameters - returns error if parameter name doesn't exist
let x_parameter = fcs.find_parameter("FSC-A")
.map_err(|e| anyhow::anyhow!("Parameter 'FSC-A' not found: {}", e))?;
let y_parameter = fcs.find_parameter("SSC-A")
.map_err(|e| anyhow::anyhow!("Parameter 'SSC-A' not found: {}", e))?;
// Create options with automatic configuration
let options = helpers::density_options_from_fcs(&fcs, x_parameter, y_parameter)?
.width(800)
.height(600)
.build()?;
// Extract data - returns error if parameters don't exist
let data = fcs.get_xy_pairs("FSC-A", "SSC-A")?;
// Render the plot
let plot = DensityPlot::new();
let mut render_config = RenderConfig::default();
let bytes = plot.render(data, &options, &mut render_config)?;
Ok(bytes)
}
use flow_plots::{DensityPlot, DensityPlotOptions, RenderConfig, ProgressInfo};
use crate::plot_executor::with_render_lock;
use crate::commands::PlotProgressEvent;
let options = DensityPlotOptions::new()
.width(800)
.height(600)
// ... configure options
.build()?;
// Configure rendering with app-specific concerns
let mut render_config = RenderConfig {
progress: Some(Box::new(move |info: ProgressInfo| {
channel.send(PlotProgressEvent::Progress {
pixels: info.pixels,
percent: info.percent,
})?;
Ok(())
})),
};
// Wrap the render call with your executor's render lock
let bytes = with_render_lock(|| {
let plot = DensityPlot::new();
plot.render(data, &options, &mut render_config)
})?;
For processing multiple plots together, use the batch API:
use flow_plots::{DensityPlot, DensityPlotOptions};
use flow_plots::render::RenderConfig;
let plot = DensityPlot::new();
let mut render_config = RenderConfig::default();
// Prepare multiple plot requests
let requests: Vec<(Vec<(f32, f32)>, DensityPlotOptions)> = vec![
(
vec![(100.0, 200.0), (150.0, 250.0)], // Data for plot 1
DensityPlotOptions::new()
.width(800)
.height(600)
.title("Plot 1")
.build()?,
),
(
vec![(200.0, 300.0), (250.0, 350.0)], // Data for plot 2
DensityPlotOptions::new()
.width(800)
.height(600)
.title("Plot 2")
.build()?,
),
];
// Render all plots in batch
let plot_bytes: Vec<Vec<u8>> = plot.render_batch(&requests, &mut render_config)?;
// plot_bytes[0] contains the JPEG bytes for the first plot
// plot_bytes[1] contains the JPEG bytes for the second plot
For applications that want to orchestrate rendering themselves (e.g., custom progress reporting, parallel rendering, etc.), use the low-level batch density calculation:
use flow_plots::density_calc::calculate_density_per_pixel_batch;
use flow_plots::{DensityPlotOptions};
use flow_plots::render::{RenderConfig, plotters_backend::render_pixels};
use anyhow::Result;
// Calculate density for multiple plots
let requests: Vec<(Vec<(f32, f32)>, DensityPlotOptions)> = vec![
(data1, options1),
(data2, options2),
(data3, options3),
];
// Get raw pixel data for all plots (density calculation only)
let raw_pixels_batch = calculate_density_per_pixel_batch(&requests);
// Now you can orchestrate rendering yourself
let mut render_config = RenderConfig::default();
// Example: Render plots in parallel using rayon
use rayon::prelude::*;
let plot_bytes: Result<Vec<Vec<u8>>> = raw_pixels_batch
.par_iter()
.enumerate()
.map(|(i, raw_pixels)| {
// Custom rendering logic here
// You have access to raw_pixels and requests[i].1 (options)
render_pixels(raw_pixels.clone(), &requests[i].1, &mut render_config)
})
.collect();
let plot_bytes = plot_bytes?;
Note: While batch processing is available, sequential processing (calling render() in a loop) is typically faster for most use cases. See GPU_EVALUATION.md for performance analysis.
The library is organized into several modules:
options: Plot configuration types using the builder pattern
BasePlotOptions: Layout and display settingsAxisOptions: Axis configuration (range, transform, label)DensityPlotOptions: Complete density plot configurationplots: Plot implementations
DensityPlot: 2D density plot implementationPlot trait: Interface for all plot typesrender: Rendering infrastructure
RenderConfig: Configuration for rendering (progress callbacks)ProgressInfo: Progress information structureplotters_backend: Plotters-based rendering implementationdensity: Density calculation algorithmscolormap: Color map implementationshelpers: Helper functions for common initialization patternsTo add a new plot type:
DotPlotOptions) that implements PlotOptionsDotPlot) that implements the Plot traitrender method with your plot-specific logicExample:
use flow_plots::plots::traits::Plot;
use flow_plots::options::PlotOptions;
use flow_plots::render::RenderConfig;
use flow_plots::PlotBytes;
use anyhow::Result;
struct DotPlotOptions {
base: BasePlotOptions,
// ... your plot-specific options
}
impl PlotOptions for DotPlotOptions {
fn base(&self) -> &BasePlotOptions {
&self.base
}
}
struct DotPlot;
impl Plot for DotPlot {
type Options = DotPlotOptions;
type Data = Vec<(f32, f32)>;
fn render(
&self,
data: Self::Data,
options: &Self::Options,
render_config: &mut RenderConfig,
) -> Result<PlotBytes> {
// ... your rendering logic
Ok(vec![])
}
}
PlotOptions APIOld API:
let options = PlotOptions::new(
fcs,
x_parameter,
y_parameter,
Some(800),
Some(600),
None,
None,
None,
None,
None,
)?;
New API:
// Option 1: Use helper function
let mut builder = helpers::density_options_from_fcs(fcs, x_param, y_param)?;
let options = builder
.width(800)
.height(600)
.build()?;
// Option 2: Manual construction
let options = DensityPlotOptions::new()
.width(800)
.height(600)
.x_axis(|axis| axis
.range(0.0..=200_000.0)
.transform(TransformType::Arcsinh { cofactor: 150.0 }))
.y_axis(|axis| axis
.range(0.0..=200_000.0)
.transform(TransformType::Arcsinh { cofactor: 150.0 }))
.build()?;
draw_plot FunctionOld API:
let (bytes, _, _, _) = draw_plot(pixels, &options)?;
New API:
let plot = DensityPlot::new();
let mut render_config = RenderConfig::default();
let bytes = plot.render(data, &options, &mut render_config)?;
MIT