@sjcrh/proteinpaint-rust 2.185.0 → 2.186.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/volcano.rs +73 -12
package/package.json
CHANGED
package/src/volcano.rs
CHANGED
|
@@ -40,10 +40,21 @@ struct Input {
|
|
|
40
40
|
|
|
41
41
|
#[derive(Serialize)]
|
|
42
42
|
struct PlotExtent {
|
|
43
|
+
/// Padded data extents — used to position overlay dots so points near the
|
|
44
|
+
/// real-data edge stay fully visible (mirror of manhattan's yPlot domain).
|
|
43
45
|
x_min: f64,
|
|
44
46
|
x_max: f64,
|
|
45
47
|
y_min: f64,
|
|
46
48
|
y_max: f64,
|
|
49
|
+
/// Unpadded data extents — used for the visible axis labels/ticks so the
|
|
50
|
+
/// axis only spans the real data region (mirror of manhattan's yAxisScale).
|
|
51
|
+
x_min_unpadded: f64,
|
|
52
|
+
x_max_unpadded: f64,
|
|
53
|
+
y_min_unpadded: f64,
|
|
54
|
+
y_max_unpadded: f64,
|
|
55
|
+
/// Dot radius in pixels (echoed back so the client can size overlay rings
|
|
56
|
+
/// to match the PNG without recomputing the heuristic).
|
|
57
|
+
dot_radius_px: f64,
|
|
47
58
|
pixel_width: u32,
|
|
48
59
|
pixel_height: u32,
|
|
49
60
|
/// Inner drawing rect inside the PNG. Client overlay circles are
|
|
@@ -145,19 +156,45 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|
|
145
156
|
y_max_data = y_max_data.max(pt.y);
|
|
146
157
|
}
|
|
147
158
|
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
// Unpadded axis extents — symmetric on x, raw data bounds. The dot-radius
|
|
160
|
+
// pad below provides pixel-level headroom so we don't need extra data-range
|
|
161
|
+
// breathing room (mirrors manhattan_plot.rs's tighter feel). Fallback to 1.0
|
|
162
|
+
// when the data has zero spread to keep the chart range valid.
|
|
163
|
+
let x_span = if x_abs_max > 0.0 { x_abs_max } else { 1.0 };
|
|
164
|
+
let (x_min_unpadded, x_max_unpadded) = (-x_span, x_span);
|
|
165
|
+
let y_min_unpadded = 0f64;
|
|
166
|
+
let y_max_unpadded = if y_max_data > 0.0 { y_max_data } else { 1.0 };
|
|
152
167
|
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
168
|
+
// Normalize the dot radius once into the integer pixel count plotters will
|
|
169
|
+
// actually draw, with a min of 1 so sub-pixel inputs don't collapse to a
|
|
170
|
+
// zero-radius dot. This single value drives both PNG padding and circle
|
|
171
|
+
// rendering, keeping the geometry self-consistent (matches manhattan).
|
|
172
|
+
let radius_px = (input.dot_radius as i32).max(1);
|
|
173
|
+
// Pad PNG by 2*radius_px so dots near the data edges stay fully visible.
|
|
174
|
+
let pad_px = (2 * radius_px) as u32;
|
|
175
|
+
let (w, h) = (input.pixel_width + pad_px, input.pixel_height + pad_px);
|
|
157
176
|
if w == 0 || h == 0 || w > 4000 || h > 4000 {
|
|
158
177
|
return Err(format!("pixel dimensions {}x{} out of range (1–4000)", w, h).into());
|
|
159
178
|
}
|
|
179
|
+
|
|
180
|
+
// Convert pixel padding to data units using the unpadded extents and the
|
|
181
|
+
// unpadded pixel dimensions. Per-axis pad in data space = radius_px * (data
|
|
182
|
+
// range / pixel range) — keeps the data/pixel ratio identical between
|
|
183
|
+
// padded and unpadded space.
|
|
184
|
+
let x_data_per_px = (x_max_unpadded - x_min_unpadded) / input.pixel_width as f64;
|
|
185
|
+
let y_data_per_px = (y_max_unpadded - y_min_unpadded) / input.pixel_height as f64;
|
|
186
|
+
let x_pad_data = radius_px as f64 * x_data_per_px;
|
|
187
|
+
let y_pad_data = radius_px as f64 * y_data_per_px;
|
|
188
|
+
let x_min = x_min_unpadded - x_pad_data;
|
|
189
|
+
let x_max = x_max_unpadded + x_pad_data;
|
|
190
|
+
let y_min = y_min_unpadded - y_pad_data;
|
|
191
|
+
let y_max = y_max_unpadded + y_pad_data;
|
|
160
192
|
let mut buffer = vec![0u8; (w as usize) * (h as usize) * 3];
|
|
193
|
+
// Per-point pixel coords as plotters actually rasterizes them. Returned to
|
|
194
|
+
// the client so the SVG overlay rings sit exactly on top of the PNG dots
|
|
195
|
+
// instead of being recomputed from data coords (which loses sub-pixel
|
|
196
|
+
// precision under plotters' integer truncation).
|
|
197
|
+
let mut all_pixel_coords: Vec<(f64, f64)> = Vec::with_capacity(points.len());
|
|
161
198
|
{
|
|
162
199
|
let backend = BitMapBackend::with_buffer(&mut buffer, (w, h));
|
|
163
200
|
let root = backend.into_drawing_area();
|
|
@@ -191,20 +228,26 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|
|
191
228
|
filled: false,
|
|
192
229
|
stroke_width: 1,
|
|
193
230
|
};
|
|
194
|
-
let radius = input.dot_radius as i32;
|
|
195
231
|
|
|
196
232
|
// Draw non-significant first so significant rings overlay on top.
|
|
197
233
|
chart.draw_series(
|
|
198
234
|
points
|
|
199
235
|
.iter()
|
|
200
236
|
.filter(|p| !p.significant)
|
|
201
|
-
.map(|p| Circle::new((p.fc, p.y),
|
|
237
|
+
.map(|p| Circle::new((p.fc, p.y), radius_px, ring(color_non))),
|
|
202
238
|
)?;
|
|
203
239
|
chart.draw_series(points.iter().filter(|p| p.significant).map(|p| {
|
|
204
240
|
let c = if p.fc > 0.0 { color_up } else { color_down };
|
|
205
|
-
Circle::new((p.fc, p.y),
|
|
241
|
+
Circle::new((p.fc, p.y), radius_px, ring(c))
|
|
206
242
|
}))?;
|
|
207
243
|
|
|
244
|
+
// Mirror manhattan_plot.rs: capture the exact pixel coords plotters
|
|
245
|
+
// used for each point so the client overlay can land on them precisely.
|
|
246
|
+
for p in points.iter() {
|
|
247
|
+
let (px, py) = chart.backend_coord(&(p.fc, p.y));
|
|
248
|
+
all_pixel_coords.push((px as f64, py as f64));
|
|
249
|
+
}
|
|
250
|
+
|
|
208
251
|
root.present()?;
|
|
209
252
|
}
|
|
210
253
|
|
|
@@ -216,7 +259,18 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|
|
216
259
|
if let Some(cap) = input.max_interactive_dots {
|
|
217
260
|
sig_points.truncate(cap);
|
|
218
261
|
}
|
|
219
|
-
let dots: Vec<Value> = sig_points
|
|
262
|
+
let dots: Vec<Value> = sig_points
|
|
263
|
+
.iter()
|
|
264
|
+
.map(|p| {
|
|
265
|
+
let mut row = input.rows[p.idx].clone();
|
|
266
|
+
let (px, py) = all_pixel_coords[p.idx];
|
|
267
|
+
if let Value::Object(ref mut m) = row {
|
|
268
|
+
m.insert("pixel_x".to_string(), Value::from(px));
|
|
269
|
+
m.insert("pixel_y".to_string(), Value::from(py));
|
|
270
|
+
}
|
|
271
|
+
row
|
|
272
|
+
})
|
|
273
|
+
.collect();
|
|
220
274
|
|
|
221
275
|
let output = Output {
|
|
222
276
|
png: BASE64.encode(&encode_rgb_to_png(&buffer, w, h)?),
|
|
@@ -225,6 +279,13 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|
|
225
279
|
x_max,
|
|
226
280
|
y_min,
|
|
227
281
|
y_max,
|
|
282
|
+
x_min_unpadded,
|
|
283
|
+
x_max_unpadded,
|
|
284
|
+
y_min_unpadded,
|
|
285
|
+
y_max_unpadded,
|
|
286
|
+
// Echo the normalized integer radius plotters actually drew so the
|
|
287
|
+
// SVG overlay sizes its rings to match the rasterized PNG dots.
|
|
288
|
+
dot_radius_px: radius_px as f64,
|
|
228
289
|
pixel_width: w,
|
|
229
290
|
pixel_height: h,
|
|
230
291
|
plot_left: 0,
|