implement intesections + misc function options
This commit is contained in:
parent
bccb19cecc
commit
d95b29fb7f
6
TODO.md
6
TODO.md
@ -1,14 +1,8 @@
|
|||||||
## TODO:
|
## TODO:
|
||||||
1. Function management
|
1. Function management
|
||||||
- Integrals between functions (too hard to implement, maybe will shelve)
|
- Integrals between functions (too hard to implement, maybe will shelve)
|
||||||
- Display intersection between functions (would have to rewrite a lot of the function plotting handling)
|
|
||||||
- [Drag and drop support](https://github.com/emilk/egui/discussions/1530) in the UI to re-order functions
|
|
||||||
- Hide/disable functions
|
|
||||||
- Prevent user from making too many function entries
|
- Prevent user from making too many function entries
|
||||||
- Display function errors as tooltips or a warning box (not preventing the display of the graph)
|
|
||||||
- Clone functions
|
|
||||||
2. Smart display of graph
|
2. Smart display of graph
|
||||||
- Display of intersections between functions
|
|
||||||
3. Allow constants in min/max integral input (like pi or euler's number)
|
3. Allow constants in min/max integral input (like pi or euler's number)
|
||||||
4. Sliding values for functions (like a user-interactable slider that adjusts a variable in the function, like desmos)
|
4. Sliding values for functions (like a user-interactable slider that adjusts a variable in the function, like desmos)
|
||||||
5. Fix integral display
|
5. Fix integral display
|
||||||
|
|||||||
7
build.rs
7
build.rs
@ -131,7 +131,12 @@ fn main() {
|
|||||||
"emoji-icon-font".to_owned(),
|
"emoji-icon-font".to_owned(),
|
||||||
Arc::new(
|
Arc::new(
|
||||||
FontData::from_owned(
|
FontData::from_owned(
|
||||||
font_stripper("emoji-icon-font.ttf", "emoji-icon.ttf", vec!['⚙']).unwrap(),
|
font_stripper(
|
||||||
|
"emoji-icon-font.ttf",
|
||||||
|
"emoji-icon.ttf",
|
||||||
|
vec!['⚙', '⎘', '👁', '○', '⬆', '⬇', '⚠'],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.tweak(FontTweak {
|
.tweak(FontTweak {
|
||||||
scale: 0.8,
|
scale: 0.8,
|
||||||
|
|||||||
@ -45,6 +45,9 @@ pub struct FunctionEntry {
|
|||||||
|
|
||||||
pub nth_derviative: bool,
|
pub nth_derviative: bool,
|
||||||
|
|
||||||
|
/// If the function is visible on the graph
|
||||||
|
pub visible: bool,
|
||||||
|
|
||||||
pub back_data: Vec<PlotPoint>,
|
pub back_data: Vec<PlotPoint>,
|
||||||
pub integral_data: Option<(Vec<Bar>, f64)>,
|
pub integral_data: Option<(Vec<Bar>, f64)>,
|
||||||
pub derivative_data: Vec<PlotPoint>,
|
pub derivative_data: Vec<PlotPoint>,
|
||||||
@ -67,6 +70,7 @@ impl Hash for FunctionEntry {
|
|||||||
self.nth_derviative.hash(state);
|
self.nth_derviative.hash(state);
|
||||||
self.curr_nth.hash(state);
|
self.curr_nth.hash(state);
|
||||||
self.settings_opened.hash(state);
|
self.settings_opened.hash(state);
|
||||||
|
self.visible.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,11 +79,12 @@ impl Serialize for FunctionEntry {
|
|||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
let mut s = serializer.serialize_struct("FunctionEntry", 4)?;
|
let mut s = serializer.serialize_struct("FunctionEntry", 5)?;
|
||||||
s.serialize_field("raw_func_str", &self.raw_func_str)?;
|
s.serialize_field("raw_func_str", &self.raw_func_str)?;
|
||||||
s.serialize_field("integral", &self.integral)?;
|
s.serialize_field("integral", &self.integral)?;
|
||||||
s.serialize_field("derivative", &self.derivative)?;
|
s.serialize_field("derivative", &self.derivative)?;
|
||||||
s.serialize_field("curr_nth", &self.curr_nth)?;
|
s.serialize_field("curr_nth", &self.curr_nth)?;
|
||||||
|
s.serialize_field("visible", &self.visible)?;
|
||||||
|
|
||||||
s.end()
|
s.end()
|
||||||
}
|
}
|
||||||
@ -96,6 +101,12 @@ impl<'de> Deserialize<'de> for FunctionEntry {
|
|||||||
integral: bool,
|
integral: bool,
|
||||||
derivative: bool,
|
derivative: bool,
|
||||||
curr_nth: usize,
|
curr_nth: usize,
|
||||||
|
#[serde(default = "default_visible")]
|
||||||
|
visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_visible() -> bool {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
let helper = Helper::deserialize(deserializer)?;
|
let helper = Helper::deserialize(deserializer)?;
|
||||||
@ -115,6 +126,7 @@ impl<'de> Deserialize<'de> for FunctionEntry {
|
|||||||
new_func_entry.integral = helper.integral;
|
new_func_entry.integral = helper.integral;
|
||||||
new_func_entry.derivative = helper.derivative;
|
new_func_entry.derivative = helper.derivative;
|
||||||
new_func_entry.curr_nth = helper.curr_nth;
|
new_func_entry.curr_nth = helper.curr_nth;
|
||||||
|
new_func_entry.visible = helper.visible;
|
||||||
|
|
||||||
Ok(new_func_entry)
|
Ok(new_func_entry)
|
||||||
}
|
}
|
||||||
@ -129,6 +141,7 @@ impl Default for FunctionEntry {
|
|||||||
integral: false,
|
integral: false,
|
||||||
derivative: false,
|
derivative: false,
|
||||||
nth_derviative: false,
|
nth_derviative: false,
|
||||||
|
visible: true,
|
||||||
back_data: Vec::new(),
|
back_data: Vec::new(),
|
||||||
integral_data: None,
|
integral_data: None,
|
||||||
derivative_data: Vec::new(),
|
derivative_data: Vec::new(),
|
||||||
@ -374,7 +387,7 @@ impl FunctionEntry {
|
|||||||
settings: &AppSettings,
|
settings: &AppSettings,
|
||||||
main_plot_color: Color32,
|
main_plot_color: Color32,
|
||||||
) -> Option<f64> {
|
) -> Option<f64> {
|
||||||
if self.test_result.is_some() | self.function.is_none() {
|
if self.test_result.is_some() | self.function.is_none() | !self.visible {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -86,9 +86,14 @@ impl FunctionManager {
|
|||||||
let initial_hash = self.get_hash();
|
let initial_hash = self.get_hash();
|
||||||
|
|
||||||
let can_remove = self.functions.len() > 1;
|
let can_remove = self.functions.len() > 1;
|
||||||
|
let can_add = self.functions.len() < COLORS.len();
|
||||||
|
let num_functions = self.functions.len();
|
||||||
|
|
||||||
let available_width = ui.available_width();
|
let available_width = ui.available_width();
|
||||||
let mut remove_i: Option<usize> = None;
|
let mut remove_i: Option<usize> = None;
|
||||||
|
let mut clone_i: Option<usize> = None;
|
||||||
|
let mut move_up_i: Option<usize> = None;
|
||||||
|
let mut move_down_i: Option<usize> = None;
|
||||||
let target_size = vec2(available_width, crate::consts::FONT_SIZE);
|
let target_size = vec2(available_width, crate::consts::FONT_SIZE);
|
||||||
for (i, (te_id, function)) in self.functions.iter_mut().map(|(a, b)| (*a, b)).enumerate() {
|
for (i, (te_id, function)) in self.functions.iter_mut().map(|(a, b)| (*a, b)).enumerate() {
|
||||||
let mut new_string = function.autocomplete.string.clone();
|
let mut new_string = function.autocomplete.string.clone();
|
||||||
@ -116,6 +121,19 @@ impl FunctionManager {
|
|||||||
// Only keep valid chars
|
// Only keep valid chars
|
||||||
new_string.retain(crate::misc::is_valid_char);
|
new_string.retain(crate::misc::is_valid_char);
|
||||||
|
|
||||||
|
// Display error indicator with tooltip if there's a parsing error
|
||||||
|
if let Some(error) = function.get_test_result() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(egui::RichText::new("⚠").color(egui::Color32::YELLOW))
|
||||||
|
.on_hover_text(error);
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new(error)
|
||||||
|
.color(egui::Color32::LIGHT_RED)
|
||||||
|
.small(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// If not fully open, return here as buttons cannot yet be displayed, therefore the user is inable to mark it for deletion
|
// If not fully open, return here as buttons cannot yet be displayed, therefore the user is inable to mark it for deletion
|
||||||
let animate_bool = ui.ctx().animate_bool(te_id, re.has_focus());
|
let animate_bool = ui.ctx().animate_bool(te_id, re.has_focus());
|
||||||
if animate_bool == 1.0 {
|
if animate_bool == 1.0 {
|
||||||
@ -197,6 +215,47 @@ impl FunctionManager {
|
|||||||
remove_i = Some(i);
|
remove_i = Some(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle visibility
|
||||||
|
function.visible.bitxor_assign(
|
||||||
|
ui.add(button_area_button(if function.visible {
|
||||||
|
"👁"
|
||||||
|
} else {
|
||||||
|
"○"
|
||||||
|
}))
|
||||||
|
.on_hover_text(match function.visible {
|
||||||
|
true => "Hide Function",
|
||||||
|
false => "Show Function",
|
||||||
|
})
|
||||||
|
.clicked(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clone function
|
||||||
|
if ui
|
||||||
|
.add_enabled(can_add, button_area_button("⎘"))
|
||||||
|
.on_hover_text("Clone Function")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
clone_i = Some(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move up (only if not first)
|
||||||
|
if ui
|
||||||
|
.add_enabled(i > 0, button_area_button("⬆"))
|
||||||
|
.on_hover_text("Move Up")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
move_up_i = Some(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move down (only if not last)
|
||||||
|
if ui
|
||||||
|
.add_enabled(i < num_functions - 1, button_area_button("⬇"))
|
||||||
|
.on_hover_text("Move Down")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
move_down_i = Some(i);
|
||||||
|
}
|
||||||
|
|
||||||
ui.add_enabled_ui(function.is_some(), |ui| {
|
ui.add_enabled_ui(function.is_some(), |ui| {
|
||||||
// Toggle integral being enabled or not
|
// Toggle integral being enabled or not
|
||||||
function.integral.bitxor_assign(
|
function.integral.bitxor_assign(
|
||||||
@ -240,6 +299,26 @@ impl FunctionManager {
|
|||||||
self.functions.remove(remove_i_unwrap);
|
self.functions.remove(remove_i_unwrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone function if the user requests it
|
||||||
|
if let Some(clone_i_unwrap) = clone_i {
|
||||||
|
let cloned = self.functions[clone_i_unwrap].1.clone();
|
||||||
|
self.push_cloned(cloned);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move function up if the user requests it
|
||||||
|
if let Some(i) = move_up_i
|
||||||
|
&& i > 0
|
||||||
|
{
|
||||||
|
self.functions.swap(i, i - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move function down if the user requests it
|
||||||
|
if let Some(i) = move_down_i
|
||||||
|
&& i < self.functions.len() - 1
|
||||||
|
{
|
||||||
|
self.functions.swap(i, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
let final_hash = self.get_hash();
|
let final_hash = self.get_hash();
|
||||||
|
|
||||||
initial_hash != final_hash
|
initial_hash != final_hash
|
||||||
@ -253,6 +332,16 @@ impl FunctionManager {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a cloned function entry
|
||||||
|
pub fn push_cloned(&mut self, mut entry: FunctionEntry) {
|
||||||
|
// Reset settings_opened so the cloned function doesn't have settings open
|
||||||
|
entry.settings_opened = false;
|
||||||
|
self.functions.push((
|
||||||
|
create_id(random_u64().expect("unable to generate random id")),
|
||||||
|
entry,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/// Detect if any functions are using integrals
|
/// Detect if any functions are using integrals
|
||||||
pub fn any_using_integral(&self) -> bool {
|
pub fn any_using_integral(&self) -> bool {
|
||||||
self.functions.iter().any(|(_, func)| func.integral)
|
self.functions.iter().any(|(_, func)| func.integral)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use crate::{
|
|||||||
consts::{BUILD_INFO, COLORS, DEFAULT_INTEGRAL_NUM, DEFAULT_MAX_X, DEFAULT_MIN_X, build},
|
consts::{BUILD_INFO, COLORS, DEFAULT_INTEGRAL_NUM, DEFAULT_MAX_X, DEFAULT_MIN_X, build},
|
||||||
function_entry::Riemann,
|
function_entry::Riemann,
|
||||||
function_manager::FunctionManager,
|
function_manager::FunctionManager,
|
||||||
misc::option_vec_printer,
|
misc::{EguiHelper, find_intersections, option_vec_printer},
|
||||||
};
|
};
|
||||||
use eframe::App;
|
use eframe::App;
|
||||||
use egui::{
|
use egui::{
|
||||||
@ -13,7 +13,7 @@ use egui_plot::Plot;
|
|||||||
|
|
||||||
use emath::{Align, Align2};
|
use emath::{Align, Align2};
|
||||||
use epaint::{CornerRadius, Margin};
|
use epaint::{CornerRadius, Margin};
|
||||||
use itertools::Itertools;
|
|
||||||
use std::{io::Read, ops::BitXorAssign};
|
use std::{io::Read, ops::BitXorAssign};
|
||||||
use web_time::Instant;
|
use web_time::Instant;
|
||||||
|
|
||||||
@ -47,6 +47,9 @@ pub struct AppSettings {
|
|||||||
/// Stores whether or not displaying roots is enabled
|
/// Stores whether or not displaying roots is enabled
|
||||||
pub do_roots: bool,
|
pub do_roots: bool,
|
||||||
|
|
||||||
|
/// Stores whether or not displaying intersections between functions is enabled
|
||||||
|
pub do_intersections: bool,
|
||||||
|
|
||||||
/// Stores current plot pixel width
|
/// Stores current plot pixel width
|
||||||
pub plot_width: usize,
|
pub plot_width: usize,
|
||||||
}
|
}
|
||||||
@ -64,6 +67,7 @@ impl Default for AppSettings {
|
|||||||
integral_num: DEFAULT_INTEGRAL_NUM,
|
integral_num: DEFAULT_INTEGRAL_NUM,
|
||||||
do_extrema: true,
|
do_extrema: true,
|
||||||
do_roots: true,
|
do_roots: true,
|
||||||
|
do_intersections: true,
|
||||||
plot_width: 0,
|
plot_width: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,6 +112,9 @@ pub struct MathApp {
|
|||||||
|
|
||||||
/// Stores settings (pretty self-explanatory)
|
/// Stores settings (pretty self-explanatory)
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
|
|
||||||
|
/// Stores intersection points between functions
|
||||||
|
intersections: Vec<egui_plot::PlotPoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@ -246,6 +253,7 @@ impl MathApp {
|
|||||||
last_info: (None, None),
|
last_info: (None, None),
|
||||||
opened: Opened::default(),
|
opened: Opened::default(),
|
||||||
settings: AppSettings::default(),
|
settings: AppSettings::default(),
|
||||||
|
intersections: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,6 +362,15 @@ impl MathApp {
|
|||||||
})
|
})
|
||||||
.clicked(),
|
.clicked(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self.settings.do_intersections.bitxor_assign(
|
||||||
|
ui.add(Button::new("Intersections"))
|
||||||
|
.on_hover_text(match self.settings.do_intersections {
|
||||||
|
true => "Disable Displaying Intersections",
|
||||||
|
false => "Display Intersections between functions",
|
||||||
|
})
|
||||||
|
.clicked(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.functions.display_entries(ui) {
|
if self.functions.display_entries(ui) {
|
||||||
@ -530,7 +547,7 @@ impl App for MathApp {
|
|||||||
self.side_panel(ctx);
|
self.side_panel(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Central panel which contains the central plot (or an error created when parsing)
|
// Central panel which contains the central plot
|
||||||
CentralPanel::default()
|
CentralPanel::default()
|
||||||
.frame(Frame {
|
.frame(Frame {
|
||||||
inner_margin: Margin::ZERO,
|
inner_margin: Margin::ZERO,
|
||||||
@ -540,29 +557,6 @@ impl App for MathApp {
|
|||||||
..Frame::NONE
|
..Frame::NONE
|
||||||
})
|
})
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
// Display an error if it exists
|
|
||||||
let errors_formatted: String = self
|
|
||||||
.functions
|
|
||||||
.get_entries()
|
|
||||||
.iter()
|
|
||||||
.map(|(_, func)| func.get_test_result())
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, error)| error.is_some())
|
|
||||||
.map(|(i, error)| {
|
|
||||||
// use unwrap_unchecked as None Errors are already filtered out
|
|
||||||
unsafe {
|
|
||||||
format!("(Function #{}) {}\n", i, error.as_ref().unwrap_unchecked())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
if !errors_formatted.is_empty() {
|
|
||||||
ui.centered_and_justified(|ui| {
|
|
||||||
ui.heading(errors_formatted);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let available_width: usize = (ui.available_width() as usize) + 1; // Used in later logic
|
let available_width: usize = (ui.available_width() as usize) + 1; // Used in later logic
|
||||||
let width_changed = available_width != self.settings.plot_width;
|
let width_changed = available_width != self.settings.plot_width;
|
||||||
self.settings.plot_width = available_width;
|
self.settings.plot_width = available_width;
|
||||||
@ -607,6 +601,41 @@ impl App for MathApp {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Calculate and display intersections between functions
|
||||||
|
if self.settings.do_intersections {
|
||||||
|
let entries = self.functions.get_entries();
|
||||||
|
let visible_entries: Vec<_> = entries
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, f)| f.visible && f.is_some())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Clear previous intersections
|
||||||
|
self.intersections.clear();
|
||||||
|
|
||||||
|
// Find intersections between all pairs of visible functions
|
||||||
|
for i in 0..visible_entries.len() {
|
||||||
|
for j in (i + 1)..visible_entries.len() {
|
||||||
|
let (_, func1) = visible_entries[i];
|
||||||
|
let (_, func2) = visible_entries[j];
|
||||||
|
|
||||||
|
let mut intersections =
|
||||||
|
find_intersections(&func1.back_data, &func2.back_data);
|
||||||
|
self.intersections.append(&mut intersections);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display intersection points
|
||||||
|
if !self.intersections.is_empty() {
|
||||||
|
plot_ui.points(
|
||||||
|
self.intersections
|
||||||
|
.clone()
|
||||||
|
.to_points()
|
||||||
|
.color(Color32::from_rgb(255, 105, 180)) // Hot pink for visibility
|
||||||
|
.radius(6.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.last_info.0 = if area.iter().any(|e| e.is_some()) {
|
self.last_info.0 = if area.iter().any(|e| e.is_some()) {
|
||||||
Some(format!("Area: {}", option_vec_printer(area.as_slice())))
|
Some(format!("Area: {}", option_vec_printer(area.as_slice())))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
44
src/misc.rs
44
src/misc.rs
@ -214,3 +214,47 @@ include!(concat!(env!("OUT_DIR"), "/valid_chars.rs"));
|
|||||||
pub fn is_valid_char(c: char) -> bool {
|
pub fn is_valid_char(c: char) -> bool {
|
||||||
c.is_alphanumeric() | VALID_EXTRA_CHARS.contains(&c)
|
c.is_alphanumeric() | VALID_EXTRA_CHARS.contains(&c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find intersection points between two functions given their plotted data
|
||||||
|
/// Returns a vector of PlotPoints where the functions intersect
|
||||||
|
pub fn find_intersections(data1: &[PlotPoint], data2: &[PlotPoint]) -> Vec<PlotPoint> {
|
||||||
|
if data1.is_empty() || data2.is_empty() || data1.len() != data2.len() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate difference between functions at each x point
|
||||||
|
let differences: Vec<(f64, f64)> = data1
|
||||||
|
.iter()
|
||||||
|
.zip(data2.iter())
|
||||||
|
.filter(|(p1, p2)| p1.y.is_finite() && p2.y.is_finite())
|
||||||
|
.map(|(p1, p2)| (p1.x, p1.y - p2.y))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Find where sign changes (intersection points)
|
||||||
|
differences
|
||||||
|
.iter()
|
||||||
|
.tuple_windows()
|
||||||
|
.filter(|((_, diff1), (_, diff2))| diff1.signum() != diff2.signum())
|
||||||
|
.map(|((x1, diff1), (x2, diff2))| {
|
||||||
|
// Linear interpolation to find approximate x of intersection
|
||||||
|
let t = diff1.abs() / (diff1.abs() + diff2.abs());
|
||||||
|
let x = x1 + t * (x2 - x1);
|
||||||
|
|
||||||
|
// Find corresponding y values and average them for the intersection point
|
||||||
|
// We need to interpolate y values from both functions
|
||||||
|
let y1_at_x1 = data1
|
||||||
|
.iter()
|
||||||
|
.find(|p| (p.x - x1).abs() < f64::EPSILON)
|
||||||
|
.map(|p| p.y)
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
let y1_at_x2 = data1
|
||||||
|
.iter()
|
||||||
|
.find(|p| (p.x - x2).abs() < f64::EPSILON)
|
||||||
|
.map(|p| p.y)
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
let y = y1_at_x1 + t * (y1_at_x2 - y1_at_x1);
|
||||||
|
|
||||||
|
PlotPoint::new(x, y)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ fn app_settings_constructor(
|
|||||||
integral_num,
|
integral_num,
|
||||||
do_extrema: false,
|
do_extrema: false,
|
||||||
do_roots: false,
|
do_roots: false,
|
||||||
|
do_intersections: false,
|
||||||
plot_width: pixel_width,
|
plot_width: pixel_width,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user