#include "Curve.h" #include "Curve_Utils_p.h" #include #include #include #include #include #include #include #include #include #include #include "../base/Memory.h" #include "../base/global.h" #include "../render/Canvas.h" #include "../render/Frame_Prepare_Subflow.h" #include "../render/Render_Frame_Snapshot.h" namespace renderive { enum class Curve_Input_Mode : std::uint8_t { None, Samples, Points }; struct Curve_Input_Data : Input_Data {}; struct Curve_Render_State : Render_State, Curve_Prop {}; struct Curve_Sample_Source { Curve_Sample_Source() : values(memory_resource(Memory_Domain::Curve)) {} explicit Curve_Sample_Source(std::span input) : values(memory_resource(Memory_Domain::Curve)) { values.assign(input.begin(), input.end()); } explicit Curve_Sample_Source(std::pmr::vector&& input) : values(std::move(input)) {} std::pmr::vector values; }; struct Curve_Point_Source { Curve_Point_Source() : values(memory_resource(Memory_Domain::Curve)) {} explicit Curve_Point_Source(std::span input) : values(memory_resource(Memory_Domain::Curve)) { values.assign(input.begin(), input.end()); } explicit Curve_Point_Source(std::pmr::vector&& input) : values(std::move(input)) {} std::pmr::vector values; }; using Curve_Source = std::variant; static Curve_Input_Mode curve_source_mode(const Curve_Source& source) { if (std::holds_alternative(source)) return Curve_Input_Mode::Samples; if (std::holds_alternative(source)) return Curve_Input_Mode::Points; return Curve_Input_Mode::None; } static std::size_t curve_sample_count(const Curve_Source& source) { const auto* samples = std::get_if(&source); return samples ? samples->values.size() : 0u; } static std::size_t curve_point_count(const Curve_Source& source) { const auto* points = std::get_if(&source); return points ? points->values.size() : 0u; } struct Curve_Input_Block { std::shared_ptr source = std::make_shared(); std::shared_ptr completion_state; static std::shared_ptr samples_block(std::span values, std::shared_ptr state = {}) { auto block = std::make_shared(); block->source = std::make_shared(Curve_Sample_Source(values)); block->completion_state = std::move(state); return block; } static std::shared_ptr samples_block(std::pmr::vector&& values, std::shared_ptr state = {}) { auto block = std::make_shared(); block->source = std::make_shared(Curve_Sample_Source(std::move(values))); block->completion_state = std::move(state); return block; } static std::shared_ptr points_block(std::span values, std::shared_ptr state = {}) { auto block = std::make_shared(); block->source = std::make_shared(Curve_Point_Source(values)); block->completion_state = std::move(state); return block; } static std::shared_ptr points_block(std::pmr::vector&& values, std::shared_ptr state = {}) { auto block = std::make_shared(); block->source = std::make_shared(Curve_Point_Source(std::move(values))); block->completion_state = std::move(state); return block; } }; struct Curve_Axis_Geometry_Key { Orientation orientation = Orientation::Horizontal; Range coord_range{0.0, 1.0}; Range pixel_range{0.0, 1.0}; bool operator==(const Curve_Axis_Geometry_Key& other) const { return orientation == other.orientation && coord_range == other.coord_range && pixel_range == other.pixel_range; } }; struct Curve_Geometry_Key { std::uint64_t input_generation{}; std::uint64_t state_version{}; Curve_Input_Mode mode = Curve_Input_Mode::None; Line_Sampling_Strategy sample_mode = Line_Sampling_Strategy::Pixel_Min_Max_Envelope; Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Value; bool visible_range_only{}; Range data_range{0.0, 1.0}; std::size_t sample_count{}; std::size_t point_count{}; Curve_Axis_Geometry_Key domain; Curve_Axis_Geometry_Key value; bool operator==(const Curve_Geometry_Key& other) const { return input_generation == other.input_generation && state_version == other.state_version && mode == other.mode && sample_mode == other.sample_mode && interpolation_mode == other.interpolation_mode && visible_range_only == other.visible_range_only && data_range == other.data_range && sample_count == other.sample_count && point_count == other.point_count && domain == other.domain && value == other.value; } }; struct Curve_Geometry_Result { struct Segment { explicit Segment(std::pmr::memory_resource* resource = memory_resource(Memory_Domain::Curve)) : points(resource) {} std::pmr::vector points; }; Curve_Geometry_Result() : segments(memory_resource(Memory_Domain::Curve)), update_states(memory_resource(Memory_Domain::Update_Completion)) {} Axis_Mapping_2D mapping; std::pmr::vector segments; std::pmr::vector> update_states; }; struct Curve_Geometry_Request { Curve_Geometry_Request() : update_states(memory_resource(Memory_Domain::Update_Completion)) {} Curve_Input_Mode mode = Curve_Input_Mode::None; Axis_Mapping_2D mapping; Range data_range{0.0, 1.0}; bool visible_range_only{}; Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Value; Line_Sampling_Strategy sample_mode = Line_Sampling_Strategy::Pixel_Min_Max_Envelope; std::shared_ptr source; std::pmr::vector> update_states; }; struct Curve_Geometry_Partition { std::size_t source_begin{}; std::size_t source_end{}; int pixel_begin{}; int pixel_end{}; bool joins_previous{}; }; struct Curve_Geometry_Partial_Result { Curve_Geometry_Partial_Result() : segments(memory_resource(Memory_Domain::Curve)) {} std::pmr::vector segments; std::size_t source_begin{}; std::size_t source_end{}; bool begins_with_continuous_point{}; bool ends_with_continuous_point{}; bool joins_previous{}; }; struct Curve_Geometry_Job { explicit Curve_Geometry_Job(std::shared_ptr request, Curve_Geometry_Key key) : request(std::move(request)), key(std::move(key)), update_states(memory_resource(Memory_Domain::Update_Completion)) {} ~Curve_Geometry_Job() { for (auto& state : update_states) { if (state) state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); } update_states.clear(); } std::shared_ptr request; Curve_Geometry_Key key; std::vector partitions; std::vector results; std::pmr::vector> update_states; }; static constexpr std::size_t Min_Curve_Items_Per_Task = 4096; static constexpr std::size_t Max_Curve_Partition_Count = 8; static Curve_Axis_Geometry_Key curve_axis_key(const Axis_Frame_Snapshot& axis) { return {axis.orientation, axis.coord_range, axis.pixel_range}; } static void complete_curve_result_states(Curve_Geometry_Result& result, std::error_code error, Update_Outcome outcome) { for (const auto& state : result.update_states) { if (state) state->complete_all(error, outcome); } result.update_states.clear(); } static bool finite_point(const PointF& point) { return std::isfinite(point.x) && std::isfinite(point.y); } static void append_curve_point_segment(Curve_Geometry_Result& result, Curve_Geometry_Result::Segment*& segment, const Axis_Mapping_2D& mapping, const PointF& point) { if (!finite_point(point)) { segment = nullptr; return; } PointF mapped = mapping.map(point); if (!finite_point(mapped)) { segment = nullptr; return; } if (!segment) { result.segments.emplace_back(memory_resource(Memory_Domain::Curve)); segment = &result.segments.back(); } if (!segment->points.empty() && segment->points.back().x == mapped.x && segment->points.back().y == mapped.y) return; segment->points.push_back(mapped); } static void append_curve_sample_run(Curve_Geometry_Result& result, const Curve_Geometry_Request& request, const Curve_Sample_Source& samples, int run_start, int run_end) { if (run_start > run_end) return; auto coord_at = [&](int index) { return source_index_to_coordinate(request.data_range, static_cast(samples.values.size()), index); }; if (run_start == run_end) { double coord = coord_at(run_start); if (request.visible_range_only && !request.mapping.domain.coord_range.contains(coord)) return; PointF point = request.mapping.map(coord, samples.values[static_cast(run_start)]); if (!finite_point(point)) return; result.segments.emplace_back(memory_resource(Memory_Domain::Curve)); result.segments.back().points.push_back(point); return; } int run_count = run_end - run_start + 1; Range run_range{coord_at(run_start), coord_at(run_end)}; auto value_at = [&samples, run_start](int i) { return samples.values[static_cast(run_start + i)]; }; std::pmr::vector points(memory_resource(Memory_Domain::Curve)); if (request.sample_mode == Line_Sampling_Strategy::Pixel_Min_Max_Envelope) { points = Curve_Utils::build_pixel_envelope_points(request.mapping, run_range, run_count, run_count, request.visible_range_only, value_at, memory_resource(Memory_Domain::Curve)); } else { points = Curve_Utils::build_resampled_points(request.mapping, run_range, run_count, run_count, request.visible_range_only, request.interpolation_mode, value_at, memory_resource(Memory_Domain::Curve)); } if (points.empty()) return; result.segments.emplace_back(memory_resource(Memory_Domain::Curve)); result.segments.back().points = std::move(points); } std::size_t resolve_curve_partition_count(std::size_t source_count, std::size_t worker_count) { if (source_count == 0) return 0; if (worker_count <= 1 || source_count < Min_Curve_Items_Per_Task) return 1; std::size_t by_size = (source_count + Min_Curve_Items_Per_Task - 1) / Min_Curve_Items_Per_Task; return std::max(1, std::min({worker_count, by_size, Max_Curve_Partition_Count})); } static std::size_t curve_request_source_count(const Curve_Geometry_Request& request) { if (!request.source) return 0; if (const auto* data_points = std::get_if(request.source.get())) return data_points->values.size(); if (const auto* samples = std::get_if(request.source.get())) return samples->values.size(); return 0; } static bool curve_sample_valid(const Curve_Geometry_Request& request, std::size_t index) { if (!request.source) return false; if (const auto* data_points = std::get_if(request.source.get())) { if (index >= data_points->values.size()) return false; PointF point = data_points->values[index]; return finite_point(point) && finite_point(request.mapping.map(point)); } if (const auto* samples = std::get_if(request.source.get())) { if (index >= samples->values.size()) return false; double value = samples->values[index]; if (!std::isfinite(value)) return false; double coord = source_index_to_coordinate(request.data_range, static_cast(samples->values.size()), static_cast(index)); return finite_point(request.mapping.map(coord, value)); } return false; } static std::shared_ptr create_curve_geometry_job(std::shared_ptr request, Curve_Geometry_Key key, std::size_t worker_count) { if (!request) return {}; std::size_t source_count = curve_request_source_count(*request); std::size_t partition_count = resolve_curve_partition_count(source_count, worker_count); std::pmr::vector> update_states = std::move(request->update_states); auto job = std::make_shared( std::shared_ptr(std::move(request)), std::move(key)); job->update_states = std::move(update_states); job->partitions.reserve(partition_count); job->results.resize(partition_count); for (std::size_t i = 0; i < partition_count; ++i) { std::size_t begin = source_count * i / partition_count; std::size_t end = source_count * (i + 1) / partition_count; Curve_Geometry_Partition partition; partition.source_begin = begin; partition.source_end = end; partition.joins_previous = begin > 0 && curve_sample_valid(*job->request, begin - 1) && curve_sample_valid(*job->request, begin); job->partitions.push_back(partition); } return job; } static void build_curve_partition(Curve_Geometry_Job& job, std::size_t partition_index) { if (!job.request || partition_index >= job.partitions.size() || partition_index >= job.results.size()) return; const Curve_Geometry_Request& request = *job.request; const Curve_Geometry_Partition& partition = job.partitions[partition_index]; Curve_Geometry_Partial_Result partial; partial.source_begin = partition.source_begin; partial.source_end = partition.source_end; partial.joins_previous = partition.joins_previous; Curve_Geometry_Result result; result.mapping = request.mapping; if (!request.source || partition.source_begin >= partition.source_end) { job.results[partition_index] = std::move(partial); return; } if (const auto* data_points = std::get_if(request.source.get())) { result.segments.reserve(partition.source_end - partition.source_begin); Curve_Geometry_Result::Segment* segment = nullptr; for (std::size_t i = partition.source_begin; i < partition.source_end && i < data_points->values.size(); ++i) append_curve_point_segment(result, segment, request.mapping, data_points->values[i]); } else if (const auto* samples = std::get_if(request.source.get())) { if (samples->values.size() >= 2 && request.data_range.length() != 0.0) { std::size_t compute_begin = partition.source_begin; if (partition.joins_previous && compute_begin > 0) --compute_begin; int run_start = -1; std::size_t end = std::min(partition.source_end, samples->values.size()); for (std::size_t i = compute_begin; i < end; ++i) { double value = samples->values[i]; if (std::isfinite(value)) { if (run_start < 0) run_start = static_cast(i); continue; } if (run_start >= 0) append_curve_sample_run(result, request, *samples, run_start, static_cast(i) - 1); run_start = -1; } if (run_start >= 0) append_curve_sample_run(result, request, *samples, run_start, static_cast(end) - 1); } } partial.begins_with_continuous_point = partition.source_begin < partition.source_end && curve_sample_valid(request, partition.source_begin); partial.ends_with_continuous_point = partition.source_end > partition.source_begin && curve_sample_valid(request, partition.source_end - 1); partial.segments = std::move(result.segments); job.results[partition_index] = std::move(partial); } static void append_curve_segment(Curve_Geometry_Result& result, Curve_Geometry_Result::Segment&& source_segment) { if (!source_segment.points.empty()) result.segments.push_back(std::move(source_segment)); } static void merge_curve_first_segment(Curve_Geometry_Result& result, Curve_Geometry_Result::Segment& source_segment) { if (source_segment.points.empty()) return; if (result.segments.empty()) { result.segments.push_back(std::move(source_segment)); return; } auto& target_points = result.segments.back().points; for (const PointF& point : source_segment.points) { if (!target_points.empty() && target_points.back().x == point.x && target_points.back().y == point.y) continue; target_points.push_back(point); } } static Curve_Geometry_Result merge_curve_geometry(Curve_Geometry_Job& job) { Curve_Geometry_Result result; if (job.request) result.mapping = job.request->mapping; bool previous_can_join = false; for (std::size_t i = 0; i < job.results.size(); ++i) { Curve_Geometry_Partial_Result& partial = job.results[i]; if (partial.segments.empty()) { previous_can_join = false; continue; } bool join = previous_can_join && partial.joins_previous; if (join) { merge_curve_first_segment(result, partial.segments.front()); for (std::size_t s = 1; s < partial.segments.size(); ++s) append_curve_segment(result, std::move(partial.segments[s])); } else { for (auto& segment : partial.segments) append_curve_segment(result, std::move(segment)); } previous_can_join = partial.ends_with_continuous_point; } result.update_states = std::move(job.update_states); return result; } struct Curve_Private : Typed_Render_Data, Frame_Prepare_Subtask_Source { static constexpr std::size_t Input_Queue_Capacity = 8; std::shared_ptr source = std::make_shared(); rigtorp::MPMCQueue> input_queue; std::atomic_uint64_t dropped_input_count{0}; std::pmr::vector> pending_geometry_update_states{memory_resource(Memory_Domain::Update_Completion)}; std::optional geometry_result; std::optional submitted_geometry_key; std::uint64_t input_generation{}; Curve_Private() : input_queue(Input_Queue_Capacity) {} ~Curve_Private() override { if (geometry_result) complete_curve_result_states(*geometry_result, std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); std::shared_ptr block; while (input_queue.try_pop(block)) { if (block && block->completion_state) block->completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); } for (auto& state : pending_geometry_update_states) { if (state) state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); } pending_geometry_update_states.clear(); } void enqueue_input(std::shared_ptr block) { if (!block) return; if (!input_queue.try_push(block)) { if (block->completion_state) block->completion_state->complete_all({}, Update_Outcome::Superseded); dropped_input_count.fetch_add(1, std::memory_order_relaxed); return; } if (block->completion_state) block->completion_state->complete_until(Update_Stage::Input_Released); if (q()) q()->mark_render_dirty(); } void consume_input_queue() { std::shared_ptr selected; std::shared_ptr block; while (input_queue.try_pop(block)) { if (!block) continue; if (selected && selected->completion_state) selected->completion_state->complete_all({}, Update_Outcome::Superseded); selected = std::move(block); } if (!selected) return; source = std::move(selected->source); ++input_generation; if (selected->completion_state) { selected->completion_state->complete_until(Update_Stage::Committed); pending_geometry_update_states.push_back(std::move(selected->completion_state)); } } void prepare_data(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { (void)snapshot; (void)frame_view; } bool build_prepare_subtasks(Render_Task_Subflow& subflow, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { consume_input_queue(); auto job = create_geometry_job_if_needed(snapshot, frame_view); if (!job) return true; std::vector partition_tasks; partition_tasks.reserve(job->partitions.size()); for (std::size_t i = 0; i < job->partitions.size(); ++i) { partition_tasks.push_back(subflow.emplace(Render_Task_Kind::Curve, Task([job, i]() { build_curve_partition(*job, i); }))); } auto merge = subflow.emplace(Render_Task_Kind::Compose, Task([this, job]() { if (q() && q()->get_retiring()) return; Curve_Geometry_Result result = merge_curve_geometry(*job); if (q() && q()->get_retiring()) { complete_curve_result_states(result, std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return; } geometry_result = std::move(result); submitted_geometry_key = job->key; })); for (auto task : partition_tasks) subflow.precede(task, merge); return true; } std::shared_ptr create_geometry_job_if_needed(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) { Curve_Render_State* s = render_state(frame_view); if (!s) return {}; auto domain_axis = s->domain_axis.lock(); auto value_axis = s->value_axis.lock(); if (!domain_axis || !value_axis) { for (auto& state : pending_geometry_update_states) { if (state) state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); } pending_geometry_update_states.clear(); return {}; } Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get(), snapshot); Curve_Geometry_Key key; key.input_generation = input_generation; key.state_version = s->version; key.mode = source ? curve_source_mode(*source) : Curve_Input_Mode::None; key.sample_mode = s->sample_mode; key.interpolation_mode = s->interpolation_mode; key.visible_range_only = s->visible_range_only; key.data_range = s->data_range; key.sample_count = source ? curve_sample_count(*source) : 0u; key.point_count = source ? curve_point_count(*source) : 0u; key.domain = curve_axis_key(mapping.domain); key.value = curve_axis_key(mapping.value); if (submitted_geometry_key && key == *submitted_geometry_key) return {}; auto request = std::make_shared(); request->mode = key.mode; request->mapping = mapping; request->data_range = s->data_range; request->visible_range_only = s->visible_range_only; request->interpolation_mode = s->interpolation_mode; request->sample_mode = s->sample_mode; request->source = source; request->update_states = std::move(pending_geometry_update_states); pending_geometry_update_states = std::pmr::vector>(memory_resource(Memory_Domain::Update_Completion)); std::size_t worker_count = std::max(1, Global::instance()->render_scheduler().render_worker_count()); return create_curve_geometry_job(std::move(request), std::move(key), worker_count); } void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { Curve_Render_State* s = render_state(frame_view); if (!s) return; if (!geometry_result) return; if (snapshot.frame_update_states) { for (const auto& state : geometry_result->update_states) snapshot.frame_update_states->push_back(state); geometry_result->update_states.clear(); } if (geometry_result->segments.empty()) return; canvas.save(); canvas.set_antialias(s->antialias); for (const auto& segment : geometry_result->segments) { if (segment.points.empty()) continue; Curve_Utils::draw_polyline(&canvas, segment.points, s->pen); Curve_Utils::draw_fill_to_value(&canvas, segment.points, geometry_result->mapping, geometry_result->mapping.value.coord_range.target, s->fill_brush); } canvas.restore(); } }; RENDERIVE_DEFINE_RENDERABLE_BINDING(Curve, Curve_Private, Curve_Render_State, Curve_Input_Data, "Curve") void Curve::update_samples(std::span values) { if (!ok()) return; d()->enqueue_input(Curve_Input_Block::samples_block(values)); } void Curve::update_samples(std::pmr::vector&& values) { if (!ok()) return; d()->enqueue_input(Curve_Input_Block::samples_block(std::move(values))); } Update_Ticket Curve::submit_samples(std::span values) { auto state = make_update_state(); if (!ok()) { state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return Update_Ticket(state); } d()->enqueue_input(Curve_Input_Block::samples_block(values, state)); return Update_Ticket(state); } Update_Ticket Curve::submit_samples(std::pmr::vector&& values) { auto state = make_update_state(); if (!ok()) { state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return Update_Ticket(state); } d()->enqueue_input(Curve_Input_Block::samples_block(std::move(values), state)); return Update_Ticket(state); } void Curve::update_points(std::span points) { if (!ok()) return; d()->enqueue_input(Curve_Input_Block::points_block(points)); } void Curve::update_points(std::pmr::vector&& points) { if (!ok()) return; d()->enqueue_input(Curve_Input_Block::points_block(std::move(points))); } Update_Ticket Curve::submit_points(std::span points) { auto state = make_update_state(); if (!ok()) { state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return Update_Ticket(state); } d()->enqueue_input(Curve_Input_Block::points_block(points, state)); return Update_Ticket(state); } Update_Ticket Curve::submit_points(std::pmr::vector&& points) { auto state = make_update_state(); if (!ok()) { state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return Update_Ticket(state); } d()->enqueue_input(Curve_Input_Block::points_block(std::move(points), state)); return Update_Ticket(state); } RENDERIVE_DEFINE_WEAK_STATE_PROP(Curve, Curve_Render_State, Abs_Axis, domain_axis) RENDERIVE_DEFINE_WEAK_STATE_PROP(Curve, Curve_Render_State, Abs_Axis, value_axis) Range Curve::data_range() { return d()->edit_state_value(&Curve_Render_State::data_range); } void Curve::set_data_range(Range value) { if (value.length() == 0.0) return; d()->set_state_value(&Curve_Render_State::data_range, value); } RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Pen, pen) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Brush, fill_brush) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, bool, visible_range_only) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, bool, antialias) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Line_Interpolation_Mode, interpolation_mode) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Line_Sampling_Strategy, sample_mode) Curve::Builder::Builder(std::shared_ptr parent, std::shared_ptr domain_axis, std::shared_ptr value_axis) { ASSERT(parent, "Curve build error! parent == nullptr"); ASSERT(domain_axis && value_axis, "Curve build error! axis == nullptr"); ASSERT(domain_axis->attached_to_plot(), "Curve build error! domain_axis->plot == nullptr"); ASSERT(domain_axis->shares_plot_with(*value_axis), "Curve build error! domain_axis->plot != value_axis->plot"); ASSERT(parent->shares_plot_with(*domain_axis), "Curve build error! parent->plot != axis->plot"); ASSERT(domain_axis->orientation() != value_axis->orientation(), "Curve build error! axes must be orthogonal"); this->parent = parent; this->domain_axis = domain_axis; this->value_axis = value_axis; } std::shared_ptr Curve::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Curve_Private* pd = ret->d(); Render_Edit_Lease edit(pd); Curve_Render_State* sc = edit.operator->(); PROP_R(std::weak_ptr, domain_axis) PROP_R(std::weak_ptr, value_axis) PROP_R(Range, data_range) PROP_R(Pen, pen) PROP_R(Brush, fill_brush) PROP_R(bool, visible_range_only) PROP_R(bool, antialias) PROP_R(Line_Interpolation_Mode, interpolation_mode) PROP_R(Line_Sampling_Strategy, sample_mode) if (sc->data_range.length() == 0.0) sc->data_range = {0.0, 1.0}; return ret; } void Curve_Utils::draw_polyline(Canvas* canvas, std::span points, const Pen& pen) { if (points.empty() || pen.style == Line_Style::None) return; canvas->set_pen(pen); canvas->draw_polyline(points); } void Curve_Utils::draw_fill_to_value(Canvas* canvas, std::span points, const Axis_Mapping_2D& mapping, double baseline_value, const Brush& brush) { if (points.empty() || brush.style == Brush_Style::None) return; double first_domain = mapping.domain.pixel_to_coord(mapping.domain_pixel(points.front())); double last_domain = mapping.domain.pixel_to_coord(mapping.domain_pixel(points.back())); std::pmr::vector fill_points(points.size() + 2, frame_memory_resource()); fill_points.front() = mapping.map(first_domain, baseline_value); fill_points.back() = mapping.map(last_domain, baseline_value); for (int i = 0; i < points.size(); ++i) fill_points[i + 1] = points[i]; canvas->set_brush(brush); canvas->fill_polygon(std::span(fill_points.data(), fill_points.size())); } void Curve_Utils::append_envelope_point(std::pmr::vector& points, const Axis_Mapping_2D& mapping, const Range& data_range, int total_count, int index, double value) { if (index < 0) return; if (!std::isfinite(value)) return; PointF point = mapping.map(source_index_to_coordinate(data_range, total_count, index), value); if (!points.empty() && points.back().x == point.x && points.back().y == point.y) return; points.push_back(point); } } // namespace renderive