From 576cb65e8dbdd81c35923a5229bb139c838b977c Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Mon, 6 Nov 2023 17:17:15 +0100 Subject: [PATCH 001/253] Time for update() supplied from js api --- src/apps/weblib/cinterface.cpp | 2 ++ src/apps/weblib/cinterface.h | 1 + src/apps/weblib/interface.cpp | 13 +++++++++++-- src/apps/weblib/interface.h | 1 + src/apps/weblib/ts-api/cvizzu.types.d.ts | 1 + src/apps/weblib/ts-api/module/cchart.ts | 10 ++++++++-- src/apps/weblib/ts-api/render.ts | 3 ++- 7 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/apps/weblib/cinterface.cpp b/src/apps/weblib/cinterface.cpp index 336b96e08..dac678cba 100644 --- a/src/apps/weblib/cinterface.cpp +++ b/src/apps/weblib/cinterface.cpp @@ -180,12 +180,14 @@ void vizzu_update(APIHandles::Chart chart, APIHandles::Canvas canvas, double width, double height, + double timeInMSecs, int renderControl) { return Interface::getInstance().update(chart, canvas, width, height, + timeInMSecs, static_cast(renderControl)); } diff --git a/src/apps/weblib/cinterface.h b/src/apps/weblib/cinterface.h index 7f28bce02..c4f068152 100644 --- a/src/apps/weblib/cinterface.h +++ b/src/apps/weblib/cinterface.h @@ -68,6 +68,7 @@ extern void vizzu_update(APIHandles::Chart chart, APIHandles::Canvas canvas, double width, double height, + double timeInMSecs, int renderControl); extern const char *vizzu_errorMessage( APIHandles::Exception exceptionPtr, diff --git a/src/apps/weblib/interface.cpp b/src/apps/weblib/interface.cpp index 4513b393f..5ab14cf90 100644 --- a/src/apps/weblib/interface.cpp +++ b/src/apps/weblib/interface.cpp @@ -335,11 +335,20 @@ void Interface::update(ObjectRegistry::Handle chart, ObjectRegistry::Handle canvas, double width, double height, + double timeInMSecs, RenderControl renderControl) { auto &&widget = objects.get(chart); - auto now = std::chrono::steady_clock::now(); - widget->getChart().getAnimControl().update(now); + + std::chrono::duration milliSecs(timeInMSecs); + + auto nanoSecs = + std::chrono::duration_cast( + milliSecs); + + ::Anim::TimePoint time(nanoSecs); + + widget->getChart().getAnimControl().update(time); const Geom::Size size{width, height}; diff --git a/src/apps/weblib/interface.h b/src/apps/weblib/interface.h index c2e9832bb..2d9a83fc8 100644 --- a/src/apps/weblib/interface.h +++ b/src/apps/weblib/interface.h @@ -47,6 +47,7 @@ class Interface ObjectRegistry::Handle canvas, double width, double height, + double timeInMSecs, RenderControl renderControl); ObjectRegistry::Handle storeAnim(ObjectRegistry::Handle chart); diff --git a/src/apps/weblib/ts-api/cvizzu.types.d.ts b/src/apps/weblib/ts-api/cvizzu.types.d.ts index 43d23ddc4..37076daaa 100644 --- a/src/apps/weblib/ts-api/cvizzu.types.d.ts +++ b/src/apps/weblib/ts-api/cvizzu.types.d.ts @@ -95,6 +95,7 @@ export interface CVizzu { canvas: CCanvasPtr, width: number, height: number, + time: number, renderControl: number ): void _vizzu_errorMessage(exceptionPtr: CException, typeinfo: CTypeInfo): CString diff --git a/src/apps/weblib/ts-api/module/cchart.ts b/src/apps/weblib/ts-api/module/cchart.ts index 6b59154b2..4b78efcf7 100644 --- a/src/apps/weblib/ts-api/module/cchart.ts +++ b/src/apps/weblib/ts-api/module/cchart.ts @@ -39,9 +39,15 @@ export class CChart extends CObject { this.animOptions = this._makeAnimOptions() } - update(cCanvas: CCanvas, width: number, height: number, renderControl: number): void { + update( + cCanvas: CCanvas, + width: number, + height: number, + time: number, + renderControl: number + ): void { this._cCanvas = cCanvas - this._call(this._wasm._vizzu_update)(cCanvas.getId(), width, height, renderControl) + this._call(this._wasm._vizzu_update)(cCanvas.getId(), width, height, time, renderControl) } animate(callback: (ok: boolean) => void): void { diff --git a/src/apps/weblib/ts-api/render.ts b/src/apps/weblib/ts-api/render.ts index 6eb8df813..194b03028 100644 --- a/src/apps/weblib/ts-api/render.ts +++ b/src/apps/weblib/ts-api/render.ts @@ -48,8 +48,9 @@ export class Render implements Plugin, Canvas { updateFrame(force: boolean = false): void { const size = this._canvas.calcSize() if (size.x > 0 && size.y > 0) { + const time = performance.now() const renderControl = !this._enabled ? 2 : force ? 1 : 0 - this._cchart.update(this._ccanvas, size.x, size.y, renderControl) + this._cchart.update(this._ccanvas, size.x, size.y, time, renderControl) } } From 9f561496c54e655025602790ab2fa89885b84cb5 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Wed, 17 Jan 2024 16:44:47 +0100 Subject: [PATCH 002/253] Fixed prettier. --- src/apps/weblib/ts-api/events.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/weblib/ts-api/events.ts b/src/apps/weblib/ts-api/events.ts index 24c21256a..7486af558 100644 --- a/src/apps/weblib/ts-api/events.ts +++ b/src/apps/weblib/ts-api/events.ts @@ -384,8 +384,8 @@ export class Events { const eventParam = this._isJSEvent(eventName) ? this._makeJSEventParam(param, state) : cEvent - ? this._makeCEventParam(cEvent, param, state) - : param + ? this._makeCEventParam(cEvent, param, state) + : param handler(eventParam) } } From 2185abaa3049f9732be0afc095bb2b85ccfa4746 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 22 Jan 2024 17:56:28 +0100 Subject: [PATCH 003/253] first new interface --- src/dataframe/interface.h | 117 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/dataframe/interface.h diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h new file mode 100644 index 000000000..d5db70e35 --- /dev/null +++ b/src/dataframe/interface.h @@ -0,0 +1,117 @@ +#ifndef VIZZU_DATAFRAME_INTERFACE_H +#define VIZZU_DATAFRAME_INTERFACE_H + +#include +#include +#include +#include +#include + +namespace Vizzu::dataframe +{ + +enum class value_type { dimension, measure }; +enum class aggregator_type { + sum, + min, + max, + mean, + count, + distinct, + exists +}; + +enum class sort_type { no, less, greater }; +enum class adding_type { + create_or_add, + create_or_throw, + override_and_add, + override_all_with_rotation +}; + +struct custom_aggregator +{ + std::function add; + std::function get; +}; + +class dataframe_interface : + std::enable_shared_from_this +{ +public: + struct record_type + { + std::function( + const char *)> + getValueByColumn; + + std::string recordId; + }; + + virtual ~dataframe_interface() = default; + + virtual std::shared_ptr copy() const & = 0; + + virtual void set_aggregate(std::span dimensions, + std::span>> + measures) & = 0; + + virtual void set_filter( + std::function filter) & = 0; + + virtual void set_sort( + std::span> columns) & = 0; + + virtual void set_sort( + std::function + custom_sort) & = 0; + + virtual void add_dimension( + std::span dimension_values, + const char *name, + adding_type adding_strategy = + adding_type::create_or_add) & = 0; + + virtual void add_measure(std::span measure_values, + const char *name, + const char *unit = "", + adding_type adding_strategy = + adding_type::create_or_add) & = 0; + + virtual void add_series_by_other(const char *curr_series, + const char *new_name, + std::function + value_transform, + const char *unit = nullptr) & = 0; + + virtual void remove_series(std::span names) & = 0; + + virtual void add_records( + std::span> values) & = 0; + + virtual void remove_record(const char *record_id) & = 0; + + virtual void remove_records( + std::function filter) & = 0; + + virtual void change_data(const char *record_id, + const char *column, + const char *value) & = 0; + + virtual void change_data(const char *column, + const char *value) & = 0; + + virtual void fill_na(const char *column, double value) & = 0; + + virtual std::string get_data() const & = 0; + + virtual std::span get_categories( + const char *dimension) const & = 0; + + virtual std::pair get_min_max( + const char *measure) const & = 0; +}; +} + +#endif // VIZZU_DATAFRAME_INTERFACE_H From 631f53063b5124a22492d985829a4f0614283325 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Tue, 23 Jan 2024 15:54:44 +0100 Subject: [PATCH 004/253] some interface changes --- src/dataframe/interface.h | 96 ++++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index d5db70e35..8906981ce 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -1,6 +1,7 @@ #ifndef VIZZU_DATAFRAME_INTERFACE_H #define VIZZU_DATAFRAME_INTERFACE_H +#include #include #include #include @@ -10,7 +11,8 @@ namespace Vizzu::dataframe { -enum class value_type { dimension, measure }; +using cell_value = std::variant; + enum class aggregator_type { sum, min, @@ -22,95 +24,115 @@ enum class aggregator_type { }; enum class sort_type { no, less, greater }; + enum class adding_type { create_or_add, create_or_throw, - override_and_add, + override_full, override_all_with_rotation }; struct custom_aggregator { - std::function add; - std::function get; + using id_type = std::any; + std::function(std::string_view)> + create; + std::function add; }; class dataframe_interface : std::enable_shared_from_this { public: + using series_identifier = std::variant; + using record_identifier = std::variant; + struct record_type { - std::function( - const char *)> - getValueByColumn; + std::function getValueByColumn; - std::string recordId; + record_identifier recordId; }; virtual ~dataframe_interface() = default; virtual std::shared_ptr copy() const & = 0; - virtual void set_aggregate(std::span dimensions, - std::span>> - measures) & = 0; + virtual void set_aggregate(series_identifier series, + std::variant aggregator = {}) & = 0; virtual void set_filter( std::function filter) & = 0; - virtual void set_sort( - std::span> columns) & = 0; + virtual void set_sort(series_identifier series, + sort_type sort) & = 0; virtual void set_sort( std::function custom_sort) & = 0; virtual void add_dimension( - std::span dimension_values, + std::span dimension_categories, + std::span dimension_indices, const char *name, - adding_type adding_strategy = - adding_type::create_or_add) & = 0; + adding_type adding_strategy = adding_type::create_or_add, + std::span> info = + {}) & = 0; virtual void add_measure(std::span measure_values, const char *name, - const char *unit = "", - adding_type adding_strategy = - adding_type::create_or_add) & = 0; + adding_type adding_strategy = adding_type::create_or_add, + std::span> info = + {}) & = 0; - virtual void add_series_by_other(const char *curr_series, - const char *new_name, - std::function + virtual void add_series_by_other(series_identifier curr_series, + const char *name, + std::function value_transform, - const char *unit = nullptr) & = 0; + std::span> info = + {}) & = 0; - virtual void remove_series(std::span names) & = 0; + virtual void remove_series( + std::span names) & = 0; - virtual void add_records( - std::span> values) & = 0; + virtual void add_records(std::span values) & = 0; - virtual void remove_record(const char *record_id) & = 0; + virtual void remove_records( + std::span record_ids) & = 0; virtual void remove_records( std::function filter) & = 0; - virtual void change_data(const char *record_id, - const char *column, - const char *value) & = 0; + virtual void change_data(record_identifier record_id, + series_identifier column, + cell_value value) & = 0; - virtual void change_data(const char *column, - const char *value) & = 0; + virtual void fill_na(series_identifier column, + double value) & = 0; - virtual void fill_na(const char *column, double value) & = 0; + virtual std::string as_string() const & = 0; - virtual std::string get_data() const & = 0; + virtual std::span get_dimensions() const & = 0; + virtual std::span get_measures() const & = 0; virtual std::span get_categories( - const char *dimension) const & = 0; + series_identifier dimension) const & = 0; virtual std::pair get_min_max( - const char *measure) const & = 0; + series_identifier measure) const & = 0; + + virtual series_identifier change_series_identifier_type( + const series_identifier &id) const & = 0; + + virtual record_identifier change_record_identifier_type( + const record_identifier &id) const & = 0; + + virtual cell_value get_data(record_identifier record_id, + series_identifier column) const & = 0; + + virtual void visit(std::function) const & = 0; }; } From b6622a47902fe4c2b752883e05a018d1892ce247 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Tue, 23 Jan 2024 16:34:49 +0100 Subject: [PATCH 005/253] review 2 interface --- src/dataframe/interface.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 8906981ce..e37bfb9c4 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -34,10 +34,10 @@ enum class adding_type { struct custom_aggregator { + std::string name; using id_type = std::any; - std::function(std::string_view)> - create; - std::function add; + std::function create; + std::function add; }; class dataframe_interface : @@ -75,7 +75,7 @@ class dataframe_interface : virtual void add_dimension( std::span dimension_categories, - std::span dimension_indices, + std::span dimension_values, const char *name, adding_type adding_strategy = adding_type::create_or_add, std::span> info = @@ -132,6 +132,8 @@ class dataframe_interface : virtual cell_value get_data(record_identifier record_id, series_identifier column) const & = 0; + virtual std::size_t get_record_count() const & = 0; + virtual void visit(std::function) const & = 0; }; } From 84f2c76a7431c3818865cf2f00f2366f38f709ff Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 24 Jan 2024 20:21:18 +0100 Subject: [PATCH 006/253] change interface by impl --- project/cmake/lib/CMakeLists.txt | 6 +++-- src/dataframe/interface.h | 41 +++++++++++++++++++------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/project/cmake/lib/CMakeLists.txt b/project/cmake/lib/CMakeLists.txt index 805de1513..e6c9d90f0 100644 --- a/project/cmake/lib/CMakeLists.txt +++ b/project/cmake/lib/CMakeLists.txt @@ -4,7 +4,8 @@ file(GLOB_RECURSE sources CONFIGURE_DEPENDS ${root}/plugin/*.cpp ${root}/src/base/*.cpp ${root}/src/data/*.cpp - ${root}/src/chart/*.cpp) + ${root}/src/chart/*.cpp + ${root}/src/dataframe/*.cpp) include(../version.txt) @@ -14,7 +15,8 @@ file(GLOB_RECURSE headers CONFIGURE_DEPENDS ${root}/src/data/*.h ${root}/src/data/*.inc ${root}/src/chart/*.h - ${root}/src/chart/*.inc) + ${root}/src/chart/*.inc + ${root}/src/dataframe/*.h) add_library(vizzulib ${sources}) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index e37bfb9c4..22a9d9089 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -41,11 +41,13 @@ struct custom_aggregator }; class dataframe_interface : - std::enable_shared_from_this + public std::enable_shared_from_this { public: - using series_identifier = std::variant; - using record_identifier = std::variant; + using series_identifier = + std::variant; + using record_identifier = + std::variant; struct record_type { @@ -56,7 +58,8 @@ class dataframe_interface : virtual ~dataframe_interface() = default; - virtual std::shared_ptr copy() const & = 0; + [[nodiscard]] virtual std::shared_ptr + copy() const & = 0; virtual void set_aggregate(series_identifier series, std::variant dimension_categories, - std::span dimension_values, + std::span dimension_values, const char *name, adding_type adding_strategy = adding_type::create_or_add, std::span> info = @@ -110,31 +113,37 @@ class dataframe_interface : cell_value value) & = 0; virtual void fill_na(series_identifier column, - double value) & = 0; + cell_value value) & = 0; - virtual std::string as_string() const & = 0; + [[nodiscard]] virtual std::string as_string() const & = 0; - virtual std::span get_dimensions() const & = 0; - virtual std::span get_measures() const & = 0; + [[nodiscard]] virtual std::span + get_dimensions() const & = 0; + [[nodiscard]] virtual std::span + get_measures() const & = 0; - virtual std::span get_categories( + [[nodiscard]] virtual std::span get_categories( series_identifier dimension) const & = 0; - virtual std::pair get_min_max( + [[nodiscard]] virtual std::pair get_min_max( series_identifier measure) const & = 0; - virtual series_identifier change_series_identifier_type( + [[nodiscard]] virtual series_identifier + change_series_identifier_type( const series_identifier &id) const & = 0; - virtual record_identifier change_record_identifier_type( + [[nodiscard]] virtual record_identifier + change_record_identifier_type( const record_identifier &id) const & = 0; - virtual cell_value get_data(record_identifier record_id, + [[nodiscard]] virtual cell_value get_data( + record_identifier record_id, series_identifier column) const & = 0; - virtual std::size_t get_record_count() const & = 0; + [[nodiscard]] virtual std::size_t get_record_count() const & = 0; - virtual void visit(std::function) const & = 0; + virtual void visit(std::function function, + bool filtered = true) const & = 0; }; } From a0fd39625bfba9aaf56346850b548cb04b68f14e Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 25 Jan 2024 16:38:20 +0100 Subject: [PATCH 007/253] change interface -> add copy parameters --- src/dataframe/interface.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 22a9d9089..d9474b496 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -23,7 +23,7 @@ enum class aggregator_type { exists }; -enum class sort_type { no, less, greater }; +enum class sort_type { less, greater }; enum class adding_type { create_or_add, @@ -58,8 +58,9 @@ class dataframe_interface : virtual ~dataframe_interface() = default; - [[nodiscard]] virtual std::shared_ptr - copy() const & = 0; + [[nodiscard]] virtual std::shared_ptr copy( + bool remove_filtered = false, + bool inherit_sorting = true) const & = 0; virtual void set_aggregate(series_identifier series, std::variant aggregator = {}) & = 0; virtual void set_filter( - std::function filter) & = 0; + std::function &&filter) & = 0; virtual void set_sort(series_identifier series, sort_type sort) & = 0; @@ -119,6 +120,7 @@ class dataframe_interface : [[nodiscard]] virtual std::span get_dimensions() const & = 0; + [[nodiscard]] virtual std::span get_measures() const & = 0; From 0a9a33ff64136310e6f3a2ef3868d4899f5f0ede Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 25 Jan 2024 18:20:14 +0100 Subject: [PATCH 008/253] sort dimensions too. need categories sort. --- src/dataframe/interface.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index d9474b496..0dd88a6ec 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -62,16 +62,22 @@ class dataframe_interface : bool remove_filtered = false, bool inherit_sorting = true) const & = 0; + using any_aggregator_type = std:: + variant; + + using any_sort_type = std::variant>; + virtual void set_aggregate(series_identifier series, - std::variant aggregator = {}) & = 0; + any_aggregator_type aggregator = {}) & = 0; virtual void set_filter( std::function &&filter) & = 0; virtual void set_sort(series_identifier series, - sort_type sort) & = 0; + any_sort_type sort) & = 0; virtual void set_sort( std::function From 299bd2397ba236d7546c0a24e7fba384950c74d5 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 25 Jan 2024 18:24:23 +0100 Subject: [PATCH 009/253] revert removing computedStyles --- src/chart/main/chart.cpp | 13 ++++++------- src/chart/main/chart.h | 8 +++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/chart/main/chart.cpp b/src/chart/main/chart.cpp index e506f430d..37f19c1d6 100644 --- a/src/chart/main/chart.cpp +++ b/src/chart/main/chart.cpp @@ -9,6 +9,7 @@ namespace Vizzu Chart::Chart() : animator(std::make_shared()), stylesheet(Styles::Chart::def()), + computedStyles(stylesheet.getDefaultParams()), events(getEventDispatcher()) { stylesheet.setActiveParams(actStyles); @@ -63,6 +64,7 @@ void Chart::animate(const OnComplete &onComplete) else { *nextOptions = prevOptions; actStyles = prevStyles; + computedStyles = plot->getStyle(); } if (onComplete) onComplete(ok); }; @@ -116,9 +118,12 @@ Gen::PlotPtr Chart::plot(const Gen::PlotOptionsPtr &options) { options->setAutoParameters(); + computedStyles = + stylesheet.getFullParams(options, layout.boundary.size); + auto res = std::make_shared(table, options, - stylesheet.getFullParams(options, layout.boundary.size), + computedStyles, false); Styles::Sheet::setAfterStyles(*res, layout.boundary.size); @@ -126,10 +131,4 @@ Gen::PlotPtr Chart::plot(const Gen::PlotOptionsPtr &options) return res; } -const Styles::Chart &Chart::getComputedStyles() const -{ - return actPlot ? actPlot->getStyle() - : stylesheet.getDefaultParams(); -} - } \ No newline at end of file diff --git a/src/chart/main/chart.h b/src/chart/main/chart.h index b1e08ca66..df33277d0 100644 --- a/src/chart/main/chart.h +++ b/src/chart/main/chart.h @@ -37,9 +37,10 @@ class Chart Gen::OptionsSetter getSetter(); Styles::Sheet &getStylesheet() { return stylesheet; } Styles::Chart &getStyles() { return actStyles; } - - [[nodiscard]] const Styles::Chart &getComputedStyles() const; - + [[nodiscard]] const Styles::Chart &getComputedStyles() const + { + return computedStyles; + } void setStyles(const Styles::Chart &styles) { actStyles = styles; @@ -90,6 +91,7 @@ class Chart Styles::Sheet stylesheet; Styles::Chart actStyles; Styles::Chart prevStyles; + Styles::Chart computedStyles; Util::EventDispatcher eventDispatcher; Draw::RenderedChart renderedChart; Events events; From 1975c71f8df9e396a82d63bf934df705d77f1280 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 25 Jan 2024 18:28:33 +0100 Subject: [PATCH 010/253] postpone computedStyles after computed missings --- src/chart/main/chart.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/chart/main/chart.cpp b/src/chart/main/chart.cpp index 37f19c1d6..65090b573 100644 --- a/src/chart/main/chart.cpp +++ b/src/chart/main/chart.cpp @@ -118,16 +118,15 @@ Gen::PlotPtr Chart::plot(const Gen::PlotOptionsPtr &options) { options->setAutoParameters(); - computedStyles = - stylesheet.getFullParams(options, layout.boundary.size); - auto res = std::make_shared(table, options, - computedStyles, + stylesheet.getFullParams(options, layout.boundary.size), false); Styles::Sheet::setAfterStyles(*res, layout.boundary.size); + computedStyles = res->getStyle(); + return res; } From 62efe29f384121778084ec6d8e6d0cfaef39bc40 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 25 Jan 2024 20:33:31 +0100 Subject: [PATCH 011/253] nosort is not an option --- src/dataframe/interface.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 0dd88a6ec..3bdfad7b8 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -65,8 +65,7 @@ class dataframe_interface : using any_aggregator_type = std:: variant; - using any_sort_type = std::variant>; From 491f11073a42d1406651fb347ce8d0e3c82aaeec Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 29 Jan 2024 18:13:25 +0100 Subject: [PATCH 012/253] remove marker alpha on tooltip --- CHANGELOG.md | 1 + src/chart/rendering/markerrenderer.cpp | 22 ---------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3ec13ae6..bed66e556 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ were not escaped properly. - Fix dimension label transition on axis and legend. - Through event handler call, when a new event handler is registered, undefined behaviour happened. +- Removed marker's alpha color when tooltip is shown. ### Added diff --git a/src/chart/rendering/markerrenderer.cpp b/src/chart/rendering/markerrenderer.cpp index db3ccc1bd..bbdc68318 100644 --- a/src/chart/rendering/markerrenderer.cpp +++ b/src/chart/rendering/markerrenderer.cpp @@ -403,28 +403,6 @@ std::pair MarkerRenderer::getColor( auto finalBorderColor = actBorderColor * alpha; auto itemColor = selectedColor * alpha * fillAlpha; - double highlight = 0.0; - double anyHighlight = 0.0; - auto markerInfo = plot->getMarkersInfo(); - for (auto &info : markerInfo) { - auto allHighlight = 0.0; - info.second.visit( - [&highlight, - &allHighlight, - idx = abstractMarker.marker.idx](int, - const auto &info) - { - highlight += info.value.markerId == idx ? 1.0 : 0.0; - if (info.value.markerId.has_value()) - allHighlight += info.weight; - }); - anyHighlight = std::max(anyHighlight, allHighlight); - } - - auto highlightAlpha = 1 - (0.65 * anyHighlight) * (1 - highlight); - finalBorderColor = (finalBorderColor * highlightAlpha); - itemColor = (itemColor * highlightAlpha); - return std::make_pair(finalBorderColor, itemColor); } From 4d7ca9f4008b3ac96c45cd13dbd763338ad8d204 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 2 Feb 2024 13:36:43 +0100 Subject: [PATCH 013/253] Add constness of all span + add finalize --- src/dataframe/interface.h | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 3bdfad7b8..78a97c5f5 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -23,7 +23,7 @@ enum class aggregator_type { exists }; -enum class sort_type { less, greater }; +enum class sort_type { less, greater, natural_less, natural_greater }; enum class adding_type { create_or_add, @@ -45,9 +45,9 @@ class dataframe_interface : { public: using series_identifier = - std::variant; + std::variant; using record_identifier = - std::variant; + std::variant; struct record_type { @@ -83,33 +83,33 @@ class dataframe_interface : custom_sort) & = 0; virtual void add_dimension( - std::span dimension_categories, - std::span dimension_values, + std::span dimension_categories, + std::span dimension_values, const char *name, adding_type adding_strategy = adding_type::create_or_add, - std::span> info = + std::span> info = {}) & = 0; virtual void add_measure(std::span measure_values, const char *name, adding_type adding_strategy = adding_type::create_or_add, - std::span> info = + std::span> info = {}) & = 0; virtual void add_series_by_other(series_identifier curr_series, const char *name, std::function value_transform, - std::span> info = + std::span> info = {}) & = 0; virtual void remove_series( - std::span names) & = 0; + std::span names) & = 0; - virtual void add_records(std::span values) & = 0; + virtual void add_record(std::span values) & = 0; virtual void remove_records( - std::span record_ids) & = 0; + std::span record_ids) & = 0; virtual void remove_records( std::function filter) & = 0; @@ -121,15 +121,17 @@ class dataframe_interface : virtual void fill_na(series_identifier column, cell_value value) & = 0; + virtual void finalize() & = 0; + [[nodiscard]] virtual std::string as_string() const & = 0; - [[nodiscard]] virtual std::span + [[nodiscard]] virtual std::span get_dimensions() const & = 0; - [[nodiscard]] virtual std::span + [[nodiscard]] virtual std::span get_measures() const & = 0; - [[nodiscard]] virtual std::span get_categories( + [[nodiscard]] virtual std::span get_categories( series_identifier dimension) const & = 0; [[nodiscard]] virtual std::pair get_min_max( From 91446227228afaaf597aa9a0f6395a274f0c5840 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 2 Feb 2024 13:55:39 +0100 Subject: [PATCH 014/253] Change test, add EnumVariant, modify naturalcmp --- src/base/refl/auto_enum.h | 55 ++++++++ src/base/text/naturalcmp.cpp | 51 +++----- src/base/text/naturalcmp.h | 6 +- test/unit/util/case.h | 48 ++++++- test/unit/util/case_registry.h | 4 +- test/unit/util/collection.h | 6 + test/unit/util/condition.h | 233 +++++++++++++++++++++++++++------ test/unit/util/suite_proxy.h | 37 +++++- test/unit/util/to_string.h | 44 +++++++ 9 files changed, 402 insertions(+), 82 deletions(-) diff --git a/src/base/refl/auto_enum.h b/src/base/refl/auto_enum.h index bd23025c6..a191a305f 100644 --- a/src/base/refl/auto_enum.h +++ b/src/base/refl/auto_enum.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "auto_name.h" @@ -173,6 +174,60 @@ struct EnumArray : std::array)> } }; +template + requires(std::is_enum_v + && sizeof...(Args) == Detail::count() + && Detail::from_to().first == 0) +struct EnumVariant : std::variant +{ + using base_variant = std::variant; + + [[nodiscard]] explicit(false) constexpr + operator E() const noexcept + { + return static_cast(base_variant::index()); + } +}; + +template +constexpr decltype(auto) unsafe_get( + EnumVariant const &e) +{ + return *std::get_if(E)>(&e); +} + +template +constexpr decltype(auto) unsafe_get( + EnumVariant &e) +{ + return *std::get_if(E)>(&e); +} + +template +constexpr decltype(auto) unsafe_get(EnumVariant const &e) +{ + return *std::get_if(e); +} + +template +constexpr decltype(auto) unsafe_get(EnumVariant &e) +{ + return *std::get_if(e); +} + +template +constexpr decltype(auto) get_if( + EnumVariant const *e) +{ + return std::get_if(E)>(e); +} + +template +constexpr decltype(auto) get_if(EnumVariant *e) +{ + return std::get_if(E)>(e); +} + } #endif \ No newline at end of file diff --git a/src/base/text/naturalcmp.cpp b/src/base/text/naturalcmp.cpp index 7b4c4f4e2..ea8499073 100644 --- a/src/base/text/naturalcmp.cpp +++ b/src/base/text/naturalcmp.cpp @@ -17,57 +17,44 @@ NaturalCmp::NaturalCmp(bool ignoreCase, bool ignoreSpace) : bool NaturalCmp::operator()(const std::string &op0, const std::string &op1) const { - const auto *s0 = op0.c_str(); - const auto *s1 = op1.c_str(); - auto res = cmp(s0, s1); - return res == -1; + return std::is_lt(cmp(op0.c_str(), op1.c_str())); } -int NaturalCmp::cmp(const char *&s0, const char *&s1) const +std::weak_ordering NaturalCmp::cmp(const char *s0, + const char *s1) const { - int res = cmpChar(s0, s1); - for (; res != 0 && *s0 != '\0'; ++s0, ++s1) { + auto res{std::weak_ordering::equivalent}; + while (std::is_eq(res) && (*s0 != '\0' || *s1 != '\0')) { if (ignoreSpace) { skipSpaces(s0); skipSpaces(s1); } - if (SC::isDigit(*s0) && SC::isDigit(*s1)) { + if (SC::isDigit(*s0) && SC::isDigit(*s1)) res = cmpNum(s0, s1); - if (res != 0) break; - } - res = cmpChar(s0, s1); + else + res = cmpChar(*s0++, *s1++); } return res; } -int NaturalCmp::cmpChar(const char *&s0, const char *&s1) const +std::weak_ordering NaturalCmp::cmpChar(const char &s0, + const char &s1) const { - auto c0 = *s0; - auto c1 = *s1; - - if (ignoreCase) { - c0 = SC::toUpper(c0); - c1 = SC::toUpper(c1); - } - - if (c0 < c1) return -1; - if (c0 > c1) return 1; - return 0; + return ignoreCase ? SC::toUpper(s0) <=> SC::toUpper(s1) + : s0 <=> s1; } -int NaturalCmp::cmpNum(const char *&s0, const char *&s1) +std::weak_ordering NaturalCmp::cmpNum(const char *&s0, + const char *&s1) { - double v0 = 0; - double v1 = 0; + double v0{}; + double v1{}; while (SC::isDigit(*s0) || SC::isDigit(*s1)) { - if (SC::isDigit(*s0)) v0 = v0 * 10 + SC::toNumber(*s0); - if (SC::isDigit(*s1)) v1 = v1 * 10 + SC::toNumber(*s1); - - if (SC::isDigit(*s0)) ++s0; - if (SC::isDigit(*s1)) ++s1; + if (SC::isDigit(*s0)) v0 = v0 * 10 + SC::toNumber(*s0++); + if (SC::isDigit(*s1)) v1 = v1 * 10 + SC::toNumber(*s1++); } - return v0 < v1 ? -1 : v0 > v1 ? 1 : 0; + return std::weak_order(v0, v1); } void NaturalCmp::skipSpaces(const char *&s) diff --git a/src/base/text/naturalcmp.h b/src/base/text/naturalcmp.h index 56e487e3a..94451d08d 100644 --- a/src/base/text/naturalcmp.h +++ b/src/base/text/naturalcmp.h @@ -16,9 +16,9 @@ class NaturalCmp private: bool ignoreCase; bool ignoreSpace; - int cmp(const char *&, const char *&) const; - int cmpChar(const char *&, const char *&) const; - static int cmpNum(const char *&, const char *&); + std::weak_ordering cmp(const char *, const char *) const; + std::weak_ordering cmpChar(const char &, const char &) const; + static std::weak_ordering cmpNum(const char *&, const char *&); static void skipSpaces(const char *&); }; diff --git a/test/unit/util/case.h b/test/unit/util/case.h index b01f292b2..bc273a76e 100644 --- a/test/unit/util/case.h +++ b/test/unit/util/case.h @@ -13,7 +13,29 @@ namespace test { -using runnable = void (*)(); +using runnable = std::function; + +struct assertion_error : std::runtime_error +{ + src_location location; + + explicit assertion_error(const std::string &msg, + src_location location = src_location()) noexcept : + runtime_error("Assert " + msg), + location(location) + {} +}; + +struct skip_error : std::runtime_error +{ + src_location location; + + explicit skip_error(const std::string &msg, + src_location location = src_location()) noexcept : + runtime_error("Skip " + msg), + location(location) + {} +}; class case_type { @@ -51,7 +73,8 @@ class case_type [[nodiscard]] std::string full_name() const { return "[" + std::string{suite_name} + "] " - + std::string{case_name}; + + std::string{case_name} + " (" + file_name() + ":" + + std::to_string(location.get_line()) + ")"; } [[nodiscard]] std::string file_name() const @@ -59,12 +82,16 @@ class case_type return location.get_file_name(); } - explicit operator bool() const { return error_messages.empty(); } + explicit operator bool() const + { + return skip || error_messages.empty(); + } private: std::string_view suite_name; std::string_view case_name; runnable runner; + bool skip = false; src_location location; std::map error_messages; @@ -73,7 +100,13 @@ class case_type try { runner(); } - + catch (const assertion_error &e) { + fail(e.location, e.what()); + } + catch (const skip_error &e) { + if (error_messages.empty()) { skip = true; } + fail(e.location, e.what()); + } catch (std::exception &e) { fail(location, "exception thrown: " + std::string(e.what())); @@ -97,14 +130,17 @@ class case_type std::cout << (error_messages.empty() ? std::string(ansi::fg_green) + "[ OK ] " + : skip + ? std::string(ansi::fg_yellow) + + "[ SKIP ] " : std::string(ansi::fg_red) + "[ FAILED ] ") << ansi::reset << full_name() << " (" << (duration_cast(duration).count()) << " ms)\n"; for (const auto &error : error_messages) - std::cerr << error.first.error_prefix() - << "error: " << error.second << "\n"; + std::cerr << error.first.error_prefix() << error.second + << "\n"; } }; diff --git a/test/unit/util/case_registry.h b/test/unit/util/case_registry.h index f317ad713..b4bda08a5 100644 --- a/test/unit/util/case_registry.h +++ b/test/unit/util/case_registry.h @@ -21,13 +21,13 @@ class case_registry void add_record(std::string_view suite_name, std::string_view case_name, - runnable runner, + runnable &&runner, src_location location) noexcept { try { cases.emplace_back(suite_name, case_name, - runner, + std::move(runner), location); } catch (...) { diff --git a/test/unit/util/collection.h b/test/unit/util/collection.h index 712626e74..7a9bce4a2 100644 --- a/test/unit/util/collection.h +++ b/test/unit/util/collection.h @@ -113,6 +113,12 @@ class collection : public case_registry } }; +inline suite_proxy operator""_suite(const char *s, + std::size_t n) noexcept +{ + return collection::add_suite(std::string_view{s, n}); +} + } #endif diff --git a/test/unit/util/condition.h b/test/unit/util/condition.h index 8c618aa0c..5830207e6 100644 --- a/test/unit/util/condition.h +++ b/test/unit/util/condition.h @@ -9,101 +9,205 @@ namespace test { -struct check; +struct check_t; + +template struct throws_t; template struct decomposer { public: - friend check; + friend check_t; - bool operator==(const auto &ref) const + template bool operator==(const U &ref) const { - return evaluate(value == ref, ref); + if constexpr (requires { ref == value; }) { + return evaluate(value == ref, "==", ref); + } + else if constexpr (std::ranges::range) { + auto &&[lhs, rhs] = std::ranges::mismatch(value, ref); + return evaluate(lhs == std::end(value) + && rhs == std::end(ref), + "==", + ref); + } + else if constexpr (std::is_convertible_v) { + return *this == static_cast(ref); + } + else { + static_assert( + requires { ref == value; }, + "Cannot compare types"); + } } + bool operator<=(const auto &ref) const { - return evaluate(value <= ref, ref); + return evaluate(value <= ref, "<=", ref); } bool operator>=(const auto &ref) const { - return evaluate(value >= ref, ref); + return evaluate(value >= ref, ">=", ref); } bool operator<(const auto &ref) const { - return evaluate(value < ref, ref); + return evaluate(value < ref, "<", ref); } bool operator>(const auto &ref) const { - return evaluate(value > ref, ref); + return evaluate(value > ref, ">", ref); } private: const T &value; src_location location; + void (*throw_error)(const std::string &, src_location); - decomposer(const T &value, src_location loc) : + decomposer(const T &value, + src_location loc, + void (*throw_error)(const std::string &, + src_location) = nullptr) : value(value), - location(loc) + location(loc), + throw_error(throw_error) {} - [[nodiscard]] bool evaluate(bool condition, const auto &ref) const + [[nodiscard]] bool + evaluate(bool condition, const char *op, const auto &ref) const { using details::to_debug_string; if (!condition) { + if (throw_error) { + throw_error( + std::string("comparison failed\n") + "\t(actual) " + + to_debug_string(value) + " " + op + " " + + to_debug_string(ref) + " (expected)", + location); + } + collection::instance().running_test()->fail(location, - std::string("comparison failed\n") - + "\tactual: " + to_debug_string(value) + "\n" - + "\texpected: " + to_debug_string(ref)); + std::string("Check comparison failed\n") + + "\t(actual) " + to_debug_string(value) + " " + + op + " " + to_debug_string(ref) + + " (expected)"); } return condition; } }; -struct check +inline namespace consts +{ +struct impl_check_t +{ + test::check_t operator()(src_location loc = src_location()) const; +}; +struct assert_t +{}; +struct skip_t +{}; +template struct impl_throws_t +{ + test::throws_t operator()( + src_location loc = src_location()) const; +}; +} + +struct check_t { - explicit check(src_location loc = src_location()) noexcept : + explicit check_t(src_location loc = src_location()) noexcept : + location(loc) + {} + + explicit(false) check_t(consts::impl_check_t, + src_location loc = src_location()) noexcept : location(loc) {} + explicit(false) check_t(consts::assert_t, + src_location loc = src_location()) noexcept : + location(loc), + throw_error{+[](const std::string &msg, src_location loc) + { + throw assertion_error(msg, loc); + }} + {} + + explicit(false) check_t(consts::skip_t, + src_location loc = src_location()) noexcept : + location(loc), + throw_error{+[](const std::string &msg, src_location loc) + { + throw skip_error(msg, loc); + }} + {} + [[nodiscard]] auto operator<<(const auto &value) const { - return decomposer(value, location); + return decomposer(value, location, throw_error); } src_location location; + void (*throw_error)(const std::string &, src_location) = nullptr; }; -struct assertion +struct expected_bool_t { - explicit assertion(src_location loc = src_location()) : - location(loc) + bool value; + std::string_view msg; + + constexpr explicit(false) + expected_bool_t(std::string_view msg, bool value = true) : + value(value), + msg(msg) {} +}; - auto &operator<<(const auto &condition) const - { - if (!condition) { - collection::instance().running_test()->fail(location, - "assertion failed"); - } - return *this; - } +constexpr expected_bool_t operator""_is_true(const char *msg, + std::size_t len) +{ + return expected_bool_t{{msg, len}}; +} - src_location location; -}; +constexpr expected_bool_t operator""_is_false(const char *msg, + std::size_t len) +{ + return expected_bool_t{{msg, len}, false}; +} -struct fail +struct bool_check_t : check_t { - explicit fail(const src_location &loc = src_location()) + bool value; + + void operator==(expected_bool_t exp) const { - collection::instance().running_test()->fail(loc, - "assertion failed"); + using details::to_debug_string; + + if (value != exp.value) { + if (throw_error) { + throw_error(std::string("expectation failed\n") + + "\t\"" + std::string{exp.msg} + + "\" is not " + + (exp.value ? "true" : "false"), + location); + } + + collection::instance().running_test()->fail(location, + std::string("Check expectation failed\n") + "\t\"" + + std::string{exp.msg} + "\" is not " + + (exp.value ? "true" : "false")); + } } }; -template struct throws +template struct throws_t { - explicit throws(src_location loc = src_location()) : location(loc) + explicit throws_t(src_location loc = src_location()) : + location(loc) + {} + + explicit(false) throws_t(consts::impl_throws_t, + src_location loc = src_location()) : + location(loc) {} auto &operator<<(const auto &f) const @@ -111,16 +215,71 @@ template struct throws try { f(); collection::instance().running_test()->fail(location, - "exception expected but not thrown"); + "Exception expected but not thrown"); } catch (const exception &) { } + catch (const assertion_error &) { + throw; + } + catch (const skip_error &) { + throw; + } + catch (...) { + collection::instance().running_test()->fail(location, + "Exception thrown, but not the expected type"); + } return *this; } src_location location; }; +template +throws_t(consts::impl_throws_t, + src_location loc = src_location()) -> throws_t; + +[[nodiscard]] inline auto operator->*(check_t &&check, + const auto &value) +{ + return check << value; +} +[[nodiscard]] inline bool_check_t operator->*(check_t &&check, + bool value) +{ + return bool_check_t{check, value}; +} + +inline auto operator->*(throws_t &&throws, + const auto &value) +{ + return throws << value; +} + +inline namespace consts +{ + +[[nodiscard]] inline test::check_t impl_check_t::operator()( + src_location loc) const +{ + return test::check_t{loc}; +} + +template +[[nodiscard]] inline test::throws_t +impl_throws_t::operator()(src_location loc) const +{ + return test::throws_t{loc}; +} + +static inline constexpr impl_check_t check{}; +static inline constexpr assert_t assert{}; +static inline constexpr skip_t skip{}; + +static inline constexpr impl_throws_t throw_{}; +template +static inline constexpr impl_throws_t throws{}; +} } #endif diff --git a/test/unit/util/suite_proxy.h b/test/unit/util/suite_proxy.h index bf5881ed0..f177450b1 100644 --- a/test/unit/util/suite_proxy.h +++ b/test/unit/util/suite_proxy.h @@ -25,13 +25,40 @@ class suite_proxy {} suite_proxy &add_case(std::string_view case_name, - runnable test, + runnable &&test, src_location location = src_location()) noexcept { - parent.add_record(name, case_name, test, location); + parent.add_record(name, case_name, std::move(test), location); return *this; } + struct case_name_proxy + { + std::string_view case_name; + src_location location; + explicit(false) case_name_proxy(std::string_view case_name, + src_location location = src_location()) : + case_name(case_name), + location(location) + {} + }; + + struct case_proxy : case_name_proxy + { + suite_proxy &suite; + + suite_proxy &operator|(runnable &&test) noexcept + { + suite.add_case(case_name, std::move(test), location); + return suite; + } + }; + + case_proxy operator|(case_name_proxy &&case_) noexcept + { + return {case_, *this}; + } + suite_proxy generate(const generator &gen) { gen(*this); @@ -42,6 +69,12 @@ class suite_proxy case_registry &parent; }; +inline std::string_view operator"" _case(const char *name, + size_t size) +{ + return {name, size}; +} + } #endif diff --git a/test/unit/util/to_string.h b/test/unit/util/to_string.h index d643e5f1e..2f2c7e774 100644 --- a/test/unit/util/to_string.h +++ b/test/unit/util/to_string.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace test::details { @@ -28,6 +29,49 @@ std::string to_debug_string(const from &value) else if constexpr (std::is_constructible_v) { return std::string(value); } + else if constexpr (requires { + std::visit( + [](const auto &) + { + }, + value); + }) { + return std::visit( + [](const auto &v) + { + return to_debug_string(v); + }, + value); + } + else if constexpr (std::ranges::range) { + auto ss = std::stringstream{}; + ss << "["; + bool first = true; + for (auto &&v : value) { + if (!first) + ss << ", "; + else + first = false; + ss << to_debug_string(v); + } + ss << "]"; + return ss.str(); + } + else if constexpr (requires { std::tuple_size::value; }) { + auto ss = std::stringstream{}; + ss << "("; + bool first = true; + std::apply( + [&](auto &&...v) + { + ((ss << (first ? "" : ", ") << to_debug_string(v), + first = false), + ...); + }, + value); + ss << ")"; + return ss.str(); + } else return ""; } From 8f0aca3a81f345b12ca9f93786bd0eaac11ae999 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Fri, 2 Feb 2024 14:40:14 +0100 Subject: [PATCH 015/253] update dev deps --- package-lock.json | 6 +- tools/ci/pdm.lock | 787 ++++++++++++++++++---------------------------- 2 files changed, 309 insertions(+), 484 deletions(-) diff --git a/package-lock.json b/package-lock.json index 255fad620..e077c3de2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3871,9 +3871,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "dev": true, "funding": [ { diff --git a/tools/ci/pdm.lock b/tools/ci/pdm.lock index 8b8dd621d..03540c38f 100644 --- a/tools/ci/pdm.lock +++ b/tools/ci/pdm.lock @@ -2,86 +2,11 @@ # It is not intended for manual editing. [metadata] -groups = ["codequality", "docs"] +groups = ["default", "codequality", "docs"] strategy = ["cross_platform"] lock_version = "4.4" content_hash = "sha256:d9b85b7701c9533e23b6da936f433eafb1968fa46ac07790e3e797aafe12e6e2" -[[package]] -name = "aiohttp" -version = "3.9.1" -requires_python = ">=3.8" -summary = "Async http client/server framework (asyncio)" -dependencies = [ - "aiosignal>=1.1.2", - "async-timeout<5.0,>=4.0; python_version < \"3.11\"", - "attrs>=17.3.0", - "frozenlist>=1.1.1", - "multidict<7.0,>=4.5", - "yarl<2.0,>=1.0", -] -files = [ - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, - {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, - {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, - {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, - {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, - {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, - {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, - {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, -] - -[[package]] -name = "aiosignal" -version = "1.3.1" -requires_python = ">=3.7" -summary = "aiosignal: a list of registered asynchronous callbacks" -dependencies = [ - "frozenlist>=1.1.0", -] -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - [[package]] name = "astroid" version = "3.0.2" @@ -95,26 +20,6 @@ files = [ {file = "astroid-3.0.2.tar.gz", hash = "sha256:4a61cf0a59097c7bb52689b0fd63717cd2a8a14dc9f1eee97b82d814881c8c91"}, ] -[[package]] -name = "async-timeout" -version = "4.0.3" -requires_python = ">=3.7" -summary = "Timeout context manager for asyncio programs" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[[package]] -name = "attrs" -version = "23.1.0" -requires_python = ">=3.7" -summary = "Classes Without Boilerplate" -files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, -] - [[package]] name = "babel" version = "2.14.0" @@ -127,30 +32,30 @@ files = [ [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" requires_python = ">=3.6.0" summary = "Screen-scraping library" dependencies = [ "soupsieve>1.2", ] files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" extras = ["lxml"] requires_python = ">=3.6.0" summary = "Screen-scraping library" dependencies = [ - "beautifulsoup4==4.12.2", + "beautifulsoup4==4.12.3", "lxml", ] files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [[package]] @@ -170,11 +75,10 @@ files = [ [[package]] name = "black" -version = "23.12.0" +version = "24.1.1" requires_python = ">=3.8" summary = "The uncompromising code formatter." dependencies = [ - "aiohttp>=3.7.4; sys_platform != \"win32\" or implementation_name != \"pypy\" and extra == \"d\"", "click>=8.0.0", "mypy-extensions>=0.4.3", "packaging>=22.0", @@ -184,20 +88,20 @@ dependencies = [ "typing-extensions>=4.0.1; python_version < \"3.11\"", ] files = [ - {file = "black-23.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67f19562d367468ab59bd6c36a72b2c84bc2f16b59788690e02bbcb140a77175"}, - {file = "black-23.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbd75d9f28a7283b7426160ca21c5bd640ca7cd8ef6630b4754b6df9e2da8462"}, - {file = "black-23.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:593596f699ca2dcbbbdfa59fcda7d8ad6604370c10228223cd6cf6ce1ce7ed7e"}, - {file = "black-23.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:12d5f10cce8dc27202e9a252acd1c9a426c83f95496c959406c96b785a92bb7d"}, - {file = "black-23.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e73c5e3d37e5a3513d16b33305713237a234396ae56769b839d7c40759b8a41c"}, - {file = "black-23.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba09cae1657c4f8a8c9ff6cfd4a6baaf915bb4ef7d03acffe6a2f6585fa1bd01"}, - {file = "black-23.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace64c1a349c162d6da3cef91e3b0e78c4fc596ffde9413efa0525456148873d"}, - {file = "black-23.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:72db37a2266b16d256b3ea88b9affcdd5c41a74db551ec3dd4609a59c17d25bf"}, - {file = "black-23.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fdf6f23c83078a6c8da2442f4d4eeb19c28ac2a6416da7671b72f0295c4a697b"}, - {file = "black-23.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39dda060b9b395a6b7bf9c5db28ac87b3c3f48d4fdff470fa8a94ab8271da47e"}, - {file = "black-23.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7231670266ca5191a76cb838185d9be59cfa4f5dd401b7c1c70b993c58f6b1b5"}, - {file = "black-23.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:193946e634e80bfb3aec41830f5d7431f8dd5b20d11d89be14b84a97c6b8bc75"}, - {file = "black-23.12.0-py3-none-any.whl", hash = "sha256:a7c07db8200b5315dc07e331dda4d889a56f6bf4db6a9c2a526fa3166a81614f"}, - {file = "black-23.12.0.tar.gz", hash = "sha256:330a327b422aca0634ecd115985c1c7fd7bdb5b5a2ef8aa9888a82e2ebe9437a"}, + {file = "black-24.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c"}, + {file = "black-24.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445"}, + {file = "black-24.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a"}, + {file = "black-24.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4"}, + {file = "black-24.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7"}, + {file = "black-24.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8"}, + {file = "black-24.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161"}, + {file = "black-24.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d"}, + {file = "black-24.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8"}, + {file = "black-24.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e"}, + {file = "black-24.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6"}, + {file = "black-24.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b"}, + {file = "black-24.1.1-py3-none-any.whl", hash = "sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168"}, + {file = "black-24.1.1.tar.gz", hash = "sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b"}, ] [[package]] @@ -212,12 +116,12 @@ files = [ [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" requires_python = ">=3.6" summary = "Python package for providing Mozilla's CA Bundle." files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -313,12 +217,12 @@ files = [ [[package]] name = "dill" -version = "0.3.7" -requires_python = ">=3.7" +version = "0.3.8" +requires_python = ">=3.8" summary = "serialize all of Python" files = [ - {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, - {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, ] [[package]] @@ -330,45 +234,6 @@ files = [ {file = "EditorConfig-0.12.3.tar.gz", hash = "sha256:57f8ce78afcba15c8b18d46b5170848c88d56fd38f05c2ec60dbbfcb8996e89e"}, ] -[[package]] -name = "frozenlist" -version = "1.4.0" -requires_python = ">=3.8" -summary = "A list-like structure which implements collections.abc.MutableSequence" -files = [ - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, - {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, - {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, - {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, - {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, - {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, -] - [[package]] name = "ghp-import" version = "2.1.0" @@ -393,15 +258,15 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.0.0" +version = "7.0.1" requires_python = ">=3.8" summary = "Read metadata from Python packages" dependencies = [ "zipp>=0.5", ] files = [ - {file = "importlib_metadata-7.0.0-py3-none-any.whl", hash = "sha256:d97503976bb81f40a193d41ee6570868479c69d5068651eb039c40d850c59d67"}, - {file = "importlib_metadata-7.0.0.tar.gz", hash = "sha256:7fc841f8b8332803464e5dc1c63a2e59121f46ca186c0e2e182e80bf8c1319f7"}, + {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, + {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, ] [[package]] @@ -416,25 +281,25 @@ files = [ [[package]] name = "isort" -version = "5.13.1" +version = "5.13.2" requires_python = ">=3.8.0" summary = "A Python utility / library to sort Python imports." files = [ - {file = "isort-5.13.1-py3-none-any.whl", hash = "sha256:56a51732c25f94ca96f6721be206dd96a95f42950502eb26c1015d333bc6edb7"}, - {file = "isort-5.13.1.tar.gz", hash = "sha256:aaed790b463e8703fb1eddb831dfa8e8616bacde2c083bd557ef73c8189b7263"}, + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" requires_python = ">=3.7" summary = "A very fast and expressive template engine." dependencies = [ "MarkupSafe>=2.0", ] files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [[package]] @@ -449,61 +314,78 @@ files = [ {file = "jsbeautifier-1.14.11.tar.gz", hash = "sha256:6b632581ea60dd1c133cd25a48ad187b4b91f526623c4b0fb5443ef805250505"}, ] +[[package]] +name = "linkify-it-py" +version = "2.0.2" +requires_python = ">=3.7" +summary = "Links recognition library with FULL unicode support." +dependencies = [ + "uc-micro-py", +] +files = [ + {file = "linkify-it-py-2.0.2.tar.gz", hash = "sha256:19f3060727842c254c808e99d465c80c49d2c7306788140987a1a7a29b0d6ad2"}, + {file = "linkify_it_py-2.0.2-py3-none-any.whl", hash = "sha256:a3a24428f6c96f27370d7fe61d2ac0be09017be5190d68d8658233171f1b6541"}, +] + [[package]] name = "lxml" -version = "4.9.3" -requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +version = "5.1.0" +requires_python = ">=3.6" summary = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." files = [ - {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, - {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, - {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, - {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, - {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, - {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, - {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, - {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, - {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, - {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, + {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, + {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, + {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, + {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, + {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, + {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, + {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, ] [[package]] name = "markdown" -version = "3.5.1" +version = "3.5.2" requires_python = ">=3.8" summary = "Python implementation of John Gruber's Markdown." files = [ - {file = "Markdown-3.5.1-py3-none-any.whl", hash = "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc"}, - {file = "Markdown-3.5.1.tar.gz", hash = "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd"}, + {file = "Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd"}, + {file = "Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8"}, ] [[package]] @@ -519,43 +401,58 @@ files = [ {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +extras = ["linkify"] +requires_python = ">=3.8" +summary = "Python port of markdown-it. Markdown parsing, done right!" +dependencies = [ + "linkify-it-py<3,>=1", + "markdown-it-py==3.0.0", +] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.4" requires_python = ">=3.7" summary = "Safely add untrusted strings to HTML/XML markup." files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, ] [[package]] @@ -584,7 +481,7 @@ files = [ [[package]] name = "mdformat-admon" -version = "1.0.2" +version = "2.0.1" requires_python = ">=3.8.0" summary = "An mdformat plugin for admonitions." dependencies = [ @@ -592,8 +489,8 @@ dependencies = [ "mdit-py-plugins>=0.4.0", ] files = [ - {file = "mdformat_admon-1.0.2-py3-none-any.whl", hash = "sha256:1ab208bd1bc95122b4eca16aa65f42b4c1cf9ea579201dd946dfc40a4cb005b8"}, - {file = "mdformat_admon-1.0.2.tar.gz", hash = "sha256:95b9397d88d1cb7935f976107a93993c2d029f044a0a8794dfaa379abd815c56"}, + {file = "mdformat_admon-2.0.1-py3-none-any.whl", hash = "sha256:0d6347b729dff1468b15275d683ce0f4b8592dfe5f2145b95b7983167ee9cc78"}, + {file = "mdformat_admon-2.0.1.tar.gz", hash = "sha256:82bb44d541d266a49501a0c751d9e4367934876093c97c85efa2e1a5d1bb4577"}, ] [[package]] @@ -685,18 +582,18 @@ files = [ [[package]] name = "mdformat-gfm" -version = "0.3.0" -requires_python = ">=3.6.1,<4.0.0" +version = "0.3.6" +requires_python = ">=3.8" summary = "Mdformat plugin for GitHub Flavored Markdown compatibility" dependencies = [ - "markdown-it-py>=0.5.8", + "markdown-it-py[linkify]", "mdformat-tables>=0.4.0", - "mdformat<0.8.0,>=0.7.0", + "mdformat<0.8.0,>=0.7.5", "mdit-py-plugins>=0.2.0", ] files = [ - {file = "mdformat-gfm-0.3.0.tar.gz", hash = "sha256:bceb7b320607fdf245b439c23097ce55574a0bfe1199a9e1a1cc868dcfecdd6b"}, - {file = "mdformat_gfm-0.3.0-py3-none-any.whl", hash = "sha256:29cb6c37655245285ac4ddc00f3225a5ba4b6c550223d81c0b4f14269c4956fd"}, + {file = "mdformat_gfm-0.3.6-py3-none-any.whl", hash = "sha256:579e3619bedd3b7123df888b6929ab8ac5dfc8205d0b67153b1633262bdafc42"}, + {file = "mdformat_gfm-0.3.6.tar.gz", hash = "sha256:b405ebf651a15c186ca06712100e33bbe72afeafb02aa4a4a28ea26cc3219678"}, ] [[package]] @@ -883,7 +780,7 @@ files = [ [[package]] name = "mkdocs-material" -version = "9.5.2" +version = "9.5.6" requires_python = ">=3.8" summary = "Documentation that simply works" dependencies = [ @@ -892,7 +789,7 @@ dependencies = [ "jinja2~=3.0", "markdown~=3.2", "mkdocs-material-extensions~=1.3", - "mkdocs>=1.5.3,~=1.5", + "mkdocs~=1.5.3", "paginate~=0.5", "pygments~=2.16", "pymdown-extensions~=10.2", @@ -900,8 +797,8 @@ dependencies = [ "requests~=2.26", ] files = [ - {file = "mkdocs_material-9.5.2-py3-none-any.whl", hash = "sha256:6ed0fbf4682491766f0ec1acc955db6901c2fd424c7ab343964ef51b819741f5"}, - {file = "mkdocs_material-9.5.2.tar.gz", hash = "sha256:ca8b9cd2b3be53e858e5a1a45ac9668bd78d95d77a30288bb5ebc1a31db6184c"}, + {file = "mkdocs_material-9.5.6-py3-none-any.whl", hash = "sha256:e115b90fccf5cd7f5d15b0c2f8e6246b21041628b8f590630e7fca66ed7fcf6c"}, + {file = "mkdocs_material-9.5.6.tar.gz", hash = "sha256:5b24df36d8ac6cecd611241ce6f6423ccde3e1ad89f8360c3f76d5565fc2d82a"}, ] [[package]] @@ -940,48 +837,9 @@ files = [ {file = "mkdocs_section_index-0.3.8.tar.gz", hash = "sha256:bbd209f0da79441baf136ef3a9c40665bb9681d1fb62c73ca2f116fd1388a404"}, ] -[[package]] -name = "multidict" -version = "6.0.4" -requires_python = ">=3.7" -summary = "multidict implementation" -files = [ - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, - {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, - {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, - {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, - {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, - {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, -] - [[package]] name = "mypy" -version = "1.7.1" +version = "1.8.0" requires_python = ">=3.8" summary = "Optional static typing for Python" dependencies = [ @@ -990,23 +848,23 @@ dependencies = [ "typing-extensions>=4.1.0", ] files = [ - {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, - {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, - {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, - {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, - {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, - {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, - {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, - {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, - {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, - {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, - {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, - {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, - {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, ] [[package]] @@ -1049,56 +907,67 @@ files = [ [[package]] name = "pillow" -version = "10.1.0" +version = "10.2.0" requires_python = ">=3.8" summary = "Python Imaging Library (Fork)" files = [ - {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, - {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, - {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, - {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, - {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, - {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, ] [[package]] name = "platformdirs" -version = "4.1.0" +version = "4.2.0" requires_python = ">=3.8" summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [[package]] @@ -1135,7 +1004,7 @@ files = [ [[package]] name = "pymdown-extensions" -version = "10.5" +version = "10.7" requires_python = ">=3.8" summary = "Extension pack for Python Markdown." dependencies = [ @@ -1143,8 +1012,8 @@ dependencies = [ "pyyaml", ] files = [ - {file = "pymdown_extensions-10.5-py3-none-any.whl", hash = "sha256:1f0ca8bb5beff091315f793ee17683bc1390731f6ac4c5eb01e27464b80fe879"}, - {file = "pymdown_extensions-10.5.tar.gz", hash = "sha256:1b60f1e462adbec5a1ed79dac91f666c9c0d241fa294de1989f29d20096cfd0b"}, + {file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"}, + {file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"}, ] [[package]] @@ -1194,6 +1063,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1216,54 +1086,57 @@ files = [ [[package]] name = "regex" -version = "2023.10.3" +version = "2023.12.25" requires_python = ">=3.7" summary = "Alternative regular expression module, to replace re." files = [ - {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, - {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, - {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, - {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, - {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, - {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, - {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, - {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, - {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, + {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, + {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, + {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, + {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, + {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, + {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, + {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, ] [[package]] @@ -1380,31 +1253,32 @@ files = [ [[package]] name = "types-colorama" -version = "0.4.15.12" +version = "0.4.15.20240106" +requires_python = ">=3.8" summary = "Typing stubs for colorama" files = [ - {file = "types-colorama-0.4.15.12.tar.gz", hash = "sha256:fbdfc5d9d24d85c33bd054fbe33adc6cec44eedb19cfbbabfbbb57dc257ae4b8"}, - {file = "types_colorama-0.4.15.12-py3-none-any.whl", hash = "sha256:23c9d4a00961227f7ef018d5a1c190c4bbc282119c3ee76a17677a793f13bb82"}, + {file = "types-colorama-0.4.15.20240106.tar.gz", hash = "sha256:49096b4c4cbfcaa11699a0470c36e4f5631f193fb980188e013ea64445d35656"}, + {file = "types_colorama-0.4.15.20240106-py3-none-any.whl", hash = "sha256:18294bc18f60dc0b4895de8119964a5d895f5e180c2d1308fdd33009c0fa0f38"}, ] [[package]] name = "types-markdown" -version = "3.5.0.3" -requires_python = ">=3.7" +version = "3.5.0.20240129" +requires_python = ">=3.8" summary = "Typing stubs for Markdown" files = [ - {file = "types-Markdown-3.5.0.3.tar.gz", hash = "sha256:9afd38a8f53e19d43de3f8d89742b3674b5736767806ed9356d64ccb09f76439"}, - {file = "types_Markdown-3.5.0.3-py3-none-any.whl", hash = "sha256:2299b9086c695f408a3ebabf820f1fba3b239f1b3bfdbb32bf42d530b42cdd83"}, + {file = "types-Markdown-3.5.0.20240129.tar.gz", hash = "sha256:9acd36fef264d9ed5a96345c45f7d80f0d967059e92213998b3046fbb64f67fc"}, + {file = "types_Markdown-3.5.0.20240129-py3-none-any.whl", hash = "sha256:d6861d9d68e8268a5346d8a43d14727e6c636ebc6d49f2b8fc034c25996d35dd"}, ] [[package]] name = "types-pillow" -version = "10.1.0.2" -requires_python = ">=3.7" +version = "10.2.0.20240125" +requires_python = ">=3.8" summary = "Typing stubs for Pillow" files = [ - {file = "types-Pillow-10.1.0.2.tar.gz", hash = "sha256:525c1c5ee67b0ac1721c40d2bc618226ef2123c347e527e14e05b920721a13b9"}, - {file = "types_Pillow-10.1.0.2-py3-none-any.whl", hash = "sha256:131078ffa547bf9a201d39ffcdc65633e108148085f4f1b07d4647fcfec6e923"}, + {file = "types-Pillow-10.2.0.20240125.tar.gz", hash = "sha256:c449b2c43b9fdbe0494a7b950e6b39a4e50516091213fec24ef3f33c1d017717"}, + {file = "types_Pillow-10.2.0.20240125-py3-none-any.whl", hash = "sha256:322dbae32b4b7918da5e8a47c50ac0f24b0aa72a804a23857620f2722b03c858"}, ] [[package]] @@ -1435,14 +1309,24 @@ files = [ {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] +[[package]] +name = "uc-micro-py" +version = "1.0.2" +requires_python = ">=3.7" +summary = "Micro subset of unicode data files for linkify-it-py projects." +files = [ + {file = "uc-micro-py-1.0.2.tar.gz", hash = "sha256:30ae2ac9c49f39ac6dce743bd187fcd2b574b16ca095fa74cd9396795c954c54"}, + {file = "uc_micro_py-1.0.2-py3-none-any.whl", hash = "sha256:8c9110c309db9d9e87302e2f4ad2c3152770930d88ab385cd544e7a7e75f3de0"}, +] + [[package]] name = "urllib3" -version = "2.1.0" +version = "2.2.0" requires_python = ">=3.8" summary = "HTTP library with thread-safe connection pooling, file post, and more." files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, + {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, + {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, ] [[package]] @@ -1495,65 +1379,6 @@ files = [ {file = "wcmatch-8.5.tar.gz", hash = "sha256:86c17572d0f75cbf3bcb1a18f3bf2f9e72b39a9c08c9b4a74e991e1882a8efb3"}, ] -[[package]] -name = "yarl" -version = "1.9.4" -requires_python = ">=3.7" -summary = "Yet another URL library" -dependencies = [ - "idna>=2.0", - "multidict>=4.0", -] -files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, -] - [[package]] name = "zipp" version = "3.17.0" From 65f991d67fddb9629ac7a8b56bfc00d454f5ddd0 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Fri, 2 Feb 2024 14:40:21 +0100 Subject: [PATCH 016/253] fix format --- docs/tutorial/align_range.md | 6 +++--- docs/tutorial/assets/setup/setup_a.md | 8 +++++--- docs/tutorial/assets/setup/setup_b.md | 8 +++++--- docs/tutorial/assets/setup/setup_c.md | 8 +++++--- docs/tutorial/channels_legend.md | 12 ++++++------ docs/tutorial/data.md | 6 +++--- docs/tutorial/filter_add_new_records.md | 8 ++++---- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/docs/tutorial/align_range.md b/docs/tutorial/align_range.md index 129703a93..beb3f1652 100644 --- a/docs/tutorial/align_range.md +++ b/docs/tutorial/align_range.md @@ -15,9 +15,9 @@ the chart. For example, on a column chart, elements will be vertically centered, whereas on a bar chart, horizontally. !!! info - In the first example, the y-axis labels are hidden because they don't - properly represent the values shown on the column chart anymore, as the - chart elements float off the x-axis. + In the first example, the y-axis labels are hidden because they don't properly + represent the values shown on the column chart anymore, as the chart elements + float off the x-axis.
diff --git a/docs/tutorial/assets/setup/setup_a.md b/docs/tutorial/assets/setup/setup_a.md index 908a429a8..17c43501e 100644 --- a/docs/tutorial/assets/setup/setup_a.md +++ b/docs/tutorial/assets/setup/setup_a.md @@ -1,4 +1,6 @@ -??? info "Info - How to setup Vizzu" - {% include-markdown "tutorial/assets/setup/init.md" %} +??? info "Info - How to setup Vizzu" {% include-markdown +"tutorial/assets/setup/init.md" %} - {% include-markdown "tutorial/assets/setup/config_a.md" %} +``` +{% include-markdown "tutorial/assets/setup/config_a.md" %} +``` diff --git a/docs/tutorial/assets/setup/setup_b.md b/docs/tutorial/assets/setup/setup_b.md index 87e80d7b5..77767cb80 100644 --- a/docs/tutorial/assets/setup/setup_b.md +++ b/docs/tutorial/assets/setup/setup_b.md @@ -1,4 +1,6 @@ -??? info "Info - How to setup Vizzu" - {% include-markdown "tutorial/assets/setup/init.md" %} +??? info "Info - How to setup Vizzu" {% include-markdown +"tutorial/assets/setup/init.md" %} - {% include-markdown "tutorial/assets/setup/config_b.md" %} +``` +{% include-markdown "tutorial/assets/setup/config_b.md" %} +``` diff --git a/docs/tutorial/assets/setup/setup_c.md b/docs/tutorial/assets/setup/setup_c.md index 1fcf82a3c..b00c9a5b0 100644 --- a/docs/tutorial/assets/setup/setup_c.md +++ b/docs/tutorial/assets/setup/setup_c.md @@ -1,4 +1,6 @@ -??? info "Info - How to setup Vizzu" - {% include-markdown "tutorial/assets/setup/init.md" %} +??? info "Info - How to setup Vizzu" {% include-markdown +"tutorial/assets/setup/init.md" %} - {% include-markdown "tutorial/assets/setup/config_c.md" %} +``` +{% include-markdown "tutorial/assets/setup/config_c.md" %} +``` diff --git a/docs/tutorial/channels_legend.md b/docs/tutorial/channels_legend.md index 46cf603ac..379faa983 100644 --- a/docs/tutorial/channels_legend.md +++ b/docs/tutorial/channels_legend.md @@ -39,10 +39,10 @@ columns’ height and lightness represent the same values. The legend for the `lightness` channel is turned on using the `legend` property. !!! info - This is an example when we explicitly instruct `Vizzu` to show the legend. - By default `Vizzu` automatically shows/hides the legend when it's necessary. - You can also turn it off with the `legend`: `null` setting or set back to - automatic mode with `legend`: `'auto'`. + This is an example when we explicitly instruct `Vizzu` to show the legend. By + default `Vizzu` automatically shows/hides the legend when it's necessary. You + can also turn it off with the `legend`: `null` setting or set back to automatic + mode with `legend`: `'auto'`.
@@ -64,8 +64,8 @@ is put on it that is on the x-axis resulting in each bar having a different color. If a measure is put on the `color` channel, a color range will be used. !!! info - The value on the `lightness` channel is removed in this step as it doesn’t - make sense to use it together with the `color` channel in this case. + The value on the `lightness` channel is removed in this step as it doesn’t make + sense to use it together with the `color` channel in this case.
diff --git a/docs/tutorial/data.md b/docs/tutorial/data.md index 4d3e1578b..bf1c8d9c2 100644 --- a/docs/tutorial/data.md +++ b/docs/tutorial/data.md @@ -131,9 +131,9 @@ let data = { Using data cube form: !!! note - In the example below, the record `Rock,Experimental,36` has been replaced - with `Rock,Smooth,36` in order to illustrate that only data with same - dimensions can be used in the data cube form. + In the example below, the record `Rock,Experimental,36` has been replaced with + `Rock,Smooth,36` in order to illustrate that only data with same dimensions can + be used in the data cube form. diff --git a/docs/tutorial/filter_add_new_records.md b/docs/tutorial/filter_add_new_records.md index 2c292911f..5ecebeaa9 100644 --- a/docs/tutorial/filter_add_new_records.md +++ b/docs/tutorial/filter_add_new_records.md @@ -72,9 +72,9 @@ chart.animate({ ``` !!! info - Combining this option with the [store](./shorthands_store.md) function makes - it easy to update previously configured states with fresh data since this - function saves the config and style parameters of the chart into a variable - but not the data. + Combining this option with the [store](./shorthands_store.md) function makes it + easy to update previously configured states with fresh data since this function + saves the config and style parameters of the chart into a variable but not the + data. From f67ba1f46db5776d43fc551e8241b42d5cf878ec Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 2 Feb 2024 15:14:21 +0100 Subject: [PATCH 017/253] interface -> remove change identifier, only get name + unique id --- src/dataframe/interface.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 78a97c5f5..62227b128 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -137,12 +137,10 @@ class dataframe_interface : [[nodiscard]] virtual std::pair get_min_max( series_identifier measure) const & = 0; - [[nodiscard]] virtual series_identifier - change_series_identifier_type( + [[nodiscard]] virtual std::string_view get_series_name( const series_identifier &id) const & = 0; - [[nodiscard]] virtual record_identifier - change_record_identifier_type( + [[nodiscard]] virtual std::string_view get_record_unique_id( const record_identifier &id) const & = 0; [[nodiscard]] virtual cell_value get_data( From 5e70b888b3f730c788236c3860e28634ffebdc4c Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 2 Feb 2024 15:15:59 +0100 Subject: [PATCH 018/253] fix catches --- test/unit/util/condition.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/util/condition.h b/test/unit/util/condition.h index 5830207e6..d6abe70a3 100644 --- a/test/unit/util/condition.h +++ b/test/unit/util/condition.h @@ -217,14 +217,14 @@ template struct throws_t collection::instance().running_test()->fail(location, "Exception expected but not thrown"); } - catch (const exception &) { - } catch (const assertion_error &) { throw; } catch (const skip_error &) { throw; } + catch (const exception &) { + } catch (...) { collection::instance().running_test()->fail(location, "Exception thrown, but not the expected type"); From 1ea71d8798c0613df7897ca9dd791ece1e973f72 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 2 Feb 2024 15:40:05 +0100 Subject: [PATCH 019/253] Fix clangtidy --- src/base/refl/auto_enum.h | 4 ++-- src/base/text/naturalcmp.h | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/base/refl/auto_enum.h b/src/base/refl/auto_enum.h index a191a305f..766ed565f 100644 --- a/src/base/refl/auto_enum.h +++ b/src/base/refl/auto_enum.h @@ -181,9 +181,9 @@ template struct EnumVariant : std::variant { using base_variant = std::variant; + using base_variant::base_variant; - [[nodiscard]] explicit(false) constexpr - operator E() const noexcept + [[nodiscard]] constexpr operator E() const noexcept // NOLINT { return static_cast(base_variant::index()); } diff --git a/src/base/text/naturalcmp.h b/src/base/text/naturalcmp.h index 94451d08d..336a2afe4 100644 --- a/src/base/text/naturalcmp.h +++ b/src/base/text/naturalcmp.h @@ -11,14 +11,18 @@ class NaturalCmp public: explicit NaturalCmp(bool ignoreCase = true, bool ignoreSpace = true); - bool operator()(const std::string &, const std::string &) const; + [[nodiscard]] bool operator()(const std::string &, + const std::string &) const; private: bool ignoreCase; bool ignoreSpace; - std::weak_ordering cmp(const char *, const char *) const; - std::weak_ordering cmpChar(const char &, const char &) const; - static std::weak_ordering cmpNum(const char *&, const char *&); + [[nodiscard]] std::weak_ordering cmp(const char *, + const char *) const; + [[nodiscard]] std::weak_ordering cmpChar(const char &, + const char &) const; + [[nodiscard]] static std::weak_ordering cmpNum(const char *&, + const char *&); static void skipSpaces(const char *&); }; From d3bdd98f51558cc832dd11f596316f31f9e6f497 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 2 Feb 2024 20:23:28 +0100 Subject: [PATCH 020/253] modifying record_type --- src/dataframe/interface.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 62227b128..8d32fa21e 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -51,8 +51,12 @@ class dataframe_interface : struct record_type { - std::function getValueByColumn; + [[nodiscard]] cell_value getValue(series_identifier i) const + { + return parent->get_data(recordId, i); + } + const dataframe_interface *parent; record_identifier recordId; }; From 244b5bb7a85518288ae74010d2b250b330195632 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 2 Feb 2024 20:26:34 +0100 Subject: [PATCH 021/253] test: show latest seen location before exception. Add emplace by enum --- src/base/refl/auto_enum.h | 10 +++++++++ test/unit/util/case.h | 10 +++++++-- test/unit/util/condition.h | 43 ++++++++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/base/refl/auto_enum.h b/src/base/refl/auto_enum.h index 766ed565f..91413cb08 100644 --- a/src/base/refl/auto_enum.h +++ b/src/base/refl/auto_enum.h @@ -187,6 +187,16 @@ struct EnumVariant : std::variant { return static_cast(base_variant::index()); } + + using base_variant::emplace; + + template + decltype(auto) emplace(Constr &&...args) + { + return base_variant::template emplace< + static_cast(value)>( + std::forward(args)...); + } }; template diff --git a/test/unit/util/case.h b/test/unit/util/case.h index bc273a76e..661c99c6f 100644 --- a/test/unit/util/case.h +++ b/test/unit/util/case.h @@ -87,12 +87,18 @@ class case_type return skip || error_messages.empty(); } + void set_latest_location(const src_location &loc) + { + latest_location = loc; + } + private: std::string_view suite_name; std::string_view case_name; runnable runner; bool skip = false; src_location location; + src_location latest_location = location; std::map error_messages; void run_safely() noexcept @@ -108,11 +114,11 @@ class case_type fail(e.location, e.what()); } catch (std::exception &e) { - fail(location, + fail(latest_location, "exception thrown: " + std::string(e.what())); } catch (...) { - fail(location, "unknown exception thrown"); + fail(latest_location, "unknown exception thrown"); } } diff --git a/test/unit/util/condition.h b/test/unit/util/condition.h index d6abe70a3..57bc729c0 100644 --- a/test/unit/util/condition.h +++ b/test/unit/util/condition.h @@ -114,31 +114,39 @@ template struct impl_throws_t struct check_t { - explicit check_t(src_location loc = src_location()) noexcept : - location(loc) - {} + explicit check_t(src_location loc = src_location(), + void (*err)(const std::string &, + src_location) = nullptr) noexcept : + location(loc), + throw_error(err) + { + collection::instance().running_test()->set_latest_location( + loc); + } explicit(false) check_t(consts::impl_check_t, src_location loc = src_location()) noexcept : - location(loc) + check_t(loc) {} explicit(false) check_t(consts::assert_t, src_location loc = src_location()) noexcept : - location(loc), - throw_error{+[](const std::string &msg, src_location loc) - { - throw assertion_error(msg, loc); - }} + check_t( + loc, + +[](const std::string &msg, src_location loc) + { + throw assertion_error(msg, loc); + }) {} explicit(false) check_t(consts::skip_t, src_location loc = src_location()) noexcept : - location(loc), - throw_error{+[](const std::string &msg, src_location loc) - { - throw skip_error(msg, loc); - }} + check_t( + loc, + +[](const std::string &msg, src_location loc) + { + throw skip_error(msg, loc); + }) {} [[nodiscard]] auto operator<<(const auto &value) const @@ -203,11 +211,14 @@ template struct throws_t { explicit throws_t(src_location loc = src_location()) : location(loc) - {} + { + collection::instance().running_test()->set_latest_location( + loc); + } explicit(false) throws_t(consts::impl_throws_t, src_location loc = src_location()) : - location(loc) + throws_t(loc) {} auto &operator<<(const auto &f) const From 5461f2df58cfb59c859d3824a3ec0317e5d44583 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 2 Feb 2024 20:56:26 +0100 Subject: [PATCH 022/253] Add interface test --- test/unit/dataframe/interface_test.cpp | 307 +++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 test/unit/dataframe/interface_test.cpp diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp new file mode 100644 index 000000000..619d01fd5 --- /dev/null +++ b/test/unit/dataframe/interface_test.cpp @@ -0,0 +1,307 @@ + +#include "../util/test.h" +#include "dataframe/impl/dataframe.h" +// #include "dataframe/interface.h" + +using test::assert; +using test::check; +using test::skip; +using test::throw_; +using test::operator"" _suite; +using test::operator"" _case; +using test::operator"" _is_true; +using bool_msg = test::expected_bool_t; +using Vizzu::dataframe::cell_value; +using record_type = + Vizzu::dataframe::dataframe_interface::record_type; +using test::skip_error; + +auto setup(std::initializer_list dimensions = {}, + std::initializer_list measures = {}, + std::initializer_list> records = {}) +{ + struct S + { + std::vector dims; + std::vector meas; + std::vector> rec; + std::shared_ptr first_check = + std::make_shared(false); + std::shared_ptr df{ + // std::make_shared() + }; + + Vizzu::dataframe::dataframe_interface *operator->() const + { + if (!*first_check) { + try { + skip->*static_cast(df) + == "dataframe exists"_is_true; + + for (auto d : dims) df->add_dimension({}, {}, d); + for (auto m : meas) df->add_measure({}, m); + + skip->*df->get_dimensions() == dims; + skip->*df->get_measures() == meas; + + const auto ms = meas.size(); + for (auto r = 0u; r < rec.size(); ++r) { + df->add_record(rec[r]); + + for (auto m = 0u; m < ms; ++m) { + if (auto *d = + std::get_if(&rec[r][m]); + d && std::isnan(*d)) { + auto z = df->get_data(r, m); + auto *st = std::get_if(&z); + skip->*static_cast(st) + == "value is a double"_is_true; + skip->*std::isnan(*st) + == "value is nan"_is_true; + continue; + } + skip->*df->get_data(r, m) == rec[r][m]; + } + for (auto d = 0u; d < dims.size(); ++d) { + skip->*df->get_data(r, d + ms) + == rec[r][d + ms]; + } + } + } + catch (skip_error const &) { + throw; + } + catch (std::exception const &e) { + throw skip_error( + std::string{"setup failed: "} + e.what()); + } + catch (...) { + throw skip_error("setup failed"); + } + + *first_check = true; + + skip->*true == "setup succeeded"_is_true; + } + + return df.get(); + } + }; + + return S{dimensions, measures, records}; +}; + +// clang-format off +const static auto tests = + "DataFrame::interface"_suite + + | "construct_empty"_case | + [df = setup()] + { + check ->* df->get_dimensions().size() == std::size_t{}; + check ->* df->get_measures().size() == std::size_t{}; + check ->* df->get_record_count() == std::size_t{}; + + throw_ ->* [&df]() { df->add_record({}); }; + throw_ ->* [&df]() { return df->get_data({}, {}); }; + throw_ ->* [&df]() { return df->get_categories({}); }; + throw_ ->* [&df]() { return df->get_min_max({}); }; + throw_ ->* [&df]() + { + return df->add_series_by_other( + {}, + "", + [](auto, cell_value c) -> cell_value + { + return c; + }); + }; + + check ->* df->get_dimensions().size() == std::size_t{}; + check ->* df->get_measures().size() == std::size_t{}; + check ->* df->get_record_count() == std::size_t{}; + } + + | "add_dimension_and_measure"_case | + [df = setup()] + { + constexpr auto nan = std::numeric_limits::quiet_NaN(); + + df->add_dimension({{"t2", "t1", "tt3"}}, {{0, 0, 2}}, "d1"); + df->add_dimension({{"a"}}, {{0}}, "d0"); + + df->add_measure({{0.0, 22.5, nan, 6.0}}, "m1"); + df->add_measure({{1.0}}, "m2"); + + assert ->* df->get_dimensions() == std::array{"d0", "d1"}; + assert ->* df->get_measures() == std::array{"m1", "m2"}; + + check ->* df->get_categories("d0") == std::array{"a"}; + + assert ->* df->get_categories("d1") + == std::array{"t2", "t1", "tt3"}; + + check ->* df->get_min_max("m1") + == std::pair{0.0, 22.5}; + + check ->* df->get_min_max("m2") + == std::pair{1.0, 1.0}; + + df->finalize(); + + assert ->* df->get_record_count() == std::size_t{4}; + + auto check_nan = [] (cell_value const& cell, std::string&& prefix) { + auto str = prefix + " is a double"; + assert ->* std::holds_alternative(cell) == + bool_msg{str}; + str = prefix + " is nan"; + check ->* std::isnan(std::get(cell)) == + bool_msg{str}; + }; + + auto check_nav = [] (cell_value const& cell, std::string&& prefix) { + auto str = prefix + " is a string"; + assert ->* std::holds_alternative(cell) == + bool_msg{str}; + str = prefix + " is nav"; + check ->* (std::get(cell).data() == nullptr) == + bool_msg{str}; + }; + + check ->* df->get_data(std::size_t{0}, "m1") == 0.0; + check ->* df->get_data(std::size_t{1}, "m1") == 22.5; + check_nan(df->get_data(std::size_t{2}, "m1"), "table_20"); + check ->* df->get_data(std::size_t{3}, "m1") == 6.0; + + check ->* df->get_data(std::size_t{0}, "m2") == 1.0; + check_nan(df->get_data(std::size_t{1}, "m2"), "table_11"); + check_nan(df->get_data(std::size_t{2}, "m2"), "table_21"); + check_nan(df->get_data(std::size_t{3}, "m2"), "table_31"); + + check ->* df->get_data(std::size_t{0}, "d0") == "a"; + check_nav(df->get_data(std::size_t{1}, "d0"), "table_12"); + check_nav(df->get_data(std::size_t{2}, "d0"), "table_22"); + check_nav(df->get_data(std::size_t{3}, "d0"), "table_32"); + + assert->* df->get_data(std::size_t{0}, "d1") == "t2"; + check ->* df->get_data(std::size_t{1}, "d1") == "t2"; + check ->* df->get_data(std::size_t{2}, "d1") == "tt3"; + check_nav(df->get_data(std::size_t{3}, "d1"), "table_33"); + + check ->* (std::get(df->get_data(std::size_t{0}, "d1")).data() == + df->get_categories("d1")[0].data()) == + "Not points to the same memory address"_is_true; + } + + | "add_record"_case | + [df = setup({"test_dim"}, {"test_meas"})] + { + df->add_record({{"test_dim_val", 2.0}}); + df->add_record({{-1.0, "test_dim_val2"}}); + + throw_ ->* [&df]() { df->add_record({}); }; + throw_ ->* [&df]() { df->add_record({{0.0}}); }; + throw_ ->* [&df]() { df->add_record({{0.0, 0.0}}); }; + throw_ ->* [&df]() { df->add_record({{"test", "t"}}); }; + throw_ ->* [&df]() + { + df->add_record({{0.0, "test", 0.0}}); + }; + throw_ ->* [&df]() + { + df->add_record({{0.0, "test", "test"}}); + }; + + df->finalize(); + + assert ->* df->get_record_count() == std::size_t{2}; + + check ->* df->get_categories("test_dim") + == std::array{"test_dim_val", "test_dim_val2"}; + + check ->* df->get_min_max("test_meas") == std::pair{-1.0, 2.0}; + + check ->* df->get_data({}, "test_meas") == 2.0; + check ->* df->get_data({}, "test_dim") + == "test_dim_val"; + + check ->* df->get_data(std::size_t{1}, "test_meas") == -1.0; + check ->* df->get_data(std::size_t{1}, "test_dim") + == "test_dim_val2"; + } + + | "add_series_by_other"_case | + [df = setup({"d1", "d2"}, {"m1"}, { + {{"dm1", "dm2", 0.0}}, + {{"dm1", "dmX", 1.0}}, + {{"s1", "s2", -1.0}}, + {{"s1", "s3", 3.0}} + })] { + + df->add_series_by_other( + "m1", + "m0", + [](auto, cell_value c) -> cell_value + { + const double* v = std::get_if(&c); + assert ->* static_cast(v) + == "value is a double"_is_true; + + return *v * 2; + }); + + assert ->* df->get_measures() == std::array{"m0", "m1"}; + + check ->* df->get_data(std::size_t{0}, {}) == 0.0; + check ->* df->get_data(std::size_t{1}, {}) == 2.0; + check ->* df->get_data(std::size_t{2}, {}) == -2.0; + check ->* df->get_data(std::size_t{3}, {}) == 6.0; + + df->add_series_by_other( + "d1", + "d15", + [](const record_type& r, cell_value c) -> cell_value + { + auto v = std::get_if(&c); + skip ->* static_cast(v) + == "value is string"_is_true; + + auto oth_v = r.getValue("d2"); + + auto v2 = std::get_if(&oth_v); + skip ->* static_cast(v2) + == "value is string"_is_true; + + thread_local std::string val; + val = std::string{*v} + "5" + std::string{*v2}; + + return val; + }); + + assert ->* df->get_dimensions() == std::array{"d1", "d15", "d2"}; + + check ->* df->get_data(std::size_t{0}, std::size_t{3}) == "dm15dm2"; + check ->* df->get_data(std::size_t{1}, std::size_t{3}) == "dm15dmX"; + check ->* df->get_data(std::size_t{2}, std::size_t{3}) == "s15s2"; + check ->* df->get_data(std::size_t{3}, std::size_t{3}) == "s15s3"; + } + + | "remove_series"_case | + [df = setup({"d1", "d2", "d3"}, {"m1", "m2", "m3"}, { + {{"dm1", "dx2", "dm3", 0.0, 0.1, 0.2}}, + {{"dm1", "dx2", "am3", 1.0, 2.1, 3.2}}, + {{"dm2", "dm2", "bm3", 1.0, 1.5, 1.2}}, + })] { + df->remove_series({{"m1", "d2", "m3"}}); + + assert ->* df->get_measures() == std::array{"m2"}; + assert ->* df->get_dimensions() == std::array{"d1", "d3"}; + + check ->* df->get_data(std::size_t{2}, "m2") == 1.5; + check ->* df->get_data(std::size_t{0}, "d3") == "dm3"; + + } + +; +// clang-format on From 621e2ed9567ab9ac6284bf87c255e42214cfc606 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 5 Feb 2024 20:08:12 +0100 Subject: [PATCH 023/253] add test + make custom_aggregator to trivially_copiable --- src/dataframe/interface.h | 6 +- test/unit/dataframe/interface_test.cpp | 240 +++++++++++++++++++++++-- 2 files changed, 225 insertions(+), 21 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 8d32fa21e..58bf7ee59 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -34,10 +34,10 @@ enum class adding_type { struct custom_aggregator { - std::string name; + std::variant name; using id_type = std::any; - std::function create; - std::function add; + id_type (*create)(); + double (*add)(id_type &, double); }; class dataframe_interface : diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index 619d01fd5..1403ebe86 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -44,15 +44,18 @@ auto setup(std::initializer_list dimensions = {}, skip->*df->get_dimensions() == dims; skip->*df->get_measures() == meas; - const auto ms = meas.size(); + const auto ds = dims.size(); for (auto r = 0u; r < rec.size(); ++r) { df->add_record(rec[r]); - for (auto m = 0u; m < ms; ++m) { - if (auto *d = - std::get_if(&rec[r][m]); + for (auto d = 0u; d < ds; ++d) + skip->*df->get_data(r, d) == rec[r][d]; + + for (auto m = 0u; m < meas.size(); ++m) { + if (auto *d = std::get_if( + &rec[r][m + ds]); d && std::isnan(*d)) { - auto z = df->get_data(r, m); + auto z = df->get_data(r, m + ds); auto *st = std::get_if(&z); skip->*static_cast(st) == "value is a double"_is_true; @@ -60,11 +63,8 @@ auto setup(std::initializer_list dimensions = {}, == "value is nan"_is_true; continue; } - skip->*df->get_data(r, m) == rec[r][m]; - } - for (auto d = 0u; d < dims.size(); ++d) { - skip->*df->get_data(r, d + ms) - == rec[r][d + ms]; + skip->*df->get_data(r, m + ds) + == rec[r][m + ds]; } } } @@ -253,10 +253,10 @@ const static auto tests = assert ->* df->get_measures() == std::array{"m0", "m1"}; - check ->* df->get_data(std::size_t{0}, {}) == 0.0; - check ->* df->get_data(std::size_t{1}, {}) == 2.0; - check ->* df->get_data(std::size_t{2}, {}) == -2.0; - check ->* df->get_data(std::size_t{3}, {}) == 6.0; + check ->* df->get_data(std::size_t{0}, "m0") == 0.0; + check ->* df->get_data(std::size_t{1}, "m0") == 2.0; + check ->* df->get_data(std::size_t{2}, "m0") == -2.0; + check ->* df->get_data(std::size_t{3}, "m0") == 6.0; df->add_series_by_other( "d1", @@ -281,10 +281,10 @@ const static auto tests = assert ->* df->get_dimensions() == std::array{"d1", "d15", "d2"}; - check ->* df->get_data(std::size_t{0}, std::size_t{3}) == "dm15dm2"; - check ->* df->get_data(std::size_t{1}, std::size_t{3}) == "dm15dmX"; - check ->* df->get_data(std::size_t{2}, std::size_t{3}) == "s15s2"; - check ->* df->get_data(std::size_t{3}, std::size_t{3}) == "s15s3"; + check ->* df->get_data(std::size_t{0}, "d15") == "dm15dm2"; + check ->* df->get_data(std::size_t{1}, "d15") == "dm15dmX"; + check ->* df->get_data(std::size_t{2}, "d15") == "s15s2"; + check ->* df->get_data(std::size_t{3}, "d15") == "s15s3"; } | "remove_series"_case | @@ -297,11 +297,215 @@ const static auto tests = assert ->* df->get_measures() == std::array{"m2"}; assert ->* df->get_dimensions() == std::array{"d1", "d3"}; + assert ->* df->get_record_count() == std::size_t{3}; check ->* df->get_data(std::size_t{2}, "m2") == 1.5; check ->* df->get_data(std::size_t{0}, "d3") == "dm3"; + } + + | "remove_records"_case | + [df = setup({"d1"}, {"m1"}, { + {{"dm0", NAN}}, + {{"dm1", NAN}}, + {{"dm2", NAN}}, + {{"dm3", NAN}}, + {{"dm4", NAN}}, + {{"dm5", NAN}}, + {{"dm6", NAN}}, + {{"dm7", NAN}}, + {{"dm8", 4.2}}, + {{"dm9", NAN}}, + })] { + df->remove_records({{0ul, 2ul, 4ul, 5ul, 8ul, 9ul}}); + + assert ->* df->get_measures() == std::array{"m1"}; + assert ->* df->get_dimensions() == std::array{"d1"}; + assert ->* df->get_record_count() == std::size_t{4}; + + check ->* std::isnan(std::get(df->get_data(std::size_t{2}, "m1"))) + == "is nan"_is_true; + check ->* df->get_data(std::size_t{0}, "d1") == "dm6"; + check ->* df->get_data(std::size_t{1}, "d1") == "dm1"; + check ->* df->get_data(std::size_t{2}, "d1") == "dm7"; + check ->* df->get_data(std::size_t{3}, "d1") == "dm3"; + } + | "remove_records_filter"_case | + [df = setup({"d1"}, {"m1"}, { + {{"dm0", 5.3}}, + {{"dm1", 2.0}}, + {{"dm2", 3.3}}, + {{"dm3", 10.1}}, + {{"dm4", 88.0}}, + {{"dm5", 2.2}}, + {{"dm6", 7.4}}, + {{"dm7", 0.0}}, + {{"dm8", 4.2}}, + {{"dm9", NAN}}, + })] { + df->remove_records( + [] (record_type r) -> bool + { + auto v = r.getValue("m1"); + return *std::get_if(&v) < 5.0; + }); + + assert ->* df->get_record_count() == std::size_t{5}; + + check ->* df->get_data(std::size_t{0}, "d1") == "dm0"; + check ->* df->get_data(std::size_t{1}, "d1") == "dm9"; + check ->* df->get_data(std::size_t{2}, "d1") == "dm6"; + check ->* df->get_data(std::size_t{3}, "d1") == "dm3"; + check ->* df->get_data(std::size_t{4}, "d1") == "dm4"; } + | "change_data"_case | + [df = setup({"d1"}, {"m1"}, { + {{"dm0", 5.3}}, + {{"dm1", 2.0}}, + {{"dm2", 3.3}} + })] { + df->change_data(std::size_t{1}, "m1", 3.0); + df->change_data(std::size_t{2}, "d1", "dmX"); + + throw_ ->* [&df]() { df->change_data(std::size_t{0}, "d1", NAN); }; + throw_ ->* [&df]() { df->change_data(std::size_t{0}, "m1", ""); }; + + assert ->* df->get_record_count() == std::size_t{3}; + + check ->* df->get_data(std::size_t{0}, "m1") == 5.3; + check ->* df->get_data(std::size_t{1}, "m1") == 3.0; + check ->* df->get_data(std::size_t{2}, "m1") == 3.3; + + check ->* df->get_data(std::size_t{0}, "d1") == "dm0"; + check ->* df->get_data(std::size_t{1}, "d1") == "dm1"; + check ->* df->get_data(std::size_t{2}, "d1") == "dmX"; + } + + | "fill_na"_case | + [df = setup({"d1"}, {"m1"}, { + {{"dm0", 5.3}}, + {{std::string_view{nullptr, 0}, 2.0}}, + {{"dm2", NAN}} + })] { + df->fill_na("m1", 3.0); + df->fill_na("d1", "dmX"); + + assert ->* df->get_record_count() == std::size_t{3}; + + check ->* df->get_data(std::size_t{0}, "m1") == 5.3; + check ->* df->get_data(std::size_t{1}, "m1") == 2.0; + check ->* df->get_data(std::size_t{2}, "m1") == 3.0; + + check ->* df->get_data(std::size_t{0}, "d1") == "dm0"; + check ->* df->get_data(std::size_t{1}, "d1") == "dmX"; + check ->* df->get_data(std::size_t{2}, "d1") == "dm2"; + } + + | "aggregate types"_case | + [df = setup({"d1"}, {"m1"}, { + {{"dm0", 5.5}}, + {{"dm0", 2.0}}, + {{"dm0", 3.5}}, + {{"dm0", 10.25}}, + {{"dm0", 88.0}}, + {{"dm1", 3.5}}, + {{"dm1", 7.25}}, + {{"dm1", NAN}}, + {{"dm1", 4.25}}, + {{"dm2", NAN}}, + {{std::string_view{nullptr, 0}, 0.0}}, + })] { + df->set_aggregate("d1"); + df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::count); + df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::distinct); + df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::exists); + df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::sum); + df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::min); + df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::max); + df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::mean); + df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::count); + df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::distinct); + df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::exists); + + df->set_aggregate("m1", Vizzu::dataframe::custom_aggregator{ + std::string{"test"}, + []() -> Vizzu::dataframe::custom_aggregator::id_type + { + return std::pair{ + std::numeric_limits::max(), + std::numeric_limits::max() + }; + }, + [](Vizzu::dataframe::custom_aggregator::id_type &id, double v) -> double + { + auto &[min, min2] = std::any_cast&>(id); + if (v < min) { + min2 = min; + min = v; + } else if (v < min2) { + min2 = v; + } + return min2; + } + }); + + df->finalize(); + + assert ->* df->get_dimensions() == std::array{ + "d1" + }; + + assert ->* df->get_measures() == std::array{ + "count(d1)", "count(m1)", "distinct(d1)", "distinct(m1)", + "exists(d1)", "exists(m1)", + "max(m1)", "mean(m1)", "min(m1)", "sum(m1)", "test(m1)" + }; + + assert ->* df->get_record_count() == std::size_t{4}; + + check ->* df->get_data(std::size_t{0}, "count(d1)") == 5.0; + check ->* df->get_data(std::size_t{0}, "count(m1)") == 5.0; + check ->* df->get_data(std::size_t{0}, "distinct(d1)") == 1.0; + check ->* df->get_data(std::size_t{0}, "distinct(m1)") == 5.0; + check ->* df->get_data(std::size_t{0}, "exists(d1)") == 1.0; + check ->* df->get_data(std::size_t{0}, "exists(m1)") == 1.0; + check ->* df->get_data(std::size_t{0}, "max(m1)") == 88.0; + check ->* df->get_data(std::size_t{0}, "mean(m1)") == 21.85; + check ->* df->get_data(std::size_t{0}, "min(m1)") == 2.0; + check ->* df->get_data(std::size_t{0}, "sum(m1)") == 109.25; + check ->* df->get_data(std::size_t{0}, "test(m1)") == 3.5; + + check ->* df->get_data(std::size_t{1}, "count(d1)") == 4.0; + check ->* df->get_data(std::size_t{1}, "count(m1)") == 3.0; + check ->* df->get_data(std::size_t{1}, "distinct(d1)") == 1.0; + check ->* df->get_data(std::size_t{1}, "distinct(m1)") == 3.0; + check ->* df->get_data(std::size_t{1}, "exists(d1)") == 1.0; + check ->* df->get_data(std::size_t{1}, "exists(m1)") == 1.0; + check ->* df->get_data(std::size_t{1}, "max(m1)") == 7.25; + check ->* df->get_data(std::size_t{1}, "mean(m1)") == 5.0; + check ->* df->get_data(std::size_t{1}, "min(m1)") == 3.5; + check ->* df->get_data(std::size_t{1}, "sum(m1)") == 15.0; + check ->* df->get_data(std::size_t{1}, "test(m1)") == 4.25; + + check ->* df->get_data(std::size_t{2}, "count(d1)") == 1.0; + check ->* df->get_data(std::size_t{2}, "count(m1)") == 0.0; + check ->* df->get_data(std::size_t{2}, "distinct(d1)") == 1.0; + check ->* df->get_data(std::size_t{2}, "distinct(m1)") == 0.0; + check ->* df->get_data(std::size_t{2}, "exists(d1)") == 1.0; + check ->* df->get_data(std::size_t{2}, "exists(m1)") == 0.0; + check ->* std::isnan(std::get(df->get_data(std::size_t{2}, "max(m1)"))) == "is nan"_is_true; + check ->* std::isnan(std::get(df->get_data(std::size_t{2}, "mean(m1)"))) == "is nan"_is_true; + check ->* std::isnan(std::get(df->get_data(std::size_t{2}, "min(m1)"))) == "is nan"_is_true; + check ->* df->get_data(std::size_t{2}, "sum(m1)") == 0.0; + check ->* df->get_data(std::size_t{2}, "test(m1)") == std::numeric_limits::max(); + + check ->* df->get_data(std::size_t{3}, "count(d1)") == 0.0; + check ->* df->get_data(std::size_t{3}, "count(m1)") == 1.0; + check ->* df->get_data(std::size_t{3}, "distinct(d1)") == 0.0; + check ->* df->get_data(std::size_t{3}, "distinct(m1)") == 1.0; + check ->* df->get_data(std::size_t{3}, "exists(d1)") == 0.0; + check ->* df->get_data(std::size_t{3}, "exists(m1)") == 1.0; + } ; // clang-format on From b7423e5139940cadac479b6c24f8a0bafc5938f2 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 5 Feb 2024 20:59:13 +0100 Subject: [PATCH 024/253] Remove interface default arguments. Add aggregators --- src/dataframe/impl/aggregators.h | 114 +++++++++++++++++++++++++ src/dataframe/interface.h | 25 +++--- test/unit/dataframe/interface_test.cpp | 27 +++--- 3 files changed, 140 insertions(+), 26 deletions(-) create mode 100644 src/dataframe/impl/aggregators.h diff --git a/src/dataframe/impl/aggregators.h b/src/dataframe/impl/aggregators.h new file mode 100644 index 000000000..6dcb28e67 --- /dev/null +++ b/src/dataframe/impl/aggregators.h @@ -0,0 +1,114 @@ +#ifndef VIZZU_DATAFRAME_AGGREGATORS_H +#define VIZZU_DATAFRAME_AGGREGATORS_H + +#include +#include +#include + +#include "../interface.h" +#include "base/refl/auto_enum.h" + +namespace Vizzu::dataframe +{ + +// NOLINTNEXTLINE(fuchsia-statically-constructed-objects) +constinit const static inline Refl::EnumArray + aggregators{{{{std::string_view{"sum"}, + []() -> custom_aggregator::id_type + { + return 0.0; + }, + [](custom_aggregator::id_type &id, + double value) -> double + { + auto &ref = std::any_cast(id); + if (!std::isnan(value) + && !std::isinf(value)) + ref += value; + + return ref; + }}, + {std::string_view{"min"}, + []() -> custom_aggregator::id_type + { + return std::numeric_limits::quiet_NaN(); + }, + [](custom_aggregator::id_type &id, double value) -> double + { + auto &ref = std::any_cast(id); + if (!std::isnan(value) && !std::isinf(value) + && !(value >= ref)) + ref = value; + + return ref; + }}, + {std::string_view{"max"}, + []() -> custom_aggregator::id_type + { + return std::numeric_limits::quiet_NaN(); + }, + [](custom_aggregator::id_type &id, double value) -> double + { + auto &ref = std::any_cast(id); + if (!std::isnan(value) && !std::isinf(value) + && !(value <= ref)) + ref = value; + + return ref; + }}, + {std::string_view{"mean"}, + []() -> custom_aggregator::id_type + { + return std::pair(0.0, 0); + }, + [](custom_aggregator::id_type &id, double value) -> double + { + auto &[sum, count] = + std::any_cast &>( + id); + if (!std::isnan(value) && !std::isinf(value)) { + sum += value; + ++count; + } + return count == 0 ? NAN + : sum / static_cast(count); + }}, + {std::string_view{"count"}, + []() -> custom_aggregator::id_type + { + return std::size_t{}; + }, + [](custom_aggregator::id_type &id, double value) -> double + { + auto &s = std::any_cast(id); + if (!std::isnan(value) && !std::isinf(value)) s += 1; + return static_cast(s); + }}, + {std::string_view{"distinct"}, + []() -> custom_aggregator::id_type + { + return std::set{}; + }, + [](custom_aggregator::id_type &id, double value) -> double + { + auto &set = std::any_cast &>(id); + if (!std::isnan(value) && !std::isinf(value)) + set.insert(value); + return static_cast(set.size()); + }}, + {std::string_view{"exists"}, + []() -> custom_aggregator::id_type + { + return bool{}; + }, + [](custom_aggregator::id_type &id, double value) -> double + { + auto &b = std::any_cast(id); + if (!std::isnan(value) && !std::isinf(value)) + b = true; + return b; + }}}}}; +} + +#endif // VIZZU_DATAFRAME_AGGREGATORS_H diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 58bf7ee59..aedc00439 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -62,9 +62,8 @@ class dataframe_interface : virtual ~dataframe_interface() = default; - [[nodiscard]] virtual std::shared_ptr copy( - bool remove_filtered = false, - bool inherit_sorting = true) const & = 0; + [[nodiscard]] virtual std::shared_ptr + copy(bool remove_filtered, bool inherit_sorting) const & = 0; using any_aggregator_type = std:: variant; @@ -74,7 +73,7 @@ class dataframe_interface : std::string_view)>>; virtual void set_aggregate(series_identifier series, - any_aggregator_type aggregator = {}) & = 0; + any_aggregator_type aggregator) & = 0; virtual void set_filter( std::function &&filter) & = 0; @@ -90,22 +89,22 @@ class dataframe_interface : std::span dimension_categories, std::span dimension_values, const char *name, - adding_type adding_strategy = adding_type::create_or_add, - std::span> info = - {}) & = 0; + adding_type adding_strategy, + std::span> info) + & = 0; virtual void add_measure(std::span measure_values, const char *name, - adding_type adding_strategy = adding_type::create_or_add, - std::span> info = - {}) & = 0; + adding_type adding_strategy, + std::span> info) + & = 0; virtual void add_series_by_other(series_identifier curr_series, const char *name, std::function value_transform, - std::span> info = - {}) & = 0; + std::span> info) + & = 0; virtual void remove_series( std::span names) & = 0; @@ -154,7 +153,7 @@ class dataframe_interface : [[nodiscard]] virtual std::size_t get_record_count() const & = 0; virtual void visit(std::function function, - bool filtered = true) const & = 0; + bool filtered) const & = 0; }; } diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index 1403ebe86..fbc431383 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -1,7 +1,7 @@ #include "../util/test.h" #include "dataframe/impl/dataframe.h" -// #include "dataframe/interface.h" +#include "dataframe/interface.h" using test::assert; using test::check; @@ -28,8 +28,7 @@ auto setup(std::initializer_list dimensions = {}, std::shared_ptr first_check = std::make_shared(false); std::shared_ptr df{ - // std::make_shared() - }; + std::make_shared()}; Vizzu::dataframe::dataframe_interface *operator->() const { @@ -38,8 +37,10 @@ auto setup(std::initializer_list dimensions = {}, skip->*static_cast(df) == "dataframe exists"_is_true; - for (auto d : dims) df->add_dimension({}, {}, d); - for (auto m : meas) df->add_measure({}, m); + for (auto d : dims) + df->add_dimension({}, {}, d, {}, {}); + for (auto m : meas) + df->add_measure({}, m, {}, {}); skip->*df->get_dimensions() == dims; skip->*df->get_measures() == meas; @@ -114,7 +115,7 @@ const static auto tests = [](auto, cell_value c) -> cell_value { return c; - }); + }, {}); }; check ->* df->get_dimensions().size() == std::size_t{}; @@ -127,11 +128,11 @@ const static auto tests = { constexpr auto nan = std::numeric_limits::quiet_NaN(); - df->add_dimension({{"t2", "t1", "tt3"}}, {{0, 0, 2}}, "d1"); - df->add_dimension({{"a"}}, {{0}}, "d0"); + df->add_dimension({{"t2", "t1", "tt3"}}, {{0, 0, 2}}, "d1", {}, {}); + df->add_dimension({{"a"}}, {{0}}, "d0", {}, {}); - df->add_measure({{0.0, 22.5, nan, 6.0}}, "m1"); - df->add_measure({{1.0}}, "m2"); + df->add_measure({{0.0, 22.5, nan, 6.0}}, "m1", {}, {}); + df->add_measure({{1.0}}, "m2", {}, {}); assert ->* df->get_dimensions() == std::array{"d0", "d1"}; assert ->* df->get_measures() == std::array{"m1", "m2"}; @@ -249,7 +250,7 @@ const static auto tests = == "value is a double"_is_true; return *v * 2; - }); + }, {}); assert ->* df->get_measures() == std::array{"m0", "m1"}; @@ -277,7 +278,7 @@ const static auto tests = val = std::string{*v} + "5" + std::string{*v2}; return val; - }); + }, {}); assert ->* df->get_dimensions() == std::array{"d1", "d15", "d2"}; @@ -416,7 +417,7 @@ const static auto tests = {{"dm2", NAN}}, {{std::string_view{nullptr, 0}, 0.0}}, })] { - df->set_aggregate("d1"); + df->set_aggregate("d1", {}); df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::count); df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::distinct); df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::exists); From b4bd55fdcff2a66d6df777625b556983f2d79081 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Tue, 6 Feb 2024 11:17:01 +0100 Subject: [PATCH 025/253] Fixed accepting empty channel array in case of no shorthand plugin. --- CHANGELOG.md | 3 ++- src/apps/weblib/ts-api/chart.ts | 14 ++++++++++- test/e2e/tests/fixes.json | 3 +++ test/e2e/tests/fixes/320.mjs | 44 +++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 test/e2e/tests/fixes/320.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index bed66e556..6451e3751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,13 @@ were not escaped properly. - Fix dimension label transition on axis and legend. - Through event handler call, when a new event handler is registered, undefined behaviour happened. -- Removed marker's alpha color when tooltip is shown. +- Fixed channel reset with empty array when shorthands plugin switched off. ### Added - Added optional `categories` member to the `legend-marker`, `legend-label` and `plot-axis-label` events. - Remove unused marker selection and selected marker coloring. +- Removed marker's alpha color when tooltip is shown. - Remove cursor modification over logo. - Make `channel.step` option to work on dimensions. - When X axis dimension labels are close to each other, they are rotated to avoid overlapping. diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index aeec458cb..92f55f5b3 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -105,13 +105,25 @@ export class Chart { } else { if (target.data) this._data.set(target.data) if (target.style) this._cChart.style.set(target.style) - if (target.config) this._cChart.config.set(target.config) + if (target.config) { + this._nullEmptyChannels(target.config) + this._cChart.config.set(target.config) + } } } if (options) this._cChart.animOptions.set(options) this._cChart.setKeyframe() } + private _nullEmptyChannels(config: Config.Chart): void { + if (!config.channels) return + Object.values(config.channels).forEach((channel: Config.Channel): void => { + if (Array.isArray(channel?.set) && channel?.set.length === 0) { + channel.set = null + } + }) + } + destruct(): void { this._canvas.destruct() if (this._updateInterval) clearInterval(this._updateInterval) diff --git a/test/e2e/tests/fixes.json b/test/e2e/tests/fixes.json index d501807b7..f352f59c1 100644 --- a/test/e2e/tests/fixes.json +++ b/test/e2e/tests/fixes.json @@ -16,6 +16,9 @@ "163": { "refs": ["241592d"] }, + "320": { + "refs": ["e4b8a2f"] + }, "333": { "refs": ["22c1f69"] }, diff --git a/test/e2e/tests/fixes/320.mjs b/test/e2e/tests/fixes/320.mjs new file mode 100644 index 000000000..ef2c4f465 --- /dev/null +++ b/test/e2e/tests/fixes/320.mjs @@ -0,0 +1,44 @@ +/** empty array should reset channel even without shorthand plugin */ + +const testSteps = [ + (chart) => + chart.animate({ + data: { + series: [ + { name: 'Foo', values: ['Alice', 'Bob', 'Ted'] }, + { name: 'Bar', values: [15, 32, 12] }, + { name: 'Baz', values: [5, 3, 2] } + ] + } + }), + (chart) => { + chart.feature('shorthands', false) + return chart + }, + (chart) => + chart.animate([ + { + target: { + config: { + channels: { + label: { set: ['Foo'] } + } + } + } + } + ]), + (chart) => + chart.animate([ + { + target: { + config: { + channels: { + label: { set: [] } + } + } + } + } + ]) +] + +export default testSteps From 5c342ca184e6826ec89fa2265d07d5aecd2b2866 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Tue, 6 Feb 2024 18:21:19 +0100 Subject: [PATCH 026/253] modify tests setup. It goes to the default argument. Add aggregation example --- test/unit/dataframe/interface_test.cpp | 226 +++++++++++++++---------- 1 file changed, 132 insertions(+), 94 deletions(-) diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index fbc431383..e81b91939 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -1,6 +1,6 @@ #include "../util/test.h" -#include "dataframe/impl/dataframe.h" +// #include "dataframe/impl/dataframe.h" #include "dataframe/interface.h" using test::assert; @@ -15,81 +15,67 @@ using Vizzu::dataframe::cell_value; using record_type = Vizzu::dataframe::dataframe_interface::record_type; using test::skip_error; +using interface = Vizzu::dataframe::dataframe_interface; -auto setup(std::initializer_list dimensions = {}, - std::initializer_list measures = {}, - std::initializer_list> records = {}) +auto setup(std::initializer_list dims = {}, + std::initializer_list meas = {}, + std::vector> rec = {}) { struct S { - std::vector dims; - std::vector meas; - std::vector> rec; - std::shared_ptr first_check = - std::make_shared(false); - std::shared_ptr df{ - std::make_shared()}; - - Vizzu::dataframe::dataframe_interface *operator->() const + std::unique_ptr df{ + // std::make_unique() + }; + + explicit(false) operator interface *() const { - if (!*first_check) { - try { - skip->*static_cast(df) - == "dataframe exists"_is_true; - - for (auto d : dims) - df->add_dimension({}, {}, d, {}, {}); - for (auto m : meas) - df->add_measure({}, m, {}, {}); - - skip->*df->get_dimensions() == dims; - skip->*df->get_measures() == meas; - - const auto ds = dims.size(); - for (auto r = 0u; r < rec.size(); ++r) { - df->add_record(rec[r]); - - for (auto d = 0u; d < ds; ++d) - skip->*df->get_data(r, d) == rec[r][d]; - - for (auto m = 0u; m < meas.size(); ++m) { - if (auto *d = std::get_if( - &rec[r][m + ds]); - d && std::isnan(*d)) { - auto z = df->get_data(r, m + ds); - auto *st = std::get_if(&z); - skip->*static_cast(st) - == "value is a double"_is_true; - skip->*std::isnan(*st) - == "value is nan"_is_true; - continue; - } - skip->*df->get_data(r, m + ds) - == rec[r][m + ds]; - } - } - } - catch (skip_error const &) { - throw; - } - catch (std::exception const &e) { - throw skip_error( - std::string{"setup failed: "} + e.what()); - } - catch (...) { - throw skip_error("setup failed"); - } + return df.get(); + } + } result{}; + interface *df = result; - *first_check = true; + try { + skip->*static_cast(df) == "dataframe exists"_is_true; - skip->*true == "setup succeeded"_is_true; - } + for (auto d : dims) df->add_dimension({}, {}, d, {}, {}); + for (auto m : meas) df->add_measure({}, m, {}, {}); - return df.get(); - } - }; + skip->*df->get_dimensions() == dims; + skip->*df->get_measures() == meas; + + const auto ds = dims.size(); + for (auto r = 0u; r < rec.size(); ++r) { + df->add_record(rec[r]); + + for (auto d = 0u; d < ds; ++d) + skip->*df->get_data(r, d) == rec[r][d]; - return S{dimensions, measures, records}; + for (auto m = 0u; m < meas.size(); ++m) { + if (auto *d = std::get_if(&rec[r][m + ds]); + d && std::isnan(*d)) { + auto z = df->get_data(r, m + ds); + auto *st = std::get_if(&z); + skip->*static_cast(st) + == "value is a double"_is_true; + skip->*std::isnan(*st) == "value is nan"_is_true; + continue; + } + skip->*df->get_data(r, m + ds) == rec[r][m + ds]; + } + } + } + catch (skip_error const &) { + throw; + } + catch (std::exception const &e) { + throw skip_error(std::string{"setup failed: "} + e.what()); + } + catch (...) { + throw skip_error("setup failed"); + } + + skip->*true == "setup succeeded"_is_true; + return result; }; // clang-format off @@ -97,7 +83,7 @@ const static auto tests = "DataFrame::interface"_suite | "construct_empty"_case | - [df = setup()] + [] (interface* df = setup()) { check ->* df->get_dimensions().size() == std::size_t{}; check ->* df->get_measures().size() == std::size_t{}; @@ -124,7 +110,7 @@ const static auto tests = } | "add_dimension_and_measure"_case | - [df = setup()] + [] (interface* df = setup()) { constexpr auto nan = std::numeric_limits::quiet_NaN(); @@ -196,7 +182,7 @@ const static auto tests = } | "add_record"_case | - [df = setup({"test_dim"}, {"test_meas"})] + [] (interface* df = setup({"test_dim"}, {"test_meas"})) { df->add_record({{"test_dim_val", 2.0}}); df->add_record({{-1.0, "test_dim_val2"}}); @@ -233,12 +219,12 @@ const static auto tests = } | "add_series_by_other"_case | - [df = setup({"d1", "d2"}, {"m1"}, { + [] (interface* df = setup({"d1", "d2"}, {"m1"}, { {{"dm1", "dm2", 0.0}}, {{"dm1", "dmX", 1.0}}, {{"s1", "s2", -1.0}}, {{"s1", "s3", 3.0}} - })] { + })) { df->add_series_by_other( "m1", @@ -289,11 +275,11 @@ const static auto tests = } | "remove_series"_case | - [df = setup({"d1", "d2", "d3"}, {"m1", "m2", "m3"}, { + [] (interface* df = setup({"d1", "d2", "d3"}, {"m1", "m2", "m3"}, { {{"dm1", "dx2", "dm3", 0.0, 0.1, 0.2}}, {{"dm1", "dx2", "am3", 1.0, 2.1, 3.2}}, {{"dm2", "dm2", "bm3", 1.0, 1.5, 1.2}}, - })] { + })) { df->remove_series({{"m1", "d2", "m3"}}); assert ->* df->get_measures() == std::array{"m2"}; @@ -305,7 +291,7 @@ const static auto tests = } | "remove_records"_case | - [df = setup({"d1"}, {"m1"}, { + [] (interface* df = setup({"d1"}, {"m1"}, { {{"dm0", NAN}}, {{"dm1", NAN}}, {{"dm2", NAN}}, @@ -316,7 +302,7 @@ const static auto tests = {{"dm7", NAN}}, {{"dm8", 4.2}}, {{"dm9", NAN}}, - })] { + })) { df->remove_records({{0ul, 2ul, 4ul, 5ul, 8ul, 9ul}}); assert ->* df->get_measures() == std::array{"m1"}; @@ -332,7 +318,7 @@ const static auto tests = } | "remove_records_filter"_case | - [df = setup({"d1"}, {"m1"}, { + [] (interface* df = setup({"d1"}, {"m1"}, { {{"dm0", 5.3}}, {{"dm1", 2.0}}, {{"dm2", 3.3}}, @@ -343,7 +329,7 @@ const static auto tests = {{"dm7", 0.0}}, {{"dm8", 4.2}}, {{"dm9", NAN}}, - })] { + })) { df->remove_records( [] (record_type r) -> bool { @@ -361,11 +347,11 @@ const static auto tests = } | "change_data"_case | - [df = setup({"d1"}, {"m1"}, { + [] (interface* df = setup({"d1"}, {"m1"}, { {{"dm0", 5.3}}, {{"dm1", 2.0}}, {{"dm2", 3.3}} - })] { + })) { df->change_data(std::size_t{1}, "m1", 3.0); df->change_data(std::size_t{2}, "d1", "dmX"); @@ -384,11 +370,11 @@ const static auto tests = } | "fill_na"_case | - [df = setup({"d1"}, {"m1"}, { + [] (interface* df = setup({"d1"}, {"m1"}, { {{"dm0", 5.3}}, {{std::string_view{nullptr, 0}, 2.0}}, {{"dm2", NAN}} - })] { + })) { df->fill_na("m1", 3.0); df->fill_na("d1", "dmX"); @@ -404,7 +390,7 @@ const static auto tests = } | "aggregate types"_case | - [df = setup({"d1"}, {"m1"}, { + [] (interface* df = setup({"d1"}, {"m1"}, { {{"dm0", 5.5}}, {{"dm0", 2.0}}, {{"dm0", 3.5}}, @@ -416,7 +402,7 @@ const static auto tests = {{"dm1", 4.25}}, {{"dm2", NAN}}, {{std::string_view{nullptr, 0}, 0.0}}, - })] { + })) { df->set_aggregate("d1", {}); df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::count); df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::distinct); @@ -430,8 +416,8 @@ const static auto tests = df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::exists); df->set_aggregate("m1", Vizzu::dataframe::custom_aggregator{ - std::string{"test"}, - []() -> Vizzu::dataframe::custom_aggregator::id_type + std::string_view{"test"}, + [] () -> Vizzu::dataframe::custom_aggregator::id_type { return std::pair{ std::numeric_limits::max(), @@ -441,21 +427,17 @@ const static auto tests = [](Vizzu::dataframe::custom_aggregator::id_type &id, double v) -> double { auto &[min, min2] = std::any_cast&>(id); - if (v < min) { - min2 = min; - min = v; - } else if (v < min2) { + if (v < min) + min2 = std::exchange(min, v); + else if (v < min2) min2 = v; - } return min2; } }); df->finalize(); - assert ->* df->get_dimensions() == std::array{ - "d1" - }; + assert ->* df->get_dimensions() == std::array{"d1"}; assert ->* df->get_measures() == std::array{ "count(d1)", "count(m1)", "distinct(d1)", "distinct(m1)", @@ -508,5 +490,61 @@ const static auto tests = check ->* df->get_data(std::size_t{3}, "exists(d1)") == 0.0; check ->* df->get_data(std::size_t{3}, "exists(m1)") == 1.0; } + + | "aggregate multiple dim"_case | + [] (interface* df = setup({"d1", "d2", "d3"}, {"m1"}, { + {{"dx0", "dm0", "doa", 5.5}}, + {{"dx0", "dm0", "dob", 2.0}}, + {{"dx0", "dm1", std::string_view{nullptr, 0}, 3.5}}, + {{"dx0", "dm1", "dob", 10.25}}, + {{"dx0", "dm0", "doa", 88.0}}, + {{"dx1", "dm0", "doa", 3.5}}, + {{"dx1", "dm1", std::string_view{nullptr, 0}, 7.25}}, + {{"dx1", "dm2", "doa", NAN}}, + {{"dx1", "dm0", "doa", 4.25}}, + {{"dx2", "dm0", "dob", NAN}}, + {{"dx2", "dm0", "doa", 0.5}}, + })) { + df->set_aggregate("d1", {}); + df->set_aggregate("d2", {}); + df->set_aggregate("d3", Vizzu::dataframe::aggregator_type::count); + df->set_aggregate("d3", Vizzu::dataframe::aggregator_type::distinct); + + df->finalize(); + + assert ->* df->get_dimensions() == std::array{"d1", "d2"}; + assert ->* df->get_measures() == std::array{"count(d3)", "distinct(d3)"}; + + assert ->* df->get_record_count() == std::size_t{6}; + + check ->* df->get_data(std::size_t{0}, "d1") == "dx0"; + check ->* df->get_data(std::size_t{0}, "d2") == "dm0"; + check ->* df->get_data(std::size_t{0}, "count(d3)") == 3.0; + check ->* df->get_data(std::size_t{0}, "distinct(d3)") == 2.0; + + check ->* df->get_data(std::size_t{1}, "d1") == "dx0"; + check ->* df->get_data(std::size_t{1}, "d2") == "dm1"; + check ->* df->get_data(std::size_t{1}, "count(d3)") == 1.0; + check ->* df->get_data(std::size_t{1}, "distinct(d3)") == 1.0; + + check ->* df->get_data(std::size_t{2}, "d1") == "dx1"; + check ->* df->get_data(std::size_t{2}, "d2") == "dm0"; + check ->* df->get_data(std::size_t{2}, "count(d3)") == 2.0; + check ->* df->get_data(std::size_t{2}, "distinct(d3)") == 1.0; + + check ->* df->get_data(std::size_t{3}, "d1") == "dx1"; + check ->* df->get_data(std::size_t{3}, "d2") == "dm1"; + check ->* df->get_data(std::size_t{3}, "count(d3)") == 0.0; + check ->* df->get_data(std::size_t{3}, "distinct(d3)") == 0.0; + + check ->* df->get_data(std::size_t{4}, "d1") == "dx1"; + check ->* df->get_data(std::size_t{4}, "d2") == "dm2"; + + check ->* df->get_data(std::size_t{5}, "d1") == "dx2"; + check ->* df->get_data(std::size_t{5}, "d2") == "dm0"; + check ->* df->get_data(std::size_t{5}, "count(d3)") == 2.0; + check ->* df->get_data(std::size_t{5}, "distinct(d3)") == 2.0; + } + ; // clang-format on From e2fd6fe4653d4d79a392655bc7baf17c7b2b8a4f Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 9 Feb 2024 17:01:41 +0100 Subject: [PATCH 027/253] Add na_pos + add sorting tests --- src/dataframe/interface.h | 5 +- test/unit/dataframe/interface_test.cpp | 392 +++++++++++++++++++------ 2 files changed, 299 insertions(+), 98 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index aedc00439..163bc581f 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -25,6 +25,8 @@ enum class aggregator_type { enum class sort_type { less, greater, natural_less, natural_greater }; +enum class na_position { last, first }; + enum class adding_type { create_or_add, create_or_throw, @@ -79,7 +81,8 @@ class dataframe_interface : std::function &&filter) & = 0; virtual void set_sort(series_identifier series, - any_sort_type sort) & = 0; + any_sort_type sort, + na_position na_pos) & = 0; virtual void set_sort( std::function diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index e81b91939..cc55a76a3 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -3,97 +3,134 @@ // #include "dataframe/impl/dataframe.h" #include "dataframe/interface.h" +using bool_msg = test::expected_bool_t; using test::assert; using test::check; +using test::input; using test::skip; +using test::skip_error; using test::throw_; using test::operator"" _suite; -using test::operator"" _case; using test::operator"" _is_true; -using bool_msg = test::expected_bool_t; using Vizzu::dataframe::cell_value; -using record_type = - Vizzu::dataframe::dataframe_interface::record_type; -using test::skip_error; +using Vizzu::dataframe::na_position; +using Vizzu::dataframe::sort_type; using interface = Vizzu::dataframe::dataframe_interface; +using record_type = interface::record_type; -auto setup(std::initializer_list dims = {}, - std::initializer_list meas = {}, - std::vector> rec = {}) +struct setup { - struct S + std::vector dims{}; + std::vector meas{}; + std::vector> data{}; + bool copied{}; + std::shared_ptr _df{ + // std::make_shared() // TODO + }; + + explicit(false) operator interface *() { - std::unique_ptr df{ - // std::make_unique() - }; - - explicit(false) operator interface *() const - { - return df.get(); - } - } result{}; - interface *df = result; - - try { - skip->*static_cast(df) == "dataframe exists"_is_true; + interface *df = _df.get(); - for (auto d : dims) df->add_dimension({}, {}, d, {}, {}); - for (auto m : meas) df->add_measure({}, m, {}, {}); + try { + skip->*static_cast(df) + == "dataframe exists"_is_true; - skip->*df->get_dimensions() == dims; - skip->*df->get_measures() == meas; + for (auto d : dims) df->add_dimension({}, {}, d, {}, {}); + for (auto m : meas) df->add_measure({}, m, {}, {}); - const auto ds = dims.size(); - for (auto r = 0u; r < rec.size(); ++r) { - df->add_record(rec[r]); - - for (auto d = 0u; d < ds; ++d) - skip->*df->get_data(r, d) == rec[r][d]; + const auto ds = dims.size(); + for (const auto &r : data) df->add_record(r); + if (copied) { + df->finalize(); + df = (_df = df->copy(false, false)).get(); + } - for (auto m = 0u; m < meas.size(); ++m) { - if (auto *d = std::get_if(&rec[r][m + ds]); - d && std::isnan(*d)) { - auto z = df->get_data(r, m + ds); - auto *st = std::get_if(&z); - skip->*static_cast(st) - == "value is a double"_is_true; - skip->*std::isnan(*st) == "value is nan"_is_true; - continue; + skip->*df->get_dimensions() == dims; + skip->*df->get_measures() == meas; + skip->*df->get_record_count() == data.size(); + + for (auto r = 0u; r < data.size(); ++r) { + for (auto d = 0u; d < ds; ++d) + skip->*df->get_data(r, d) == data[r][d]; + + for (auto m = 0u; m < meas.size(); ++m) { + if (auto *d = + std::get_if(&data[r][m + ds]); + d && std::isnan(*d)) { + auto z = df->get_data(r, m + ds); + auto *st = std::get_if(&z); + skip->*static_cast(st) + == "value is a double"_is_true; + skip->*std::isnan(*st) + == "value is nan"_is_true; + continue; + } + skip->*df->get_data(r, m + ds) == data[r][m + ds]; } - skip->*df->get_data(r, m + ds) == rec[r][m + ds]; } } - } - catch (skip_error const &) { - throw; - } - catch (std::exception const &e) { - throw skip_error(std::string{"setup failed: "} + e.what()); - } - catch (...) { - throw skip_error("setup failed"); - } + catch (skip_error const &) { + throw; + } + catch (std::exception const &e) { + throw skip_error( + std::string{"setup failed: "} + e.what()); + } + catch (...) { + throw skip_error("setup failed"); + } + + auto test = test::collection::instance().running_test(); + test->set_latest_location(test->get_test_location()); - skip->*true == "setup succeeded"_is_true; - return result; + return df; + } }; +static inline const auto empty_input = input{[] + { + return setup{}; + }, + "empty_input"}; + +static inline const auto empty_input_copied = input{[] + { + return setup{.copied = true}; + }, + "empty_input_copied"}; + +static inline const auto one_one_empty = input{[] + { + return setup{.dims = {"test_dim"}, .meas = {"test_meas"}}; + }, + "one_one_empty"}; + +static inline const auto one_one_empty_copied = input{[] + { + return setup{.dims = {"test_dim"}, + .meas = {"test_meas"}, + .copied = true}; + }, + "one_one_empty_copied"}; + // clang-format off const static auto tests = "DataFrame::interface"_suite - | "construct_empty"_case | - [] (interface* df = setup()) + | "construct_empty" | + empty_input + empty_input_copied + <=> [] (interface* df) { check ->* df->get_dimensions().size() == std::size_t{}; check ->* df->get_measures().size() == std::size_t{}; check ->* df->get_record_count() == std::size_t{}; - throw_ ->* [&df]() { df->add_record({}); }; - throw_ ->* [&df]() { return df->get_data({}, {}); }; - throw_ ->* [&df]() { return df->get_categories({}); }; - throw_ ->* [&df]() { return df->get_min_max({}); }; - throw_ ->* [&df]() + throw_ ->* [&df] { df->add_record({}); }; + throw_ ->* [&df] { return df->get_data({}, {}); }; + throw_ ->* [&df] { return df->get_categories({}); }; + throw_ ->* [&df] { return df->get_min_max({}); }; + throw_ ->* [&df] { return df->add_series_by_other( {}, @@ -104,13 +141,17 @@ const static auto tests = }, {}); }; + throw_ ->* [&df] { return df->set_aggregate({}, {}); }; + throw_ ->* [&df] { return df->set_sort({}, {}, {}); }; + check ->* df->get_dimensions().size() == std::size_t{}; check ->* df->get_measures().size() == std::size_t{}; check ->* df->get_record_count() == std::size_t{}; } - | "add_dimension_and_measure"_case | - [] (interface* df = setup()) + | "add_dimension_and_measure" | + empty_input + empty_input_copied + <=> [] (interface* df) { constexpr auto nan = std::numeric_limits::quiet_NaN(); @@ -181,21 +222,22 @@ const static auto tests = "Not points to the same memory address"_is_true; } - | "add_record"_case | - [] (interface* df = setup({"test_dim"}, {"test_meas"})) + | "add_record" | + one_one_empty + one_one_empty_copied + <=> [] (interface* df) { df->add_record({{"test_dim_val", 2.0}}); df->add_record({{-1.0, "test_dim_val2"}}); - throw_ ->* [&df]() { df->add_record({}); }; - throw_ ->* [&df]() { df->add_record({{0.0}}); }; - throw_ ->* [&df]() { df->add_record({{0.0, 0.0}}); }; - throw_ ->* [&df]() { df->add_record({{"test", "t"}}); }; - throw_ ->* [&df]() + throw_ ->* [&df] { df->add_record({}); }; + throw_ ->* [&df] { df->add_record({{0.0}}); }; + throw_ ->* [&df] { df->add_record({{0.0, 0.0}}); }; + throw_ ->* [&df] { df->add_record({{"test", "t"}}); }; + throw_ ->* [&df] { df->add_record({{0.0, "test", 0.0}}); }; - throw_ ->* [&df]() + throw_ ->* [&df] { df->add_record({{0.0, "test", "test"}}); }; @@ -218,13 +260,13 @@ const static auto tests = == "test_dim_val2"; } - | "add_series_by_other"_case | - [] (interface* df = setup({"d1", "d2"}, {"m1"}, { + | "add_series_by_other" | + [] (interface* df = setup{{"d1", "d2"}, {"m1"}, { {{"dm1", "dm2", 0.0}}, {{"dm1", "dmX", 1.0}}, {{"s1", "s2", -1.0}}, {{"s1", "s3", 3.0}} - })) { + }}) { df->add_series_by_other( "m1", @@ -274,12 +316,12 @@ const static auto tests = check ->* df->get_data(std::size_t{3}, "d15") == "s15s3"; } - | "remove_series"_case | - [] (interface* df = setup({"d1", "d2", "d3"}, {"m1", "m2", "m3"}, { + | "remove_series" | + [] (interface* df = setup{{"d1", "d2", "d3"}, {"m1", "m2", "m3"}, { {{"dm1", "dx2", "dm3", 0.0, 0.1, 0.2}}, {{"dm1", "dx2", "am3", 1.0, 2.1, 3.2}}, {{"dm2", "dm2", "bm3", 1.0, 1.5, 1.2}}, - })) { + }}) { df->remove_series({{"m1", "d2", "m3"}}); assert ->* df->get_measures() == std::array{"m2"}; @@ -290,8 +332,8 @@ const static auto tests = check ->* df->get_data(std::size_t{0}, "d3") == "dm3"; } - | "remove_records"_case | - [] (interface* df = setup({"d1"}, {"m1"}, { + | "remove_records" | + [] (interface* df = setup{{"d1"}, {"m1"}, { {{"dm0", NAN}}, {{"dm1", NAN}}, {{"dm2", NAN}}, @@ -302,7 +344,7 @@ const static auto tests = {{"dm7", NAN}}, {{"dm8", 4.2}}, {{"dm9", NAN}}, - })) { + }}) { df->remove_records({{0ul, 2ul, 4ul, 5ul, 8ul, 9ul}}); assert ->* df->get_measures() == std::array{"m1"}; @@ -317,8 +359,8 @@ const static auto tests = check ->* df->get_data(std::size_t{3}, "d1") == "dm3"; } - | "remove_records_filter"_case | - [] (interface* df = setup({"d1"}, {"m1"}, { + | "remove_records_filter" | + [] (interface* df = setup{{"d1"}, {"m1"}, { {{"dm0", 5.3}}, {{"dm1", 2.0}}, {{"dm2", 3.3}}, @@ -329,7 +371,7 @@ const static auto tests = {{"dm7", 0.0}}, {{"dm8", 4.2}}, {{"dm9", NAN}}, - })) { + }}) { df->remove_records( [] (record_type r) -> bool { @@ -346,17 +388,17 @@ const static auto tests = check ->* df->get_data(std::size_t{4}, "d1") == "dm4"; } - | "change_data"_case | - [] (interface* df = setup({"d1"}, {"m1"}, { + | "change_data" | + [] (interface* df = setup{{"d1"}, {"m1"}, { {{"dm0", 5.3}}, {{"dm1", 2.0}}, {{"dm2", 3.3}} - })) { + }}) { df->change_data(std::size_t{1}, "m1", 3.0); df->change_data(std::size_t{2}, "d1", "dmX"); - throw_ ->* [&df]() { df->change_data(std::size_t{0}, "d1", NAN); }; - throw_ ->* [&df]() { df->change_data(std::size_t{0}, "m1", ""); }; + throw_ ->* [&df] { df->change_data(std::size_t{0}, "d1", NAN); }; + throw_ ->* [&df] { df->change_data(std::size_t{0}, "m1", ""); }; assert ->* df->get_record_count() == std::size_t{3}; @@ -369,12 +411,12 @@ const static auto tests = check ->* df->get_data(std::size_t{2}, "d1") == "dmX"; } - | "fill_na"_case | - [] (interface* df = setup({"d1"}, {"m1"}, { + | "fill_na" | + [] (interface* df = setup{{"d1"}, {"m1"}, { {{"dm0", 5.3}}, {{std::string_view{nullptr, 0}, 2.0}}, {{"dm2", NAN}} - })) { + }}) { df->fill_na("m1", 3.0); df->fill_na("d1", "dmX"); @@ -389,8 +431,8 @@ const static auto tests = check ->* df->get_data(std::size_t{2}, "d1") == "dm2"; } - | "aggregate types"_case | - [] (interface* df = setup({"d1"}, {"m1"}, { + | "aggregate types" | + [] (interface* df = setup{{"d1"}, {"m1"}, { {{"dm0", 5.5}}, {{"dm0", 2.0}}, {{"dm0", 3.5}}, @@ -402,7 +444,7 @@ const static auto tests = {{"dm1", 4.25}}, {{"dm2", NAN}}, {{std::string_view{nullptr, 0}, 0.0}}, - })) { + }}) { df->set_aggregate("d1", {}); df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::count); df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::distinct); @@ -491,8 +533,8 @@ const static auto tests = check ->* df->get_data(std::size_t{3}, "exists(m1)") == 1.0; } - | "aggregate multiple dim"_case | - [] (interface* df = setup({"d1", "d2", "d3"}, {"m1"}, { + | "aggregate multiple dim" | + [] (interface* df = setup{{"d1", "d2", "d3"}, {"m1"}, { {{"dx0", "dm0", "doa", 5.5}}, {{"dx0", "dm0", "dob", 2.0}}, {{"dx0", "dm1", std::string_view{nullptr, 0}, 3.5}}, @@ -504,7 +546,7 @@ const static auto tests = {{"dx1", "dm0", "doa", 4.25}}, {{"dx2", "dm0", "dob", NAN}}, {{"dx2", "dm0", "doa", 0.5}}, - })) { + }}) { df->set_aggregate("d1", {}); df->set_aggregate("d2", {}); df->set_aggregate("d3", Vizzu::dataframe::aggregator_type::count); @@ -546,5 +588,161 @@ const static auto tests = check ->* df->get_data(std::size_t{5}, "distinct(d3)") == 2.0; } + | "cannot finalize contains same dim" | + [] (interface* df = setup{.dims = {"d1", "d2"}, .data={ + {{"dx0", "dm0"}}, + {{"dx0", "dm0"}} + }}) { + throw_ ->* [&df] { df->finalize(); }; + } + + | "sort dimension" | + [] (interface* df = setup{{"d1", "d2"}, {"m1"}, { + {{"dx2", "dm2", 5.5}}, + {{"dx1", "dm0", 2.0}}, + {{std::string_view{nullptr, 0}, "dm1", 3.5}}, + {{"dx0", "dm1", 10.25}}, + {{"dx2", "dm0", 88.0}}, + {{"dx1", "dm0", 3.5}}, + {{"dx2", "dm1", 7.25}}, + {{"dx1", "dm2", NAN}}, + {{"dx0", "dm0", 4.25}}, + {{"dx0", "dm0", NAN}}, + {{"dx2", "dm0", 0.5}}, + }}) { + df->set_sort("d1", {}, {}); + df->set_sort("m1", sort_type::greater, na_position::first); + + // cannot finalize because of duplicated dimensions + df->remove_records(std::span{}); + + assert ->* df->get_record_count() == std::size_t{11}; + + assert ->* df->get_categories("d1") == std::array{"dx0", "dx1", "dx2"}; + check ->* df->get_categories("d2") == std::array{"dm2", "dm0", "dm1"}; + + check ->* df->get_data(std::size_t{0}, "d1") == "dx0"; + check ->* df->get_data(std::size_t{1}, "d1") == "dx0"; + check ->* df->get_data(std::size_t{2}, "d1") == "dx0"; + check ->* df->get_data(std::size_t{3}, "d1") == "dx1"; + check ->* df->get_data(std::size_t{4}, "d1") == "dx1"; + check ->* df->get_data(std::size_t{5}, "d1") == "dx1"; + check ->* df->get_data(std::size_t{6}, "d1") == "dx2"; + check ->* df->get_data(std::size_t{7}, "d1") == "dx2"; + check ->* df->get_data(std::size_t{8}, "d1") == "dx2"; + check ->* df->get_data(std::size_t{9}, "d1") == "dx2"; + check ->* std::get(df->get_data(std::size_t{10}, "d1")).data() == nullptr; + + check ->* std::isnan(std::get(df->get_data(std::size_t{0}, "m1"))) + == "is nan"_is_true; + check ->* df->get_data(std::size_t{1}, "m1") == 10.25; + check ->* df->get_data(std::size_t{2}, "m1") == 4.25; + + check ->* std::isnan(std::get(df->get_data(std::size_t{3}, "m1"))) + == "is nan"_is_true; + check ->* df->get_data(std::size_t{4}, "m1") == 3.5; + check ->* df->get_data(std::size_t{5}, "m1") == 2.0; + + check ->* df->get_data(std::size_t{6}, "m1") == 88.0; + check ->* df->get_data(std::size_t{7}, "m1") == 7.25; + check ->* df->get_data(std::size_t{8}, "m1") == 5.5; + check ->* df->get_data(std::size_t{9}, "m1") == 0.5; + + check ->* df->get_data(std::size_t{10}, "m1") == 3.5; + } + + | "another sort example" | + [] (interface *df = setup{{"d1", "d2"}, {"m1"}, { + {{"dx2", "dm2", 88.0}}, + {{"dx1", "dm0", 2.0}}, + {{std::string_view{nullptr, 0}, "dm1", 3.5}}, + {{"dx0", "dm1", 10.25}}, + {{"dx2", "dm8", 5.5}}, + {{"dx1", "dm8", 3.5}}, + {{"dx2", "dm1", 7.25}}, + {{"dx1", "dm2", NAN}}, + {{"dx0", "dm8", 4.25}}, + {{"dx0", "dm0", NAN}}, + {{"dx2", "dm0", 0.5}}, + }}){ + df->set_sort("d1", sort_type::greater, na_position::first); + df->set_sort([](const record_type &lhs, const record_type &rhs) { + auto l = (std::get(lhs.getValue("d2"))[2] - '0') + + std::get(lhs.getValue("m1")); + auto r = (std::get(rhs.getValue("d2"))[2] - '0') + + std::get(rhs.getValue("m1")); + return std::weak_order(l, r); + }); + + df->finalize(); + + assert ->* df->get_record_count() == std::size_t{11}; + + assert ->* df->get_categories("d1") == std::array{"dx2", "dx1", "dx0"}; + check ->* df->get_categories("d2") == std::array{"dm2", "dm0", "dm1", "dm8"}; + + check ->* std::get(df->get_data(std::size_t{0}, "d1")).data() == nullptr; + check ->* df->get_data(std::size_t{1}, "d1") == "dx2"; + check ->* df->get_data(std::size_t{2}, "d1") == "dx2"; + check ->* df->get_data(std::size_t{3}, "d1") == "dx2"; + check ->* df->get_data(std::size_t{4}, "d1") == "dx2"; + check ->* df->get_data(std::size_t{5}, "d1") == "dx1"; + check ->* df->get_data(std::size_t{6}, "d1") == "dx1"; + check ->* df->get_data(std::size_t{7}, "d1") == "dx1"; + check ->* df->get_data(std::size_t{8}, "d1") == "dx0"; + check ->* df->get_data(std::size_t{9}, "d1") == "dx0"; + check ->* df->get_data(std::size_t{10}, "d1") == "dx0"; + + check ->* df->get_data(std::size_t{0}, "m1") == 3.5; + + check ->* df->get_data(std::size_t{1}, "m1") == 0.5; + check ->* df->get_data(std::size_t{2}, "m1") == 7.25; + check ->* df->get_data(std::size_t{3}, "m1") == 5.5; + check ->* df->get_data(std::size_t{4}, "m1") == 88.0; + + check ->* df->get_data(std::size_t{5}, "m1") == 2.0; + check ->* df->get_data(std::size_t{6}, "m1") == 3.5; + check ->* std::isnan(std::get(df->get_data(std::size_t{7}, "m1"))) + == "is nan"_is_true; + + check ->* df->get_data(std::size_t{8}, "m1") == 10.25; + check ->* df->get_data(std::size_t{9}, "m1") == 4.25; + check ->* std::isnan(std::get(df->get_data(std::size_t{10}, "m1"))) + == "is nan"_is_true; + } + + | "sort measure" | + [] (interface* df = setup{{"d1"}, {"m1"}, { + {{"dm0", 5.5}}, + {{"dm1", 2.0}}, + {{"dm2", 3.5}}, + {{"dm3", 10.25}}, + {{"dm4", 88.0}}, + {{"dm5", 2.2}}, + {{"dm6", 7.4}}, + {{"dm7", NAN}}, + {{"dm8", 4.2}}, + {{"dm9", 0.0}}, + }}) { + df->set_sort("m1", sort_type::greater, na_position::last); + + df->finalize(); + + assert ->* df->get_record_count() == std::size_t{10}; + + check ->* df->get_data(std::size_t{0}, "m1") == 88.0; + check ->* df->get_data(std::size_t{1}, "m1") == 10.25; + check ->* df->get_data(std::size_t{2}, "m1") == 7.4; + check ->* df->get_data(std::size_t{3}, "m1") == 5.5; + check ->* df->get_data(std::size_t{4}, "m1") == 4.2; + check ->* df->get_data(std::size_t{5}, "m1") == 3.5; + check ->* df->get_data(std::size_t{6}, "m1") == 2.2; + check ->* df->get_data(std::size_t{7}, "m1") == 2.0; + check ->* df->get_data(std::size_t{8}, "m1") == 0.0; + check ->* std::isnan(std::get(df->get_data(std::size_t{9}, "m1"))) + == "is nan"_is_true; + } + + ; // clang-format on From 1db59016682e5f28f12c36c5db7754b332097a97 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 9 Feb 2024 17:02:35 +0100 Subject: [PATCH 028/253] test framework add input strategy --- test/unit/util/case.h | 18 +++-- test/unit/util/case_registry.h | 4 +- test/unit/util/condition.h | 13 ++- test/unit/util/suite_proxy.h | 142 ++++++++++++++++++++++++++++++--- 4 files changed, 153 insertions(+), 24 deletions(-) diff --git a/test/unit/util/case.h b/test/unit/util/case.h index 661c99c6f..00e68037f 100644 --- a/test/unit/util/case.h +++ b/test/unit/util/case.h @@ -13,7 +13,7 @@ namespace test { -using runnable = std::function; +using runnable = void (*)(); struct assertion_error : std::runtime_error { @@ -66,8 +66,8 @@ class case_type void fail(const src_location &location, const std::string &message) { - if (!error_messages.contains(location)) - error_messages.insert({location, message}); + error_messages.try_emplace({location, suffix}, + message + suffix); } [[nodiscard]] std::string full_name() const @@ -92,6 +92,10 @@ class case_type latest_location = loc; } + const src_location &get_test_location() const { return location; } + + void set_suffix(const std::string &s) { suffix = s; } + private: std::string_view suite_name; std::string_view case_name; @@ -99,7 +103,9 @@ class case_type bool skip = false; src_location location; src_location latest_location = location; - std::map error_messages; + std::map, std::string> + error_messages; + std::string suffix; void run_safely() noexcept { @@ -145,8 +151,8 @@ class case_type << " ms)\n"; for (const auto &error : error_messages) - std::cerr << error.first.error_prefix() << error.second - << "\n"; + std::cerr << error.first.first.error_prefix() + << error.second << "\n"; } }; diff --git a/test/unit/util/case_registry.h b/test/unit/util/case_registry.h index b4bda08a5..f317ad713 100644 --- a/test/unit/util/case_registry.h +++ b/test/unit/util/case_registry.h @@ -21,13 +21,13 @@ class case_registry void add_record(std::string_view suite_name, std::string_view case_name, - runnable &&runner, + runnable runner, src_location location) noexcept { try { cases.emplace_back(suite_name, case_name, - std::move(runner), + runner, location); } catch (...) { diff --git a/test/unit/util/condition.h b/test/unit/util/condition.h index 57bc729c0..edc1672f1 100644 --- a/test/unit/util/condition.h +++ b/test/unit/util/condition.h @@ -18,7 +18,7 @@ template struct decomposer public: friend check_t; - template bool operator==(const U &ref) const + template void operator==(const U &ref) const { if constexpr (requires { ref == value; }) { return evaluate(value == ref, "==", ref); @@ -40,19 +40,19 @@ template struct decomposer } } - bool operator<=(const auto &ref) const + void operator<=(const auto &ref) const { return evaluate(value <= ref, "<=", ref); } - bool operator>=(const auto &ref) const + void operator>=(const auto &ref) const { return evaluate(value >= ref, ">=", ref); } - bool operator<(const auto &ref) const + void operator<(const auto &ref) const { return evaluate(value < ref, "<", ref); } - bool operator>(const auto &ref) const + void operator>(const auto &ref) const { return evaluate(value > ref, ">", ref); } @@ -71,7 +71,7 @@ template struct decomposer throw_error(throw_error) {} - [[nodiscard]] bool + void evaluate(bool condition, const char *op, const auto &ref) const { using details::to_debug_string; @@ -91,7 +91,6 @@ template struct decomposer + op + " " + to_debug_string(ref) + " (expected)"); } - return condition; } }; diff --git a/test/unit/util/suite_proxy.h b/test/unit/util/suite_proxy.h index f177450b1..a4305cc54 100644 --- a/test/unit/util/suite_proxy.h +++ b/test/unit/util/suite_proxy.h @@ -15,6 +15,79 @@ class suite_proxy; using generator = std::function; +template struct multi_input; + +template + requires(std::is_default_constructible_v) +struct input +{ + explicit(false) input(T &&, + std::string_view test_desc = {}, + src_location loc = test::src_location{}) : + loc(loc), + test_desc(test_desc) + {} + + template + explicit input(input const &oth) : + loc(oth.loc), + test_desc(oth.test_desc) + {} + + template + requires(std::is_default_constructible_v + && std::is_invocable_v>) + multi_input, F, T> operator<=>(F) const + { + return {input<>{*this}}; + } + + src_location loc; + std::string_view test_desc; +}; + +template input(T &&, std::string_view = {}) -> input; + +template + requires(sizeof...(Ts) > 0 && sizeof...(Ix) == sizeof...(Ts) + && !std::is_void_v) +struct multi_input, L, Ts...> +{ + std::array, sizeof...(Ts)> locations; +}; + +template + requires(sizeof...(Ts) > 0 && sizeof...(Ix) == sizeof...(Ts)) +struct multi_input, void, Ts...> +{ + template + requires(std::is_default_constructible_v && ... + && std::is_invocable_v>) + multi_input, F, Ts...> operator<=>( + F) const + { + return {locations}; + } + + std::array, sizeof...(Ts)> locations; +}; + +template +multi_input, void, T, U> +operator+(const input &a, const input &b) +{ + return {input<>{a}, input<>{b}}; +} + +template +multi_input, void, T..., U> +operator+( + const multi_input, void, T...> &a, + const input &b) +{ + return {a.locations[Ix]..., input<>{b}}; +} + class suite_proxy { public: @@ -25,10 +98,10 @@ class suite_proxy {} suite_proxy &add_case(std::string_view case_name, - runnable &&test, + runnable test, src_location location = src_location()) noexcept { - parent.add_record(name, case_name, std::move(test), location); + parent.add_record(name, case_name, test, location); return *this; } @@ -36,20 +109,77 @@ class suite_proxy { std::string_view case_name; src_location location; + explicit(false) case_name_proxy(std::string_view case_name, src_location location = src_location()) : case_name(case_name), location(location) {} + + template + explicit(false) case_name_proxy(const char (&a)[N], + src_location location = src_location()) : + case_name_proxy({a, N - 1}, location) + {} }; struct case_proxy : case_name_proxy { suite_proxy &suite; + template + suite_proxy &operator|( + multi_input, Lambda, Inputs...> + &&i) + { + + static const auto s = suite.parent.cases.size(); + static auto &p = suite.parent; + static const auto mi = i; + static const auto set = [] + { + p.cases[s].set_suffix( + "\n\t on " + + std::string{mi.locations[I].loc.error_prefix()} + + (mi.locations[I].test_desc.data() + ? std::string{mi.locations[I].test_desc} + : std::to_string(I) + ". input.")); + + Lambda{}(In{}()); + + p.cases[s].set_suffix(""); + }; + + suite.add_case( + case_name, + +[] + { + (set.template operator()(), ...); + }, + location); + + return suite; + } + + template + requires(std::is_trivially_constructible_v + && std::is_invocable_v + && !std::is_convertible_v) + suite_proxy &operator|(Lambda &&) noexcept + { + suite.add_case( + case_name, + +[] + { + Lambda{}(); + }, + location); + return suite; + } + suite_proxy &operator|(runnable &&test) noexcept { - suite.add_case(case_name, std::move(test), location); + suite.add_case(case_name, test, location); return suite; } }; @@ -69,12 +199,6 @@ class suite_proxy case_registry &parent; }; -inline std::string_view operator"" _case(const char *name, - size_t size) -{ - return {name, size}; -} - } #endif From f885db4ff22e90ea9859d370c77aceb6383c0d75 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 9 Feb 2024 17:02:35 +0100 Subject: [PATCH 029/253] test framework add input strategy --- test/unit/util/case.h | 18 +++-- test/unit/util/case_registry.h | 4 +- test/unit/util/condition.h | 13 ++- test/unit/util/suite_proxy.h | 142 ++++++++++++++++++++++++++++++--- 4 files changed, 153 insertions(+), 24 deletions(-) diff --git a/test/unit/util/case.h b/test/unit/util/case.h index 661c99c6f..00e68037f 100644 --- a/test/unit/util/case.h +++ b/test/unit/util/case.h @@ -13,7 +13,7 @@ namespace test { -using runnable = std::function; +using runnable = void (*)(); struct assertion_error : std::runtime_error { @@ -66,8 +66,8 @@ class case_type void fail(const src_location &location, const std::string &message) { - if (!error_messages.contains(location)) - error_messages.insert({location, message}); + error_messages.try_emplace({location, suffix}, + message + suffix); } [[nodiscard]] std::string full_name() const @@ -92,6 +92,10 @@ class case_type latest_location = loc; } + const src_location &get_test_location() const { return location; } + + void set_suffix(const std::string &s) { suffix = s; } + private: std::string_view suite_name; std::string_view case_name; @@ -99,7 +103,9 @@ class case_type bool skip = false; src_location location; src_location latest_location = location; - std::map error_messages; + std::map, std::string> + error_messages; + std::string suffix; void run_safely() noexcept { @@ -145,8 +151,8 @@ class case_type << " ms)\n"; for (const auto &error : error_messages) - std::cerr << error.first.error_prefix() << error.second - << "\n"; + std::cerr << error.first.first.error_prefix() + << error.second << "\n"; } }; diff --git a/test/unit/util/case_registry.h b/test/unit/util/case_registry.h index b4bda08a5..f317ad713 100644 --- a/test/unit/util/case_registry.h +++ b/test/unit/util/case_registry.h @@ -21,13 +21,13 @@ class case_registry void add_record(std::string_view suite_name, std::string_view case_name, - runnable &&runner, + runnable runner, src_location location) noexcept { try { cases.emplace_back(suite_name, case_name, - std::move(runner), + runner, location); } catch (...) { diff --git a/test/unit/util/condition.h b/test/unit/util/condition.h index 57bc729c0..edc1672f1 100644 --- a/test/unit/util/condition.h +++ b/test/unit/util/condition.h @@ -18,7 +18,7 @@ template struct decomposer public: friend check_t; - template bool operator==(const U &ref) const + template void operator==(const U &ref) const { if constexpr (requires { ref == value; }) { return evaluate(value == ref, "==", ref); @@ -40,19 +40,19 @@ template struct decomposer } } - bool operator<=(const auto &ref) const + void operator<=(const auto &ref) const { return evaluate(value <= ref, "<=", ref); } - bool operator>=(const auto &ref) const + void operator>=(const auto &ref) const { return evaluate(value >= ref, ">=", ref); } - bool operator<(const auto &ref) const + void operator<(const auto &ref) const { return evaluate(value < ref, "<", ref); } - bool operator>(const auto &ref) const + void operator>(const auto &ref) const { return evaluate(value > ref, ">", ref); } @@ -71,7 +71,7 @@ template struct decomposer throw_error(throw_error) {} - [[nodiscard]] bool + void evaluate(bool condition, const char *op, const auto &ref) const { using details::to_debug_string; @@ -91,7 +91,6 @@ template struct decomposer + op + " " + to_debug_string(ref) + " (expected)"); } - return condition; } }; diff --git a/test/unit/util/suite_proxy.h b/test/unit/util/suite_proxy.h index f177450b1..a4305cc54 100644 --- a/test/unit/util/suite_proxy.h +++ b/test/unit/util/suite_proxy.h @@ -15,6 +15,79 @@ class suite_proxy; using generator = std::function; +template struct multi_input; + +template + requires(std::is_default_constructible_v) +struct input +{ + explicit(false) input(T &&, + std::string_view test_desc = {}, + src_location loc = test::src_location{}) : + loc(loc), + test_desc(test_desc) + {} + + template + explicit input(input const &oth) : + loc(oth.loc), + test_desc(oth.test_desc) + {} + + template + requires(std::is_default_constructible_v + && std::is_invocable_v>) + multi_input, F, T> operator<=>(F) const + { + return {input<>{*this}}; + } + + src_location loc; + std::string_view test_desc; +}; + +template input(T &&, std::string_view = {}) -> input; + +template + requires(sizeof...(Ts) > 0 && sizeof...(Ix) == sizeof...(Ts) + && !std::is_void_v) +struct multi_input, L, Ts...> +{ + std::array, sizeof...(Ts)> locations; +}; + +template + requires(sizeof...(Ts) > 0 && sizeof...(Ix) == sizeof...(Ts)) +struct multi_input, void, Ts...> +{ + template + requires(std::is_default_constructible_v && ... + && std::is_invocable_v>) + multi_input, F, Ts...> operator<=>( + F) const + { + return {locations}; + } + + std::array, sizeof...(Ts)> locations; +}; + +template +multi_input, void, T, U> +operator+(const input &a, const input &b) +{ + return {input<>{a}, input<>{b}}; +} + +template +multi_input, void, T..., U> +operator+( + const multi_input, void, T...> &a, + const input &b) +{ + return {a.locations[Ix]..., input<>{b}}; +} + class suite_proxy { public: @@ -25,10 +98,10 @@ class suite_proxy {} suite_proxy &add_case(std::string_view case_name, - runnable &&test, + runnable test, src_location location = src_location()) noexcept { - parent.add_record(name, case_name, std::move(test), location); + parent.add_record(name, case_name, test, location); return *this; } @@ -36,20 +109,77 @@ class suite_proxy { std::string_view case_name; src_location location; + explicit(false) case_name_proxy(std::string_view case_name, src_location location = src_location()) : case_name(case_name), location(location) {} + + template + explicit(false) case_name_proxy(const char (&a)[N], + src_location location = src_location()) : + case_name_proxy({a, N - 1}, location) + {} }; struct case_proxy : case_name_proxy { suite_proxy &suite; + template + suite_proxy &operator|( + multi_input, Lambda, Inputs...> + &&i) + { + + static const auto s = suite.parent.cases.size(); + static auto &p = suite.parent; + static const auto mi = i; + static const auto set = [] + { + p.cases[s].set_suffix( + "\n\t on " + + std::string{mi.locations[I].loc.error_prefix()} + + (mi.locations[I].test_desc.data() + ? std::string{mi.locations[I].test_desc} + : std::to_string(I) + ". input.")); + + Lambda{}(In{}()); + + p.cases[s].set_suffix(""); + }; + + suite.add_case( + case_name, + +[] + { + (set.template operator()(), ...); + }, + location); + + return suite; + } + + template + requires(std::is_trivially_constructible_v + && std::is_invocable_v + && !std::is_convertible_v) + suite_proxy &operator|(Lambda &&) noexcept + { + suite.add_case( + case_name, + +[] + { + Lambda{}(); + }, + location); + return suite; + } + suite_proxy &operator|(runnable &&test) noexcept { - suite.add_case(case_name, std::move(test), location); + suite.add_case(case_name, test, location); return suite; } }; @@ -69,12 +199,6 @@ class suite_proxy case_registry &parent; }; -inline std::string_view operator"" _case(const char *name, - size_t size) -{ - return {name, size}; -} - } #endif From c27ff6eac25de65e939c89ea18043a7eaf6b0c62 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 9 Feb 2024 18:13:30 +0100 Subject: [PATCH 030/253] Fix legend outerRect --- src/chart/rendering/drawlegend.cpp | 2 +- test/e2e/tests/features.json | 2 +- test/e2e/tests/features/events/drawing_events.mjs | 11 ++++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/chart/rendering/drawlegend.cpp b/src/chart/rendering/drawlegend.cpp index 82760bba0..39d8b6a9e 100644 --- a/src/chart/rendering/drawlegend.cpp +++ b/src/chart/rendering/drawlegend.cpp @@ -132,7 +132,7 @@ Geom::TransformedRect DrawLegend::getLabelRect(const Info &info, { Geom::Rect res = itemRect; res.pos.x += info.markerSize; - res.size.x -= std::max(0.0, res.size.x - info.markerSize); + res.size.x -= std::max(0.0, info.markerSize); return Geom::TransformedRect::fromRect(res); } diff --git a/test/e2e/tests/features.json b/test/e2e/tests/features.json index 154a83fce..2def624bf 100644 --- a/test/e2e/tests/features.json +++ b/test/e2e/tests/features.json @@ -14,7 +14,7 @@ "refs": ["9d5443f"] }, "events/drawing_events": { - "refs": ["ed448a0"] + "refs": ["49abe43"] }, "subtitle_caption": { "refs": ["5c04f20"] diff --git a/test/e2e/tests/features/events/drawing_events.mjs b/test/e2e/tests/features/events/drawing_events.mjs index 0f47fa086..5408eac9d 100644 --- a/test/e2e/tests/features/events/drawing_events.mjs +++ b/test/e2e/tests/features/events/drawing_events.mjs @@ -38,6 +38,14 @@ function overlay(e, chart) { const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent ctx.transform(t[0][0], t[1][0], t[0][1], t[1][1], t[0][2], t[1][2]) + + if (e.type.includes('legend-label-draw')) { + const orectsize = e.detail.outerRect.size + ctx.fillStyle = '#FF00FFA0' + ctx.fillRect(0, 0, orectsize.x, orectsize.y) + ctx.fillStyle = '#FF00000F' + } + ctx.translate(inPos.x, inPos.y) if (e.detail.innerRect.size.x > width) { ctx.translate(((e.detail.innerRect.size.x - width) / 2) * (1 + e.detail.align), 0) @@ -48,11 +56,12 @@ function overlay(e, chart) { ctx.fillStyle = '#FF0000A0' ctx.textAlign = 'left' ctx.textBaseline = 'top' - console.log(e) ctx.fillText(e.detail.text, 0, 0) } } else if (e.detail.rect && e.detail.rect.pos && e.detail.rect.size) { const r = e.detail.rect + if (e.type.includes('legend-background-draw')) + ctx.fillStyle = '#FFFF00A0' ctx.fillRect(r.pos.x, r.pos.y, r.size.x, r.size.y) ctx.fillStyle = '#FF0000A0' if (e.detail.text) ctx.fillText(e.detail.text, r.pos.x, r.pos.y, r.pos.y + r.size.y) From 0018fd74d167ce97b95d383d74812355cfc1b281 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 9 Feb 2024 18:14:35 +0100 Subject: [PATCH 031/253] Fix of fix --- src/chart/rendering/drawlegend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chart/rendering/drawlegend.cpp b/src/chart/rendering/drawlegend.cpp index 39d8b6a9e..96dd33b82 100644 --- a/src/chart/rendering/drawlegend.cpp +++ b/src/chart/rendering/drawlegend.cpp @@ -132,7 +132,7 @@ Geom::TransformedRect DrawLegend::getLabelRect(const Info &info, { Geom::Rect res = itemRect; res.pos.x += info.markerSize; - res.size.x -= std::max(0.0, info.markerSize); + res.size.x = std::max(0.0, res.size.x - info.markerSize); return Geom::TransformedRect::fromRect(res); } From 89be94512b4bc520aeaa3e902aebf6053563a8cc Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 9 Feb 2024 18:15:32 +0100 Subject: [PATCH 032/253] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6451e3751..6897a2f57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Fix dimension label transition on axis and legend. - Through event handler call, when a new event handler is registered, undefined behaviour happened. - Fixed channel reset with empty array when shorthands plugin switched off. +- Legend label outerRect was not properly calculated. ### Added From 782c1e7ad0e0df71304be3daa310a079a8fbb1b1 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Sun, 11 Feb 2024 08:59:04 +0100 Subject: [PATCH 033/253] Fix format --- test/e2e/tests/features/events/drawing_events.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/e2e/tests/features/events/drawing_events.mjs b/test/e2e/tests/features/events/drawing_events.mjs index 5408eac9d..deea4f1c2 100644 --- a/test/e2e/tests/features/events/drawing_events.mjs +++ b/test/e2e/tests/features/events/drawing_events.mjs @@ -60,8 +60,7 @@ function overlay(e, chart) { } } else if (e.detail.rect && e.detail.rect.pos && e.detail.rect.size) { const r = e.detail.rect - if (e.type.includes('legend-background-draw')) - ctx.fillStyle = '#FFFF00A0' + if (e.type.includes('legend-background-draw')) ctx.fillStyle = '#FFFF00A0' ctx.fillRect(r.pos.x, r.pos.y, r.size.x, r.size.y) ctx.fillStyle = '#FF0000A0' if (e.detail.text) ctx.fillText(e.detail.text, r.pos.x, r.pos.y, r.pos.y + r.size.y) From c8cd17a7332ffbc8919d060cc1b6deaf08701d7e Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Tue, 13 Feb 2024 15:25:02 +0100 Subject: [PATCH 034/253] Data series descriptor on TS API changed from formatted string to object. --- CHANGELOG.md | 2 + src/apps/weblib/ts-api/module/cchart.ts | 38 ++- src/apps/weblib/ts-api/module/cproxy.ts | 20 +- .../weblib/ts-api/plugins/presetconfigs.js | 276 +++++++++--------- src/apps/weblib/ts-api/plugins/presets.ts | 2 +- src/apps/weblib/ts-api/plugins/shorthands.ts | 76 ++++- src/apps/weblib/ts-api/utils.ts | 16 +- src/apps/weblib/typeschema-api/data.yaml | 10 +- .../cookbook/style/highligh_markers.mjs | 2 +- tools/ci/type/gen-presets.cjs | 11 +- 10 files changed, 282 insertions(+), 171 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6897a2f57..2bd191862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ ### Added +- In config channels, data series and their aggregators can be specified separately + in a descriptor object, besides encoding them into one string (old way). - Added optional `categories` member to the `legend-marker`, `legend-label` and `plot-axis-label` events. - Remove unused marker selection and selected marker coloring. - Removed marker's alpha color when tooltip is shown. diff --git a/src/apps/weblib/ts-api/module/cchart.ts b/src/apps/weblib/ts-api/module/cchart.ts index 06ed3c30d..9dc78ab68 100644 --- a/src/apps/weblib/ts-api/module/cchart.ts +++ b/src/apps/weblib/ts-api/module/cchart.ts @@ -3,6 +3,7 @@ import { CString, CFunction, CEventPtr } from '../cvizzu.types' import * as Anim from '../types/anim.js' import * as Config from '../types/config.js' import * as Styles from '../types/styles.js' +import * as Data from '../types/data.js' import { CObject, CEnv } from './cenv.js' import { CPointerClosure } from './objregistry.js' @@ -10,6 +11,8 @@ import { CProxy } from './cproxy.js' import { CCanvas } from './ccanvas.js' import { CAnimation } from './canimctrl.js' +import { isIterable } from '../utils.js' + /** Stored Chart object. */ export class Snapshot extends CObject {} @@ -124,10 +127,43 @@ export class CChart extends CObject { this, this._wasm._chart_getList, this._wasm._chart_getValue, - this._wasm._chart_setValue + this._wasm._chart_setValue, + (value: unknown): value is Record => + isIterable(value) && !this._isSeriesDescriptor(value), + (value: unknown): string => { + // workaround: we should be able to pass series descriptor as two string + if (this._isSeriesDescriptor(value)) { + return value.aggregator ? `${value.aggregator}(${value.name})` : value.name + } else { + return String(value).toString() + } + }, + (path: string, value: string): unknown => { + // workaround because channel.*.set returns already json instead of scalars + if (path.startsWith('channels.') && path.endsWith('.set')) { + return JSON.parse(value).map((v: string) => this._toSeriesDescriptor(v)) + } else return value + } ) } + private _toSeriesDescriptor(value: string): Data.SeriesDescriptor { + const pattern = /^(\w+)\((.*?)\)$/ + const match = value.match(pattern) + if (match) { + return { + name: match[2]!, + aggregator: match[1]! as Data.AggregatorType + } + } else { + return { name: value } + } + } + + private _isSeriesDescriptor(value: unknown): value is Data.SeriesDescriptor { + return typeof value === 'object' && value !== null && 'name' in value + } + private _makeStyle(computed: boolean): CStyle { return new CStyle( this.getId, diff --git a/src/apps/weblib/ts-api/module/cproxy.ts b/src/apps/weblib/ts-api/module/cproxy.ts index f3e0a2e93..1ab8dfd4d 100644 --- a/src/apps/weblib/ts-api/module/cproxy.ts +++ b/src/apps/weblib/ts-api/module/cproxy.ts @@ -1,7 +1,7 @@ import { CPointer, CString } from '../cvizzu.types' import { CObject } from './cenv.js' -import { mirrorObject, iterateObject } from '../utils.js' +import { mirrorObject, iterateObject, isIterable, type ShouldIterate } from '../utils.js' import { Mirrored } from '../tsutils.js' import { CPointerClosure } from './objregistry.js' @@ -13,22 +13,31 @@ export class CProxy extends CObject { private _lister: Lister private _getter: Getter private _setter: Setter + private _shouldIterate: ShouldIterate + private _toString: (value: unknown) => string + private _fromString: (path: string, str: string) => unknown constructor( getId: CPointerClosure, cenv: CObject, lister: Lister, getter: Getter, - setter: Setter + setter: Setter, + shouldIterate?: ShouldIterate, + toString?: (value: unknown) => string, + fromString?: (path: string, str: string) => unknown ) { super(getId, cenv) this._lister = lister this._getter = getter this._setter = setter + this._shouldIterate = shouldIterate || isIterable + this._toString = toString || ((value: unknown): string => String(value).toString()) + this._fromString = fromString || ((_path: string, str: string): unknown => str) } set(value: T): void { - iterateObject(value, this.setParam.bind(this)) + iterateObject(value, this.setParam.bind(this), this._shouldIterate) } get(): Mirrored { @@ -47,8 +56,7 @@ export class CProxy extends CObject { try { const cvalue = this._call(this._getter)(cpath) const value = this._fromCString(cvalue) - // dirty workaround because config.channel.*.set returns already json instead of scalars - return value.startsWith('[') || value.startsWith('{') ? JSON.parse(value) : value + return this._fromString(path, value) } finally { this._wasm._free(cpath) } @@ -56,7 +64,7 @@ export class CProxy extends CObject { setParam(path: string, value: unknown): void { const cpath = this._toCString(path) - const cvalue = this._toCString(String(value).toString()) + const cvalue = this._toCString(this._toString(value)) try { this._call(this._setter)(cpath, cvalue) } finally { diff --git a/src/apps/weblib/ts-api/plugins/presetconfigs.js b/src/apps/weblib/ts-api/plugins/presetconfigs.js index 90ee57052..2fa5bf4a8 100644 --- a/src/apps/weblib/ts-api/plugins/presetconfigs.js +++ b/src/apps/weblib/ts-api/plugins/presetconfigs.js @@ -1,195 +1,195 @@ export const presetConfigs = { column: { channels: { - x: { set: ['x'] }, - y: { set: ['y'] }, - label: { set: ['y'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }] }, + label: { set: [{ name: 'y' }] } } }, groupedColumn: { channels: { - x: { set: ['groupedBy', 'x'] }, - y: { set: ['y'] }, - label: { set: ['y'] }, - color: { set: ['x'] } + x: { set: [{ name: 'groupedBy' }, { name: 'x' }] }, + y: { set: [{ name: 'y' }] }, + label: { set: [{ name: 'y' }] }, + color: { set: [{ name: 'x' }] } } }, stackedColumn: { channels: { - x: { set: ['x'] }, - y: { set: ['y', 'stackedBy'] }, - label: { set: ['y'] }, - color: { set: ['stackedBy'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }, { name: 'stackedBy' }] }, + label: { set: [{ name: 'y' }] }, + color: { set: [{ name: 'stackedBy' }] } } }, splittedColumn: { channels: { - x: { set: ['x'] }, - y: { set: ['y', 'splittedBy'] }, - color: { set: ['splittedBy'] }, - label: { set: ['y'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }, { name: 'splittedBy' }] }, + color: { set: [{ name: 'splittedBy' }] }, + label: { set: [{ name: 'y' }] } }, split: true }, percentageColumn: { channels: { - x: { set: ['x'] }, - y: { set: ['y', 'stackedBy'] }, - color: { set: ['stackedBy'] }, - label: { set: ['y'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }, { name: 'stackedBy' }] }, + color: { set: [{ name: 'stackedBy' }] }, + label: { set: [{ name: 'y' }] } }, align: 'stretch' }, waterfall: { channels: { - x: { set: ['x'] }, - y: { set: ['y', 'x'] }, - label: { set: ['y'] }, - color: { set: ['y'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }, { name: 'x' }] }, + label: { set: [{ name: 'y' }] }, + color: { set: [{ name: 'y' }] } }, align: 'stretch' }, mekko: { channels: { - x: { set: ['x', 'groupedBy'] }, - y: { set: ['y', 'stackedBy'] }, - color: { set: ['stackedBy'] }, - label: { set: ['groupedBy'] } + x: { set: [{ name: 'x' }, { name: 'groupedBy' }] }, + y: { set: [{ name: 'y' }, { name: 'stackedBy' }] }, + color: { set: [{ name: 'stackedBy' }] }, + label: { set: [{ name: 'groupedBy' }] } } }, marimekko: { channels: { - x: { set: ['x', 'groupedBy'] }, - y: { set: ['y', 'stackedBy'] }, - color: { set: ['stackedBy'] }, - label: { set: ['groupedBy'] } + x: { set: [{ name: 'x' }, { name: 'groupedBy' }] }, + y: { set: [{ name: 'y' }, { name: 'stackedBy' }] }, + color: { set: [{ name: 'stackedBy' }] }, + label: { set: [{ name: 'groupedBy' }] } }, align: 'stretch' }, bar: { channels: { - x: { set: ['x'] }, - y: { set: ['y'] }, - label: { set: ['x'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }] }, + label: { set: [{ name: 'x' }] } } }, groupedBar: { channels: { - x: { set: ['x'] }, - y: { set: ['groupedBy', 'y'] }, - label: { set: ['x'] }, - color: { set: ['y'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'groupedBy' }, { name: 'y' }] }, + label: { set: [{ name: 'x' }] }, + color: { set: [{ name: 'y' }] } } }, stackedBar: { channels: { - x: { set: ['x', 'stackedBy'] }, - y: { set: ['y'] }, - label: { set: ['x'] }, - color: { set: ['stackedBy'] } + x: { set: [{ name: 'x' }, { name: 'stackedBy' }] }, + y: { set: [{ name: 'y' }] }, + label: { set: [{ name: 'x' }] }, + color: { set: [{ name: 'stackedBy' }] } } }, splittedBar: { channels: { - x: { set: ['x', 'splittedBy'] }, - y: { set: ['y'] }, - color: { set: ['splittedBy'] }, - label: { set: ['x'] } + x: { set: [{ name: 'x' }, { name: 'splittedBy' }] }, + y: { set: [{ name: 'y' }] }, + color: { set: [{ name: 'splittedBy' }] }, + label: { set: [{ name: 'x' }] } }, split: true }, percentageBar: { channels: { - x: { set: ['x', 'stackedBy'] }, - y: { set: ['y'] }, - color: { set: ['stackedBy'] }, - label: { set: ['x'] } + x: { set: [{ name: 'x' }, { name: 'stackedBy' }] }, + y: { set: [{ name: 'y' }] }, + color: { set: [{ name: 'stackedBy' }] }, + label: { set: [{ name: 'x' }] } }, align: 'stretch' }, lollipop: { channels: { - x: { set: ['x'] }, - y: { set: ['y'] }, - label: { set: ['y'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }] }, + label: { set: [{ name: 'y' }] } }, geometry: 'circle' }, scatter: { channels: { - x: { set: ['x'] }, - y: { set: ['y'] }, - noop: { set: ['dividedBy'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }] }, + noop: { set: [{ name: 'dividedBy' }] } }, geometry: 'circle' }, bubbleplot: { channels: { - x: { set: ['x'] }, - y: { set: ['y'] }, - color: { set: ['color'] }, - size: { set: ['size'] }, - label: { set: ['dividedBy'] }, - noop: { set: ['dividedBy'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }] }, + color: { set: [{ name: 'color' }] }, + size: { set: [{ name: 'size' }] }, + label: { set: [{ name: 'dividedBy' }] }, + noop: { set: [{ name: 'dividedBy' }] } }, geometry: 'circle' }, area: { channels: { - x: { set: ['x'] }, - y: { set: ['y'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }] } }, geometry: 'area' }, stackedArea: { channels: { - x: { set: ['x'] }, - y: { set: ['y', 'stackedBy'] }, - color: { set: ['stackedBy'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }, { name: 'stackedBy' }] }, + color: { set: [{ name: 'stackedBy' }] } }, geometry: 'area' }, percentageArea: { channels: { - x: { set: ['x'] }, - y: { set: ['y', 'stackedBy'] }, - color: { set: ['stackedBy'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }, { name: 'stackedBy' }] }, + color: { set: [{ name: 'stackedBy' }] } }, align: 'stretch', geometry: 'area' }, splittedArea: { channels: { - x: { set: ['x'] }, - y: { set: ['y', 'splittedBy'] }, - color: { set: ['splittedBy'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }, { name: 'splittedBy' }] }, + color: { set: [{ name: 'splittedBy' }] } }, split: true, geometry: 'area' }, stream: { channels: { - x: { set: ['x'] }, - y: { set: ['y', 'stackedBy'] }, - color: { set: ['stackedBy'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }, { name: 'stackedBy' }] }, + color: { set: [{ name: 'stackedBy' }] } }, geometry: 'area', align: 'center' }, verticalStream: { channels: { - x: { set: ['x', 'stackedBy'] }, - y: { set: ['y'] }, - color: { set: ['stackedBy'] } + x: { set: [{ name: 'x' }, { name: 'stackedBy' }] }, + y: { set: [{ name: 'y' }] }, + color: { set: [{ name: 'stackedBy' }] } }, geometry: 'area', align: 'center' }, violin: { channels: { - x: { set: ['x'] }, - y: { set: ['y', 'splittedBy'] }, - color: { set: ['splittedBy'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }, { name: 'splittedBy' }] }, + color: { set: [{ name: 'splittedBy' }] } }, geometry: 'area', align: 'center', @@ -197,9 +197,9 @@ export const presetConfigs = { }, verticalViolin: { channels: { - x: { set: ['x', 'splittedBy'] }, - y: { set: ['y'] }, - color: { set: ['splittedBy'] } + x: { set: [{ name: 'x' }, { name: 'splittedBy' }] }, + y: { set: [{ name: 'y' }] }, + color: { set: [{ name: 'splittedBy' }] } }, geometry: 'area', align: 'center', @@ -207,140 +207,140 @@ export const presetConfigs = { }, line: { channels: { - x: { set: ['x'] }, - y: { set: ['y'] }, - color: { set: ['dividedBy'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }] }, + color: { set: [{ name: 'dividedBy' }] } }, geometry: 'line' }, verticalLine: { channels: { - x: { set: ['x'] }, - y: { set: ['y'] }, - color: { set: ['dividedBy'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }] }, + color: { set: [{ name: 'dividedBy' }] } }, geometry: 'line' }, pie: { channels: { - x: { set: ['angle', 'by'] }, - color: { set: ['by'] }, - label: { set: ['angle'] } + x: { set: [{ name: 'angle' }, { name: 'by' }] }, + color: { set: [{ name: 'by' }] }, + label: { set: [{ name: 'angle' }] } }, coordSystem: 'polar' }, polarColumn: { channels: { - x: { set: ['angle'] }, - y: { set: ['radius'] }, - label: { set: ['radius'] } + x: { set: [{ name: 'angle' }] }, + y: { set: [{ name: 'radius' }] }, + label: { set: [{ name: 'radius' }] } }, coordSystem: 'polar' }, polarStackedColumn: { channels: { - x: { set: ['angle'] }, - y: { set: ['radius', 'stackedBy'] }, - color: { set: ['stackedBy'] } + x: { set: [{ name: 'angle' }] }, + y: { set: [{ name: 'radius' }, { name: 'stackedBy' }] }, + color: { set: [{ name: 'stackedBy' }] } }, coordSystem: 'polar' }, variableRadiusPie: { channels: { - x: { set: ['angle', 'by'] }, - y: { set: ['radius'] }, - color: { set: ['by'] }, - label: { set: ['radius'] } + x: { set: [{ name: 'angle' }, { name: 'by' }] }, + y: { set: [{ name: 'radius' }] }, + color: { set: [{ name: 'by' }] }, + label: { set: [{ name: 'radius' }] } }, coordSystem: 'polar' }, radialBar: { channels: { - x: { set: ['angle'] }, - y: { set: ['radius'], range: { min: '-50%' } }, - label: { set: ['angle'] } + x: { set: [{ name: 'angle' }] }, + y: { set: [{ name: 'radius' }], range: { min: '-50%' } }, + label: { set: [{ name: 'angle' }] } }, coordSystem: 'polar' }, radialStackedBar: { channels: { - x: { set: ['angle', 'stackedBy'] }, - y: { set: ['radius'], range: { min: '-50%' } }, - color: { set: ['stackedBy'] }, - label: { set: ['angle'] } + x: { set: [{ name: 'angle' }, { name: 'stackedBy' }] }, + y: { set: [{ name: 'radius' }], range: { min: '-50%' } }, + color: { set: [{ name: 'stackedBy' }] }, + label: { set: [{ name: 'angle' }] } }, coordSystem: 'polar' }, donut: { channels: { - x: { set: ['angle', 'stackedBy'] }, + x: { set: [{ name: 'angle' }, { name: 'stackedBy' }] }, y: { range: { min: '-200%', max: '100%' } }, - color: { set: ['stackedBy'] } + color: { set: [{ name: 'stackedBy' }] } }, coordSystem: 'polar' }, nestedDonut: { channels: { - x: { set: ['angle', 'stackedBy'] }, - y: { set: ['radius'], range: { min: '-50%' } }, - color: { set: ['stackedBy'] }, - label: { set: ['angle'] } + x: { set: [{ name: 'angle' }, { name: 'stackedBy' }] }, + y: { set: [{ name: 'radius' }], range: { min: '-50%' } }, + color: { set: [{ name: 'stackedBy' }] }, + label: { set: [{ name: 'angle' }] } }, coordSystem: 'polar', align: 'stretch' }, polarScatter: { channels: { - x: { set: ['angle'] }, - y: { set: ['radius'] }, - noop: { set: ['dividedBy'] } + x: { set: [{ name: 'angle' }] }, + y: { set: [{ name: 'radius' }] }, + noop: { set: [{ name: 'dividedBy' }] } }, coordSystem: 'polar', geometry: 'circle' }, polarLine: { channels: { - x: { set: ['angle'] }, - y: { set: ['radius'] }, - color: { set: ['dividedBy'] } + x: { set: [{ name: 'angle' }] }, + y: { set: [{ name: 'radius' }] }, + color: { set: [{ name: 'dividedBy' }] } }, coordSystem: 'polar', geometry: 'line' }, treemap: { channels: { - size: { set: ['size', 'color'] }, - color: { set: ['color'] }, - label: { set: ['color'] } + size: { set: [{ name: 'size' }, { name: 'color' }] }, + color: { set: [{ name: 'color' }] }, + label: { set: [{ name: 'color' }] } } }, stackedTreemap: { channels: { - size: { set: ['size', 'dividedBy'] }, - color: { set: ['color'] }, - label: { set: ['dividedBy'] }, - lightness: { set: ['size'] } + size: { set: [{ name: 'size' }, { name: 'dividedBy' }] }, + color: { set: [{ name: 'color' }] }, + label: { set: [{ name: 'dividedBy' }] }, + lightness: { set: [{ name: 'size' }] } } }, heatmap: { channels: { - x: { set: ['x'] }, - y: { set: ['y'] }, - lightness: { set: ['lightness'] } + x: { set: [{ name: 'x' }] }, + y: { set: [{ name: 'y' }] }, + lightness: { set: [{ name: 'lightness' }] } } }, bubble: { channels: { - size: { set: ['size'] }, - color: { set: ['color'] }, - label: { set: ['color'] } + size: { set: [{ name: 'size' }] }, + color: { set: [{ name: 'color' }] }, + label: { set: [{ name: 'color' }] } }, geometry: 'circle' }, stackedBubble: { channels: { - size: { set: ['size', 'stackedBy'] }, - color: { set: ['color'] } + size: { set: [{ name: 'size' }, { name: 'stackedBy' }] }, + color: { set: [{ name: 'color' }] } }, geometry: 'circle' } diff --git a/src/apps/weblib/ts-api/plugins/presets.ts b/src/apps/weblib/ts-api/plugins/presets.ts index 035eb3c69..1b3dce1be 100644 --- a/src/apps/weblib/ts-api/plugins/presets.ts +++ b/src/apps/weblib/ts-api/plugins/presets.ts @@ -78,7 +78,7 @@ export default class Presets { } else if (Array.isArray(channel.set)) { const newChannel = [] for (let i = 0; i < channel.set.length; i++) { - const key = channel.set[i]! as keyof Config.Chart + const key = channel.set[i]!.name as keyof Config.Chart const channelConfig = this._getChannelCopy(config[key] as Data.SeriesList) if (channelConfig !== null) { newChannel.push(channelConfig) diff --git a/src/apps/weblib/ts-api/plugins/shorthands.ts b/src/apps/weblib/ts-api/plugins/shorthands.ts index 1f4e04f91..350621f8d 100644 --- a/src/apps/weblib/ts-api/plugins/shorthands.ts +++ b/src/apps/weblib/ts-api/plugins/shorthands.ts @@ -9,7 +9,7 @@ import Vizzu from '../vizzu.js' import { Snapshot } from '../module/cchart.js' import { CAnimation } from '../module/canimctrl.js' -export type AnySeriesList = Data.SeriesList | Data.SeriesDescriptor | null +export type AnySeriesList = Data.SeriesList | Data.SeriesDescriptor | string | string[] | null export type LazyChannel = Config.Channel & { set?: AnySeriesList @@ -201,26 +201,78 @@ export class Shorthands implements Plugin { channel = [channel] } - if (channel === null || Array.isArray(channel)) { - channel = { set: channel } + if (this._isAnySeriesList(channel)) { + channel = { set: this._normalizeSeriesList(channel) } + } else if (channel.set !== undefined && this._isAnySeriesList(channel.set)) { + channel.set = this._normalizeSeriesList(channel.set) } - if (typeof channel?.attach === 'string') { - channel.attach = [channel.attach] + if (channel.attach !== undefined && this._isAnySeriesList(channel.attach)) { + const attach = this._normalizeSeriesList(channel.attach) + if (attach !== null) { + channel.attach = attach + } else { + delete channel.attach + } } - if (typeof channel?.detach === 'string') { - channel.detach = [channel.detach] + if (channel.detach !== undefined && this._isAnySeriesList(channel.detach)) { + const detach = this._normalizeSeriesList(channel.detach) + if (detach !== null) { + channel.detach = detach + } else { + delete channel.detach + } } - if (typeof channel?.set === 'string') { - channel.set = [channel.set] + return channel + } + + private _isAnySeriesList(value: unknown): value is AnySeriesList { + return ( + value === null || + typeof value === 'string' || + Array.isArray(value) || + (typeof value === 'object' && 'name' in value) + ) + } + + private _normalizeSeriesList(seriesList: AnySeriesList): Data.SeriesList | null { + if (seriesList === null) { + return null } + if (Array.isArray(seriesList)) { + if (seriesList.length === 0) return null + else + return seriesList.map((seriesDescriptor) => + this._normalizeSeriesDescriptor(seriesDescriptor) + ) + } + if (typeof seriesList === 'string') { + return [this._normalizeSeriesDescriptor(seriesList)] + } + if (typeof seriesList === 'object' && 'name' in seriesList) { + return [seriesList] + } + return null + } - if (Array.isArray(channel?.set) && channel?.set.length === 0) { - channel.set = null + private _normalizeSeriesDescriptor( + seriesDescriptor: Data.SeriesDescriptor | string + ): Data.SeriesDescriptor { + if (typeof seriesDescriptor === 'string') { + const pattern = /^(\w+)\((.*?)\)$/ + const match = seriesDescriptor.match(pattern) + if (match) { + return { + name: match[2]!, + aggregator: match[1]! as Data.AggregatorType + } + } else { + return { name: seriesDescriptor } + } } - return channel + return seriesDescriptor } private _normalizeOptions(options: AnyOptions | undefined): Anim.Options | undefined { diff --git a/src/apps/weblib/ts-api/utils.ts b/src/apps/weblib/ts-api/utils.ts index 6fee600aa..4d4a8c2b1 100644 --- a/src/apps/weblib/ts-api/utils.ts +++ b/src/apps/weblib/ts-api/utils.ts @@ -27,14 +27,24 @@ export function recursiveCopy(value: T, Ignore?: new (...args: never[]) => un } type Visitor = (path: string, value: unknown) => void +export type ShouldIterate = (value: unknown) => value is Record -export function iterateObject(obj: T, paramHandler: Visitor, path: string = ''): void { +export function isIterable(value: unknown): value is Record { + return typeof value === 'object' && value !== null +} + +export function iterateObject( + obj: T, + paramHandler: Visitor, + shouldIterate: ShouldIterate = isIterable, + path: string = '' +): void { if (obj && obj !== null && typeof obj === 'object') { Object.keys(obj).forEach((key) => { const newPath = path + (path.length === 0 ? '' : '.') + key const value = obj[key as keyof T] - if (value !== null && typeof value === 'object') { - iterateObject(value, paramHandler, newPath) + if (shouldIterate(value)) { + iterateObject(value, paramHandler, shouldIterate, newPath) } else { paramHandler(newPath, value) } diff --git a/src/apps/weblib/typeschema-api/data.yaml b/src/apps/weblib/typeschema-api/data.yaml index 43381e1f6..4ded1fe65 100644 --- a/src/apps/weblib/typeschema-api/data.yaml +++ b/src/apps/weblib/typeschema-api/data.yaml @@ -384,10 +384,12 @@ definitions: SeriesDescriptor: description: | - The name of a series either alone or combined with an aggregator function. - oneOf: - - { $ref: SeriesName } - - { type: string, mask: /:AggregatorType:\(:SeriesName:\)/ } + The name of a series with an aggregator function. + type: object + properties: + name: { $ref: SeriesName } + aggregator: { $ref: AggregatorType } + required: [name] SeriesList: description: Array or a single data series. diff --git a/test/e2e/test_cases/web_content/cookbook/style/highligh_markers.mjs b/test/e2e/test_cases/web_content/cookbook/style/highligh_markers.mjs index 7430f1ce7..6587714f0 100644 --- a/test/e2e/test_cases/web_content/cookbook/style/highligh_markers.mjs +++ b/test/e2e/test_cases/web_content/cookbook/style/highligh_markers.mjs @@ -12,7 +12,7 @@ class Highlighter { const colorchannels = this.chart.config.channels.color.set if (colorchannels.length === 0) this.colorchannel = null - else if (colorchannels.length === 1) this.colorchannel = colorchannels[0] + else if (colorchannels.length === 1) this.colorchannel = colorchannels[0].name else throw new Error('charts with multiple color series are not supported by highlighter') } diff --git a/tools/ci/type/gen-presets.cjs b/tools/ci/type/gen-presets.cjs index 42595de98..4b24b5cd4 100644 --- a/tools/ci/type/gen-presets.cjs +++ b/tools/ci/type/gen-presets.cjs @@ -12,9 +12,11 @@ function collectUniqueValues(obj) { for (const key in obj) { const value = obj[key].set ? obj[key].set : obj[key] if (typeof value === 'string') { + values.push({ name: value }) + } else if (value.name) { values.push(value) } else if (Array.isArray(value)) { - values = values.concat(value) + values = values.concat(value.map((v) => (v.name ? v : { name: v }))) } } return [...new Set(values)] @@ -31,7 +33,7 @@ function genPreset(presetName, preset) { } for (let i = 0; i < properties.length; i++) { - const propertyName = properties[i] + const propertyName = properties[i].name definition.properties[propertyName] = { description: `The ${propertyName} channel.`, oneOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }] @@ -117,14 +119,13 @@ async function writeSchema(schema, outputPath) { console.log('Writing to ' + outputPath) const warningText = ` -# This file is auto-generated by preset-typeschema-gen.js +# This file is auto-generated by gen-presets.cjs # Do not edit this file directly. # Instead, edit the presets in src/apps/weblib/ts-api/plugins/presetconfigs.js -# and run tools/preset-typeschema-gen to regenerate this file. +# and run \`npm run build:ts\` to regenerate this file. --- $import: Config: ./config - ` const content = warningText + YAML.stringify(schema, null, 2) From 2942145873d54556afd55fd38f2026bf0ffbe23b Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Wed, 14 Feb 2024 17:08:58 +0100 Subject: [PATCH 035/253] Tutorial updated: data series descriptor objects --- docs/tutorial/aggregating_data.md | 8 +++++++- docs/tutorial/axes_title_tooltip.md | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/aggregating_data.md b/docs/tutorial/aggregating_data.md index d44049cde..c19726325 100644 --- a/docs/tutorial/aggregating_data.md +++ b/docs/tutorial/aggregating_data.md @@ -39,7 +39,13 @@ chart.animate({ Next to the default logic of sum, there are a handful of other aggregation logics that are available in `Vizzu`: `min`, `max`, `mean`, `count` and -`distinct`. Let's go through them to see how they work. +`distinct`. Aggregators can be set for data series using: + +- data series descriptor objects: `{ name: 'Popularity', aggregator: 'min' }` +- or by encoding them into the name of the data series: `'min(Popularity)'` + +We will use the second method in the following examples. Let's go through them +to see how they work. Minimum value: the height of the bars show the minimum value in the `Popularity` measure in each of the `Genres`. diff --git a/docs/tutorial/axes_title_tooltip.md b/docs/tutorial/axes_title_tooltip.md index ef8a71b61..2ed0a209e 100644 --- a/docs/tutorial/axes_title_tooltip.md +++ b/docs/tutorial/axes_title_tooltip.md @@ -32,6 +32,31 @@ chart.animate({ }) ``` +We will reference the data series by names for clarity throughout the tutorial. +However, you can also use data series descriptor objects as well. That way you +can define aggregators to the series. For more information about aggregators, +see the [Aggregating data](./aggregating_data.md) chapter. The previous example +can be rewritten using data series descriptor objects as follows: + +```javascript +chart.animate({ + config: { + channels: { + y: { + set: [{ + name: 'Popularity' + }] + }, + x: { + set: [{ + name: 'Genres' + }] + } + } + } +}) +``` + In the next step, the chart is rearranged by putting both series on the y-axis using once again the set property, resulting in a single column chart. `Vizzu` automatically animates between the initial state and this one. From 8b36f7954ccbcffa76193d1d8802815c3fc12992 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 14 Feb 2024 18:40:17 +0100 Subject: [PATCH 036/253] Minor interface change --- src/dataframe/interface.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 163bc581f..e7badfee1 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -40,6 +40,11 @@ struct custom_aggregator using id_type = std::any; id_type (*create)(); double (*add)(id_type &, double); + + [[nodiscard]] std::string_view get_name() const + { + return std::visit(std::identity{}, name); + } }; class dataframe_interface : @@ -75,7 +80,7 @@ class dataframe_interface : std::string_view)>>; virtual void set_aggregate(series_identifier series, - any_aggregator_type aggregator) & = 0; + const any_aggregator_type &aggregator) & = 0; virtual void set_filter( std::function &&filter) & = 0; From 5a71f933d5678ee3bdc3e808d17d2a01093c2203 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 15 Feb 2024 17:09:00 +0100 Subject: [PATCH 037/253] Fix testcases --- src/chart/rendering/markerrenderer.cpp | 8 +- src/chart/rendering/painter/drawline.cpp | 8 - src/chart/rendering/painter/drawline.h | 2 - src/chart/rendering/painter/painter.cpp | 12 +- src/chart/rendering/painter/painter.h | 4 +- test/e2e/test_cases/test_cases.json | 476 +++++++++--------- .../cookbook/rendering/custom_linetype.mjs | 3 +- test/e2e/tests/config_tests.json | 12 +- 8 files changed, 253 insertions(+), 272 deletions(-) diff --git a/src/chart/rendering/markerrenderer.cpp b/src/chart/rendering/markerrenderer.cpp index bbdc68318..6dc82624f 100644 --- a/src/chart/rendering/markerrenderer.cpp +++ b/src/chart/rendering/markerrenderer.cpp @@ -233,6 +233,9 @@ void MarkerRenderer::draw(Gfx::ICanvas &canvas, auto p0 = coordSys.convert(line.begin); auto p1 = coordSys.convert(line.end); + canvas.setBrushColor( + colors.second + * static_cast(abstractMarker.connected)); canvas.setLineColor( colors.second * static_cast(abstractMarker.connected)); @@ -242,10 +245,7 @@ void MarkerRenderer::draw(Gfx::ICanvas &canvas, {Geom::Line(p0, p1), false}))) { painter.drawStraightLine(line, abstractMarker.lineWidth, - static_cast(abstractMarker.linear), - colors.second, - colors.second - * static_cast(abstractMarker.connected)); + static_cast(abstractMarker.linear)); renderedChart.emplace( Draw::Marker{abstractMarker.marker.enabled != false, diff --git a/src/chart/rendering/painter/drawline.cpp b/src/chart/rendering/painter/drawline.cpp index 37bad9d54..3fbc93b93 100644 --- a/src/chart/rendering/painter/drawline.cpp +++ b/src/chart/rendering/painter/drawline.cpp @@ -21,8 +21,6 @@ DrawLine::DrawLine(const Geom::Line &line, DrawLine::DrawLine(const Geom::Line &line, std::array widths, [[maybe_unused]] double straightFactor, - const Gfx::Color &endColor, - const Gfx::Color &lineColor, CoordinateSystem &coordSys, Gfx::ICanvas &canvas) { @@ -36,17 +34,11 @@ DrawLine::DrawLine(const Geom::Line &line, Geom::ConvexQuad::Isosceles(pBeg, pEnd, wBeg * 2, wEnd * 2) .points; - canvas.setBrushColor(endColor); - canvas.setLineColor(endColor); - canvas.circle(Geom::Circle(pBeg, wBeg)); if (pBeg != pEnd) { canvas.circle(Geom::Circle(pEnd, wEnd)); - canvas.setBrushColor(lineColor); - canvas.setLineColor(lineColor); - canvas.beginPolygon(); canvas.addPoint(p0); canvas.addPoint(p1); diff --git a/src/chart/rendering/painter/drawline.h b/src/chart/rendering/painter/drawline.h index 5bfb554f1..fb193c5b4 100644 --- a/src/chart/rendering/painter/drawline.h +++ b/src/chart/rendering/painter/drawline.h @@ -23,8 +23,6 @@ class DrawLine DrawLine(const Geom::Line &line, std::array widths, double straightFactor, - const Gfx::Color &endColor, - const Gfx::Color &lineColor, CoordinateSystem &coordSys, Gfx::ICanvas &canvas); diff --git a/src/chart/rendering/painter/painter.cpp b/src/chart/rendering/painter/painter.cpp index 2fe574f9e..41d52d127 100644 --- a/src/chart/rendering/painter/painter.cpp +++ b/src/chart/rendering/painter/painter.cpp @@ -13,17 +13,9 @@ void Painter::drawLine(const Geom::Line &line) void Painter::drawStraightLine(const Geom::Line &line, std::array widths, - double straightFactor, - const Gfx::Color &endColor, - const Gfx::Color &lineColor) + double straightFactor) { - Draw::DrawLine(line, - widths, - straightFactor, - endColor, - lineColor, - system, - getCanvas()); + Draw::DrawLine(line, widths, straightFactor, system, getCanvas()); } void Painter::drawPolygon(const std::array &ps, diff --git a/src/chart/rendering/painter/painter.h b/src/chart/rendering/painter/painter.h index ac2a9b049..42a5c0825 100644 --- a/src/chart/rendering/painter/painter.h +++ b/src/chart/rendering/painter/painter.h @@ -27,9 +27,7 @@ class Painter void drawStraightLine(const Geom::Line &line, std::array widths, - double straightFactor, - const Gfx::Color &endColor, - const Gfx::Color &lineColor); + double straightFactor); void setPolygonToCircleFactor(double factor) { diff --git a/test/e2e/test_cases/test_cases.json b/test/e2e/test_cases/test_cases.json index c41944b00..fae1fc769 100644 --- a/test/e2e/test_cases/test_cases.json +++ b/test/e2e/test_cases/test_cases.json @@ -110,7 +110,7 @@ "refs": ["927adbc"] }, "basic_animations/markers_morph/marker_trans_neg_1dis_1con": { - "refs": ["b51fba3"] + "refs": ["00f7b3e"] }, "basic_animations/someOtherTests/merge_split_area_stream_2dis_1con": { "refs": ["940d210"] @@ -122,10 +122,10 @@ "refs": ["1b27db5"] }, "basic_animations/someOtherTests/total_time_bar_line": { - "refs": ["5fe98b0"] + "refs": ["158f19c"] }, "basic_animations/someOtherTests/total_time_column_line": { - "refs": ["3037126"] + "refs": ["a28a3b8"] }, "chart_precision/area_negative_x": { "refs": ["7b8bffc"] @@ -155,16 +155,16 @@ "refs": ["9f4f365"] }, "chart_precision/line_negative_x": { - "refs": ["19a1d4d"] + "refs": ["8849792"] }, "chart_precision/line_negative_y": { - "refs": ["8f6c794"] + "refs": ["5d22f9a"] }, "chart_precision/line_x": { - "refs": ["78fa5d0"] + "refs": ["6253a29"] }, "chart_precision/line_y": { - "refs": ["ab1f390"] + "refs": ["98d9db3"] }, "chart_precision/rectangle_negative_x": { "refs": ["be1c257"] @@ -200,31 +200,31 @@ "refs": ["997f1a7"] }, "lay_out/full_line_negative_2dis_1con": { - "refs": ["7111815"] + "refs": ["2606799"] }, "lay_out/legend_plot_coxcomb_rectangle_2dis_1con": { "refs": ["01b0b8e"] }, "lay_out/legend_plot_line_negative_2dis_1con": { - "refs": ["49cdf02"] + "refs": ["ac16799"] }, "lay_out/plot_coxcomb_rectangle_2dis_1con": { "refs": ["797b1fa"] }, "lay_out/plot_line_negative_2dis_1con": { - "refs": ["735c487"] + "refs": ["978acf9"] }, "lay_out/title_plot_coxcomb_rectangle_2dis_1con": { "refs": ["05811b7"] }, "lay_out/title_plot_line_negative_2dis_1con": { - "refs": ["138491d"] + "refs": ["df703f1"] }, "operations/all_operations": { "refs": ["4263129"] }, "operations/all_operations_sizeing": { - "refs": ["95d6654"] + "refs": ["cc900b8"] }, "operations/drilldown_aggregate_tutorial_data/area_drilldown_aggregate": { "refs": ["8f3c3b6"] @@ -236,10 +236,10 @@ "refs": ["d5dacd9"] }, "operations/drilldown_aggregate_tutorial_data/line_drilldown_aggregate_x": { - "refs": ["4ff6dbf"] + "refs": ["38e0f94"] }, "operations/drilldown_aggregate_tutorial_data/line_drilldown_aggregate_y": { - "refs": ["494cd94"] + "refs": ["5e49f4e"] }, "operations/drilldown_aggregate_tutorial_data/rectangle_drilldown_aggregate_X": { "refs": ["3265d36"] @@ -266,10 +266,10 @@ "refs": ["c48435b"] }, "operations/filter_tutorial_data/line_filter_x": { - "refs": ["a4449ee"] + "refs": ["2c73ba2"] }, "operations/filter_tutorial_data/line_filter_y": { - "refs": ["335d151"] + "refs": ["f78e156"] }, "operations/filter_tutorial_data/rectangle_filter_treemap": { "refs": ["7f5a115"] @@ -281,13 +281,13 @@ "refs": ["574aec3"] }, "operations/group_stack_tutorial_data/area_group_stack": { - "refs": ["f09dec1"] + "refs": ["fbd3063"] }, "operations/group_stack_tutorial_data/bubble_group_stack": { "refs": ["36813ef"] }, "operations/group_stack_tutorial_data/line_group_stack": { - "refs": ["f0a40ec"] + "refs": ["a8ed21b"] }, "operations/group_stack_tutorial_data/treemap_group_stack": { "refs": ["fc6a0ae"] @@ -299,7 +299,7 @@ "refs": ["b61766f"] }, "operations/orientation_tutorial_data/line_orientation": { - "refs": ["e90c2a0"] + "refs": ["50dcc9e"] }, "operations/orientation_tutorial_data/rectangle_orientation": { "refs": ["5af7144"] @@ -311,7 +311,7 @@ "refs": ["bb1ddf8"] }, "operations/split_merge_tutorial_data/line_split_merge": { - "refs": ["da6acc3"] + "refs": ["a2de32c"] }, "operations/split_merge_tutorial_data/rectangle_split_merge": { "refs": ["ab5615c"] @@ -353,10 +353,10 @@ "refs": ["9d0d00a"] }, "static_chart_types/cartesian_coo_sys/line_negative_1dis_1con": { - "refs": ["2ceb802"] + "refs": ["a62e14f"] }, "static_chart_types/cartesian_coo_sys/line_negative_2dis_1con": { - "refs": ["3966d03"] + "refs": ["bbd0067"] }, "static_chart_types/cartesian_coo_sys/marimekko_rectangle_1dis_2con": { "refs": ["1108d45"] @@ -386,7 +386,7 @@ "refs": ["ee1c839"] }, "static_chart_types/polar_coo_sys/NO_spiderweb_line_2dis_1con": { - "refs": ["72bf791"] + "refs": ["0ba5591"] }, "static_chart_types/polar_coo_sys/coxcomb_rectangle_1dis_1con": { "refs": ["19bd382"] @@ -410,7 +410,7 @@ "refs": ["d7aeeb1"] }, "static_chart_types/polar_coo_sys/spiderweb_line_1dis_1con": { - "refs": ["bb63562"] + "refs": ["b097feb"] }, "static_chart_types/polar_coo_sys/sunburst_rectangle_2dis_1con": { "refs": ["c249f22"] @@ -452,25 +452,25 @@ "refs": ["e0a2880"] }, "web_content/analytical_operations/change_dimension/line": { - "refs": ["fe5c424"] + "refs": ["4e92dc4"] }, "web_content/analytical_operations/change_dimension/line_polar": { - "refs": ["9003b50"] + "refs": ["a941b08"] }, "web_content/analytical_operations/compare/area_100percent_stacked": { - "refs": ["ae1efbc"] + "refs": ["954deeb"] }, "web_content/analytical_operations/compare/area_polar_split": { - "refs": ["3ed985a"] + "refs": ["aabca1c"] }, "web_content/analytical_operations/compare/area_polar_stacked": { - "refs": ["b697914"] + "refs": ["9c43219"] }, "web_content/analytical_operations/compare/area_split_stacked": { - "refs": ["f134d84"] + "refs": ["dff64e3"] }, "web_content/analytical_operations/compare/area_stacked": { - "refs": ["3264c93"] + "refs": ["19c2d3d"] }, "web_content/analytical_operations/compare/column_100percent_stacked": { "refs": ["b887eaa"] @@ -491,7 +491,7 @@ "refs": ["41b1a4e"] }, "web_content/analytical_operations/compare/column_stacked_2": { - "refs": ["812c194"] + "refs": ["028972c"] }, "web_content/analytical_operations/compare/coxcomb_1": { "refs": ["68b44ab"] @@ -500,10 +500,10 @@ "refs": ["7c30c03"] }, "web_content/analytical_operations/compare/line": { - "refs": ["8198112"] + "refs": ["b62245e"] }, "web_content/analytical_operations/compare/line_polar": { - "refs": ["03a5cfe"] + "refs": ["4d04d20"] }, "web_content/analytical_operations/compare/stream_stacked": { "refs": ["13a4f7f"] @@ -590,16 +590,16 @@ "refs": ["10db8a3"] }, "web_content/analytical_operations/drilldown/line_1": { - "refs": ["5d0b4ca"] + "refs": ["6b374fa"] }, "web_content/analytical_operations/drilldown/line_2": { - "refs": ["4bb8e34"] + "refs": ["3c93131"] }, "web_content/analytical_operations/drilldown/line_polar_1": { - "refs": ["8d3ec8e"] + "refs": ["16f332b"] }, "web_content/analytical_operations/drilldown/line_polar_2": { - "refs": ["26d8f0b"] + "refs": ["c10ab36"] }, "web_content/analytical_operations/drilldown/radial": { "refs": ["bd556a5"] @@ -614,10 +614,10 @@ "refs": ["9647e35"] }, "web_content/analytical_operations/filter/line": { - "refs": ["4b47ba3"] + "refs": ["7ebe3f3"] }, "web_content/analytical_operations/filter/line_polar": { - "refs": ["669fa0b"] + "refs": ["6c85e42"] }, "web_content/analytical_operations/filter/stream_1": { "refs": ["9d5e039"] @@ -674,7 +674,7 @@ "refs": ["3079652"] }, "web_content/analytical_operations/stretch_to_proportion/line": { - "refs": ["3ced485"] + "refs": ["b143e64"] }, "web_content/analytical_operations/sum/area_100percent_stacked": { "refs": ["4fe46e1"] @@ -752,16 +752,16 @@ "refs": ["fc87038"] }, "web_content/analytical_operations/sum/line_1": { - "refs": ["685b0bd"] + "refs": ["026ad11"] }, "web_content/analytical_operations/sum/line_2": { - "refs": ["eaa8a78"] + "refs": ["f3d6b85"] }, "web_content/analytical_operations/sum/line_polar_1": { - "refs": ["f4e3003"] + "refs": ["09a94e4"] }, "web_content/analytical_operations/sum/line_polar_2": { - "refs": ["92631b9"] + "refs": ["9189117"] }, "web_content/analytical_operations/sum/scatterplot_polar": { "refs": ["41b15a6"] @@ -794,10 +794,10 @@ "refs": ["9bf8509"] }, "web_content_removed/animated/drill_aggreg_improve_line": { - "refs": ["54718c2"] + "refs": ["aafd462"] }, "web_content_removed/animated/drilldown_aggregate_line": { - "refs": ["47693ee"] + "refs": ["cbe3e91"] }, "web_content_removed/animated/merge_split_area_stream_3dis_1con": { "refs": ["a691f16"] @@ -830,7 +830,7 @@ "refs": ["ae768b7"] }, "web_content_removed/animated/stack_group_area_line": { - "refs": ["a9baae0"] + "refs": ["1e70ec5"] }, "web_content_removed/animated/stack_group_circle": { "refs": ["0ef1d45"] @@ -854,10 +854,10 @@ "refs": ["f415237"] }, "web_content_removed/animated/zoom_line": { - "refs": ["7029760"] + "refs": ["e612ef8"] }, "web_content_removed/animated/zoom_line_polar": { - "refs": ["e104842"] + "refs": ["d4c57f3"] }, "web_content/infinite": { "refs": ["dba7ea0"] @@ -935,10 +935,10 @@ "refs": ["118b2d9"] }, "web_content/presets/chart/line": { - "refs": ["831b02b"] + "refs": ["cf6f357"] }, "web_content/presets/chart/line_vertical": { - "refs": ["fd0d49c"] + "refs": ["68607a5"] }, "web_content/presets/chart/pie": { "refs": ["df9fb71"] @@ -968,7 +968,7 @@ "refs": ["33dcb3c"] }, "web_content/presets/chart/line_polar": { - "refs": ["612c32a"] + "refs": ["17f77c1"] }, "web_content/presets/treemap": { "refs": ["ee86b8d"] @@ -1013,10 +1013,10 @@ "refs": ["6a15362"] }, "web_content/static/chart/line_single": { - "refs": ["76160aa"] + "refs": ["e373a3b"] }, "web_content/static/chart/line": { - "refs": ["f191779"] + "refs": ["3d8acde"] }, "web_content/static/chart/marimekko": { "refs": ["c4ca935"] @@ -1043,7 +1043,7 @@ "refs": ["cbe8a00"] }, "web_content/static/chart/line_polar": { - "refs": ["1446255"] + "refs": ["428e8c7"] }, "web_content/static/chart/coxcomb": { "refs": ["7f7dcf2"] @@ -1064,7 +1064,7 @@ "refs": ["ab642f5"] }, "web_content/static/chart/line_single_polar": { - "refs": ["e5e646f"] + "refs": ["88d91d4"] }, "web_content/static/chart/bubble": { "refs": ["8b1976f"] @@ -1088,7 +1088,7 @@ "refs": ["336378d"] }, "ww_animTiming/descartes-polar/04_d-p_l-r-l": { - "refs": ["25e86bc"] + "refs": ["a03998b"] }, "ww_animTiming/descartes-polar/05_d-p_r-c-r": { "refs": ["b65d696"] @@ -1100,7 +1100,7 @@ "refs": ["2003df2"] }, "ww_animTiming/descartes-polar/08_d-p_l-c-l": { - "refs": ["d067431"] + "refs": ["67f7b31"] }, "ww_animTiming/descartes-polar/09_d-p_r-a-r": { "refs": ["9027d67"] @@ -1112,19 +1112,19 @@ "refs": ["324bf4b"] }, "ww_animTiming/descartes-polar/12_d-p_l-a-l": { - "refs": ["21ad937"] + "refs": ["4d02656"] }, "ww_animTiming/descartes-polar/13_d-p_r-l-r": { - "refs": ["a491f75"] + "refs": ["534d885"] }, "ww_animTiming/descartes-polar/14_d-p_c-l-c": { - "refs": ["03bd776"] + "refs": ["d382dc2"] }, "ww_animTiming/descartes-polar/15_d-p_a-l-a": { - "refs": ["e0bfdbc"] + "refs": ["80f6d09"] }, "ww_animTiming/descartes-polar/16_d-p_l-l-l": { - "refs": ["6cae5be"] + "refs": ["a6a6ffd"] }, "ww_animTiming/descartes-polar_orient/01_d-p_o_r-r-r": { "refs": ["a01ddc2"] @@ -1136,7 +1136,7 @@ "refs": ["cb40a47"] }, "ww_animTiming/descartes-polar_orient/04_d-p_o_l-r-l": { - "refs": ["b0b1acf"] + "refs": ["d01b016"] }, "ww_animTiming/descartes-polar_orient/05_d-p_o_r-c-r": { "refs": ["bf1adce"] @@ -1148,7 +1148,7 @@ "refs": ["234adf8"] }, "ww_animTiming/descartes-polar_orient/08_d-p_o_l-c-l": { - "refs": ["19fe21a"] + "refs": ["8d4083d"] }, "ww_animTiming/descartes-polar_orient/09_d-p_o_r-a-r": { "refs": ["490574c"] @@ -1160,19 +1160,19 @@ "refs": ["5a28ea8"] }, "ww_animTiming/descartes-polar_orient/12_d-p_o_l-a-l": { - "refs": ["3692ff5"] + "refs": ["e28fe4f"] }, "ww_animTiming/descartes-polar_orient/13_d-p_o_r-l-r": { - "refs": ["e81aa3f"] + "refs": ["c12b9e1"] }, "ww_animTiming/descartes-polar_orient/14_d-p_o_c-l-c": { - "refs": ["8be0c6b"] + "refs": ["9d42787"] }, "ww_animTiming/descartes-polar_orient/15_d-p_o_a-l-a": { - "refs": ["f5e6ec2"] + "refs": ["0973e94"] }, "ww_animTiming/descartes-polar_orient/16_d-p_o_l-l-l": { - "refs": ["4ddf6f4"] + "refs": ["7a9bd54"] }, "ww_animTiming/descartes/02_d-d_c-r-c": { "refs": ["25c0583"] @@ -1181,16 +1181,16 @@ "refs": ["880ecc2"] }, "ww_animTiming/descartes/04_d-d_l-r-l": { - "refs": ["e728acc"] + "refs": ["b2be1f6"] }, "ww_animTiming/descartes/07_d-d_a-c-a": { "refs": ["34d4dba"] }, "ww_animTiming/descartes/08_d-d_l-c-l": { - "refs": ["a735c11"] + "refs": ["5329818"] }, "ww_animTiming/descartes/12_d-d_l-a-l": { - "refs": ["fc111f1"] + "refs": ["8d81e6f"] }, "ww_animTiming/descartes/easing_test": { "refs": ["5695b12"] @@ -1208,10 +1208,10 @@ "refs": ["29a5549"] }, "ww_animTiming/descartes_orientation/04_d-d_o_l-r-l": { - "refs": ["f0011a3"] + "refs": ["fd3f36a"] }, "ww_animTiming/descartes_orientation/04_d-d_o_l-r-l_stacked": { - "refs": ["0cd3931"] + "refs": ["0463407"] }, "ww_animTiming/descartes_orientation/05_d-d_o_r-c-r": { "refs": ["e54cc3c"] @@ -1223,7 +1223,7 @@ "refs": ["dfce038"] }, "ww_animTiming/descartes_orientation/08_d-d_o_l-c-l": { - "refs": ["2a406ab"] + "refs": ["34f8fd2"] }, "ww_animTiming/descartes_orientation/09_d-d_o_r-a-r": { "refs": ["1b7d7ea"] @@ -1235,19 +1235,19 @@ "refs": ["2791f1a"] }, "ww_animTiming/descartes_orientation/12_d-d_o_l-a-l": { - "refs": ["5d82e8b"] + "refs": ["f7b45db"] }, "ww_animTiming/descartes_orientation/13_d-d_o_r-l-r": { - "refs": ["e5faf5b"] + "refs": ["77a82f1"] }, "ww_animTiming/descartes_orientation/14_d-d_o_c-l-c": { - "refs": ["6ceebca"] + "refs": ["8fe7647"] }, "ww_animTiming/descartes_orientation/15_d-d_o_a-l-a": { - "refs": ["e7fe673"] + "refs": ["faff114"] }, "ww_animTiming/descartes_orientation/16_d-d_o_l-l-l": { - "refs": ["85c78fe"] + "refs": ["71dd969"] }, "ww_animTiming/polar/02_p-p_c-r-c": { "refs": ["ecf9842"] @@ -1256,16 +1256,16 @@ "refs": ["e422d17"] }, "ww_animTiming/polar/04_p-p_l-r-l": { - "refs": ["d88b358"] + "refs": ["dcee0f4"] }, "ww_animTiming/polar/07_p-p_a-c-a": { "refs": ["e72a90b"] }, "ww_animTiming/polar/08_p-p_l-c-l": { - "refs": ["0a11f41"] + "refs": ["fa66a2e"] }, "ww_animTiming/polar/12_p-p_l-a-l": { - "refs": ["6c796d3"] + "refs": ["a7b9f1a"] }, "ww_animTiming/polar_orientation/01_p-p_o_r-r-r": { "refs": ["bdd4d8e"] @@ -1277,7 +1277,7 @@ "refs": ["6bff356"] }, "ww_animTiming/polar_orientation/04_p-p_o_l-r-l": { - "refs": ["57bc900"] + "refs": ["cb78537"] }, "ww_animTiming/polar_orientation/05_p-p_o_r-c-r": { "refs": ["1046707"] @@ -1289,7 +1289,7 @@ "refs": ["e6265f9"] }, "ww_animTiming/polar_orientation/08_p-p_o_l-c-l": { - "refs": ["3f828ce"] + "refs": ["8b1c6a0"] }, "ww_animTiming/polar_orientation/09_p-p_o_r-a-r": { "refs": ["45d8a28"] @@ -1301,19 +1301,19 @@ "refs": ["22cecc9"] }, "ww_animTiming/polar_orientation/12_p-p_o_l-a-l": { - "refs": ["d96ce7e"] + "refs": ["fcdec7f"] }, "ww_animTiming/polar_orientation/13_p-p_o_r-l-r": { - "refs": ["0f41bc8"] + "refs": ["d639880"] }, "ww_animTiming/polar_orientation/14_p-p_o_c-l-c": { - "refs": ["4d9ac8d"] + "refs": ["973ce42"] }, "ww_animTiming/polar_orientation/15_p-p_o_a-l-a": { - "refs": ["a7d056d"] + "refs": ["7dcf905"] }, "ww_animTiming/polar_orientation/16_p-p_o_l-l-l": { - "refs": ["c2b7355"] + "refs": ["079fcda"] }, "ww_animTiming/without-descartes/01_w-d_r-r-r": { "refs": ["4c13f24"] @@ -1334,10 +1334,10 @@ "refs": ["0fb70f5"] }, "ww_animTiming/without-descartes/13_w-d_r-l-r": { - "refs": ["cb5a3cc"] + "refs": ["7db5639"] }, "ww_animTiming/without-descartes/14_w-d_c-l-c": { - "refs": ["5d9c288"] + "refs": ["cafaa31"] }, "ww_animTiming/without-descartes_orientation/01_w-d_o_r-r-r": { "refs": ["893cefb"] @@ -1358,10 +1358,10 @@ "refs": ["d8d6d56"] }, "ww_animTiming/without-descartes_orientation/13_w-d_o_r-l-r": { - "refs": ["361b510"] + "refs": ["37f7327"] }, "ww_animTiming/without-descartes_orientation/14_w-d_o_c-l-c": { - "refs": ["b4fdadb"] + "refs": ["bffa786"] }, "ww_animTiming/without-polar/01_w-p_r-r-r": { "refs": ["92cb665"] @@ -1382,10 +1382,10 @@ "refs": ["501a1fd"] }, "ww_animTiming/without-polar/13_w-p_r-l-r": { - "refs": ["9b50545"] + "refs": ["e4cfc98"] }, "ww_animTiming/without-polar/14_w-p_c-l-c": { - "refs": ["d3c1fa9"] + "refs": ["8a16347"] }, "ww_animTiming/without-polar_orientation/01_w-p_o_r-r-r": { "refs": ["8e04790"] @@ -1406,10 +1406,10 @@ "refs": ["1e5ada2"] }, "ww_animTiming/without-polar_orientation/13_w-p_o_r-l-r": { - "refs": ["2cbfaeb"] + "refs": ["76ffa58"] }, "ww_animTiming/without-polar_orientation/14_w-p_o_c-l-c": { - "refs": ["84e352f"] + "refs": ["6909991"] }, "ww_animTiming/without/02_w-w_c-r-c": { "refs": ["3846714"] @@ -1421,7 +1421,7 @@ "refs": ["bb35082"] }, "ww_animTiming_TESTS/descartes-polar/04_d-p_l-r-l": { - "refs": ["b4b56cf"] + "refs": ["dc5aada"] }, "ww_animTiming_TESTS/descartes-polar/05_d-p_r-c-r": { "refs": ["bb0d6df"] @@ -1433,7 +1433,7 @@ "refs": ["461fdec"] }, "ww_animTiming_TESTS/descartes-polar/08_d-p_l-c-l": { - "refs": ["fdfdfaa"] + "refs": ["13ade44"] }, "ww_animTiming_TESTS/descartes-polar/09_d-p_r-a-r": { "refs": ["21d4f78"] @@ -1445,19 +1445,19 @@ "refs": ["f10b2c1"] }, "ww_animTiming_TESTS/descartes-polar/12_d-p_l-a-l": { - "refs": ["4d72bc6"] + "refs": ["251848e"] }, "ww_animTiming_TESTS/descartes-polar/13_d-p_r-l-r": { - "refs": ["38f7786"] + "refs": ["f925ef5"] }, "ww_animTiming_TESTS/descartes-polar/14_d-p_c-l-c": { - "refs": ["fbcb746"] + "refs": ["9b727cc"] }, "ww_animTiming_TESTS/descartes-polar/15_d-p_a-l-a": { - "refs": ["71bfcbb"] + "refs": ["7053a1e"] }, "ww_animTiming_TESTS/descartes-polar/16_d-p_l-l-l": { - "refs": ["91d69c9"] + "refs": ["2eab241"] }, "ww_animTiming_TESTS/descartes-polar_orient/01_d-p_o_r-r-r": { "refs": ["87c33d6"] @@ -1469,7 +1469,7 @@ "refs": ["76d8014"] }, "ww_animTiming_TESTS/descartes-polar_orient/04_d-p_o_l-r-l": { - "refs": ["d4ffa05"] + "refs": ["69cbcbd"] }, "ww_animTiming_TESTS/descartes-polar_orient/05_d-p_o_r-c-r": { "refs": ["43f00a0"] @@ -1481,7 +1481,7 @@ "refs": ["2f088e8"] }, "ww_animTiming_TESTS/descartes-polar_orient/08_d-p_o_l-c-l": { - "refs": ["167c694"] + "refs": ["fe6cdbd"] }, "ww_animTiming_TESTS/descartes-polar_orient/09_d-p_o_r-a-r": { "refs": ["8c26a23"] @@ -1493,19 +1493,19 @@ "refs": ["3959367"] }, "ww_animTiming_TESTS/descartes-polar_orient/12_d-p_o_l-a-l": { - "refs": ["f1fb6a9"] + "refs": ["69cbfbc"] }, "ww_animTiming_TESTS/descartes-polar_orient/13_d-p_o_r-l-r": { - "refs": ["b107554"] + "refs": ["3716ab5"] }, "ww_animTiming_TESTS/descartes-polar_orient/14_d-p_o_c-l-c": { - "refs": ["60499cc"] + "refs": ["6bff29e"] }, "ww_animTiming_TESTS/descartes-polar_orient/15_d-p_o_a-l-a": { - "refs": ["ff1f803"] + "refs": ["fce084c"] }, "ww_animTiming_TESTS/descartes-polar_orient/16_d-p_o_l-l-l": { - "refs": ["66c898c"] + "refs": ["a75a0a6"] }, "ww_animTiming_TESTS/descartes/02_d-d_c-r-c": { "refs": ["aa9e9d6"] @@ -1514,16 +1514,16 @@ "refs": ["2dd7f8b"] }, "ww_animTiming_TESTS/descartes/04_d-d_l-r-l": { - "refs": ["548adc8"] + "refs": ["55375fe"] }, "ww_animTiming_TESTS/descartes/07_d-d_a-c-a": { "refs": ["58c3e7b"] }, "ww_animTiming_TESTS/descartes/08_d-d_l-c-l": { - "refs": ["4734d8b"] + "refs": ["6782c8b"] }, "ww_animTiming_TESTS/descartes/12_d-d_l-a-l": { - "refs": ["5647e86"] + "refs": ["d06404c"] }, "ww_animTiming_TESTS/descartes/easing_test": { "refs": ["e4aae39"] @@ -1541,10 +1541,10 @@ "refs": ["285b038"] }, "ww_animTiming_TESTS/descartes_orientation/04_d-d_o_l-r-l": { - "refs": ["3df3405"] + "refs": ["75fa324"] }, "ww_animTiming_TESTS/descartes_orientation/04_d-d_o_l-r-l_stacked": { - "refs": ["db3345c"] + "refs": ["27a3d5e"] }, "ww_animTiming_TESTS/descartes_orientation/05_d-d_o_r-c-r": { "refs": ["aabbace"] @@ -1556,7 +1556,7 @@ "refs": ["24fc2d2"] }, "ww_animTiming_TESTS/descartes_orientation/08_d-d_o_l-c-l": { - "refs": ["b9efe87"] + "refs": ["487ae15"] }, "ww_animTiming_TESTS/descartes_orientation/09_d-d_o_r-a-r": { "refs": ["bbfa964"] @@ -1568,19 +1568,19 @@ "refs": ["ceae4ce"] }, "ww_animTiming_TESTS/descartes_orientation/12_d-d_o_l-a-l": { - "refs": ["0daf2a5"] + "refs": ["6770644"] }, "ww_animTiming_TESTS/descartes_orientation/13_d-d_o_r-l-r": { - "refs": ["bfd2420"] + "refs": ["4628a5b"] }, "ww_animTiming_TESTS/descartes_orientation/14_d-d_o_c-l-c": { - "refs": ["4443f32"] + "refs": ["b8ba573"] }, "ww_animTiming_TESTS/descartes_orientation/15_d-d_o_a-l-a": { - "refs": ["bac188f"] + "refs": ["7d366fc"] }, "ww_animTiming_TESTS/descartes_orientation/16_d-d_o_l-l-l": { - "refs": ["1ae5f2a"] + "refs": ["011b8f7"] }, "ww_animTiming_TESTS/polar/02_p-p_c-r-c": { "refs": ["f515353"] @@ -1589,16 +1589,16 @@ "refs": ["549a7e7"] }, "ww_animTiming_TESTS/polar/04_p-p_l-r-l": { - "refs": ["624428b"] + "refs": ["d6f9772"] }, "ww_animTiming_TESTS/polar/07_p-p_a-c-a": { "refs": ["f61c579"] }, "ww_animTiming_TESTS/polar/08_p-p_l-c-l": { - "refs": ["b67410e"] + "refs": ["6ef8f72"] }, "ww_animTiming_TESTS/polar/12_p-p_l-a-l": { - "refs": ["888e89f"] + "refs": ["ebe68b9"] }, "ww_animTiming_TESTS/polar_orientation/01_p-p_o_r-r-r": { "refs": ["00e1598"] @@ -1610,7 +1610,7 @@ "refs": ["f0c7467"] }, "ww_animTiming_TESTS/polar_orientation/04_p-p_o_l-r-l": { - "refs": ["9ed9325"] + "refs": ["f1962c6"] }, "ww_animTiming_TESTS/polar_orientation/05_p-p_o_r-c-r": { "refs": ["1f73c7e"] @@ -1622,7 +1622,7 @@ "refs": ["6bbffdd"] }, "ww_animTiming_TESTS/polar_orientation/08_p-p_o_l-c-l": { - "refs": ["33697f1"] + "refs": ["a207e59"] }, "ww_animTiming_TESTS/polar_orientation/09_p-p_o_r-a-r": { "refs": ["66a2833"] @@ -1634,19 +1634,19 @@ "refs": ["bc27f85"] }, "ww_animTiming_TESTS/polar_orientation/12_p-p_o_l-a-l": { - "refs": ["0e20e13"] + "refs": ["1840d3e"] }, "ww_animTiming_TESTS/polar_orientation/13_p-p_o_r-l-r": { - "refs": ["c57dd9d"] + "refs": ["2065c6d"] }, "ww_animTiming_TESTS/polar_orientation/14_p-p_o_c-l-c": { - "refs": ["8010f8a"] + "refs": ["4cc0c95"] }, "ww_animTiming_TESTS/polar_orientation/15_p-p_o_a-l-a": { - "refs": ["b3f37f8"] + "refs": ["cb0b11a"] }, "ww_animTiming_TESTS/polar_orientation/16_p-p_o_l-l-l": { - "refs": ["a25054f"] + "refs": ["edc11c7"] }, "ww_animTiming_TESTS/without-descartes/01_w-d_r-r-r": { "refs": ["fe3d177"] @@ -1667,10 +1667,10 @@ "refs": ["00d7be7"] }, "ww_animTiming_TESTS/without-descartes/13_w-d_r-l-r": { - "refs": ["fe7b3f8"] + "refs": ["e61bb5e"] }, "ww_animTiming_TESTS/without-descartes/14_w-d_c-l-c": { - "refs": ["1fd8801"] + "refs": ["073310c"] }, "ww_animTiming_TESTS/without-descartes_orientation/01_w-d_o_r-r-r": { "refs": ["a452290"] @@ -1691,10 +1691,10 @@ "refs": ["0bc5cfe"] }, "ww_animTiming_TESTS/without-descartes_orientation/13_w-d_o_r-l-r": { - "refs": ["a47ea50"] + "refs": ["2060c20"] }, "ww_animTiming_TESTS/without-descartes_orientation/14_w-d_o_c-l-c": { - "refs": ["16c2538"] + "refs": ["cc94132"] }, "ww_animTiming_TESTS/without-polar/01_w-p_r-r-r": { "refs": ["7fd171a"] @@ -1715,10 +1715,10 @@ "refs": ["052448c"] }, "ww_animTiming_TESTS/without-polar/13_w-p_r-l-r": { - "refs": ["af1227f"] + "refs": ["5cdd5ea"] }, "ww_animTiming_TESTS/without-polar/14_w-p_c-l-c": { - "refs": ["65e0fe0"] + "refs": ["d223db8"] }, "ww_animTiming_TESTS/without-polar_orientation/01_w-p_o_r-r-r": { "refs": ["3e27814"] @@ -1739,10 +1739,10 @@ "refs": ["9a61935"] }, "ww_animTiming_TESTS/without-polar_orientation/13_w-p_o_r-l-r": { - "refs": ["97a187b"] + "refs": ["276fab6"] }, "ww_animTiming_TESTS/without-polar_orientation/14_w-p_o_c-l-c": { - "refs": ["18a74af"] + "refs": ["cc61fc4"] }, "ww_animTiming_TESTS/without/02_w-w_c-r-c": { "refs": ["625ee59"] @@ -1769,13 +1769,13 @@ "refs": ["87af60f"] }, "ww_next_steps/next_steps/28_C_A": { - "refs": ["f396191"] + "refs": ["4b54e54"] }, "ww_next_steps/next_steps/35_C_A_violin": { "refs": ["25d77e1"] }, "ww_next_steps/next_steps/38_C_L_line": { - "refs": ["a08c7c4"] + "refs": ["578d265"] }, "ww_next_steps/next_steps_Tests/02_C_R": { "refs": ["f13c899"] @@ -1799,10 +1799,10 @@ "refs": ["81fcf00"] }, "ww_next_steps/next_steps_Tests/28_C_A": { - "refs": ["f952e2d"] + "refs": ["842571c"] }, "ww_next_steps/next_steps_Tests/38_C_L_line": { - "refs": ["3114075"] + "refs": ["bf582b9"] }, "ww_next_steps/next_steps_Tests/axisLabel_problem": { "refs": ["d33f180"] @@ -1814,7 +1814,7 @@ "refs": ["08a0618"] }, "ww_next_steps/next_steps_byOperations/compare/comparison_03": { - "refs": ["08d01ca"] + "refs": ["fcd81e8"] }, "ww_next_steps/next_steps_byOperations/compare/comparison_04": { "refs": ["ff004d8"] @@ -1826,13 +1826,13 @@ "refs": ["801b9e9"] }, "ww_next_steps/next_steps_byOperations/compare/comparison_09": { - "refs": ["455989a"] + "refs": ["c64f9ef"] }, "ww_next_steps/next_steps_byOperations/compare/comparison_10": { - "refs": ["981e41c"] + "refs": ["615c50c"] }, "ww_next_steps/next_steps_byOperations/compare/comparison_11": { - "refs": ["a6fd567"] + "refs": ["4e00ed7"] }, "ww_next_steps/next_steps_byOperations/components/components_01": { "refs": ["342d170"] @@ -1853,7 +1853,7 @@ "refs": ["13c460d"] }, "ww_next_steps/next_steps_byOperations/components/components_07": { - "refs": ["b1e897f"] + "refs": ["68d1446"] }, "ww_next_steps/next_steps_byOperations/distribute/distribution_01": { "refs": ["c62bfe5"] @@ -1910,7 +1910,7 @@ "refs": ["0b9b392"] }, "ww_next_steps/next_steps_byOperations/drilldown/drilldown_13": { - "refs": ["7e73c39"] + "refs": ["0eb2c5c"] }, "ww_next_steps/next_steps_byOperations/other/other_add_measure_01": { "refs": ["483158f"] @@ -1928,7 +1928,7 @@ "refs": ["42c1132"] }, "ww_next_steps/next_steps_byOperations/ratio/ratio_05": { - "refs": ["dd91874"] + "refs": ["2063d68"] }, "ww_next_steps/next_steps_byOperations/remove/remove_01": { "refs": ["3311ca6"] @@ -2003,13 +2003,13 @@ "refs": ["ecd3bbd"] }, "ww_next_steps/next_steps_byOperations/sum_aggregate/sum_aggregate_20": { - "refs": ["bc77498"] + "refs": ["6021dd9"] }, "ww_next_steps/next_steps_byOperations/sum_aggregate/sum_aggregate_21": { - "refs": ["ce484ef"] + "refs": ["e993791"] }, "ww_next_steps/next_steps_byOperations/sum_aggregate/sum_aggregate_22": { - "refs": ["464507f"] + "refs": ["a77cb99"] }, "ww_next_steps/next_steps_byOperations/total/total_01": { "refs": ["6a0650c"] @@ -2033,7 +2033,7 @@ "refs": ["be8f8c9"] }, "ww_next_steps/next_steps_byOperations/wOld_animated/drill_aggreg_improve_line": { - "refs": ["37df636"] + "refs": ["d4aa4d4"] }, "ww_next_steps/next_steps_byOperations/wOld_animated/merge_split_radial_stacked_rectangle_2dis_1con": { "refs": ["575862f"] @@ -2057,10 +2057,10 @@ "refs": ["f0c3b9d"] }, "ww_next_steps/next_steps_byOperations/wOld_animated/zoom_line": { - "refs": ["0bc591f"] + "refs": ["411cd44"] }, "ww_next_steps/next_steps_byOperations/wOld_animated/zoom_line_polar": { - "refs": ["40e952f"] + "refs": ["8dd7c40"] }, "ww_next_steps/next_steps_byOperations/wREGIEKBOL/02_cir": { "refs": ["b23583f"] @@ -2072,7 +2072,7 @@ "refs": ["8136ca8"] }, "ww_next_steps/next_steps_byOperations/wREGIEKBOL/03_d-w_lin": { - "refs": ["13ce7e7"] + "refs": ["ee772aa"] }, "ww_next_steps/next_steps_byOperations/wREGIEKBOL/03_d-w_rec": { "refs": ["36d132e"] @@ -2093,7 +2093,7 @@ "refs": ["23724f3"] }, "ww_next_steps/next_steps_byOperations/wREGIEKBOL/NoFade_Promobol/2_05b_lin": { - "refs": ["892aa0e"] + "refs": ["a3cd992"] }, "ww_next_steps/next_steps_byOperations/wREGIEKBOL/NoFade_Promobol/4_06b_rec_1c": { "refs": ["c4d93f3"] @@ -2201,31 +2201,31 @@ "refs": ["172b697"] }, "ww_noFade/wNoFade_Tests/1_des_pol/line/02a_lin": { - "refs": ["1fc3475"] + "refs": ["80b3a26"] }, "ww_noFade/wNoFade_Tests/1_des_pol/line/02b_lin": { - "refs": ["3955fa7"] + "refs": ["a87c0f3"] }, "ww_noFade/wNoFade_Tests/1_des_pol/line/03_lin": { - "refs": ["c2e3e9a"] + "refs": ["f03b85c"] }, "ww_noFade/wNoFade_Tests/1_des_pol/line/04a_lin": { - "refs": ["674e6e9"] + "refs": ["55546d0"] }, "ww_noFade/wNoFade_Tests/1_des_pol/line/04b_lin": { - "refs": ["35736c6"] + "refs": ["0d771c0"] }, "ww_noFade/wNoFade_Tests/1_des_pol/line/05a_lin": { - "refs": ["2eb5dc2"] + "refs": ["178ad6a"] }, "ww_noFade/wNoFade_Tests/1_des_pol/line/05b_lin": { - "refs": ["c953f4f"] + "refs": ["c6544cd"] }, "ww_noFade/wNoFade_Tests/1_des_pol/line/06a_lin": { - "refs": ["7ad7b93"] + "refs": ["564f4c9"] }, "ww_noFade/wNoFade_Tests/1_des_pol/line/06b_lin": { - "refs": ["1535913"] + "refs": ["72219fe"] }, "ww_noFade/wNoFade_Tests/1_des_pol/rectangle/02a_rec": { "refs": ["231442e"] @@ -2351,52 +2351,52 @@ "refs": ["1d3e949"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line-rectangle/02_d-w_lin": { - "refs": ["4937e6b"] + "refs": ["25bd99e"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line-rectangle/03_d-w_lin": { - "refs": ["f1e9fc7"] + "refs": ["9987a3b"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line-rectangle/04a_d-w_lin": { - "refs": ["5ee8720"] + "refs": ["7f94c0b"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line-rectangle/04b_d-w_lin": { - "refs": ["e52dcf5"] + "refs": ["f8ace45"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line-rectangle/05a_d-w_lin": { - "refs": ["036f9a9"] + "refs": ["3ed4a8c"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line-rectangle/05b_d-w_lin": { - "refs": ["5bb4d62"] + "refs": ["708e13a"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line-rectangle/06a_d-w_lin": { - "refs": ["cb542c7"] + "refs": ["5853ee9"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line-rectangle/06b_d-w_lin": { - "refs": ["2c02a12"] + "refs": ["e8d7144"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line/02_d-w_lin": { - "refs": ["0b378c0"] + "refs": ["5cd32f2"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line/03_d-w_lin": { - "refs": ["78a8e0e"] + "refs": ["25c5087"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line/04a_d-w_lin": { - "refs": ["646834f"] + "refs": ["c105664"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line/04b_d-w_lin": { - "refs": ["3bf10bf"] + "refs": ["f028340"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line/05a_d-w_lin": { - "refs": ["43dfc65"] + "refs": ["2a2acfb"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line/05b_d-w_lin": { - "refs": ["31b9af2"] + "refs": ["24b4a7a"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line/06a_d-w_lin": { - "refs": ["069705d"] + "refs": ["9ad077b"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/line/06b_d-w_lin": { - "refs": ["7d807d6"] + "refs": ["3a8158c"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/rectangle/02_d-w_rec": { "refs": ["6191e51"] @@ -2447,7 +2447,7 @@ "refs": ["1ffa601"] }, "ww_noFade/wNoFade_Tests/Marker_transition_problem/Bubble_Stacked_Bubble_to_Line": { - "refs": ["c346364"] + "refs": ["0f1d715"] }, "ww_noFade/wNoFade_Tests/Marker_transition_problem/Treemap_Stacked_Treemap_to_Area": { "refs": ["5cf59b1"] @@ -2459,19 +2459,19 @@ "refs": ["e7bfa6b"] }, "ww_noFade/wNoFade_Tests/Marker_transition_problem/line_bar_time_sum": { - "refs": ["10faadd"] + "refs": ["5b41e4d"] }, "ww_noFade/wNoFade_Tests/Marker_transition_problem/line_column_time_sum": { - "refs": ["3141682"] + "refs": ["891a725"] }, "ww_noFade/wNoFade_Tests/Marker_transition_problem/line_drilldown_aggregate_x": { - "refs": ["c119139"] + "refs": ["2b10dd6"] }, "ww_noFade/wNoFade_Tests/Marker_transition_problem/line_orientation": { - "refs": ["ad997c7"] + "refs": ["9ca8a56"] }, "ww_noFade/wNoFade_Tests/Marker_transition_problem/line_tooltip_test": { - "refs": ["e1e79b2"] + "refs": ["74c6b0e"] }, "ww_noFade/wNoFade_Tests/noFade_AND_Marker_transition_problem/pie_coxcomb_drilldown": { "refs": ["591692c"] @@ -2558,22 +2558,22 @@ "refs": ["f128aa0"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/04a_lin": { - "refs": ["5dc2c76"] + "refs": ["1a5b410"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/04b_lin": { - "refs": ["48a276e"] + "refs": ["0a8514e"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/05a_lin": { - "refs": ["02d0e3d"] + "refs": ["96903d3"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/05b_lin": { - "refs": ["678d295"] + "refs": ["73c8dc9"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/06a_lin": { - "refs": ["2d43903"] + "refs": ["c9ceca8"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/06b_lin": { - "refs": ["b41917c"] + "refs": ["665ca53"] }, "ww_noFade/wNoFade_cases/1_des_pol/rectangle/04a_rec_1c": { "refs": ["4397bcb"] @@ -2615,7 +2615,7 @@ "refs": ["ea87d05"] }, "ww_noFade/wNoFade_cases/1_des_pol/rectangle/09_rec_TemporalDistribution": { - "refs": ["b1dc7f4"] + "refs": ["8a2f528"] }, "ww_noFade/wNoFade_cases/1_des_pol/rectangle_V1/05b_rec_2c_V1": { "refs": ["181a9c4"] @@ -2768,79 +2768,79 @@ "refs": ["d0f432e"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/02_d-d_lin": { - "refs": ["7c20f48"] + "refs": ["b27c708"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/02_d-w_lin": { - "refs": ["c0d0d33"] + "refs": ["bc5b8f5"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/03_d-w_lin": { - "refs": ["141e639"] + "refs": ["074f490"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/04a_d-w_lin": { - "refs": ["a93d332"] + "refs": ["e58f156"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/04b_d-w_lin": { - "refs": ["25f5e39"] + "refs": ["561fbb9"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/05a_d-w_lin": { - "refs": ["11ef692"] + "refs": ["a6429e4"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/05b_d-w_lin": { - "refs": ["793c69d"] + "refs": ["9897763"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/06a_d-w_lin": { - "refs": ["8d80e81"] + "refs": ["9c4e8ab"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/06b_d-w_lin": { - "refs": ["9f636cf"] + "refs": ["16823b3"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/02_d-w_lin": { - "refs": ["abce8b4"] + "refs": ["4a4f65a"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/03_d-w_lin": { - "refs": ["d94f479"] + "refs": ["1b52593"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/04a_d-w_lin": { - "refs": ["0c89816"] + "refs": ["46c8ce8"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/04b_d-w_lin": { - "refs": ["9db1f0f"] + "refs": ["7066fed"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/05a_d-w_lin": { - "refs": ["3d5b1da"] + "refs": ["324aa85"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/05b_d-w_lin": { - "refs": ["9507372"] + "refs": ["56454e3"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/06a_d-w_lin": { - "refs": ["77cba60"] + "refs": ["e196b36"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/06b_d-w_lin": { - "refs": ["b2dfc3b"] + "refs": ["672ba90"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/02_d-w_lin_V1": { - "refs": ["09b0926"] + "refs": ["0ded4c3"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/03_d-w_lin_V1": { - "refs": ["b0dc569"] + "refs": ["1124f4f"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/04a_d-w_lin_V1": { - "refs": ["88ed61e"] + "refs": ["c3ad3a9"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/04b_d-w_lin_V1": { - "refs": ["5f81c31"] + "refs": ["0d0d4eb"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/05a_d-w_lin_V1": { - "refs": ["9b1f540"] + "refs": ["c8181ab"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/05b_d-w_lin_V1": { - "refs": ["30019d3"] + "refs": ["df70ce8"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/06a_d-w_lin_V1": { - "refs": ["7e64bad"] + "refs": ["adf156e"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/06b_d-w_lin_V1": { - "refs": ["0b37c3c"] + "refs": ["aa1b99e"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/rectangle/02_d-w_rec": { "refs": ["291bf66"] @@ -2948,7 +2948,7 @@ "refs": ["d9d6dc4"] }, "ww_noFade/wNoFade_wPromotion/2_05b_lin": { - "refs": ["fb50624"] + "refs": ["b3f0614"] }, "ww_noFade/wNoFade_wPromotion/3_04_cir": { "refs": ["960e157"] @@ -3080,16 +3080,16 @@ "refs": ["afdce6c"] }, "ww_samples_for_presets/cartesian_coo_sys/371_C_L_line_chart_nega": { - "refs": ["4f426e6"] + "refs": ["fac2855"] }, "ww_samples_for_presets/cartesian_coo_sys/37_C_A_funnel": { "refs": ["34afa08"] }, "ww_samples_for_presets/cartesian_coo_sys/38_C_L_line_chart_nega": { - "refs": ["34cb2a2"] + "refs": ["7f3ede8"] }, "ww_samples_for_presets/cartesian_coo_sys/39_C_L_line_chart_vert": { - "refs": ["aa85e89"] + "refs": ["ee9b8b4"] }, "ww_samples_for_presets/polar_coo_sys/41_P_R_multi-level_pie_chart": { "refs": ["190c250"] @@ -3137,7 +3137,7 @@ "refs": ["e2c10b8"] }, "ww_samples_for_presets/polar_coo_sys/56_P_A_polar_line_chart": { - "refs": ["19ad791"] + "refs": ["7a52140"] }, "web_content/presets_config/treemap": { "refs": ["ee86b8d"] @@ -3182,19 +3182,19 @@ "refs": ["6d24ce6"] }, "www_new_analytical_operations/operations/02_sum/Line_Line_3": { - "refs": ["decd6cf"] + "refs": ["b034d61"] }, "www_new_analytical_operations/operations/07_distribute/Bubble_Stacked_Bubble_to_Area": { "refs": ["ae0e815"] }, "www_new_analytical_operations/operations/07_distribute/Bubble_Stacked_Bubble_to_Line": { - "refs": ["f0cad11"] + "refs": ["0d9e857"] }, "www_new_analytical_operations/operations/07_distribute/Treemap_Stacked_Treemap_to_Area": { "refs": ["f2b760e"] }, "basic_animations/markers_morph/marker_trans_polar": { - "refs": ["2c59683"] + "refs": ["0633895"] }, "web_content/cookbook/chart_types/exploded_pie_chart": { "refs": ["1344b43"] @@ -3245,7 +3245,7 @@ "refs": ["111a932"] }, "web_content/cookbook/rendering/custom_linetype": { - "refs": ["8fe17e9"] + "refs": ["75c0b21"] }, "web_content/cookbook/rendering/gradient_on_marker": { "refs": ["878d4a5"] @@ -3269,10 +3269,10 @@ "refs": ["18a84af"] }, "web_content/cookbook/interactive/mouse_wheel_zoom": { - "refs": ["aacefa4"] + "refs": ["ca66001"] }, "web_content/cookbook/interactive/window_zoom": { - "refs": ["4c082d0"] + "refs": ["af0984a"] }, "web_content/cookbook/interactive/data_selector_combobox": { "refs": ["da8ec16"] @@ -3281,10 +3281,10 @@ "refs": ["87b8197"] }, "web_content/cookbook/interactive/range_slider_zoom": { - "refs": ["34f9d9c"] + "refs": ["97dd5b0"] }, "web_content/cookbook/data_source/csv_load": { - "refs": ["0be6e8c"] + "refs": ["2a18132"] }, "web_content/cookbook/rendering/motion_blur": { "refs": ["be67912"] diff --git a/test/e2e/test_cases/web_content/cookbook/rendering/custom_linetype.mjs b/test/e2e/test_cases/web_content/cookbook/rendering/custom_linetype.mjs index 97086f9bf..6033ba09e 100644 --- a/test/e2e/test_cases/web_content/cookbook/rendering/custom_linetype.mjs +++ b/test/e2e/test_cases/web_content/cookbook/rendering/custom_linetype.mjs @@ -19,10 +19,11 @@ const testSteps = [ if (!tinycolor(color).isValid()) return const series = tinycolor(color).getBrightness() + const alpha = Math.round(tinycolor(color).getAlpha() * 256).toString(16) ctx.setLineDash(dashes[series]) - ctx.strokeStyle = '#666666' + ctx.strokeStyle = '#666666' + alpha } chart.on('legend-marker-draw', (event) => { diff --git a/test/e2e/tests/config_tests.json b/test/e2e/tests/config_tests.json index 62b1b65bb..deb46a8ac 100644 --- a/test/e2e/tests/config_tests.json +++ b/test/e2e/tests/config_tests.json @@ -11,25 +11,25 @@ "refs": ["a491c17"] }, "geometry/static_line": { - "refs": ["758acea"] + "refs": ["b9bee48"] }, "geometry/static_rectangle": { "refs": ["a491c17"] }, "geometry/animated_area-line": { - "refs": ["a6f2b5c"] + "refs": ["3622f0e"] }, "geometry/animated_area-rectangle": { "refs": ["82a57d0"] }, "geometry/animated_line-area": { - "refs": ["049a517"] + "refs": ["69de3f9"] }, "geometry/animated_line-circle": { - "refs": ["ad7cb9b"] + "refs": ["f67aea6"] }, "geometry/animated_line-rectangle": { - "refs": ["a4e4f01"] + "refs": ["d2d80c6"] }, "geometry/animated_rectangle-area": { "refs": ["6577fd3"] @@ -38,7 +38,7 @@ "refs": ["12d82f2"] }, "geometry/animated_rectangle-line": { - "refs": ["3e1ffaf"] + "refs": ["5949df4"] }, "dimension_axis_title": { "refs": ["d9bab94"] From 7c5c23b8df0c03c0684ee294d5cd8a47df7b08d2 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 15 Feb 2024 17:12:27 +0100 Subject: [PATCH 038/253] Add changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bd191862..65f5ecf9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - Through event handler call, when a new event handler is registered, undefined behaviour happened. - Fixed channel reset with empty array when shorthands plugin switched off. - Legend label outerRect was not properly calculated. +- Line chart connector circles color was not contained the alpha channel. +- Line chart draws was overwrite the event's settings. ### Added From 17038b7d7d6fa9edb144f5c925a1cc9a09ceaddb Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 15 Feb 2024 18:12:56 +0100 Subject: [PATCH 039/253] fix one testcase --- test/e2e/test_cases/test_cases.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/test_cases/test_cases.json b/test/e2e/test_cases/test_cases.json index fae1fc769..4a00a75b0 100644 --- a/test/e2e/test_cases/test_cases.json +++ b/test/e2e/test_cases/test_cases.json @@ -44,7 +44,7 @@ "refs": ["db72798"] }, "basic_animations/labels/marker/line_2dis_3con": { - "refs": ["0d4cd3d"] + "refs": ["051968e"] }, "basic_animations/labels/marker/padding_test_rectangle_negative_2dis_3con": { "refs": ["dde0d76"] From 371baab26e0f0232833b70f9968b30293a2fb0f9 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 19 Feb 2024 15:32:23 +0100 Subject: [PATCH 040/253] aggregate return with name --- src/dataframe/impl/aggregators.h | 16 +-- src/dataframe/interface.h | 22 ++-- test/unit/dataframe/interface_test.cpp | 146 ++++++++++++------------- 3 files changed, 95 insertions(+), 89 deletions(-) diff --git a/src/dataframe/impl/aggregators.h b/src/dataframe/impl/aggregators.h index 6dcb28e67..464ad2470 100644 --- a/src/dataframe/impl/aggregators.h +++ b/src/dataframe/impl/aggregators.h @@ -22,7 +22,7 @@ constinit const static inline Refl::EnumArray double { - auto &ref = std::any_cast(id); + auto &ref = *std::any_cast(&id); if (!std::isnan(value) && !std::isinf(value)) ref += value; @@ -36,7 +36,7 @@ constinit const static inline Refl::EnumArray double { - auto &ref = std::any_cast(id); + auto &ref = *std::any_cast(&id); if (!std::isnan(value) && !std::isinf(value) && !(value >= ref)) ref = value; @@ -50,7 +50,7 @@ constinit const static inline Refl::EnumArray double { - auto &ref = std::any_cast(id); + auto &ref = *std::any_cast(&id); if (!std::isnan(value) && !std::isinf(value) && !(value <= ref)) ref = value; @@ -65,8 +65,8 @@ constinit const static inline Refl::EnumArray double { auto &[sum, count] = - std::any_cast &>( - id); + *std::any_cast>( + &id); if (!std::isnan(value) && !std::isinf(value)) { sum += value; ++count; @@ -81,7 +81,7 @@ constinit const static inline Refl::EnumArray double { - auto &s = std::any_cast(id); + auto &s = *std::any_cast(&id); if (!std::isnan(value) && !std::isinf(value)) s += 1; return static_cast(s); }}, @@ -92,7 +92,7 @@ constinit const static inline Refl::EnumArray double { - auto &set = std::any_cast &>(id); + auto &set = *std::any_cast>(&id); if (!std::isnan(value) && !std::isinf(value)) set.insert(value); return static_cast(set.size()); @@ -104,7 +104,7 @@ constinit const static inline Refl::EnumArray double { - auto &b = std::any_cast(id); + auto &b = *std::any_cast(&id); if (!std::isnan(value) && !std::isinf(value)) b = true; return b; diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index e7badfee1..cf0430230 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -56,6 +56,13 @@ class dataframe_interface : using record_identifier = std::variant; + using any_aggregator_type = std:: + variant; + + using any_sort_type = std::variant>; + struct record_type { [[nodiscard]] cell_value getValue(series_identifier i) const @@ -72,16 +79,15 @@ class dataframe_interface : [[nodiscard]] virtual std::shared_ptr copy(bool remove_filtered, bool inherit_sorting) const & = 0; - using any_aggregator_type = std:: - variant; - - using any_sort_type = std::variant>; - - virtual void set_aggregate(series_identifier series, + [[nodiscard]] virtual std::string set_aggregate( + series_identifier series, const any_aggregator_type &aggregator) & = 0; + void aggregate_by(series_identifier series) + { + [[maybe_unused]] auto &&_ = set_aggregate(series, {}); + } + virtual void set_filter( std::function &&filter) & = 0; diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index cc55a76a3..8364e997d 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -445,19 +445,20 @@ const static auto tests = {{"dm2", NAN}}, {{std::string_view{nullptr, 0}, 0.0}}, }}) { - df->set_aggregate("d1", {}); - df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::count); - df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::distinct); - df->set_aggregate("d1", Vizzu::dataframe::aggregator_type::exists); - df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::sum); - df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::min); - df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::max); - df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::mean); - df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::count); - df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::distinct); - df->set_aggregate("m1", Vizzu::dataframe::aggregator_type::exists); - - df->set_aggregate("m1", Vizzu::dataframe::custom_aggregator{ + df->aggregate_by("d1"); + using enum Vizzu::dataframe::aggregator_type; + auto &&d1c = df->set_aggregate("d1", count); + auto &&d1d = df->set_aggregate("d1", distinct); + auto &&d1e = df->set_aggregate("d1", exists); + auto &&m1s = df->set_aggregate("m1", sum); + auto &&m1mi = df->set_aggregate("m1", min); + auto &&m1ma = df->set_aggregate("m1", max); + auto &&m1me = df->set_aggregate("m1", mean); + auto &&m1c = df->set_aggregate("m1", count); + auto &&m1d = df->set_aggregate("m1", distinct); + auto &&m1e = df->set_aggregate("m1", exists); + + auto &&m1t = df->set_aggregate("m1", Vizzu::dataframe::custom_aggregator{ std::string_view{"test"}, [] () -> Vizzu::dataframe::custom_aggregator::id_type { @@ -482,55 +483,53 @@ const static auto tests = assert ->* df->get_dimensions() == std::array{"d1"}; assert ->* df->get_measures() == std::array{ - "count(d1)", "count(m1)", "distinct(d1)", "distinct(m1)", - "exists(d1)", "exists(m1)", - "max(m1)", "mean(m1)", "min(m1)", "sum(m1)", "test(m1)" + d1c, m1c, d1d, m1d, d1e, m1e, m1ma, m1me, m1mi, m1s, m1t }; assert ->* df->get_record_count() == std::size_t{4}; - check ->* df->get_data(std::size_t{0}, "count(d1)") == 5.0; - check ->* df->get_data(std::size_t{0}, "count(m1)") == 5.0; - check ->* df->get_data(std::size_t{0}, "distinct(d1)") == 1.0; - check ->* df->get_data(std::size_t{0}, "distinct(m1)") == 5.0; - check ->* df->get_data(std::size_t{0}, "exists(d1)") == 1.0; - check ->* df->get_data(std::size_t{0}, "exists(m1)") == 1.0; - check ->* df->get_data(std::size_t{0}, "max(m1)") == 88.0; - check ->* df->get_data(std::size_t{0}, "mean(m1)") == 21.85; - check ->* df->get_data(std::size_t{0}, "min(m1)") == 2.0; - check ->* df->get_data(std::size_t{0}, "sum(m1)") == 109.25; - check ->* df->get_data(std::size_t{0}, "test(m1)") == 3.5; - - check ->* df->get_data(std::size_t{1}, "count(d1)") == 4.0; - check ->* df->get_data(std::size_t{1}, "count(m1)") == 3.0; - check ->* df->get_data(std::size_t{1}, "distinct(d1)") == 1.0; - check ->* df->get_data(std::size_t{1}, "distinct(m1)") == 3.0; - check ->* df->get_data(std::size_t{1}, "exists(d1)") == 1.0; - check ->* df->get_data(std::size_t{1}, "exists(m1)") == 1.0; - check ->* df->get_data(std::size_t{1}, "max(m1)") == 7.25; - check ->* df->get_data(std::size_t{1}, "mean(m1)") == 5.0; - check ->* df->get_data(std::size_t{1}, "min(m1)") == 3.5; - check ->* df->get_data(std::size_t{1}, "sum(m1)") == 15.0; - check ->* df->get_data(std::size_t{1}, "test(m1)") == 4.25; - - check ->* df->get_data(std::size_t{2}, "count(d1)") == 1.0; - check ->* df->get_data(std::size_t{2}, "count(m1)") == 0.0; - check ->* df->get_data(std::size_t{2}, "distinct(d1)") == 1.0; - check ->* df->get_data(std::size_t{2}, "distinct(m1)") == 0.0; - check ->* df->get_data(std::size_t{2}, "exists(d1)") == 1.0; - check ->* df->get_data(std::size_t{2}, "exists(m1)") == 0.0; - check ->* std::isnan(std::get(df->get_data(std::size_t{2}, "max(m1)"))) == "is nan"_is_true; - check ->* std::isnan(std::get(df->get_data(std::size_t{2}, "mean(m1)"))) == "is nan"_is_true; - check ->* std::isnan(std::get(df->get_data(std::size_t{2}, "min(m1)"))) == "is nan"_is_true; - check ->* df->get_data(std::size_t{2}, "sum(m1)") == 0.0; - check ->* df->get_data(std::size_t{2}, "test(m1)") == std::numeric_limits::max(); - - check ->* df->get_data(std::size_t{3}, "count(d1)") == 0.0; - check ->* df->get_data(std::size_t{3}, "count(m1)") == 1.0; - check ->* df->get_data(std::size_t{3}, "distinct(d1)") == 0.0; - check ->* df->get_data(std::size_t{3}, "distinct(m1)") == 1.0; - check ->* df->get_data(std::size_t{3}, "exists(d1)") == 0.0; - check ->* df->get_data(std::size_t{3}, "exists(m1)") == 1.0; + check ->* df->get_data(std::size_t{0}, d1c) == 5.0; + check ->* df->get_data(std::size_t{0}, m1c) == 5.0; + check ->* df->get_data(std::size_t{0}, d1d) == 1.0; + check ->* df->get_data(std::size_t{0}, m1d) == 5.0; + check ->* df->get_data(std::size_t{0}, d1e) == 1.0; + check ->* df->get_data(std::size_t{0}, m1e) == 1.0; + check ->* df->get_data(std::size_t{0}, m1ma) == 88.0; + check ->* df->get_data(std::size_t{0}, m1me) == 21.85; + check ->* df->get_data(std::size_t{0}, m1mi) == 2.0; + check ->* df->get_data(std::size_t{0}, m1s) == 109.25; + check ->* df->get_data(std::size_t{0}, m1t) == 3.5; + + check ->* df->get_data(std::size_t{1}, d1c) == 4.0; + check ->* df->get_data(std::size_t{1}, m1c) == 3.0; + check ->* df->get_data(std::size_t{1}, d1d) == 1.0; + check ->* df->get_data(std::size_t{1}, m1d) == 3.0; + check ->* df->get_data(std::size_t{1}, d1e) == 1.0; + check ->* df->get_data(std::size_t{1}, m1e) == 1.0; + check ->* df->get_data(std::size_t{1}, m1ma) == 7.25; + check ->* df->get_data(std::size_t{1}, m1me) == 5.0; + check ->* df->get_data(std::size_t{1}, m1mi) == 3.5; + check ->* df->get_data(std::size_t{1}, m1s) == 15.0; + check ->* df->get_data(std::size_t{1}, m1t) == 4.25; + + check ->* df->get_data(std::size_t{2}, d1c) == 1.0; + check ->* df->get_data(std::size_t{2}, m1c) == 0.0; + check ->* df->get_data(std::size_t{2}, d1d) == 1.0; + check ->* df->get_data(std::size_t{2}, m1d) == 0.0; + check ->* df->get_data(std::size_t{2}, d1e) == 1.0; + check ->* df->get_data(std::size_t{2}, m1e) == 0.0; + check ->* std::isnan(std::get(df->get_data(std::size_t{2}, m1ma))) == "is nan"_is_true; + check ->* std::isnan(std::get(df->get_data(std::size_t{2}, m1me))) == "is nan"_is_true; + check ->* std::isnan(std::get(df->get_data(std::size_t{2}, m1mi))) == "is nan"_is_true; + check ->* df->get_data(std::size_t{2}, m1s) == 0.0; + check ->* df->get_data(std::size_t{2}, m1t) == std::numeric_limits::max(); + + check ->* df->get_data(std::size_t{3}, d1c) == 0.0; + check ->* df->get_data(std::size_t{3}, m1c) == 1.0; + check ->* df->get_data(std::size_t{3}, d1d) == 0.0; + check ->* df->get_data(std::size_t{3}, m1d) == 1.0; + check ->* df->get_data(std::size_t{3}, d1e) == 0.0; + check ->* df->get_data(std::size_t{3}, m1e) == 1.0; } | "aggregate multiple dim" | @@ -547,45 +546,46 @@ const static auto tests = {{"dx2", "dm0", "dob", NAN}}, {{"dx2", "dm0", "doa", 0.5}}, }}) { - df->set_aggregate("d1", {}); - df->set_aggregate("d2", {}); - df->set_aggregate("d3", Vizzu::dataframe::aggregator_type::count); - df->set_aggregate("d3", Vizzu::dataframe::aggregator_type::distinct); + df->aggregate_by("d1"); + df->aggregate_by("d2"); + using enum Vizzu::dataframe::aggregator_type; + auto &&d3c = df->set_aggregate("d3", count); + auto &&d3d = df->set_aggregate("d3", distinct); df->finalize(); assert ->* df->get_dimensions() == std::array{"d1", "d2"}; - assert ->* df->get_measures() == std::array{"count(d3)", "distinct(d3)"}; + assert ->* df->get_measures() == std::array{d3c, d3d}; assert ->* df->get_record_count() == std::size_t{6}; check ->* df->get_data(std::size_t{0}, "d1") == "dx0"; check ->* df->get_data(std::size_t{0}, "d2") == "dm0"; - check ->* df->get_data(std::size_t{0}, "count(d3)") == 3.0; - check ->* df->get_data(std::size_t{0}, "distinct(d3)") == 2.0; + check ->* df->get_data(std::size_t{0}, d3c) == 3.0; + check ->* df->get_data(std::size_t{0}, d3d) == 2.0; check ->* df->get_data(std::size_t{1}, "d1") == "dx0"; check ->* df->get_data(std::size_t{1}, "d2") == "dm1"; - check ->* df->get_data(std::size_t{1}, "count(d3)") == 1.0; - check ->* df->get_data(std::size_t{1}, "distinct(d3)") == 1.0; + check ->* df->get_data(std::size_t{1}, d3c) == 1.0; + check ->* df->get_data(std::size_t{1}, d3d) == 1.0; check ->* df->get_data(std::size_t{2}, "d1") == "dx1"; check ->* df->get_data(std::size_t{2}, "d2") == "dm0"; - check ->* df->get_data(std::size_t{2}, "count(d3)") == 2.0; - check ->* df->get_data(std::size_t{2}, "distinct(d3)") == 1.0; + check ->* df->get_data(std::size_t{2}, d3c) == 2.0; + check ->* df->get_data(std::size_t{2}, d3d) == 1.0; check ->* df->get_data(std::size_t{3}, "d1") == "dx1"; check ->* df->get_data(std::size_t{3}, "d2") == "dm1"; - check ->* df->get_data(std::size_t{3}, "count(d3)") == 0.0; - check ->* df->get_data(std::size_t{3}, "distinct(d3)") == 0.0; + check ->* df->get_data(std::size_t{3}, d3c) == 0.0; + check ->* df->get_data(std::size_t{3}, d3d) == 0.0; check ->* df->get_data(std::size_t{4}, "d1") == "dx1"; check ->* df->get_data(std::size_t{4}, "d2") == "dm2"; check ->* df->get_data(std::size_t{5}, "d1") == "dx2"; check ->* df->get_data(std::size_t{5}, "d2") == "dm0"; - check ->* df->get_data(std::size_t{5}, "count(d3)") == 2.0; - check ->* df->get_data(std::size_t{5}, "distinct(d3)") == 2.0; + check ->* df->get_data(std::size_t{5}, d3c) == 2.0; + check ->* df->get_data(std::size_t{5}, d3d) == 2.0; } | "cannot finalize contains same dim" | From 9a3333d6f6e283e255b4469c7018394464ace28a Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 21 Feb 2024 14:07:56 +0100 Subject: [PATCH 041/253] Fix draw legend title + add test --- src/chart/rendering/drawlegend.cpp | 2 +- test/e2e/tests/fixes.json | 3 +++ test/e2e/tests/fixes/53913538.mjs | 35 ++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/e2e/tests/fixes/53913538.mjs diff --git a/src/chart/rendering/drawlegend.cpp b/src/chart/rendering/drawlegend.cpp index 96dd33b82..189d72aef 100644 --- a/src/chart/rendering/drawlegend.cpp +++ b/src/chart/rendering/drawlegend.cpp @@ -50,7 +50,7 @@ void DrawLegend::draw(Gfx::ICanvas &canvas, void DrawLegend::drawTitle(const Info &info) const { auto rect = info.contentRect; - rect.size.y += info.titleHeight; + rect.size.y = info.titleHeight; plot->commonAxises.at(info.type).title.visit( [this, &info, diff --git a/test/e2e/tests/fixes.json b/test/e2e/tests/fixes.json index f352f59c1..611aa5a1c 100644 --- a/test/e2e/tests/fixes.json +++ b/test/e2e/tests/fixes.json @@ -39,6 +39,9 @@ }, "47977099": { "refs": ["5f58727"] + }, + "53913538": { + "refs": ["66d916e"] } } } diff --git a/test/e2e/tests/fixes/53913538.mjs b/test/e2e/tests/fixes/53913538.mjs new file mode 100644 index 000000000..8d7b24d13 --- /dev/null +++ b/test/e2e/tests/fixes/53913538.mjs @@ -0,0 +1,35 @@ +const testSteps = [ + (chart) => { + const data = { + series: [ + { name: 'Foo', values: ['Alice', 'Bob', 'Ted'] }, + { name: 'Bar', values: [15, 32, 12] } + ] + } + + chart.on('legend-title-draw', e => { + const rect = e.detail.outerRect + const rect2 = e.detail.innerRect + const ctx = e.renderingContext + ctx.strokeStyle = '#00000080' + ctx.beginPath() + ctx.rect(rect.transform[0][2], rect.transform[1][2], rect.size.x, rect.size.y) + ctx.stroke() + ctx.strokeStyle = '#0000FF80' + ctx.beginPath() + ctx.rect(rect.transform[0][2] + rect2.pos.x, rect.transform[1][2] + rect2.pos.y, rect2.size.x, rect2.size.y) + ctx.stroke() + }) + + return chart.animate({ data }) + }, + (chart) => + chart.animate({ + x: 'Foo', + y: 'Bar', + color: { set: 'Foo', title: 'ASD' }, + legend: 'color' + }) +] + +export default testSteps From 9469bd71702e0f86228a3a34ae37516a50ee9df8 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 21 Feb 2024 14:15:20 +0100 Subject: [PATCH 042/253] fix test --- test/e2e/tests/fixes/53913538.mjs | 63 +++++++++++++++++-------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/test/e2e/tests/fixes/53913538.mjs b/test/e2e/tests/fixes/53913538.mjs index 8d7b24d13..15c15aadb 100644 --- a/test/e2e/tests/fixes/53913538.mjs +++ b/test/e2e/tests/fixes/53913538.mjs @@ -1,35 +1,40 @@ const testSteps = [ - (chart) => { - const data = { - series: [ - { name: 'Foo', values: ['Alice', 'Bob', 'Ted'] }, - { name: 'Bar', values: [15, 32, 12] } - ] - } + (chart) => { + const data = { + series: [ + { name: 'Foo', values: ['Alice', 'Bob', 'Ted'] }, + { name: 'Bar', values: [15, 32, 12] } + ] + } - chart.on('legend-title-draw', e => { - const rect = e.detail.outerRect - const rect2 = e.detail.innerRect - const ctx = e.renderingContext - ctx.strokeStyle = '#00000080' - ctx.beginPath() - ctx.rect(rect.transform[0][2], rect.transform[1][2], rect.size.x, rect.size.y) - ctx.stroke() - ctx.strokeStyle = '#0000FF80' - ctx.beginPath() - ctx.rect(rect.transform[0][2] + rect2.pos.x, rect.transform[1][2] + rect2.pos.y, rect2.size.x, rect2.size.y) - ctx.stroke() - }) + chart.on('legend-title-draw', (e) => { + const rect = e.detail.outerRect + const rect2 = e.detail.innerRect + const ctx = e.renderingContext + ctx.strokeStyle = '#00000080' + ctx.beginPath() + ctx.rect(rect.transform[0][2], rect.transform[1][2], rect.size.x, rect.size.y) + ctx.stroke() + ctx.strokeStyle = '#0000FF80' + ctx.beginPath() + ctx.rect( + rect.transform[0][2] + rect2.pos.x, + rect.transform[1][2] + rect2.pos.y, + rect2.size.x, + rect2.size.y + ) + ctx.stroke() + }) - return chart.animate({ data }) - }, - (chart) => - chart.animate({ - x: 'Foo', - y: 'Bar', - color: { set: 'Foo', title: 'ASD' }, - legend: 'color' - }) + return chart.animate({ data }) + }, + (chart) => + chart.animate({ + x: 'Foo', + y: 'Bar', + color: { set: 'Foo', title: 'ASD' }, + legend: 'color' + }) ] export default testSteps From 72621e44c3153c9c232dd9af732dd281023a3957 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 21 Feb 2024 16:11:39 +0100 Subject: [PATCH 043/253] Fixed stacked empty aggregated values --- CHANGELOG.md | 2 ++ src/base/math/renard.cpp | 8 ++++++-- src/chart/generator/marker.cpp | 16 +++++++--------- src/chart/generator/plot.cpp | 6 +++++- src/data/datacube/aggregator.cpp | 15 ++++++++++++++- src/data/datacube/aggregator.h | 3 ++- src/data/datacube/datacube.cpp | 8 +++++--- test/e2e/tests/fixes/53978116.mjs | 20 ++++++++++++++++++++ 8 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 test/e2e/tests/fixes/53978116.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f5ecf9f..10e02a690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - Legend label outerRect was not properly calculated. - Line chart connector circles color was not contained the alpha channel. - Line chart draws was overwrite the event's settings. +- Legend title outerRect was not properly calculated. +- Fixed stacked empty min/max/mean aggregated values. ### Added diff --git a/src/base/math/renard.cpp b/src/base/math/renard.cpp index 441c222d3..5dc909dfd 100644 --- a/src/base/math/renard.cpp +++ b/src/base/math/renard.cpp @@ -37,7 +37,9 @@ double Renard::ceil(double value) return ScientificNumber(num.positive, Rnum, num.exponent) .value(); - throw std::logic_error("Internal error in R-number rounding."); + throw std::logic_error( + "Internal error in R-number ceil rounding: " + + std::to_string(value) + "."); } double Renard::floor(double value) @@ -51,7 +53,9 @@ double Renard::floor(double value) return ScientificNumber(num.positive, Rnum, num.exponent) .value(); - throw std::logic_error("Internal error in R-number rounding."); + throw std::logic_error( + "Internal error in R-number floor rounding: " + + std::to_string(value) + "."); } } \ No newline at end of file diff --git a/src/chart/generator/marker.cpp b/src/chart/generator/marker.cpp index 27b8a3af4..233618a6e 100644 --- a/src/chart/generator/marker.cpp +++ b/src/chart/generator/marker.cpp @@ -197,7 +197,7 @@ double Marker::getValueForChannel(const Channels &channels, auto measure = channel.measureId; - double value{}; + double value = channel.defaultValue; auto id = Id(data, channel.dimensionIds, index); auto &stat = stats.channels[type]; @@ -211,14 +211,12 @@ double Marker::getValueForChannel(const Channels &channels, if (enabled) stat.track(id); } else { - auto singlevalue = - static_cast(data.valueAt(index, *measure)); - - if (channel.stackable) - value = static_cast( - data.aggregateAt(index, sumBy, *measure)); - else - value = singlevalue; + if (channel.stackable) { + if (auto &&val = data.aggregateAt(index, sumBy, *measure)) + value = *val; + } + else if (auto &&val = data.valueAt(index, *measure)) + value = *val; if (enabled) { stat.track(value); } } diff --git a/src/chart/generator/plot.cpp b/src/chart/generator/plot.cpp index c2f78f7ab..b56fa12af 100644 --- a/src/chart/generator/plot.cpp +++ b/src/chart/generator/plot.cpp @@ -319,7 +319,11 @@ void Plot::calcMeasureAxis(ChannelId type, } else { auto colIndex = scale.measureId->getColIndex(); - axis = {stats.channels[type].range, + auto range = stats.channels[type].range; + if (!range.isReal()) + range = Math::Range::Raw(0.0, 0.0); + + axis = {range, colIndex ? dataTable.getInfo(colIndex.value()).getUnit() : std::string{}, diff --git a/src/data/datacube/aggregator.cpp b/src/data/datacube/aggregator.cpp index 35f8ee2d9..5d5968db2 100644 --- a/src/data/datacube/aggregator.cpp +++ b/src/data/datacube/aggregator.cpp @@ -83,6 +83,19 @@ Aggregator &Aggregator::add(const Aggregator &other) bool Aggregator::isEmpty() const { return count == 0; } -Vizzu::Data::Aggregator::operator double() const { return value; } +Vizzu::Data::Aggregator::operator bool() const +{ + switch (type) { + case Exists: + case Sum: + case Count: + case Distinct: return true; + case Min: + case Max: + case Mean: return !isEmpty(); + } +} + +double Aggregator::operator*() const { return value; } } \ No newline at end of file diff --git a/src/data/datacube/aggregator.h b/src/data/datacube/aggregator.h index 7a07989db..8f15dc60b 100644 --- a/src/data/datacube/aggregator.h +++ b/src/data/datacube/aggregator.h @@ -24,7 +24,8 @@ class Aggregator explicit Aggregator(Type type); Aggregator &add(double); Aggregator &add(const Aggregator &); - explicit operator double() const; + [[nodiscard]] explicit operator bool() const; + [[nodiscard]] double operator*() const; [[nodiscard]] bool isEmpty() const; private: diff --git a/src/data/datacube/datacube.cpp b/src/data/datacube/datacube.cpp index 095be956c..855315bb8 100644 --- a/src/data/datacube/datacube.cpp +++ b/src/data/datacube/datacube.cpp @@ -219,9 +219,11 @@ CellInfo::Values DataCube::values( if (series.getType() == SeriesType::Exists) continue; - auto value = static_cast(cell.subCells[i]); - - res.emplace_back(series, value); + if (auto &&val = cell.subCells[i]) + res.emplace_back(series, *val); + else + res.emplace_back(series, + std::numeric_limits::quiet_NaN()); } return res; } diff --git a/test/e2e/tests/fixes/53978116.mjs b/test/e2e/tests/fixes/53978116.mjs new file mode 100644 index 000000000..698644413 --- /dev/null +++ b/test/e2e/tests/fixes/53978116.mjs @@ -0,0 +1,20 @@ +const testSteps = [ + (chart) => { + const data = { + series: [ + { name: 'Foo', values: ['Alice', 'Alice', 'Ted'] }, + { name: 'Bar', values: ['x', 'y', 'z'] }, + { name: 'Baz', values: [5, 3, 2] } + ] + } + return chart.animate({ data }) + }, + (chart) => + chart.animate({ + x: 'Foo', + y: ['min(Baz)', 'Bar'], + color: 'Bar' + }) +] + +export default testSteps From 146a1ff03e9aa34177ac46d12f70972558db909c Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 21 Feb 2024 16:17:34 +0100 Subject: [PATCH 044/253] usual test format fix --- test/e2e/tests/fixes/53978116.mjs | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/e2e/tests/fixes/53978116.mjs b/test/e2e/tests/fixes/53978116.mjs index 698644413..019e7c448 100644 --- a/test/e2e/tests/fixes/53978116.mjs +++ b/test/e2e/tests/fixes/53978116.mjs @@ -1,20 +1,20 @@ const testSteps = [ - (chart) => { - const data = { - series: [ - { name: 'Foo', values: ['Alice', 'Alice', 'Ted'] }, - { name: 'Bar', values: ['x', 'y', 'z'] }, - { name: 'Baz', values: [5, 3, 2] } - ] - } - return chart.animate({ data }) - }, - (chart) => - chart.animate({ - x: 'Foo', - y: ['min(Baz)', 'Bar'], - color: 'Bar' - }) + (chart) => { + const data = { + series: [ + { name: 'Foo', values: ['Alice', 'Alice', 'Ted'] }, + { name: 'Bar', values: ['x', 'y', 'z'] }, + { name: 'Baz', values: [5, 3, 2] } + ] + } + return chart.animate({ data }) + }, + (chart) => + chart.animate({ + x: 'Foo', + y: ['min(Baz)', 'Bar'], + color: 'Bar' + }) ] export default testSteps From 96ce48be89722ce2464a90c0622b38f56a6ac54c Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 21 Feb 2024 16:26:27 +0100 Subject: [PATCH 045/253] fix clang error --- src/data/datacube/aggregator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/datacube/aggregator.cpp b/src/data/datacube/aggregator.cpp index 5d5968db2..dc63bf633 100644 --- a/src/data/datacube/aggregator.cpp +++ b/src/data/datacube/aggregator.cpp @@ -86,6 +86,7 @@ bool Aggregator::isEmpty() const { return count == 0; } Vizzu::Data::Aggregator::operator bool() const { switch (type) { + default: case Exists: case Sum: case Count: From 6414d6d841d727a78d9aa85752c15283511e206f Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 22 Feb 2024 10:06:30 +0100 Subject: [PATCH 046/253] review --- CHANGELOG.md | 2 +- src/chart/generator/marker.cpp | 12 +++++------- src/data/datacube/aggregator.cpp | 15 ++------------- src/data/datacube/aggregator.h | 3 +-- src/data/datacube/datacube.cpp | 6 +----- 5 files changed, 10 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10e02a690..05ec0e92c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ - Line chart connector circles color was not contained the alpha channel. - Line chart draws was overwrite the event's settings. - Legend title outerRect was not properly calculated. -- Fixed stacked empty min/max/mean aggregated values. +- Fixed stacked empty min/max aggregated values. ### Added diff --git a/src/chart/generator/marker.cpp b/src/chart/generator/marker.cpp index 233618a6e..3d7173a34 100644 --- a/src/chart/generator/marker.cpp +++ b/src/chart/generator/marker.cpp @@ -197,7 +197,7 @@ double Marker::getValueForChannel(const Channels &channels, auto measure = channel.measureId; - double value = channel.defaultValue; + double value{}; auto id = Id(data, channel.dimensionIds, index); auto &stat = stats.channels[type]; @@ -211,12 +211,10 @@ double Marker::getValueForChannel(const Channels &channels, if (enabled) stat.track(id); } else { - if (channel.stackable) { - if (auto &&val = data.aggregateAt(index, sumBy, *measure)) - value = *val; - } - else if (auto &&val = data.valueAt(index, *measure)) - value = *val; + if (channel.stackable) + value = double{data.aggregateAt(index, sumBy, *measure)}; + else + value = double{data.valueAt(index, *measure)}; if (enabled) { stat.track(value); } } diff --git a/src/data/datacube/aggregator.cpp b/src/data/datacube/aggregator.cpp index dc63bf633..b482f22de 100644 --- a/src/data/datacube/aggregator.cpp +++ b/src/data/datacube/aggregator.cpp @@ -83,20 +83,9 @@ Aggregator &Aggregator::add(const Aggregator &other) bool Aggregator::isEmpty() const { return count == 0; } -Vizzu::Data::Aggregator::operator bool() const +Vizzu::Data::Aggregator::operator double() const { - switch (type) { - default: - case Exists: - case Sum: - case Count: - case Distinct: return true; - case Min: - case Max: - case Mean: return !isEmpty(); - } + return isEmpty() ? 0.0 : value; } -double Aggregator::operator*() const { return value; } - } \ No newline at end of file diff --git a/src/data/datacube/aggregator.h b/src/data/datacube/aggregator.h index 8f15dc60b..03e0c183d 100644 --- a/src/data/datacube/aggregator.h +++ b/src/data/datacube/aggregator.h @@ -24,8 +24,7 @@ class Aggregator explicit Aggregator(Type type); Aggregator &add(double); Aggregator &add(const Aggregator &); - [[nodiscard]] explicit operator bool() const; - [[nodiscard]] double operator*() const; + [[nodiscard]] explicit operator double() const; [[nodiscard]] bool isEmpty() const; private: diff --git a/src/data/datacube/datacube.cpp b/src/data/datacube/datacube.cpp index 855315bb8..2db64e597 100644 --- a/src/data/datacube/datacube.cpp +++ b/src/data/datacube/datacube.cpp @@ -219,11 +219,7 @@ CellInfo::Values DataCube::values( if (series.getType() == SeriesType::Exists) continue; - if (auto &&val = cell.subCells[i]) - res.emplace_back(series, *val); - else - res.emplace_back(series, - std::numeric_limits::quiet_NaN()); + res.emplace_back(series, cell.subCells[i]); } return res; } From e9fcae1e07cf10bde9298264c9cc32660d9b6c2b Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 22 Feb 2024 10:15:11 +0100 Subject: [PATCH 047/253] Add testcase --- test/e2e/tests/fixes.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/e2e/tests/fixes.json b/test/e2e/tests/fixes.json index f352f59c1..664fd7467 100644 --- a/test/e2e/tests/fixes.json +++ b/test/e2e/tests/fixes.json @@ -39,6 +39,9 @@ }, "47977099": { "refs": ["5f58727"] + }, + "53978116": { + "refs": ["d0d0298"] } } } From 536ccd4372f803ed50e2d236c532db428bf345f4 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Thu, 22 Feb 2024 11:10:53 +0100 Subject: [PATCH 048/253] CI: pull images --- tools/ci/gcp/cloudbuild/cloudbuild.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tools/ci/gcp/cloudbuild/cloudbuild.yaml b/tools/ci/gcp/cloudbuild/cloudbuild.yaml index 1debb5f21..41d0dd1c7 100644 --- a/tools/ci/gcp/cloudbuild/cloudbuild.yaml +++ b/tools/ci/gcp/cloudbuild/cloudbuild.yaml @@ -1,8 +1,20 @@ steps: + - name: 'gcr.io/cloud-builders/docker' + id: pull_wasm + waitFor: + - '-' + args: ['pull', 'vizzu/vizzu-dev-wasm:0.9'] + + - name: 'gcr.io/cloud-builders/docker' + id: pull_desktop + waitFor: + - '-' + args: ['pull', 'vizzu/vizzu-dev-desktop:0.9'] + - name: vizzu/vizzu-dev-wasm:0.9 id: init waitFor: - - '-' + - pull_wasm entrypoint: bash args: - '-c' @@ -68,6 +80,7 @@ steps: - name: vizzu/vizzu-dev-desktop:0.9 id: build_desktop_clangformat waitFor: + - pull_desktop - check_src - check_docs - check_tools From 3e3bb9424123515f788b137ce8d1cb492a0fbaa1 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Thu, 22 Feb 2024 11:23:38 +0100 Subject: [PATCH 049/253] update dev dependencies --- package-lock.json | 1466 +++++++++++++++++++++++++++------------------ 1 file changed, 880 insertions(+), 586 deletions(-) diff --git a/package-lock.json b/package-lock.json index e077c3de2..3e71aaa09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,9 +70,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", - "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "dependencies": { "@babel/highlight": "^7.23.4", @@ -154,30 +154,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", - "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", - "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.3", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -202,12 +202,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.4.tgz", - "integrity": "sha512-esuS49Cga3HcThFNebGhlgsrVLkvhqvYDTzgjfFFlHJcIfLe5jFmRRfCQ1KuBfc4Jrtn3ndLgKWAKjBE+IraYQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.4", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -217,14 +217,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -358,23 +358,23 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.4.tgz", - "integrity": "sha512-HfcMizYz10cr3h29VqyfGL6ZWIjTwWfvYBMsBVGwpcbhNGe3wQ1ZXZRPzZoAHhd9OqHadHqjQ89iVKINXnbzuw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.4", - "@babel/types": "^7.23.4" + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" }, "engines": { "node": ">=6.9.0" @@ -466,9 +466,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -655,34 +655,34 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.4.tgz", - "integrity": "sha512-IYM8wSUwunWTB6tFC2dkKZhxbIjHoWemdK+3f8/wq8aKhbUscxD5MX72ubd90fxvFknaLPeGw5ycU84V1obHJg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.4", - "@babel/generator": "^7.23.4", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.4", - "@babel/types": "^7.23.4", - "debug": "^4.1.0", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -699,9 +699,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", - "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", @@ -743,9 +743,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -781,35 +781,79 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -824,9 +868,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -928,6 +972,12 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1230,9 +1280,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1264,9 +1314,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1309,9 +1359,9 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.8.0.tgz", - "integrity": "sha512-TkRHIV6k2D8OlUe8RtG+5jgOF/H98Myx0M6AOafC8DdNVOFiBSFa5cpRDtpm8LXOa9sVwe0+e6Q3FC56X/DZfg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz", + "integrity": "sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==", "dev": true, "dependencies": { "debug": "4.3.4", @@ -1358,9 +1408,9 @@ "dev": true }, "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" @@ -1382,15 +1432,15 @@ "dev": true }, "node_modules/@tsconfig/strictest": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/strictest/-/strictest-2.0.2.tgz", - "integrity": "sha512-jt4jIsWKvUvuY6adJnQJlb/UR7DdjC8CjHI/OaSQruj2yX9/K6+KOvDt/vD6udqos/FUk5Op66CvYT7TBLYO5Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/strictest/-/strictest-2.0.3.tgz", + "integrity": "sha512-MroLvRhMbqtXI5WBSwoomro6OQS4xnCoudUrMb20JO0vLKUs0bAaCEcvM/immEBSJjFAK1l6jW1oAO8q3Ancrg==", "dev": true }, "node_modules/@types/babel__core": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.4.tgz", - "integrity": "sha512-mLnSC22IC4vcWiuObSRjrLd9XcBTGf59vUSoq2jkQDJ/QQ8PMI9rSuzE+aEV8karUMbskw07bKYoUJCKTUaygg==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -1401,9 +1451,9 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", - "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" @@ -1420,9 +1470,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", - "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -1490,18 +1540,18 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", - "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/semver": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", - "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", + "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", "dev": true }, "node_modules/@types/stack-utils": { @@ -1517,9 +1567,9 @@ "dev": true }, "node_modules/@types/yargs": { - "version": "17.0.31", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz", - "integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -1542,16 +1592,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz", - "integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.11.0", - "@typescript-eslint/type-utils": "6.11.0", - "@typescript-eslint/utils": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1577,15 +1627,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz", - "integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.11.0", - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/typescript-estree": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -1605,13 +1655,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz", - "integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1622,13 +1672,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz", - "integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.11.0", - "@typescript-eslint/utils": "6.11.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1649,9 +1699,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz", - "integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1662,16 +1712,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz", - "integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -1689,17 +1740,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz", - "integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.11.0", - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/typescript-estree": "6.11.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -1714,12 +1765,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz", - "integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1765,9 +1816,9 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1915,13 +1966,16 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1962,18 +2016,38 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", "dev": true, "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -2021,17 +2095,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -2060,10 +2135,13 @@ "dev": true }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -2072,20 +2150,20 @@ } }, "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "dev": true, "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, "node_modules/babel-jest": { @@ -2210,6 +2288,13 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.0.tgz", + "integrity": "sha512-Yyyqff4PIFfSuthCZqLlPISTWHmnQxoPuAvkmgzsJEmG3CesdIv6Xweayl0JkCZJSB2yYIdJyEz97tpxNhgjbg==", + "dev": true, + "optional": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2231,9 +2316,9 @@ ] }, "node_modules/basic-ftp": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", - "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz", + "integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==", "dev": true, "engines": { "node": ">=10.0.0" @@ -2279,13 +2364,12 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -2301,9 +2385,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -2320,9 +2404,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -2413,14 +2497,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2445,9 +2534,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001563", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", - "integrity": "sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==", + "version": "1.0.30001589", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz", + "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==", "dev": true, "funding": [ { @@ -2511,13 +2600,13 @@ } }, "node_modules/chromium-bidi": { - "version": "0.4.33", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.33.tgz", - "integrity": "sha512-IxoFM5WGQOIAd95qrSXzJUv4eXIrh+RvU3rwwqIiwYuvfE7U/Llj4fejbsJnjJMUYCuGtVQsY2gv7oGl4aTNSQ==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.8.tgz", + "integrity": "sha512-blqh+1cEQbHBKmok3rVJkBlBxt9beKBgOsxbFgs7UJcoVbbeZ+K7+6liAsjgpc8l1Xd55cQUy14fXZdGSb4zIw==", "dev": true, "dependencies": { "mitt": "3.0.1", - "urlpattern-polyfill": "9.0.0" + "urlpattern-polyfill": "10.0.0" }, "peerDependencies": { "devtools-protocol": "*" @@ -2699,15 +2788,15 @@ "dev": true }, "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "dependencies": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" @@ -2769,9 +2858,9 @@ } }, "node_modules/data-uri-to-buffer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", - "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, "engines": { "node": ">= 14" @@ -2824,17 +2913,20 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -2906,9 +2998,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1203626", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1203626.tgz", - "integrity": "sha512-nEzHZteIUZfGCZtTiS1fRpC8UZmsfD1SiyPvaUNvS13dvKf666OAm8YTi0+Ca3n1nLEyu49Cy4+dPWpaHFJk9g==", + "version": "0.0.1232444", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1232444.tgz", + "integrity": "sha512-pM27vqEfxSxRkTMnF+XCmxSEb6duO5R+t8A9DEEJgy4Wz2RVanje2mmj99B6A3zv2r/qGfYlOvYznUhuokizmg==", "dev": true }, "node_modules/diff-sequences": { @@ -2951,9 +3043,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.588", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.588.tgz", - "integrity": "sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==", + "version": "1.4.679", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.679.tgz", + "integrity": "sha512-NhQMsz5k0d6m9z3qAxnsOR/ebal4NAGsrNVRwcDo4Kc/zQ7KdsTKZUxZoygHcVRb0QDW3waEDIcE3isZ79RP6g==", "dev": true }, "node_modules/emittery": { @@ -2992,6 +3084,15 @@ "once": "^1.4.0" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3002,50 +3103,52 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz", + "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -3054,15 +3157,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true, + "peer": true + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -3096,9 +3227,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -3144,15 +3275,15 @@ } }, "node_modules/eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -3198,10 +3329,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-compat-utils": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", + "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -3290,14 +3434,15 @@ } }, "node_modules/eslint-plugin-es-x": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.3.0.tgz", - "integrity": "sha512-W9zIs+k00I/I13+Bdkl/zG1MEO07G97XjUSQuH117w620SJ6bHtLUmoMvkGA2oYnI/gNdr+G7BONLyYnFaLLEQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", + "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", "dev": true, "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.6.0" + "@eslint-community/regexpp": "^4.6.0", + "eslint-compat-utils": "^0.1.2" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -3310,9 +3455,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "peer": true, "dependencies": { @@ -3332,7 +3477,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -3341,6 +3486,17 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -3364,6 +3520,19 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -3375,16 +3544,17 @@ } }, "node_modules/eslint-plugin-n": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.3.1.tgz", - "integrity": "sha512-w46eDIkxQ2FaTHcey7G40eD+FhTXOdKudDXPUO2n9WNcslze/i/HT2qJ3GXjHngYSGDISIgPNhwGtgoix4zeOw==", + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", + "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", "dev": true, "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "builtins": "^5.0.1", - "eslint-plugin-es-x": "^7.1.0", + "eslint-plugin-es-x": "^7.5.0", "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", "ignore": "^5.2.4", "is-builtin-module": "^3.2.1", "is-core-module": "^2.12.1", @@ -3402,6 +3572,30 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-promise": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", @@ -3459,6 +3653,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3477,6 +3681,18 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -3751,9 +3967,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -3865,9 +4081,9 @@ } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/follow-redirects": { @@ -3953,17 +4169,17 @@ } }, "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=14.14" } }, "node_modules/fs.realpath": { @@ -4041,16 +4257,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4077,13 +4297,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -4106,15 +4327,15 @@ } }, "node_modules/get-uri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", - "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", "dev": true, "dependencies": { "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.0", + "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4", - "fs-extra": "^8.1.0" + "fs-extra": "^11.2.0" }, "engines": { "node": ">= 14" @@ -4146,16 +4367,38 @@ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { - "is-glob": "^4.0.3" + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10.13.0" + "node": "*" } }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -4278,21 +4521,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -4314,12 +4557,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -4329,9 +4572,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -4369,9 +4612,9 @@ } }, "node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { "agent-base": "^7.1.0", @@ -4382,9 +4625,9 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -4436,9 +4679,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -4517,12 +4760,12 @@ "dev": true }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -4530,11 +4773,18 @@ "node": ">= 0.4" } }, - "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", - "dev": true + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -4546,14 +4796,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4703,9 +4955,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { "node": ">= 0.4" @@ -4776,12 +5028,15 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4830,12 +5085,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -4887,14 +5142,14 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", "dev": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" }, @@ -4931,9 +5186,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -5618,6 +5873,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -5673,16 +5934,19 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -5981,15 +6245,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -6113,9 +6380,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/normalize-package-data": { @@ -6185,6 +6452,16 @@ "node": ">=4" } }, + "node_modules/npm-run-all/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/npm-run-all/node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -6248,6 +6525,18 @@ "node": ">=4" } }, + "node_modules/npm-run-all/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/npm-run-all/node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -6342,13 +6631,13 @@ } }, "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, @@ -6378,16 +6667,17 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", - "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", "dev": true, "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "node_modules/object.values": { @@ -6547,13 +6837,12 @@ } }, "node_modules/pac-resolver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", - "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, "dependencies": { "degenerator": "^5.0.0", - "ip": "^1.1.8", "netmask": "^2.0.2" }, "engines": { @@ -6774,6 +7063,15 @@ "node": ">=14.19.0" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6784,9 +7082,9 @@ } }, "node_modules/prettier": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", - "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -6913,32 +7211,35 @@ } }, "node_modules/puppeteer": { - "version": "21.5.2", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-21.5.2.tgz", - "integrity": "sha512-BaAGJOq8Fl6/cck6obmwaNLksuY0Bg/lIahCLhJPGXBFUD2mCffypa4A592MaWnDcye7eaHmSK9yot0pxctY8A==", + "version": "21.11.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-21.11.0.tgz", + "integrity": "sha512-9jTHuYe22TD3sNxy0nEIzC7ZrlRnDgeX3xPkbS7PnbdwYjl2o/z/YuCrRBwezdKpbTDTJ4VqIggzNyeRcKq3cg==", "dev": true, "hasInstallScript": true, "dependencies": { - "@puppeteer/browsers": "1.8.0", - "cosmiconfig": "8.3.6", - "puppeteer-core": "21.5.2" + "@puppeteer/browsers": "1.9.1", + "cosmiconfig": "9.0.0", + "puppeteer-core": "21.11.0" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" }, "engines": { "node": ">=16.13.2" } }, "node_modules/puppeteer-core": { - "version": "21.5.2", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-21.5.2.tgz", - "integrity": "sha512-v4T0cWnujSKs+iEfmb8ccd7u4/x8oblEyKqplqKnJ582Kw8PewYAWvkH4qUWhitN3O2q9RF7dzkvjyK5HbzjLA==", + "version": "21.11.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-21.11.0.tgz", + "integrity": "sha512-ArbnyA3U5SGHokEvkfWjW+O8hOxV1RSJxOgriX/3A4xZRqixt9ZFHD0yPgZQF05Qj0oAqi8H/7stDorjoHY90Q==", "dev": true, "dependencies": { - "@puppeteer/browsers": "1.8.0", - "chromium-bidi": "0.4.33", + "@puppeteer/browsers": "1.9.1", + "chromium-bidi": "0.5.8", "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1203626", - "ws": "8.14.2" + "devtools-protocol": "0.0.1232444", + "ws": "8.16.0" }, "engines": { "node": ">=16.13.2" @@ -7041,27 +7342,6 @@ "node": ">=12" } }, - "node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/puppeteer-extra-plugin-user-preferences": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz", @@ -7212,14 +7492,15 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -7377,13 +7658,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -7415,15 +7696,18 @@ ] }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7435,9 +7719,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -7513,9 +7797,9 @@ "dev": true }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -7537,29 +7821,32 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7638,9 +7925,9 @@ } }, "node_modules/shiki": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", - "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", + "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", "dev": true, "dependencies": { "ansi-sequence-parser": "^1.1.0", @@ -7650,14 +7937,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7701,16 +7992,16 @@ "dev": true }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.3.tgz", + "integrity": "sha512-vfuYK48HXCTFD03G/1/zkIls3Ebr2YNa4qU9gHDZdblHLiqhJrJGkY3+0Nx0JpN9qBhJbVObc1CNciT1bIZJxw==", "dev": true, "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, @@ -7728,12 +8019,6 @@ "node": ">= 14" } }, - "node_modules/socks/node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "dev": true - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7764,9 +8049,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -7780,15 +8065,15 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, "node_modules/stack-utils": { @@ -7822,13 +8107,16 @@ } }, "node_modules/streamx": { - "version": "2.15.5", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.5.tgz", - "integrity": "sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", + "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", "dev": true, "dependencies": { "fast-fifo": "^1.1.0", "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, "node_modules/string-length": { @@ -8007,9 +8295,9 @@ } }, "node_modules/tar-stream": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", - "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "dependencies": { "b4a": "^1.6.4", @@ -8018,9 +8306,9 @@ } }, "node_modules/terser": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", - "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.2.tgz", + "integrity": "sha512-sHXmLSkImesJ4p5apTeT63DsV4Obe1s37qT8qvwHRmVxKTBH7Rv9Wr26VcAMmLbmk9UliiwK8z+657NyJHHy/w==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -8059,6 +8347,28 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8114,21 +8424,21 @@ "dev": true }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "peer": true, "dependencies": { @@ -8189,9 +8499,9 @@ } }, "node_modules/type-fest": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.8.1.tgz", - "integrity": "sha512-ShaaYnjf+0etG8W/FumARKMjjIToy/haCaTjN2dvcewOSoNqCQzdgG7m2JVOlM5qndGTHjkvsrWZs+k/2Z7E0Q==", + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.3.tgz", + "integrity": "sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==", "engines": { "node": ">=16" }, @@ -8213,29 +8523,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -8245,16 +8556,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -8264,29 +8576,35 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typedoc": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.3.tgz", - "integrity": "sha512-Ow8Bo7uY1Lwy7GTmphRIMEo6IOZ+yYUyrc8n5KXIZg1svpqhZSWgni2ZrDhe+wLosFS8yswowUzljTAV/3jmWw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.8.tgz", + "integrity": "sha512-mh8oLW66nwmeB9uTa0Bdcjfis+48bAjSH3uqdzSuSawfduROQLlXw//WSNZLYDdhmMVB7YcYZicq6e8T0d271A==", "dev": true, "dependencies": { "lunr": "^2.3.9", "marked": "^4.3.0", "minimatch": "^9.0.3", - "shiki": "^0.14.1" + "shiki": "^0.14.7" }, "bin": { "typedoc": "bin/typedoc" @@ -8295,7 +8613,7 @@ "node": ">= 16" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x" } }, "node_modules/typedoc-plugin-markdown": { @@ -8319,34 +8637,10 @@ "typedoc": "0.22.x || 0.23.x || 0.24.x || 0.25.x" } }, - "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8414,12 +8708,12 @@ } }, "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { - "node": ">= 4.0.0" + "node": ">= 10.0.0" } }, "node_modules/unpipe": { @@ -8471,9 +8765,9 @@ } }, "node_modules/urlpattern-polyfill": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz", - "integrity": "sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", "dev": true }, "node_modules/utils-merge": { @@ -8486,9 +8780,9 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", - "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -8654,16 +8948,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -8715,9 +9009,9 @@ } }, "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { "node": ">=10.0.0" From 30dfe227254e5091569f10cb4b33eac51bf69c1c Mon Sep 17 00:00:00 2001 From: David Vegh Date: Thu, 22 Feb 2024 11:23:47 +0100 Subject: [PATCH 050/253] fix prettier --- src/apps/weblib/ts-api/events.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/weblib/ts-api/events.ts b/src/apps/weblib/ts-api/events.ts index bfa9ad3c2..ccfebf120 100644 --- a/src/apps/weblib/ts-api/events.ts +++ b/src/apps/weblib/ts-api/events.ts @@ -390,8 +390,8 @@ export class Events { const eventParam = this._isJSEvent(eventName) ? this._makeJSEventParam(param, state) : cEvent - ? this._makeCEventParam(cEvent, param, state) - : param + ? this._makeCEventParam(cEvent, param, state) + : param handler(eventParam) } } From 6f97691fef34050de82a9f07875047e79512f174 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Thu, 22 Feb 2024 12:05:27 +0100 Subject: [PATCH 051/253] Fix typescript error --- src/apps/weblib/ts-api/plugins/presets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/weblib/ts-api/plugins/presets.ts b/src/apps/weblib/ts-api/plugins/presets.ts index 1b3dce1be..b02e49469 100644 --- a/src/apps/weblib/ts-api/plugins/presets.ts +++ b/src/apps/weblib/ts-api/plugins/presets.ts @@ -99,7 +99,7 @@ export default class Presets { ;['legend', 'title', 'subtitle', 'caption', 'reverse', 'sort'].forEach((key) => { const prop = key as keyof Config.Chart if (config[prop] !== undefined) { - base[prop] = config[prop] as undefined + base[prop] = config[prop] as never } }) } From 96bfd8629275d811fa60daadc1614bd293321b46 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Thu, 22 Feb 2024 14:15:02 +0100 Subject: [PATCH 052/253] Fix TS never --- src/apps/weblib/ts-api/plugins/presets.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/apps/weblib/ts-api/plugins/presets.ts b/src/apps/weblib/ts-api/plugins/presets.ts index b02e49469..c6e7e2c91 100644 --- a/src/apps/weblib/ts-api/plugins/presets.ts +++ b/src/apps/weblib/ts-api/plugins/presets.ts @@ -95,12 +95,17 @@ export default class Presets { return typeof channel === 'object' && channel !== null && 'set' in channel } + private _assignProperty(base: T, config: T, prop: K): void { + const value = config[prop]; + if (value !== undefined) { + base[prop] = value; + } + } + private _setupUserParams(base: Config.Chart, config: Config.Chart): void { ;['legend', 'title', 'subtitle', 'caption', 'reverse', 'sort'].forEach((key) => { const prop = key as keyof Config.Chart - if (config[prop] !== undefined) { - base[prop] = config[prop] as never - } + this._assignProperty(base, config, prop) }) } From 4c5b5e0bb74cba3f88b54c36ad74b4e543a26aba Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Thu, 22 Feb 2024 14:24:11 +0100 Subject: [PATCH 053/253] format fix --- src/apps/weblib/ts-api/plugins/presets.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/weblib/ts-api/plugins/presets.ts b/src/apps/weblib/ts-api/plugins/presets.ts index c6e7e2c91..03280f882 100644 --- a/src/apps/weblib/ts-api/plugins/presets.ts +++ b/src/apps/weblib/ts-api/plugins/presets.ts @@ -96,9 +96,9 @@ export default class Presets { } private _assignProperty(base: T, config: T, prop: K): void { - const value = config[prop]; + const value = config[prop] if (value !== undefined) { - base[prop] = value; + base[prop] = value } } From 1044d8354ede9d02b855bbbc8d9f6c2b1b46959f Mon Sep 17 00:00:00 2001 From: David Vegh Date: Thu, 22 Feb 2024 15:44:07 +0100 Subject: [PATCH 054/253] test client detach vizzu --- test/e2e/modules/e2e-test/client/index.js | 24 +++++++++++++++---- .../cookbook/exports/video_export.mjs | 4 +--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/test/e2e/modules/e2e-test/client/index.js b/test/e2e/modules/e2e-test/client/index.js index 7d4cb8373..1e3bdb054 100644 --- a/test/e2e/modules/e2e-test/client/index.js +++ b/test/e2e/modules/e2e-test/client/index.js @@ -1,4 +1,5 @@ -function catchError(err) { +function catchError(err, chart) { + detach(chart) console.error(err) let errMsg = err.toString() if (err.stack !== undefined) { @@ -37,10 +38,21 @@ function getTestSteps(testCasesModule, testType, testIndex) { return testSteps } +function detach(chart) { + try { + if (chart !== undefined) { + chart.detach() + } + } catch (err) { + console.error(err) + } +} + window.addEventListener('error', (event) => { catchError(event.error) }) +let chart try { const queryString = window.location.search const urlParams = new URLSearchParams(queryString) @@ -61,8 +73,9 @@ try { for (let seek = parseFloat(animStep); seek <= 100; seek += parseFloat(animStep)) { seeks.push(seek) } - const chart = new Vizzu('vizzuCanvas') - return chart.initializing.then((chart) => { + chart = new Vizzu('vizzuCanvas') + const initializing = chart.initializing + return initializing.then((chart) => { let promise = Promise.resolve(chart) const promises = [] const testSteps = getTestSteps(testCasesModule.default, testType, testIndex) @@ -108,6 +121,7 @@ try { }) } return promise.then(() => { + chart.detach() return Promise.all(promises).then(() => { testData.hashes.forEach((items) => { testData.hash += items.join('') @@ -153,8 +167,8 @@ try { }) }) .catch((err) => { - catchError(err) + catchError(err, chart) }) } catch (err) { - catchError(err) + catchError(err, chart) } diff --git a/test/e2e/test_cases/web_content/cookbook/exports/video_export.mjs b/test/e2e/test_cases/web_content/cookbook/exports/video_export.mjs index caf8f4d59..5b75506f0 100644 --- a/test/e2e/test_cases/web_content/cookbook/exports/video_export.mjs +++ b/test/e2e/test_cases/web_content/cookbook/exports/video_export.mjs @@ -19,12 +19,10 @@ const testSteps = [ chart.feature.videoCapture.start() }) - anim.then(async (chart) => { + return anim.then(async (chart) => { const output = await chart.feature.videoCapture.stop() window.open(output.getObjectURL()) }) - - return anim } ] From 6ee098e055b13fbe8c5b47b0aec2a1fa0a000a47 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Fri, 23 Feb 2024 09:03:51 +0100 Subject: [PATCH 055/253] update dev dependecies --- package-lock.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e71aaa09..231568ff3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1540,9 +1540,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", - "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", + "version": "20.11.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", + "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -3043,9 +3043,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.679", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.679.tgz", - "integrity": "sha512-NhQMsz5k0d6m9z3qAxnsOR/ebal4NAGsrNVRwcDo4Kc/zQ7KdsTKZUxZoygHcVRb0QDW3waEDIcE3isZ79RP6g==", + "version": "1.4.680", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.680.tgz", + "integrity": "sha512-4nToZ5jlPO14W82NkF32wyjhYqQByVaDmLy4J2/tYcAbJfgO2TKJC780Az1V13gzq4l73CJ0yuyalpXvxXXD9A==", "dev": true }, "node_modules/emittery": { @@ -7992,9 +7992,9 @@ "dev": true }, "node_modules/socks": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.3.tgz", - "integrity": "sha512-vfuYK48HXCTFD03G/1/zkIls3Ebr2YNa4qU9gHDZdblHLiqhJrJGkY3+0Nx0JpN9qBhJbVObc1CNciT1bIZJxw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", + "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", "dev": true, "dependencies": { "ip-address": "^9.0.5", From 545b03c5184b6b05b09cdf4ce57005ccdc2a5292 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 26 Feb 2024 09:56:01 +0100 Subject: [PATCH 056/253] tried to format interface tests. rename custom sort. Throws with invoke --- src/dataframe/interface.h | 2 +- test/unit/dataframe/interface_test.cpp | 1292 +++++++++++++----------- test/unit/util/condition.h | 44 +- 3 files changed, 715 insertions(+), 623 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index cf0430230..023269bd2 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -95,7 +95,7 @@ class dataframe_interface : any_sort_type sort, na_position na_pos) & = 0; - virtual void set_sort( + virtual void set_custom_sort( std::function custom_sort) & = 0; diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index 8364e997d..07aa51fde 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -92,19 +92,19 @@ static inline const auto empty_input = input{[] { return setup{}; }, - "empty_input"}; + "empty input"}; static inline const auto empty_input_copied = input{[] { return setup{.copied = true}; }, - "empty_input_copied"}; + "empty input copied"}; static inline const auto one_one_empty = input{[] { return setup{.dims = {"test_dim"}, .meas = {"test_meas"}}; }, - "one_one_empty"}; + "one one empty"}; static inline const auto one_one_empty_copied = input{[] { @@ -112,637 +112,709 @@ static inline const auto one_one_empty_copied = input{[] .meas = {"test_meas"}, .copied = true}; }, - "one_one_empty_copied"}; + "one one empty copied"}; -// clang-format off const static auto tests = "DataFrame::interface"_suite - | "construct_empty" | - empty_input + empty_input_copied - <=> [] (interface* df) - { - check ->* df->get_dimensions().size() == std::size_t{}; - check ->* df->get_measures().size() == std::size_t{}; - check ->* df->get_record_count() == std::size_t{}; - - throw_ ->* [&df] { df->add_record({}); }; - throw_ ->* [&df] { return df->get_data({}, {}); }; - throw_ ->* [&df] { return df->get_categories({}); }; - throw_ ->* [&df] { return df->get_min_max({}); }; - throw_ ->* [&df] - { - return df->add_series_by_other( - {}, - "", - [](auto, cell_value c) -> cell_value - { - return c; - }, {}); - }; - - throw_ ->* [&df] { return df->set_aggregate({}, {}); }; - throw_ ->* [&df] { return df->set_sort({}, {}, {}); }; - - check ->* df->get_dimensions().size() == std::size_t{}; - check ->* df->get_measures().size() == std::size_t{}; - check ->* df->get_record_count() == std::size_t{}; - } - - | "add_dimension_and_measure" | - empty_input + empty_input_copied - <=> [] (interface* df) - { - constexpr auto nan = std::numeric_limits::quiet_NaN(); - - df->add_dimension({{"t2", "t1", "tt3"}}, {{0, 0, 2}}, "d1", {}, {}); - df->add_dimension({{"a"}}, {{0}}, "d0", {}, {}); - - df->add_measure({{0.0, 22.5, nan, 6.0}}, "m1", {}, {}); - df->add_measure({{1.0}}, "m2", {}, {}); - - assert ->* df->get_dimensions() == std::array{"d0", "d1"}; - assert ->* df->get_measures() == std::array{"m1", "m2"}; - - check ->* df->get_categories("d0") == std::array{"a"}; - - assert ->* df->get_categories("d1") - == std::array{"t2", "t1", "tt3"}; - - check ->* df->get_min_max("m1") - == std::pair{0.0, 22.5}; - - check ->* df->get_min_max("m2") - == std::pair{1.0, 1.0}; - - df->finalize(); - - assert ->* df->get_record_count() == std::size_t{4}; - - auto check_nan = [] (cell_value const& cell, std::string&& prefix) { - auto str = prefix + " is a double"; - assert ->* std::holds_alternative(cell) == - bool_msg{str}; - str = prefix + " is nan"; - check ->* std::isnan(std::get(cell)) == - bool_msg{str}; - }; - - auto check_nav = [] (cell_value const& cell, std::string&& prefix) { - auto str = prefix + " is a string"; - assert ->* std::holds_alternative(cell) == - bool_msg{str}; - str = prefix + " is nav"; - check ->* (std::get(cell).data() == nullptr) == - bool_msg{str}; - }; - - check ->* df->get_data(std::size_t{0}, "m1") == 0.0; - check ->* df->get_data(std::size_t{1}, "m1") == 22.5; - check_nan(df->get_data(std::size_t{2}, "m1"), "table_20"); - check ->* df->get_data(std::size_t{3}, "m1") == 6.0; - - check ->* df->get_data(std::size_t{0}, "m2") == 1.0; - check_nan(df->get_data(std::size_t{1}, "m2"), "table_11"); - check_nan(df->get_data(std::size_t{2}, "m2"), "table_21"); - check_nan(df->get_data(std::size_t{3}, "m2"), "table_31"); - - check ->* df->get_data(std::size_t{0}, "d0") == "a"; - check_nav(df->get_data(std::size_t{1}, "d0"), "table_12"); - check_nav(df->get_data(std::size_t{2}, "d0"), "table_22"); - check_nav(df->get_data(std::size_t{3}, "d0"), "table_32"); - - assert->* df->get_data(std::size_t{0}, "d1") == "t2"; - check ->* df->get_data(std::size_t{1}, "d1") == "t2"; - check ->* df->get_data(std::size_t{2}, "d1") == "tt3"; - check_nav(df->get_data(std::size_t{3}, "d1"), "table_33"); - - check ->* (std::get(df->get_data(std::size_t{0}, "d1")).data() == - df->get_categories("d1")[0].data()) == - "Not points to the same memory address"_is_true; - } - - | "add_record" | - one_one_empty + one_one_empty_copied - <=> [] (interface* df) - { - df->add_record({{"test_dim_val", 2.0}}); - df->add_record({{-1.0, "test_dim_val2"}}); - - throw_ ->* [&df] { df->add_record({}); }; - throw_ ->* [&df] { df->add_record({{0.0}}); }; - throw_ ->* [&df] { df->add_record({{0.0, 0.0}}); }; - throw_ ->* [&df] { df->add_record({{"test", "t"}}); }; - throw_ ->* [&df] - { - df->add_record({{0.0, "test", 0.0}}); - }; - throw_ ->* [&df] + | "construct_empty" + | empty_input + empty_input_copied <=> + [](interface *df) +{ + check->*df->get_dimensions().size() == std::size_t{}; + check->*df->get_measures().size() == std::size_t{}; + check->*df->get_record_count() == std::size_t{}; + + throw_(&interface::add_record, df, {}); + throw_(&interface::get_data, df, {}, {}); + throw_(&interface::get_categories, df, {}); + throw_(&interface::get_min_max, df, {}); + throw_(&interface::add_series_by_other, + df, + {}, + {""}, + {[](record_type, cell_value c) -> cell_value { - df->add_record({{0.0, "test", "test"}}); - }; - - df->finalize(); - - assert ->* df->get_record_count() == std::size_t{2}; - - check ->* df->get_categories("test_dim") - == std::array{"test_dim_val", "test_dim_val2"}; - - check ->* df->get_min_max("test_meas") == std::pair{-1.0, 2.0}; - - check ->* df->get_data({}, "test_meas") == 2.0; - check ->* df->get_data({}, "test_dim") - == "test_dim_val"; - - check ->* df->get_data(std::size_t{1}, "test_meas") == -1.0; - check ->* df->get_data(std::size_t{1}, "test_dim") - == "test_dim_val2"; - } - - | "add_series_by_other" | - [] (interface* df = setup{{"d1", "d2"}, {"m1"}, { - {{"dm1", "dm2", 0.0}}, - {{"dm1", "dmX", 1.0}}, - {{"s1", "s2", -1.0}}, - {{"s1", "s3", 3.0}} - }}) { - - df->add_series_by_other( - "m1", - "m0", - [](auto, cell_value c) -> cell_value - { - const double* v = std::get_if(&c); - assert ->* static_cast(v) - == "value is a double"_is_true; - - return *v * 2; - }, {}); - - assert ->* df->get_measures() == std::array{"m0", "m1"}; - - check ->* df->get_data(std::size_t{0}, "m0") == 0.0; - check ->* df->get_data(std::size_t{1}, "m0") == 2.0; - check ->* df->get_data(std::size_t{2}, "m0") == -2.0; - check ->* df->get_data(std::size_t{3}, "m0") == 6.0; - - df->add_series_by_other( - "d1", - "d15", - [](const record_type& r, cell_value c) -> cell_value - { - auto v = std::get_if(&c); - skip ->* static_cast(v) - == "value is string"_is_true; - - auto oth_v = r.getValue("d2"); - - auto v2 = std::get_if(&oth_v); - skip ->* static_cast(v2) - == "value is string"_is_true; - - thread_local std::string val; - val = std::string{*v} + "5" + std::string{*v2}; - - return val; - }, {}); - - assert ->* df->get_dimensions() == std::array{"d1", "d15", "d2"}; - - check ->* df->get_data(std::size_t{0}, "d15") == "dm15dm2"; - check ->* df->get_data(std::size_t{1}, "d15") == "dm15dmX"; - check ->* df->get_data(std::size_t{2}, "d15") == "s15s2"; - check ->* df->get_data(std::size_t{3}, "d15") == "s15s3"; - } - - | "remove_series" | - [] (interface* df = setup{{"d1", "d2", "d3"}, {"m1", "m2", "m3"}, { - {{"dm1", "dx2", "dm3", 0.0, 0.1, 0.2}}, - {{"dm1", "dx2", "am3", 1.0, 2.1, 3.2}}, - {{"dm2", "dm2", "bm3", 1.0, 1.5, 1.2}}, - }}) { - df->remove_series({{"m1", "d2", "m3"}}); + return c; + }}, + {}); + throw_(&interface::set_aggregate, df, {}, {}); + throw_(&interface::set_sort, df, {}, {}, {}); + + check->*df->get_dimensions().size() == std::size_t{}; + check->*df->get_measures().size() == std::size_t{}; + check->*df->get_record_count() == std::size_t{}; +} + + | "add_dimension_and_measure" + | empty_input + empty_input_copied <=> + [](interface *df) +{ + constexpr auto nan = std::numeric_limits::quiet_NaN(); - assert ->* df->get_measures() == std::array{"m2"}; - assert ->* df->get_dimensions() == std::array{"d1", "d3"}; - assert ->* df->get_record_count() == std::size_t{3}; + df->add_dimension({{"t2", "t1", "tt3"}}, + {{0, 0, 2}}, + "d1", + {}, + {}); + df->add_dimension({{"a"}}, {{0}}, "d0", {}, {}); - check ->* df->get_data(std::size_t{2}, "m2") == 1.5; - check ->* df->get_data(std::size_t{0}, "d3") == "dm3"; - } + df->add_measure({{0.0, 22.5, nan, 6.0}}, "m1", {}, {}); + df->add_measure({{1.0}}, "m2", {}, {}); - | "remove_records" | - [] (interface* df = setup{{"d1"}, {"m1"}, { - {{"dm0", NAN}}, - {{"dm1", NAN}}, - {{"dm2", NAN}}, - {{"dm3", NAN}}, - {{"dm4", NAN}}, - {{"dm5", NAN}}, - {{"dm6", NAN}}, - {{"dm7", NAN}}, - {{"dm8", 4.2}}, - {{"dm9", NAN}}, - }}) { - df->remove_records({{0ul, 2ul, 4ul, 5ul, 8ul, 9ul}}); - - assert ->* df->get_measures() == std::array{"m1"}; - assert ->* df->get_dimensions() == std::array{"d1"}; - assert ->* df->get_record_count() == std::size_t{4}; - - check ->* std::isnan(std::get(df->get_data(std::size_t{2}, "m1"))) - == "is nan"_is_true; - check ->* df->get_data(std::size_t{0}, "d1") == "dm6"; - check ->* df->get_data(std::size_t{1}, "d1") == "dm1"; - check ->* df->get_data(std::size_t{2}, "d1") == "dm7"; - check ->* df->get_data(std::size_t{3}, "d1") == "dm3"; - } + assert->*df->get_dimensions() == std::array{"d0", "d1"}; + assert->*df->get_measures() == std::array{"m1", "m2"}; - | "remove_records_filter" | - [] (interface* df = setup{{"d1"}, {"m1"}, { - {{"dm0", 5.3}}, - {{"dm1", 2.0}}, - {{"dm2", 3.3}}, - {{"dm3", 10.1}}, - {{"dm4", 88.0}}, - {{"dm5", 2.2}}, - {{"dm6", 7.4}}, - {{"dm7", 0.0}}, - {{"dm8", 4.2}}, - {{"dm9", NAN}}, - }}) { - df->remove_records( - [] (record_type r) -> bool - { - auto v = r.getValue("m1"); - return *std::get_if(&v) < 5.0; - }); - - assert ->* df->get_record_count() == std::size_t{5}; - - check ->* df->get_data(std::size_t{0}, "d1") == "dm0"; - check ->* df->get_data(std::size_t{1}, "d1") == "dm9"; - check ->* df->get_data(std::size_t{2}, "d1") == "dm6"; - check ->* df->get_data(std::size_t{3}, "d1") == "dm3"; - check ->* df->get_data(std::size_t{4}, "d1") == "dm4"; - } + check->*df->get_categories("d0") == std::array{"a"}; - | "change_data" | - [] (interface* df = setup{{"d1"}, {"m1"}, { - {{"dm0", 5.3}}, - {{"dm1", 2.0}}, - {{"dm2", 3.3}} - }}) { - df->change_data(std::size_t{1}, "m1", 3.0); - df->change_data(std::size_t{2}, "d1", "dmX"); + assert->*df->get_categories("d1") + == std::array{"t2", "t1", "tt3"}; - throw_ ->* [&df] { df->change_data(std::size_t{0}, "d1", NAN); }; - throw_ ->* [&df] { df->change_data(std::size_t{0}, "m1", ""); }; + check->*df->get_min_max("m1") == std::pair{0.0, 22.5}; - assert ->* df->get_record_count() == std::size_t{3}; + check->*df->get_min_max("m2") == std::pair{1.0, 1.0}; - check ->* df->get_data(std::size_t{0}, "m1") == 5.3; - check ->* df->get_data(std::size_t{1}, "m1") == 3.0; - check ->* df->get_data(std::size_t{2}, "m1") == 3.3; + df->finalize(); - check ->* df->get_data(std::size_t{0}, "d1") == "dm0"; - check ->* df->get_data(std::size_t{1}, "d1") == "dm1"; - check ->* df->get_data(std::size_t{2}, "d1") == "dmX"; - } + assert->*df->get_record_count() == std::size_t{4}; - | "fill_na" | - [] (interface* df = setup{{"d1"}, {"m1"}, { - {{"dm0", 5.3}}, - {{std::string_view{nullptr, 0}, 2.0}}, - {{"dm2", NAN}} - }}) { - df->fill_na("m1", 3.0); - df->fill_na("d1", "dmX"); + auto check_nan = [](cell_value const &cell, std::string &&prefix) + { + auto str = prefix + " is a double"; + assert->*std::holds_alternative(cell) + == bool_msg{str}; + str = prefix + " is nan"; + check->*std::isnan(std::get(cell)) == bool_msg{str}; + }; - assert ->* df->get_record_count() == std::size_t{3}; + auto check_nav = [](cell_value const &cell, std::string &&prefix) + { + auto str = prefix + " is a string"; + assert->*std::holds_alternative(cell) + == bool_msg{str}; + str = prefix + " is nav"; + check->*(std::get(cell).data() == nullptr) + == bool_msg{str}; + }; - check ->* df->get_data(std::size_t{0}, "m1") == 5.3; - check ->* df->get_data(std::size_t{1}, "m1") == 2.0; - check ->* df->get_data(std::size_t{2}, "m1") == 3.0; + check->*df->get_data(std::size_t{0}, "m1") == 0.0; + check->*df->get_data(std::size_t{1}, "m1") == 22.5; + check_nan(df->get_data(std::size_t{2}, "m1"), "table_20"); + check->*df->get_data(std::size_t{3}, "m1") == 6.0; + + check->*df->get_data(std::size_t{0}, "m2") == 1.0; + check_nan(df->get_data(std::size_t{1}, "m2"), "table_11"); + check_nan(df->get_data(std::size_t{2}, "m2"), "table_21"); + check_nan(df->get_data(std::size_t{3}, "m2"), "table_31"); + + check->*df->get_data(std::size_t{0}, "d0") == "a"; + check_nav(df->get_data(std::size_t{1}, "d0"), "table_12"); + check_nav(df->get_data(std::size_t{2}, "d0"), "table_22"); + check_nav(df->get_data(std::size_t{3}, "d0"), "table_32"); + + assert->*df->get_data(std::size_t{0}, "d1") == "t2"; + check->*df->get_data(std::size_t{1}, "d1") == "t2"; + check->*df->get_data(std::size_t{2}, "d1") == "tt3"; + check_nav(df->get_data(std::size_t{3}, "d1"), "table_33"); + + check + ->*(std::get( + df->get_data(std::size_t{0}, "d1")) + .data() + == df->get_categories("d1")[0].data()) + == "Not points to the same memory address"_is_true; +} + + | "add_record" + | one_one_empty + one_one_empty_copied <=> + [](interface *df) +{ + df->add_record({{"test_dim_val", 2.0}}); + df->add_record({{-1.0, "test_dim_val2"}}); - check ->* df->get_data(std::size_t{0}, "d1") == "dm0"; - check ->* df->get_data(std::size_t{1}, "d1") == "dmX"; - check ->* df->get_data(std::size_t{2}, "d1") == "dm2"; - } + throw_(&interface::add_record, df, {}); + throw_(&interface::add_record, df, {{0.0}}); + throw_(&interface::add_record, df, {{0.0, 0.0}}); + throw_(&interface::add_record, df, {{"test", "t"}}); + throw_(&interface::add_record, df, {{0.0, "test", 0.0}}); + throw_(&interface::add_record, df, {{0.0, "test", "t"}}); - | "aggregate types" | - [] (interface* df = setup{{"d1"}, {"m1"}, { - {{"dm0", 5.5}}, - {{"dm0", 2.0}}, - {{"dm0", 3.5}}, - {{"dm0", 10.25}}, - {{"dm0", 88.0}}, - {{"dm1", 3.5}}, - {{"dm1", 7.25}}, - {{"dm1", NAN}}, - {{"dm1", 4.25}}, - {{"dm2", NAN}}, - {{std::string_view{nullptr, 0}, 0.0}}, - }}) { - df->aggregate_by("d1"); - using enum Vizzu::dataframe::aggregator_type; - auto &&d1c = df->set_aggregate("d1", count); - auto &&d1d = df->set_aggregate("d1", distinct); - auto &&d1e = df->set_aggregate("d1", exists); - auto &&m1s = df->set_aggregate("m1", sum); - auto &&m1mi = df->set_aggregate("m1", min); - auto &&m1ma = df->set_aggregate("m1", max); - auto &&m1me = df->set_aggregate("m1", mean); - auto &&m1c = df->set_aggregate("m1", count); - auto &&m1d = df->set_aggregate("m1", distinct); - auto &&m1e = df->set_aggregate("m1", exists); - - auto &&m1t = df->set_aggregate("m1", Vizzu::dataframe::custom_aggregator{ - std::string_view{"test"}, - [] () -> Vizzu::dataframe::custom_aggregator::id_type - { - return std::pair{ - std::numeric_limits::max(), - std::numeric_limits::max() - }; - }, - [](Vizzu::dataframe::custom_aggregator::id_type &id, double v) -> double - { - auto &[min, min2] = std::any_cast&>(id); - if (v < min) - min2 = std::exchange(min, v); - else if (v < min2) - min2 = v; - return min2; - } - }); - - df->finalize(); - - assert ->* df->get_dimensions() == std::array{"d1"}; - - assert ->* df->get_measures() == std::array{ - d1c, m1c, d1d, m1d, d1e, m1e, m1ma, m1me, m1mi, m1s, m1t - }; - - assert ->* df->get_record_count() == std::size_t{4}; - - check ->* df->get_data(std::size_t{0}, d1c) == 5.0; - check ->* df->get_data(std::size_t{0}, m1c) == 5.0; - check ->* df->get_data(std::size_t{0}, d1d) == 1.0; - check ->* df->get_data(std::size_t{0}, m1d) == 5.0; - check ->* df->get_data(std::size_t{0}, d1e) == 1.0; - check ->* df->get_data(std::size_t{0}, m1e) == 1.0; - check ->* df->get_data(std::size_t{0}, m1ma) == 88.0; - check ->* df->get_data(std::size_t{0}, m1me) == 21.85; - check ->* df->get_data(std::size_t{0}, m1mi) == 2.0; - check ->* df->get_data(std::size_t{0}, m1s) == 109.25; - check ->* df->get_data(std::size_t{0}, m1t) == 3.5; - - check ->* df->get_data(std::size_t{1}, d1c) == 4.0; - check ->* df->get_data(std::size_t{1}, m1c) == 3.0; - check ->* df->get_data(std::size_t{1}, d1d) == 1.0; - check ->* df->get_data(std::size_t{1}, m1d) == 3.0; - check ->* df->get_data(std::size_t{1}, d1e) == 1.0; - check ->* df->get_data(std::size_t{1}, m1e) == 1.0; - check ->* df->get_data(std::size_t{1}, m1ma) == 7.25; - check ->* df->get_data(std::size_t{1}, m1me) == 5.0; - check ->* df->get_data(std::size_t{1}, m1mi) == 3.5; - check ->* df->get_data(std::size_t{1}, m1s) == 15.0; - check ->* df->get_data(std::size_t{1}, m1t) == 4.25; - - check ->* df->get_data(std::size_t{2}, d1c) == 1.0; - check ->* df->get_data(std::size_t{2}, m1c) == 0.0; - check ->* df->get_data(std::size_t{2}, d1d) == 1.0; - check ->* df->get_data(std::size_t{2}, m1d) == 0.0; - check ->* df->get_data(std::size_t{2}, d1e) == 1.0; - check ->* df->get_data(std::size_t{2}, m1e) == 0.0; - check ->* std::isnan(std::get(df->get_data(std::size_t{2}, m1ma))) == "is nan"_is_true; - check ->* std::isnan(std::get(df->get_data(std::size_t{2}, m1me))) == "is nan"_is_true; - check ->* std::isnan(std::get(df->get_data(std::size_t{2}, m1mi))) == "is nan"_is_true; - check ->* df->get_data(std::size_t{2}, m1s) == 0.0; - check ->* df->get_data(std::size_t{2}, m1t) == std::numeric_limits::max(); - - check ->* df->get_data(std::size_t{3}, d1c) == 0.0; - check ->* df->get_data(std::size_t{3}, m1c) == 1.0; - check ->* df->get_data(std::size_t{3}, d1d) == 0.0; - check ->* df->get_data(std::size_t{3}, m1d) == 1.0; - check ->* df->get_data(std::size_t{3}, d1e) == 0.0; - check ->* df->get_data(std::size_t{3}, m1e) == 1.0; - } + df->finalize(); - | "aggregate multiple dim" | - [] (interface* df = setup{{"d1", "d2", "d3"}, {"m1"}, { - {{"dx0", "dm0", "doa", 5.5}}, - {{"dx0", "dm0", "dob", 2.0}}, - {{"dx0", "dm1", std::string_view{nullptr, 0}, 3.5}}, - {{"dx0", "dm1", "dob", 10.25}}, - {{"dx0", "dm0", "doa", 88.0}}, - {{"dx1", "dm0", "doa", 3.5}}, - {{"dx1", "dm1", std::string_view{nullptr, 0}, 7.25}}, - {{"dx1", "dm2", "doa", NAN}}, - {{"dx1", "dm0", "doa", 4.25}}, - {{"dx2", "dm0", "dob", NAN}}, - {{"dx2", "dm0", "doa", 0.5}}, - }}) { - df->aggregate_by("d1"); - df->aggregate_by("d2"); - using enum Vizzu::dataframe::aggregator_type; - auto &&d3c = df->set_aggregate("d3", count); - auto &&d3d = df->set_aggregate("d3", distinct); - - df->finalize(); - - assert ->* df->get_dimensions() == std::array{"d1", "d2"}; - assert ->* df->get_measures() == std::array{d3c, d3d}; - - assert ->* df->get_record_count() == std::size_t{6}; - - check ->* df->get_data(std::size_t{0}, "d1") == "dx0"; - check ->* df->get_data(std::size_t{0}, "d2") == "dm0"; - check ->* df->get_data(std::size_t{0}, d3c) == 3.0; - check ->* df->get_data(std::size_t{0}, d3d) == 2.0; - - check ->* df->get_data(std::size_t{1}, "d1") == "dx0"; - check ->* df->get_data(std::size_t{1}, "d2") == "dm1"; - check ->* df->get_data(std::size_t{1}, d3c) == 1.0; - check ->* df->get_data(std::size_t{1}, d3d) == 1.0; - - check ->* df->get_data(std::size_t{2}, "d1") == "dx1"; - check ->* df->get_data(std::size_t{2}, "d2") == "dm0"; - check ->* df->get_data(std::size_t{2}, d3c) == 2.0; - check ->* df->get_data(std::size_t{2}, d3d) == 1.0; - - check ->* df->get_data(std::size_t{3}, "d1") == "dx1"; - check ->* df->get_data(std::size_t{3}, "d2") == "dm1"; - check ->* df->get_data(std::size_t{3}, d3c) == 0.0; - check ->* df->get_data(std::size_t{3}, d3d) == 0.0; - - check ->* df->get_data(std::size_t{4}, "d1") == "dx1"; - check ->* df->get_data(std::size_t{4}, "d2") == "dm2"; - - check ->* df->get_data(std::size_t{5}, "d1") == "dx2"; - check ->* df->get_data(std::size_t{5}, "d2") == "dm0"; - check ->* df->get_data(std::size_t{5}, d3c) == 2.0; - check ->* df->get_data(std::size_t{5}, d3d) == 2.0; - } + assert->*df->get_record_count() == std::size_t{2}; - | "cannot finalize contains same dim" | - [] (interface* df = setup{.dims = {"d1", "d2"}, .data={ - {{"dx0", "dm0"}}, - {{"dx0", "dm0"}} - }}) { - throw_ ->* [&df] { df->finalize(); }; - } + check->*df->get_categories("test_dim") + == std::array{"test_dim_val", "test_dim_val2"}; - | "sort dimension" | - [] (interface* df = setup{{"d1", "d2"}, {"m1"}, { - {{"dx2", "dm2", 5.5}}, - {{"dx1", "dm0", 2.0}}, - {{std::string_view{nullptr, 0}, "dm1", 3.5}}, - {{"dx0", "dm1", 10.25}}, - {{"dx2", "dm0", 88.0}}, - {{"dx1", "dm0", 3.5}}, - {{"dx2", "dm1", 7.25}}, - {{"dx1", "dm2", NAN}}, - {{"dx0", "dm0", 4.25}}, - {{"dx0", "dm0", NAN}}, - {{"dx2", "dm0", 0.5}}, - }}) { - df->set_sort("d1", {}, {}); - df->set_sort("m1", sort_type::greater, na_position::first); - - // cannot finalize because of duplicated dimensions - df->remove_records(std::span{}); - - assert ->* df->get_record_count() == std::size_t{11}; - - assert ->* df->get_categories("d1") == std::array{"dx0", "dx1", "dx2"}; - check ->* df->get_categories("d2") == std::array{"dm2", "dm0", "dm1"}; - - check ->* df->get_data(std::size_t{0}, "d1") == "dx0"; - check ->* df->get_data(std::size_t{1}, "d1") == "dx0"; - check ->* df->get_data(std::size_t{2}, "d1") == "dx0"; - check ->* df->get_data(std::size_t{3}, "d1") == "dx1"; - check ->* df->get_data(std::size_t{4}, "d1") == "dx1"; - check ->* df->get_data(std::size_t{5}, "d1") == "dx1"; - check ->* df->get_data(std::size_t{6}, "d1") == "dx2"; - check ->* df->get_data(std::size_t{7}, "d1") == "dx2"; - check ->* df->get_data(std::size_t{8}, "d1") == "dx2"; - check ->* df->get_data(std::size_t{9}, "d1") == "dx2"; - check ->* std::get(df->get_data(std::size_t{10}, "d1")).data() == nullptr; - - check ->* std::isnan(std::get(df->get_data(std::size_t{0}, "m1"))) - == "is nan"_is_true; - check ->* df->get_data(std::size_t{1}, "m1") == 10.25; - check ->* df->get_data(std::size_t{2}, "m1") == 4.25; - - check ->* std::isnan(std::get(df->get_data(std::size_t{3}, "m1"))) - == "is nan"_is_true; - check ->* df->get_data(std::size_t{4}, "m1") == 3.5; - check ->* df->get_data(std::size_t{5}, "m1") == 2.0; - - check ->* df->get_data(std::size_t{6}, "m1") == 88.0; - check ->* df->get_data(std::size_t{7}, "m1") == 7.25; - check ->* df->get_data(std::size_t{8}, "m1") == 5.5; - check ->* df->get_data(std::size_t{9}, "m1") == 0.5; - - check ->* df->get_data(std::size_t{10}, "m1") == 3.5; - } + check->*df->get_min_max("test_meas") == std::pair{-1.0, 2.0}; - | "another sort example" | - [] (interface *df = setup{{"d1", "d2"}, {"m1"}, { - {{"dx2", "dm2", 88.0}}, - {{"dx1", "dm0", 2.0}}, - {{std::string_view{nullptr, 0}, "dm1", 3.5}}, - {{"dx0", "dm1", 10.25}}, - {{"dx2", "dm8", 5.5}}, - {{"dx1", "dm8", 3.5}}, - {{"dx2", "dm1", 7.25}}, - {{"dx1", "dm2", NAN}}, - {{"dx0", "dm8", 4.25}}, - {{"dx0", "dm0", NAN}}, - {{"dx2", "dm0", 0.5}}, - }}){ - df->set_sort("d1", sort_type::greater, na_position::first); - df->set_sort([](const record_type &lhs, const record_type &rhs) { - auto l = (std::get(lhs.getValue("d2"))[2] - '0') - + std::get(lhs.getValue("m1")); - auto r = (std::get(rhs.getValue("d2"))[2] - '0') - + std::get(rhs.getValue("m1")); - return std::weak_order(l, r); - }); - - df->finalize(); - - assert ->* df->get_record_count() == std::size_t{11}; - - assert ->* df->get_categories("d1") == std::array{"dx2", "dx1", "dx0"}; - check ->* df->get_categories("d2") == std::array{"dm2", "dm0", "dm1", "dm8"}; - - check ->* std::get(df->get_data(std::size_t{0}, "d1")).data() == nullptr; - check ->* df->get_data(std::size_t{1}, "d1") == "dx2"; - check ->* df->get_data(std::size_t{2}, "d1") == "dx2"; - check ->* df->get_data(std::size_t{3}, "d1") == "dx2"; - check ->* df->get_data(std::size_t{4}, "d1") == "dx2"; - check ->* df->get_data(std::size_t{5}, "d1") == "dx1"; - check ->* df->get_data(std::size_t{6}, "d1") == "dx1"; - check ->* df->get_data(std::size_t{7}, "d1") == "dx1"; - check ->* df->get_data(std::size_t{8}, "d1") == "dx0"; - check ->* df->get_data(std::size_t{9}, "d1") == "dx0"; - check ->* df->get_data(std::size_t{10}, "d1") == "dx0"; - - check ->* df->get_data(std::size_t{0}, "m1") == 3.5; - - check ->* df->get_data(std::size_t{1}, "m1") == 0.5; - check ->* df->get_data(std::size_t{2}, "m1") == 7.25; - check ->* df->get_data(std::size_t{3}, "m1") == 5.5; - check ->* df->get_data(std::size_t{4}, "m1") == 88.0; - - check ->* df->get_data(std::size_t{5}, "m1") == 2.0; - check ->* df->get_data(std::size_t{6}, "m1") == 3.5; - check ->* std::isnan(std::get(df->get_data(std::size_t{7}, "m1"))) - == "is nan"_is_true; - - check ->* df->get_data(std::size_t{8}, "m1") == 10.25; - check ->* df->get_data(std::size_t{9}, "m1") == 4.25; - check ->* std::isnan(std::get(df->get_data(std::size_t{10}, "m1"))) - == "is nan"_is_true; - } + check->*df->get_data({}, "test_meas") == 2.0; + check->*df->get_data({}, "test_dim") == "test_dim_val"; - | "sort measure" | - [] (interface* df = setup{{"d1"}, {"m1"}, { - {{"dm0", 5.5}}, - {{"dm1", 2.0}}, - {{"dm2", 3.5}}, - {{"dm3", 10.25}}, - {{"dm4", 88.0}}, - {{"dm5", 2.2}}, - {{"dm6", 7.4}}, - {{"dm7", NAN}}, - {{"dm8", 4.2}}, - {{"dm9", 0.0}}, - }}) { - df->set_sort("m1", sort_type::greater, na_position::last); - - df->finalize(); - - assert ->* df->get_record_count() == std::size_t{10}; - - check ->* df->get_data(std::size_t{0}, "m1") == 88.0; - check ->* df->get_data(std::size_t{1}, "m1") == 10.25; - check ->* df->get_data(std::size_t{2}, "m1") == 7.4; - check ->* df->get_data(std::size_t{3}, "m1") == 5.5; - check ->* df->get_data(std::size_t{4}, "m1") == 4.2; - check ->* df->get_data(std::size_t{5}, "m1") == 3.5; - check ->* df->get_data(std::size_t{6}, "m1") == 2.2; - check ->* df->get_data(std::size_t{7}, "m1") == 2.0; - check ->* df->get_data(std::size_t{8}, "m1") == 0.0; - check ->* std::isnan(std::get(df->get_data(std::size_t{9}, "m1"))) - == "is nan"_is_true; - } + check->*df->get_data(std::size_t{1}, "test_meas") == -1.0; + check->*df->get_data(std::size_t{1}, "test_dim") + == "test_dim_val2"; +} + | "add_series_by_other" | + [](interface *df = setup{{"d1", "d2"}, + {"m1"}, + {{{"dm1", "dm2", 0.0}}, + {{"dm1", "dmX", 1.0}}, + {{"s1", "s2", -1.0}}, + {{"s1", "s3", 3.0}}}}) +{ + df->add_series_by_other("m1", + "m0", + [](auto, cell_value c) -> cell_value + { + const double *v = std::get_if(&c); + assert->*static_cast(v) + == "value is a double"_is_true; + + return *v * 2; + }, + {}); + + assert->*df->get_measures() == std::array{"m0", "m1"}; + + check->*df->get_data(std::size_t{0}, "m0") == 0.0; + check->*df->get_data(std::size_t{1}, "m0") == 2.0; + check->*df->get_data(std::size_t{2}, "m0") == -2.0; + check->*df->get_data(std::size_t{3}, "m0") == 6.0; + + df->add_series_by_other("d1", + "d15", + [](const record_type &r, cell_value c) -> cell_value + { + auto v = std::get_if(&c); + skip->*static_cast(v) == "value is string"_is_true; + + auto oth_v = r.getValue("d2"); + + auto v2 = std::get_if(&oth_v); + skip->*static_cast(v2) == "value is string"_is_true; + + thread_local std::string val; + val = std::string{*v} + "5" + std::string{*v2}; + + return val; + }, + {}); + + assert->*df->get_dimensions() == std::array{"d1", "d15", "d2"}; + + check->*df->get_data(std::size_t{0}, "d15") == "dm15dm2"; + check->*df->get_data(std::size_t{1}, "d15") == "dm15dmX"; + check->*df->get_data(std::size_t{2}, "d15") == "s15s2"; + check->*df->get_data(std::size_t{3}, "d15") == "s15s3"; +} + + | "remove_series" | + [](interface *df = setup{{"d1", "d2", "d3"}, + {"m1", "m2", "m3"}, + { + {{"dm1", "dx2", "dm3", 0.0, 0.1, 0.2}}, + {{"dm1", "dx2", "am3", 1.0, 2.1, 3.2}}, + {{"dm2", "dm2", "bm3", 1.0, 1.5, 1.2}}, + }}) +{ + df->remove_series({{"m1", "d2", "m3"}}); + + assert->*df->get_measures() == std::array{"m2"}; + assert->*df->get_dimensions() == std::array{"d1", "d3"}; + assert->*df->get_record_count() == std::size_t{3}; + + check->*df->get_data(std::size_t{2}, "m2") == 1.5; + check->*df->get_data(std::size_t{0}, "d3") == "dm3"; +} + + | "remove_records" | + [](interface *df = setup{{"d1"}, + {"m1"}, + { + {{"dm0", NAN}}, + {{"dm1", NAN}}, + {{"dm2", NAN}}, + {{"dm3", NAN}}, + {{"dm4", NAN}}, + {{"dm5", NAN}}, + {{"dm6", NAN}}, + {{"dm7", NAN}}, + {{"dm8", 4.2}}, + {{"dm9", NAN}}, + }}) +{ + df->remove_records({{0ul, 2ul, 4ul, 5ul, 8ul, 9ul}}); + + assert->*df->get_measures() == std::array{"m1"}; + assert->*df->get_dimensions() == std::array{"d1"}; + assert->*df->get_record_count() == std::size_t{4}; + + check + ->*std::isnan(std::get( + df->get_data(std::size_t{2}, "m1"))) + == "is nan"_is_true; + check->*df->get_data(std::size_t{0}, "d1") == "dm6"; + check->*df->get_data(std::size_t{1}, "d1") == "dm1"; + check->*df->get_data(std::size_t{2}, "d1") == "dm7"; + check->*df->get_data(std::size_t{3}, "d1") == "dm3"; +} + + | "remove_records_filter" | + [](interface *df = setup{{"d1"}, + {"m1"}, + { + {{"dm0", 5.3}}, + {{"dm1", 2.0}}, + {{"dm2", 3.3}}, + {{"dm3", 10.1}}, + {{"dm4", 88.0}}, + {{"dm5", 2.2}}, + {{"dm6", 7.4}}, + {{"dm7", 0.0}}, + {{"dm8", 4.2}}, + {{"dm9", NAN}}, + }}) +{ + df->remove_records( + [](record_type r) -> bool + { + auto v = r.getValue("m1"); + return *std::get_if(&v) < 5.0; + }); + + assert->*df->get_record_count() == std::size_t{5}; + + check->*df->get_data(std::size_t{0}, "d1") == "dm0"; + check->*df->get_data(std::size_t{1}, "d1") == "dm9"; + check->*df->get_data(std::size_t{2}, "d1") == "dm6"; + check->*df->get_data(std::size_t{3}, "d1") == "dm3"; + check->*df->get_data(std::size_t{4}, "d1") == "dm4"; +} + + | "change_data" | + [](interface *df = setup{{"d1"}, + {"m1"}, + {{{"dm0", 5.3}}, {{"dm1", 2.0}}, {{"dm2", 3.3}}}}) +{ + df->change_data(std::size_t{1}, "m1", 3.0); + df->change_data(std::size_t{2}, "d1", "dmX"); + + throw_(&interface::change_data, + df, + {std::size_t{0}}, + {"d1"}, + {NAN}); + throw_(&interface::change_data, + df, + {std::size_t{0}}, + {"m1"}, + {""}); + + assert->*df->get_record_count() == std::size_t{3}; + + check->*df->get_data(std::size_t{0}, "m1") == 5.3; + check->*df->get_data(std::size_t{1}, "m1") == 3.0; + check->*df->get_data(std::size_t{2}, "m1") == 3.3; + + check->*df->get_data(std::size_t{0}, "d1") == "dm0"; + check->*df->get_data(std::size_t{1}, "d1") == "dm1"; + check->*df->get_data(std::size_t{2}, "d1") == "dmX"; +} + + | "fill_na" | + [](interface *df = setup{{"d1"}, + {"m1"}, + {{{"dm0", 5.3}}, + {{std::string_view{nullptr, 0}, 2.0}}, + {{"dm2", NAN}}}}) +{ + df->fill_na("m1", 3.0); + df->fill_na("d1", "dmX"); + + assert->*df->get_record_count() == std::size_t{3}; + + check->*df->get_data(std::size_t{0}, "m1") == 5.3; + check->*df->get_data(std::size_t{1}, "m1") == 2.0; + check->*df->get_data(std::size_t{2}, "m1") == 3.0; + + check->*df->get_data(std::size_t{0}, "d1") == "dm0"; + check->*df->get_data(std::size_t{1}, "d1") == "dmX"; + check->*df->get_data(std::size_t{2}, "d1") == "dm2"; +} + + | "aggregate types" | + [](interface *df = setup{{"d1"}, + {"m1"}, + { + {{"dm0", 5.5}}, + {{"dm0", 2.0}}, + {{"dm0", 3.5}}, + {{"dm0", 10.25}}, + {{"dm0", 88.0}}, + {{"dm1", 3.5}}, + {{"dm1", 7.25}}, + {{"dm1", NAN}}, + {{"dm1", 4.25}}, + {{"dm2", NAN}}, + {{std::string_view{nullptr, 0}, 0.0}}, + }}) +{ + df->aggregate_by("d1"); + using enum Vizzu::dataframe::aggregator_type; + auto &&d1c = df->set_aggregate("d1", count); + auto &&d1d = df->set_aggregate("d1", distinct); + auto &&d1e = df->set_aggregate("d1", exists); + auto &&m1s = df->set_aggregate("m1", sum); + auto &&m1mi = df->set_aggregate("m1", min); + auto &&m1ma = df->set_aggregate("m1", max); + auto &&m1me = df->set_aggregate("m1", mean); + auto &&m1c = df->set_aggregate("m1", count); + auto &&m1d = df->set_aggregate("m1", distinct); + auto &&m1e = df->set_aggregate("m1", exists); + + auto &&m1t = df->set_aggregate("m1", + Vizzu::dataframe::custom_aggregator{std::string_view{"test"}, + []() -> Vizzu::dataframe::custom_aggregator::id_type + { + return std::pair{ + std::numeric_limits::max(), + std::numeric_limits::max()}; + }, + [](Vizzu::dataframe::custom_aggregator::id_type &id, + double v) -> double + { + auto &[min, min2] = + std::any_cast &>(id); + if (v < min) + min2 = std::exchange(min, v); + else if (v < min2) + min2 = v; + return min2; + }}); + + df->finalize(); + + assert->*df->get_dimensions() == std::array{"d1"}; + + assert->*df->get_measures() + == std::array{d1c, + m1c, + d1d, + m1d, + d1e, + m1e, + m1ma, + m1me, + m1mi, + m1s, + m1t}; + + assert->*df->get_record_count() == std::size_t{4}; + + check->*df->get_data(std::size_t{0}, d1c) == 5.0; + check->*df->get_data(std::size_t{0}, m1c) == 5.0; + check->*df->get_data(std::size_t{0}, d1d) == 1.0; + check->*df->get_data(std::size_t{0}, m1d) == 5.0; + check->*df->get_data(std::size_t{0}, d1e) == 1.0; + check->*df->get_data(std::size_t{0}, m1e) == 1.0; + check->*df->get_data(std::size_t{0}, m1ma) == 88.0; + check->*df->get_data(std::size_t{0}, m1me) == 21.85; + check->*df->get_data(std::size_t{0}, m1mi) == 2.0; + check->*df->get_data(std::size_t{0}, m1s) == 109.25; + check->*df->get_data(std::size_t{0}, m1t) == 3.5; + + check->*df->get_data(std::size_t{1}, d1c) == 4.0; + check->*df->get_data(std::size_t{1}, m1c) == 3.0; + check->*df->get_data(std::size_t{1}, d1d) == 1.0; + check->*df->get_data(std::size_t{1}, m1d) == 3.0; + check->*df->get_data(std::size_t{1}, d1e) == 1.0; + check->*df->get_data(std::size_t{1}, m1e) == 1.0; + check->*df->get_data(std::size_t{1}, m1ma) == 7.25; + check->*df->get_data(std::size_t{1}, m1me) == 5.0; + check->*df->get_data(std::size_t{1}, m1mi) == 3.5; + check->*df->get_data(std::size_t{1}, m1s) == 15.0; + check->*df->get_data(std::size_t{1}, m1t) == 4.25; + + check->*df->get_data(std::size_t{2}, d1c) == 1.0; + check->*df->get_data(std::size_t{2}, m1c) == 0.0; + check->*df->get_data(std::size_t{2}, d1d) == 1.0; + check->*df->get_data(std::size_t{2}, m1d) == 0.0; + check->*df->get_data(std::size_t{2}, d1e) == 1.0; + check->*df->get_data(std::size_t{2}, m1e) == 0.0; + check + ->*std::isnan(std::get( + df->get_data(std::size_t{2}, m1ma))) + == "is nan"_is_true; + check + ->*std::isnan(std::get( + df->get_data(std::size_t{2}, m1me))) + == "is nan"_is_true; + check + ->*std::isnan(std::get( + df->get_data(std::size_t{2}, m1mi))) + == "is nan"_is_true; + check->*df->get_data(std::size_t{2}, m1s) == 0.0; + check->*df->get_data(std::size_t{2}, m1t) + == std::numeric_limits::max(); + + check->*df->get_data(std::size_t{3}, d1c) == 0.0; + check->*df->get_data(std::size_t{3}, m1c) == 1.0; + check->*df->get_data(std::size_t{3}, d1d) == 0.0; + check->*df->get_data(std::size_t{3}, m1d) == 1.0; + check->*df->get_data(std::size_t{3}, d1e) == 0.0; + check->*df->get_data(std::size_t{3}, m1e) == 1.0; +} + + | "aggregate multiple dim" | + [](interface *df = setup{{"d1", "d2", "d3"}, + {"m1"}, + { + {{"dx0", "dm0", "doa", 5.5}}, + {{"dx0", "dm0", "dob", 2.0}}, + {{"dx0", "dm1", std::string_view{nullptr, 0}, 3.5}}, + {{"dx0", "dm1", "dob", 10.25}}, + {{"dx0", "dm0", "doa", 88.0}}, + {{"dx1", "dm0", "doa", 3.5}}, + {{"dx1", "dm1", std::string_view{nullptr, 0}, 7.25}}, + {{"dx1", "dm2", "doa", NAN}}, + {{"dx1", "dm0", "doa", 4.25}}, + {{"dx2", "dm0", "dob", NAN}}, + {{"dx2", "dm0", "doa", 0.5}}, + }}) +{ + df->aggregate_by("d1"); + df->aggregate_by("d2"); + using enum Vizzu::dataframe::aggregator_type; + auto &&d3c = df->set_aggregate("d3", count); + auto &&d3d = df->set_aggregate("d3", distinct); + + df->finalize(); + + assert->*df->get_dimensions() == std::array{"d1", "d2"}; + assert->*df->get_measures() == std::array{d3c, d3d}; + + assert->*df->get_record_count() == std::size_t{6}; + + check->*df->get_data(std::size_t{0}, "d1") == "dx0"; + check->*df->get_data(std::size_t{0}, "d2") == "dm0"; + check->*df->get_data(std::size_t{0}, d3c) == 3.0; + check->*df->get_data(std::size_t{0}, d3d) == 2.0; + + check->*df->get_data(std::size_t{1}, "d1") == "dx0"; + check->*df->get_data(std::size_t{1}, "d2") == "dm1"; + check->*df->get_data(std::size_t{1}, d3c) == 1.0; + check->*df->get_data(std::size_t{1}, d3d) == 1.0; + + check->*df->get_data(std::size_t{2}, "d1") == "dx1"; + check->*df->get_data(std::size_t{2}, "d2") == "dm0"; + check->*df->get_data(std::size_t{2}, d3c) == 2.0; + check->*df->get_data(std::size_t{2}, d3d) == 1.0; + + check->*df->get_data(std::size_t{3}, "d1") == "dx1"; + check->*df->get_data(std::size_t{3}, "d2") == "dm1"; + check->*df->get_data(std::size_t{3}, d3c) == 0.0; + check->*df->get_data(std::size_t{3}, d3d) == 0.0; + + check->*df->get_data(std::size_t{4}, "d1") == "dx1"; + check->*df->get_data(std::size_t{4}, "d2") == "dm2"; + + check->*df->get_data(std::size_t{5}, "d1") == "dx2"; + check->*df->get_data(std::size_t{5}, "d2") == "dm0"; + check->*df->get_data(std::size_t{5}, d3c) == 2.0; + check->*df->get_data(std::size_t{5}, d3d) == 2.0; +} + + | "cannot finalize contains same dim" | + [](interface *df = setup{.dims = {"d1", "d2"}, + .data = {{{"dx0", "dm0"}}, {{"dx0", "dm0"}}}}) +{ + throw_(&interface::finalize, df); +} + + | "sort dimension" | + [](interface *df = setup{{"d1", "d2"}, + {"m1"}, + { + {{"dx2", "dm2", 5.5}}, + {{"dx1", "dm0", 2.0}}, + {{std::string_view{nullptr, 0}, "dm1", 3.5}}, + {{"dx0", "dm1", 10.25}}, + {{"dx2", "dm0", 88.0}}, + {{"dx1", "dm0", 3.5}}, + {{"dx2", "dm1", 7.25}}, + {{"dx1", "dm2", NAN}}, + {{"dx0", "dm0", 4.25}}, + {{"dx0", "dm0", NAN}}, + {{"dx2", "dm0", 0.5}}, + }}) +{ + df->set_sort("d1", {}, {}); + df->set_sort("m1", sort_type::greater, na_position::first); + + // cannot finalize because of duplicated dimensions + df->remove_records(std::span{}); + + assert->*df->get_record_count() == std::size_t{11}; + + assert->*df->get_categories("d1") + == std::array{"dx0", "dx1", "dx2"}; + check->*df->get_categories("d2") + == std::array{"dm2", "dm0", "dm1"}; + + check->*df->get_data(std::size_t{0}, "d1") == "dx0"; + check->*df->get_data(std::size_t{1}, "d1") == "dx0"; + check->*df->get_data(std::size_t{2}, "d1") == "dx0"; + check->*df->get_data(std::size_t{3}, "d1") == "dx1"; + check->*df->get_data(std::size_t{4}, "d1") == "dx1"; + check->*df->get_data(std::size_t{5}, "d1") == "dx1"; + check->*df->get_data(std::size_t{6}, "d1") == "dx2"; + check->*df->get_data(std::size_t{7}, "d1") == "dx2"; + check->*df->get_data(std::size_t{8}, "d1") == "dx2"; + check->*df->get_data(std::size_t{9}, "d1") == "dx2"; + check + ->*std::get( + df->get_data(std::size_t{10}, "d1")) + .data() + == nullptr; + + check + ->*std::isnan(std::get( + df->get_data(std::size_t{0}, "m1"))) + == "is nan"_is_true; + check->*df->get_data(std::size_t{1}, "m1") == 10.25; + check->*df->get_data(std::size_t{2}, "m1") == 4.25; + + check + ->*std::isnan(std::get( + df->get_data(std::size_t{3}, "m1"))) + == "is nan"_is_true; + check->*df->get_data(std::size_t{4}, "m1") == 3.5; + check->*df->get_data(std::size_t{5}, "m1") == 2.0; + + check->*df->get_data(std::size_t{6}, "m1") == 88.0; + check->*df->get_data(std::size_t{7}, "m1") == 7.25; + check->*df->get_data(std::size_t{8}, "m1") == 5.5; + check->*df->get_data(std::size_t{9}, "m1") == 0.5; + + check->*df->get_data(std::size_t{10}, "m1") == 3.5; +} + + | "another sort example" | + [](interface *df = setup{{"d1", "d2"}, + {"m1"}, + { + {{"dx2", "dm2", 88.0}}, + {{"dx1", "dm0", 2.0}}, + {{std::string_view{nullptr, 0}, "dm1", 3.5}}, + {{"dx0", "dm1", 10.25}}, + {{"dx2", "dm8", 5.5}}, + {{"dx1", "dm8", 3.5}}, + {{"dx2", "dm1", 7.25}}, + {{"dx1", "dm2", NAN}}, + {{"dx0", "dm8", 4.25}}, + {{"dx0", "dm0", NAN}}, + {{"dx2", "dm0", 0.5}}, + }}) +{ + df->set_sort("d1", sort_type::greater, na_position::first); + df->set_custom_sort( + [](const record_type &lhs, const record_type &rhs) + { + auto l = + (std::get(lhs.getValue("d2"))[2] + - '0') + + std::get(lhs.getValue("m1")); + auto r = + (std::get(rhs.getValue("d2"))[2] + - '0') + + std::get(rhs.getValue("m1")); + return std::weak_order(l, r); + }); + + df->finalize(); + + assert->*df->get_record_count() == std::size_t{11}; + + assert->*df->get_categories("d1") + == std::array{"dx2", "dx1", "dx0"}; + check->*df->get_categories("d2") + == std::array{"dm2", "dm0", "dm1", "dm8"}; + + check + ->*std::get( + df->get_data(std::size_t{0}, "d1")) + .data() + == nullptr; + check->*df->get_data(std::size_t{1}, "d1") == "dx2"; + check->*df->get_data(std::size_t{2}, "d1") == "dx2"; + check->*df->get_data(std::size_t{3}, "d1") == "dx2"; + check->*df->get_data(std::size_t{4}, "d1") == "dx2"; + check->*df->get_data(std::size_t{5}, "d1") == "dx1"; + check->*df->get_data(std::size_t{6}, "d1") == "dx1"; + check->*df->get_data(std::size_t{7}, "d1") == "dx1"; + check->*df->get_data(std::size_t{8}, "d1") == "dx0"; + check->*df->get_data(std::size_t{9}, "d1") == "dx0"; + check->*df->get_data(std::size_t{10}, "d1") == "dx0"; + + check->*df->get_data(std::size_t{0}, "m1") == 3.5; + + check->*df->get_data(std::size_t{1}, "m1") == 0.5; + check->*df->get_data(std::size_t{2}, "m1") == 7.25; + check->*df->get_data(std::size_t{3}, "m1") == 5.5; + check->*df->get_data(std::size_t{4}, "m1") == 88.0; + + check->*df->get_data(std::size_t{5}, "m1") == 2.0; + check->*df->get_data(std::size_t{6}, "m1") == 3.5; + check + ->*std::isnan(std::get( + df->get_data(std::size_t{7}, "m1"))) + == "is nan"_is_true; + + check->*df->get_data(std::size_t{8}, "m1") == 10.25; + check->*df->get_data(std::size_t{9}, "m1") == 4.25; + check + ->*std::isnan(std::get( + df->get_data(std::size_t{10}, "m1"))) + == "is nan"_is_true; +} + + | "sort measure" | + [](interface *df = setup{{"d1"}, + {"m1"}, + { + {{"dm0", 5.5}}, + {{"dm1", 2.0}}, + {{"dm2", 3.5}}, + {{"dm3", 10.25}}, + {{"dm4", 88.0}}, + {{"dm5", 2.2}}, + {{"dm6", 7.4}}, + {{"dm7", NAN}}, + {{"dm8", 4.2}}, + {{"dm9", 0.0}}, + }}) +{ + df->set_sort("m1", sort_type::greater, na_position::last); + + df->finalize(); + + assert->*df->get_record_count() == std::size_t{10}; + + check->*df->get_data(std::size_t{0}, "m1") == 88.0; + check->*df->get_data(std::size_t{1}, "m1") == 10.25; + check->*df->get_data(std::size_t{2}, "m1") == 7.4; + check->*df->get_data(std::size_t{3}, "m1") == 5.5; + check->*df->get_data(std::size_t{4}, "m1") == 4.2; + check->*df->get_data(std::size_t{5}, "m1") == 3.5; + check->*df->get_data(std::size_t{6}, "m1") == 2.2; + check->*df->get_data(std::size_t{7}, "m1") == 2.0; + check->*df->get_data(std::size_t{8}, "m1") == 0.0; + check + ->*std::isnan(std::get( + df->get_data(std::size_t{9}, "m1"))) + == "is nan"_is_true; +} ; -// clang-format on diff --git a/test/unit/util/condition.h b/test/unit/util/condition.h index edc1672f1..0f2dc5a6c 100644 --- a/test/unit/util/condition.h +++ b/test/unit/util/condition.h @@ -98,7 +98,7 @@ inline namespace consts { struct impl_check_t { - test::check_t operator()(src_location loc = src_location()) const; + check_t operator()(src_location loc = src_location()) const; }; struct assert_t {}; @@ -106,8 +106,34 @@ struct skip_t {}; template struct impl_throws_t { - test::throws_t operator()( + throws_t operator()( src_location loc = src_location()) const; + + template + void operator()(Res (Member::*p)(Args...) &, + Obj &&obj, + Args &&...args) const + { + (*this)() << [&] + { + std::invoke(p, + std::forward(obj), + std::forward(args)...); + }; + } + + template + void operator()(Res (Member::*p)(Args...) const &, + Obj &&obj, + Args &&...args) const + { + (*this)() << [&] + { + std::invoke(p, + std::forward(obj), + std::forward(args)...); + }; + } }; } @@ -260,26 +286,20 @@ throws_t(consts::impl_throws_t, return bool_check_t{check, value}; } -inline auto operator->*(throws_t &&throws, - const auto &value) -{ - return throws << value; -} - inline namespace consts { -[[nodiscard]] inline test::check_t impl_check_t::operator()( +[[nodiscard]] inline check_t impl_check_t::operator()( src_location loc) const { - return test::check_t{loc}; + return check_t{loc}; } template -[[nodiscard]] inline test::throws_t +[[nodiscard]] throws_t impl_throws_t::operator()(src_location loc) const { - return test::throws_t{loc}; + return throws_t{loc}; } static inline constexpr impl_check_t check{}; From e9b179a360d6449e73c45923d84bcbe1dd74adfb Mon Sep 17 00:00:00 2001 From: David Vegh Date: Mon, 26 Feb 2024 11:43:57 +0100 Subject: [PATCH 057/253] documentation: csv generation fixed --- tools/docs/examples/mjs2csv.mjs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tools/docs/examples/mjs2csv.mjs b/tools/docs/examples/mjs2csv.mjs index 0150429d7..b619dc027 100644 --- a/tools/docs/examples/mjs2csv.mjs +++ b/tools/docs/examples/mjs2csv.mjs @@ -3,10 +3,18 @@ class Js2csv { this.data = data } + addApostrophesIfContainsComma(value) { + if (typeof value === 'string' && value.includes(',')) { + return `"${value}"` + } + return value + } + getHeaderLine() { const header = [] for (const series in this.data.series) { - header.push(this.data.series[series].name) + const value = this.addApostrophesIfContainsComma(this.data.series[series].name) + header.push(value) } return header.join(',') + '\n' } @@ -14,13 +22,18 @@ class Js2csv { getDataLine(i) { const line = [] for (const key in this.data.series) { - line.push(this.data.series[key].values[i]) + const value = this.addApostrophesIfContainsComma(this.data.series[key].values[i]) + line.push(value) } return line.join(',') + '\n' } getRecordLine(i) { - const line = this.data.records[i] + const line = [] + for (const j in this.data.records[i]) { + const value = this.addApostrophesIfContainsComma(this.data.records[i][j]) + line.push(value) + } return line.join(',') + '\n' } From 2226d841af7515be556c6f88a281dd9efe4c1e64 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 26 Feb 2024 15:58:56 +0100 Subject: [PATCH 058/253] upload dataframe and data source --- src/base/refl/auto_enum.h | 4 +- src/dataframe/impl/data_source.cpp | 686 ++++++++++++++++++ src/dataframe/impl/data_source.h | 205 ++++++ src/dataframe/impl/dataframe.cpp | 920 +++++++++++++++++++++++++ src/dataframe/impl/dataframe.h | 162 +++++ test/unit/dataframe/interface_test.cpp | 5 +- 6 files changed, 1977 insertions(+), 5 deletions(-) create mode 100644 src/dataframe/impl/data_source.cpp create mode 100644 src/dataframe/impl/data_source.h create mode 100644 src/dataframe/impl/dataframe.cpp create mode 100644 src/dataframe/impl/dataframe.h diff --git a/src/base/refl/auto_enum.h b/src/base/refl/auto_enum.h index 91413cb08..e08fe9a5c 100644 --- a/src/base/refl/auto_enum.h +++ b/src/base/refl/auto_enum.h @@ -216,13 +216,13 @@ constexpr decltype(auto) unsafe_get( template constexpr decltype(auto) unsafe_get(EnumVariant const &e) { - return *std::get_if(e); + return *std::get_if(&e); } template constexpr decltype(auto) unsafe_get(EnumVariant &e) { - return *std::get_if(e); + return *std::get_if(&e); } template diff --git a/src/dataframe/impl/data_source.cpp b/src/dataframe/impl/data_source.cpp new file mode 100644 index 000000000..40480170d --- /dev/null +++ b/src/dataframe/impl/data_source.cpp @@ -0,0 +1,686 @@ + +#include "data_source.h" + +#include +#include +#include +#include +#include + +#include "base/text/naturalcmp.h" + +#include "dataframe.h" + +namespace Vizzu::dataframe +{ + +template +struct index_erase_if +{ + std::span indices; + + template + constexpr void operator()(Cont &&cont) const noexcept; +}; + +template <> +template +constexpr void index_erase_if::operator()( + Cont &&cont) const noexcept +{ + if (indices.empty()) return; + auto into = cont.begin() + indices.front(); + auto prev = into + 1; + + for (std::size_t i{1}; i < indices.size(); ++i) { + auto curr = cont.begin() + indices[i]; + into = std::move(std::exchange(prev, curr + 1), curr, into); + } + cont.erase(std::move(prev, cont.end(), into), cont.end()); +} + +template <> +template +constexpr void index_erase_if<>::operator()( + Cont &&cont) const noexcept +{ + auto from = cont.end(); + for (auto i : std::ranges::views::reverse(indices)) + cont[i] = std::move(*--from); + cont.erase(from, cont.end()); +} + +template <> +template +constexpr void index_erase_if::operator()( + Cont &&cont) const noexcept +{ + std::erase_if(cont, + [i = std::size_t{}, c = indices.begin(), e = indices.end()]( + const auto &) mutable + { + return c != e && i++ == *c && (++c, true); + }); +} + +template <> +template +constexpr void index_erase_if::operator()( + Cont &&cont) const noexcept +{ + auto from_ix = cont.size(); + for (auto first = begin(indices), + last = std::ranges::lower_bound(indices, + from_ix - indices.size()), + from = indices.end(); + first != last; + cont[*first++] = std::move(cont[from_ix])) + while (--from_ix == from[-1]) --from; +} + +std::size_t data_source::get_record_count() const +{ + if (finalized) return finalized->record_unique_ids.size(); + + std::size_t record_count{}; + for (const auto &dim : dimensions) + if (record_count < dim.values.size()) + record_count = dim.values.size(); + + for (const auto &mea : measures) + if (record_count < mea.values.size()) + record_count = mea.values.size(); + + return record_count; +} + +struct data_source::sorter +{ + [[nodiscard]] static std::weak_ordering + cmp_dim(std::uint32_t lhs, std::uint32_t rhs, na_position na) + { + return na == na_position::last || (lhs != nav && rhs != nav) + ? lhs <=> rhs + : rhs <=> lhs; + } + + [[nodiscard]] static std::weak_ordering + cmp_mea(double lhs, double rhs, na_position na, sort_type sort) + { + using std::weak_order; + return std::isnan(lhs) || std::isnan(rhs) + ? na == na_position::last ? weak_order(lhs, rhs) + : weak_order(rhs, lhs) + : sort == sort_type::less ? weak_order(lhs, rhs) + : weak_order(rhs, lhs); + } + + [[nodiscard]] static std::weak_ordering + cmp(const sort_one_series &sorter, std::size_t a, std::size_t b) + { + switch (auto &&[series, sort, na] = sorter; series) { + using enum series_type; + case dimension: { + const auto &dim = unsafe_get(series).second; + return cmp_dim(dim.values[a], dim.values[b], na); + } + case measure: { + const auto &mea = unsafe_get(series).second; + return cmp_mea(mea.values[a], mea.values[b], na, sort); + } + default: + case unknown: return std::weak_ordering::equivalent; + } + } +}; + +std::vector data_source::get_sorted_indices( + const dataframe_interface *parent, + const sorting_type &sorters) const +{ + std::vector result(get_record_count()); + std::iota(result.begin(), result.end(), 0); + std::sort(result.begin(), + result.end(), + [parent, &sorters](std::size_t a, std::size_t b) + { + for (const auto &sorter : sorters) { + if (const auto *custom = std::get_if<1>(&sorter)) { + if (auto res = + (*custom)({parent, a}, {parent, b}); + is_neq(res)) + return is_lt(res); + } + else if (auto res = + sorter::cmp(*std::get_if<0>(&sorter), + a, + b); + is_neq(res)) + return is_lt(res); + } + return false; + }); + return result; +} + +void data_source::sort(std::vector &&indices) +{ + std::vector tmp_mea(measures.size()); + std::vector tmp_dim(dimensions.size()); + + for (std::size_t i{}, max = indices.size(); i < max; ++i) { + if (i >= indices[i]) continue; + + for (std::size_t m{}; m < measures.size(); ++m) + tmp_mea[m] = measures[m].values[i]; + for (std::size_t d{}; d < dimensions.size(); ++d) + tmp_dim[d] = dimensions[d].values[i]; + + std::size_t j{i}; + for (; indices[j] != i; j = std::exchange(indices[j], i)) { + for (auto &meas : measures) + meas.values[j] = meas.values[indices[j]]; + for (auto &dims : dimensions) + dims.values[j] = dims.values[indices[j]]; + } + for (std::size_t m{}; m < measures.size(); ++m) + measures[m].values[j] = tmp_mea[m]; + for (std::size_t d{}; d < dimensions.size(); ++d) + dimensions[d].values[j] = tmp_dim[d]; + } +} + +void data_source::change_series_identifier_type( + series_identifier &id) const +{ + if (const auto *size = std::get_if(&id)) { + if (*size < dimension_names.size()) + id = dimension_names[*size]; + else if (*size + < dimension_names.size() + measure_names.size()) + id = measure_names[*size - dimension_names.size()]; + else + id = std::string_view{}; + } + else { + auto name = *std::get_if(&id); + if (finalized) + if (auto it = finalized->series_to_index.find(name); + it != finalized->series_to_index.end()) + id = it->second; + + if (auto it = std::lower_bound(dimension_names.begin(), + dimension_names.end(), + name); + it != dimension_names.end() && *it == name) + id = static_cast( + it - dimension_names.begin()); + else if (it = std::lower_bound(measure_names.begin(), + measure_names.end(), + name); + it != measure_names.end() && *it == name) + id = static_cast( + std::distance(measure_names.begin(), it)) + + dimension_names.size(); + else + id = ~std::size_t{}; + } +} + +void data_source::change_record_identifier_type( + record_identifier &id) const +{ + if (!finalized) throw std::runtime_error("not finalized yet"); + + if (const auto *size = std::get_if(&id)) { + if (*size < finalized->record_unique_ids.size()) + id = finalized->record_unique_ids[*size]; + else + id = std::string_view{}; + } + else { + auto name = *std::get_if(&id); + if (auto it = finalized->record_to_index.find(name); + it != finalized->record_to_index.end()) + id = it->second; + else + id = ~std::size_t{}; + } +} + +cell_value data_source::get_data(std::size_t record_id, + const series_identifier &column) const +{ + switch (auto &&series = get_series(column)) { + using enum series_type; + case dimension: { + const auto &dims = unsafe_get(series).second; + if (record_id >= dims.values.size()) + throw std::runtime_error("unknown record"); + return dims.get(record_id); + } + case measure: { + const auto &meas = unsafe_get(series).second; + if (record_id >= meas.values.size()) + throw std::runtime_error("unknown record"); + + return meas.values[record_id]; + } + case unknown: + default: throw std::runtime_error("unknown column"); + } +} + +data_source::series_data data_source::get_series(series_identifier id) +{ + if (std::holds_alternative(id)) + change_series_identifier_type(id); + auto size = *get_if(&id); + + if (size < dimensions.size()) + return series_data{std::in_place_index<1>, + dimension_names[size], + dimensions[size]}; + if (size < dimensions.size() + measures.size()) + return series_data{std::in_place_index<2>, + measure_names[size - dimensions.size()], + measures[size - dimensions.size()]}; + return {}; +} + +data_source::const_series_data data_source::get_series( + series_identifier id) const +{ + if (std::holds_alternative(id)) + change_series_identifier_type(id); + auto size = *std::get_if(&id); + + if (size < dimensions.size()) + return const_series_data{std::in_place_index<1>, + dimension_names[size], + dimensions[size]}; + if (size < dimensions.size() + measures.size()) + return const_series_data{std::in_place_index<2>, + measure_names[size - dimensions.size()], + measures[size - dimensions.size()]}; + + return {}; +} + +void data_source::normalize_sizes() +{ + std::size_t record_count{}; + for (auto &&dim : dimensions) + record_count = std::max(record_count, dim.values.size()); + for (auto &&mea : measures) + record_count = std::max(record_count, mea.values.size()); + + for (auto &&dim : dimensions) + dim.values.resize(record_count, nav); + for (auto &&mea : measures) mea.values.resize(record_count, nan); +} + +void data_source::remove_series(std::span dims, + std::span meas) +{ + const index_erase_if dim_remover{dims}; + dim_remover(dimensions); + dim_remover(dimension_names); + const index_erase_if mea_remover{meas}; + mea_remover(measures); + mea_remover(measure_names); +} + +std::pair data_source::get_min_max( + const measure_t &measure) const +{ + return finalized ? finalized->min_max[&measure - measures.data()] + : measure.get_min_max(); +} + +data_source::dimension_t &data_source::add_new_dimension( + std::span dimension_categories, + std::span dimension_values, + std::string_view name, + std::span> info) +{ + auto it = dimension_names.emplace( + std::lower_bound(dimension_names.begin(), + dimension_names.end(), + name), + name); + return *dimensions.emplace(dimensions.begin() + + (it - dimension_names.begin()), + dimension_categories, + dimension_values, + info); +} + +data_source::dimension_t &data_source::add_new_dimension( + dimension_t &&dim, + std::string_view name) +{ + auto it = dimension_names.emplace( + std::lower_bound(dimension_names.begin(), + dimension_names.end(), + name), + name); + return *dimensions.emplace(dimensions.begin() + + (it - dimension_names.begin()), + std::move(dim)); +} + +data_source::measure_t &data_source::add_new_measure( + std::span measure_values, + std::string_view name, + std::span> info) +{ + auto it = + measure_names.emplace(std::lower_bound(measure_names.begin(), + measure_names.end(), + name), + name); + return *measures.emplace(measures.begin() + + (it - measure_names.begin()), + measure_values, + info); +} + +data_source::measure_t &data_source::add_new_measure(measure_t &&mea, + std::string_view name) +{ + auto it = + measure_names.emplace(std::lower_bound(measure_names.begin(), + measure_names.end(), + name), + name); + return *measures.emplace(measures.begin() + + (it - measure_names.begin()), + std::move(mea)); +} + +void data_source::remove_records(std::span indices) +{ + const index_erase_if indices_remover{indices}; + for (auto &&dim : dimensions) indices_remover(dim.values); + for (auto &&mea : measures) indices_remover(mea.values); +} + +struct aggregating_helper +{ + std::size_t index; + std::vector aggregators; +}; + +data_source::data_source(aggregating_type &&aggregating, + const std::vector *filtered, + std::size_t record_count) +{ + auto &[dims, meas] = aggregating; + + dimensions.reserve(dims.size()); + dimension_names.reserve(dims.size()); + measures.reserve(meas.size()); + measure_names.reserve(meas.size()); + + for (const auto &[name, dim] : dims) { + dimension_t &new_dim = add_new_dimension({}, name); + new_dim.categories = dim.get().categories; + new_dim.info = dim.get().info; + } + + for (const auto &[name, mea] : meas) { + measure_t &new_mea = add_new_measure({}, name); + switch (auto &ser = std::get<0>(mea)) { + using enum series_type; + case dimension: + new_mea.info = unsafe_get(ser).second.info; + break; + case measure: + new_mea.info = unsafe_get(ser).second.info; + break; + case unknown: + default: break; + } + } + + std::vector cat_indices(dims.size()); + std::map, aggregating_helper> + rec_to_index; + + for (std::size_t i{}; i < record_count; ++i) { + if (filtered && (*filtered)[i]) continue; + + for (std::size_t ix{}; const auto &[name, dim] : dims) + cat_indices[ix++] = dim.get().values[i]; + + auto [it, s] = rec_to_index.try_emplace(cat_indices, + rec_to_index.size()); + + auto &[index, aggregators] = it->second; + if (s) { + for (auto &m : measures) m.values.emplace_back(NAN); + + for (std::size_t ix{}; const auto &[name, dim] : dims) + dimensions[ix++].values.emplace_back( + dim.get().values[i]); + + aggregators.reserve(meas.size()); + for (const auto &[ser, agg] : + std::ranges::views::values(meas)) + aggregators.emplace_back(agg.create()); + } + + for (std::size_t ix{}; const auto &[name, mea] : meas) { + auto &[data, agg] = mea; + double val{}; + if (data == series_type::measure) { + val = unsafe_get(data) + .second.values[i]; + } + else { + auto aval = unsafe_get(data) + .second.values[i]; + val = aval == data_source::nav ? data_source::nan + : aval; + } + measures[ix].values[index] = + agg.add(aggregators[ix], val); + ++ix; + } + } +} + +data_source::data_source( + const std::shared_ptr ©ing, + std::optional> &&filtered, + std::optional> &&sorted) : + measure_names(copying->measure_names), + measures(copying->measures), + dimension_names(copying->dimension_names), + dimensions(copying->dimensions) +{ + if (sorted) { this->sort(std::move(*sorted)); } + if (filtered) { + const auto &filt = *filtered; + std::vector remove_ix; + remove_ix.reserve(filt.size()); + for (std::size_t i{}; i < filt.size(); ++i) + if (filt[i]) remove_ix.emplace_back(i); + remove_records(remove_ix); + } +} + +data_source::final_info::final_info(const data_source &source) : + min_max(source.measure_names.size()), + record_unique_ids(source.get_record_count()) +{ + const auto measures = source.measure_names.size(); + const auto dimensions = source.dimension_names.size(); + const auto records = record_unique_ids.size(); + + series_to_index.reserve(measures + dimensions); + record_to_index.reserve(records); + + for (std::size_t i{}; i < dimensions; ++i) + series_to_index[source.dimension_names[i]] = i; + + for (std::size_t i{}; i < measures; ++i) { + series_to_index[source.measure_names[i]] = i + dimensions; + min_max[i] = source.measures[i].get_min_max(); + } + + for (std::size_t r{}; r < records; ++r) { + auto &record = record_unique_ids[r]; + + for (std::size_t d{}; d < dimensions; ++d) { + auto val = source.dimensions[d].values[r]; + record += + source.dimension_names[d] + ':' + + (val == nav ? "null" + : source.dimensions[d].categories[val]) + + ";"; + } + if (!record_to_index.try_emplace(record, r).second) + throw std::runtime_error("duplicated record"); + } +} + +std::vector data_source::dimension_t::get_indices( + const dataframe_interface::any_sort_type &sorter) const +{ + std::vector indices(categories.size()); + std::iota(indices.begin(), indices.end(), 0); + std::sort(indices.begin(), + indices.end(), + [this, &sorter, s = std::get_if(&sorter)]( + std::size_t a, + std::size_t b) + { + static const Text::NaturalCmp cmp{}; + if (s) switch (*s) { + default: + case sort_type::less: + return categories[a] < categories[b]; + case sort_type::greater: + return categories[b] < categories[a]; + case sort_type::natural_less: + return cmp(categories[a], categories[b]); + case sort_type::natural_greater: + return cmp(categories[b], categories[a]); + } + else + return std::is_lt((*std::get_if<1>( + &sorter))(categories[a], categories[b])); + }); + + return indices; +} + +void data_source::dimension_t::sort_by( + std::vector &&indices, + na_position na) +{ + for (auto &val : values) + if (val != nav) val = indices[val]; + + for (std::size_t i{}; i < categories.size(); ++i) { + if (i >= indices[i]) continue; + + auto tmp = std::move(categories[i]); + + std::size_t j{i}; + for (; indices[j] != i; j = std::exchange(indices[j], i)) + categories[j] = std::move(categories[indices[j]]); + + categories[j] = std::move(tmp); + } + na_pos = na; +} + +void data_source::dimension_t::add_element( + std::string_view const &cat) +{ + values.emplace_back(get_or_set_cat(cat)); +} +void data_source::dimension_t::add_more_data( + std::span new_categories, + std::span new_values) +{ + std::vector remap(new_categories.size()); + for (auto i = std::size_t{}; i < remap.size(); ++i) { + remap[i] = get_or_set_cat(new_categories[i]); + } + + for (const auto val : new_values) values.emplace_back(remap[val]); +} +std::string_view data_source::dimension_t::get( + std::size_t index) const +{ + return values[index] == data_source::nav + ? std::string_view{} + : categories[values[index]]; +} + +void data_source::dimension_t::set(std::size_t index, + std::string_view value) +{ + values[index] = + value.data() ? get_or_set_cat(value) : data_source::nav; +} +void data_source::dimension_t::set_nav(std::string_view value) +{ + if (value.data() == nullptr) value = ""; + + auto ix = get_or_set_cat(value); + for (auto &val : values) + if (val == data_source::nav) val = ix; +} +std::uint32_t data_source::dimension_t::get_or_set_cat( + std::string_view cat) +{ + if (cat.data() == nullptr) return nav; + auto it = std::find(categories.begin(), categories.end(), cat); + if (it == categories.end()) + it = categories.emplace(categories.end(), cat); + return static_cast(it - categories.begin()); +} + +std::pair data_source::measure_t::get_min_max() const +{ + auto mini = std::numeric_limits::max(); + auto maxi = std::numeric_limits::lowest(); + + for (auto val : values) { + if (std::isinf(val) || std::isnan(val)) continue; + if (val < mini) mini = val; + if (val > maxi) maxi = val; + } + return {mini, maxi}; +} + +std::string data_source::aggregating_type::add_aggregated( + const_series_data &&data, + const custom_aggregator &aggregator) +{ + return meas + .try_emplace( + std::string{aggregator.get_name()} + '(' + + std::visit( + [](const auto &arg) + { + if constexpr (std::is_same_v>) + return std::string{}; + else + return std::string{arg.first}; + }, + data) + + ')', + data, + aggregator) + .first->first; +} + +} \ No newline at end of file diff --git a/src/dataframe/impl/data_source.h b/src/dataframe/impl/data_source.h new file mode 100644 index 000000000..161b7835c --- /dev/null +++ b/src/dataframe/impl/data_source.h @@ -0,0 +1,205 @@ +#ifndef VIZZU_DATAFRAME_DATA_SOURCE_H +#define VIZZU_DATAFRAME_DATA_SOURCE_H + +#include +#include +#include +#include +#include +#include + +#include "../interface.h" +#include "base/refl/auto_enum.h" + +namespace Vizzu::dataframe +{ + +enum class series_type { unknown, dimension, measure }; + +class data_source : public std::enable_shared_from_this +{ +public: + using record_type = dataframe_interface::record_type; + using record_identifier = dataframe_interface::record_identifier; + using series_identifier = dataframe_interface::series_identifier; + +private: + constexpr static std::uint32_t nav = ~std::uint32_t{}; + constexpr static double nan = + std::numeric_limits::quiet_NaN(); + + struct dimension_t + { + std::vector categories; + na_position na_pos{na_position::last}; + std::vector values; + std::map info; + + dimension_t() noexcept = default; + + template + dimension_t(Range1 &&categories, + Range2 &&values, + Range3 &&info) : + categories(std::begin(categories), std::end(categories)), + values(std::begin(values), std::end(values)), + info(std::begin(info), std::end(info)) + {} + + void add_more_data(std::span categories, + std::span values); + + [[nodiscard]] std::vector get_indices( + const dataframe_interface::any_sort_type &sorter) const; + + void sort_by(std::vector &&indices, + na_position na_pos); + + std::uint32_t get_or_set_cat(std::string_view cat); + + void add_element(std::string_view const &cat); + + [[nodiscard]] std::string_view get(std::size_t index) const; + + void set(std::size_t index, std::string_view value); + + void set_nav(std::string_view value); + }; + + struct measure_t + { + std::vector values; + std::map info; + + measure_t() noexcept = default; + + template + measure_t(Range1 &&values, Range2 &&info) : + values(std::begin(values), std::end(values)), + info(std::begin(info), std::end(info)) + {} + + [[nodiscard]] std::pair get_min_max() const; + }; + + struct final_info + { + std::unordered_map + series_to_index; + std::vector> min_max; + std::vector record_unique_ids; + std::unordered_map + record_to_index; + + explicit final_info(const data_source &source); + }; + + using series_data = Refl::EnumVariant, + std::pair>; + + using const_series_data = Refl::EnumVariant, + std::pair>; + + // replace these to std::flat_map + std::vector measure_names; // sorted by name + std::vector measures; + + std::vector dimension_names; // sorted by name + std::vector dimensions; + + std::optional finalized; + + struct sorter; + +public: + using sort_one_series = + std::tuple; + + using sorting_type = std::vector>>; + + struct aggregating_type + { + std::map> + dims; + std::map, + std::less<>> + meas; + + std::string add_aggregated(const_series_data &&data, + const custom_aggregator &aggregator); + }; + + data_source() = default; + + data_source(const std::shared_ptr ©ing, + std::optional> &&filtered, + std::optional> &&sorted); + + data_source(aggregating_type &&aggregating, + std::vector const *filtered, + std::size_t record_count); + + [[nodiscard]] std::size_t get_record_count() const; + + [[nodiscard]] std::vector get_sorted_indices( + const dataframe_interface *parent, + const sorting_type &sorters) const; + + void sort(std::vector &&indices); + + void change_series_identifier_type(series_identifier &id) const; + + void change_record_identifier_type(record_identifier &id) const; + + void normalize_sizes(); + + void remove_series(std::span dims, + std::span meas); + + void remove_records(std::span indices); + + [[nodiscard]] cell_value get_data(std::size_t record_id, + const series_identifier &column) const; + + [[nodiscard]] series_data get_series(series_identifier id); + [[nodiscard]] const_series_data get_series( + series_identifier id) const; + + [[nodiscard]] std::pair get_min_max( + const measure_t &measure) const; + + [[nodiscard]] const final_info &finalize() + { + normalize_sizes(); + return finalized ? *finalized : finalized.emplace(*this); + } + + dimension_t &add_new_dimension( + std::span dimension_categories, + std::span dimension_values, + std::string_view name, + std::span> info); + + dimension_t &add_new_dimension(dimension_t &&dim, + std::string_view name); + + measure_t &add_new_measure(std::span measure_values, + std::string_view name, + std::span> info); + + measure_t &add_new_measure(measure_t &&measure, + std::string_view name); + + friend class dataframe; +}; + +} + +#endif // VIZZU_DATAFRAME_DATA_SOURCE_H diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp new file mode 100644 index 000000000..4bd0d2680 --- /dev/null +++ b/src/dataframe/impl/dataframe.cpp @@ -0,0 +1,920 @@ +#include "dataframe.h" + +#include +#include +#include +#include +#include + +#include "aggregators.h" + +namespace Vizzu::dataframe +{ +using Refl::unsafe_get; + +std::shared_ptr +dataframe::copy(bool remove_filtered, bool inherit_sorting) const & +{ + if (source == source_type::copying) { + const auto &cp = unsafe_get(source); + return std::make_shared(cp.other, + remove_filtered ? std::get_if>(&filter) + : cp.pre_remove ? &*cp.pre_remove + : nullptr, + inherit_sorting && cp.sorted_indices ? &*cp.sorted_indices + : nullptr); + } + + return std::make_shared( + unsafe_get(source), + remove_filtered ? std::get_if>(&filter) + : nullptr, + nullptr); +} + +dataframe::dataframe(std::shared_ptr other, + std::vector const *filtered, + std::vector const *sorted) : + source(std::in_place_index<1>, std::move(other)) +{ + auto &cp = unsafe_get(source); + if (filtered) cp.pre_remove.emplace(*filtered); + if (sorted) cp.sorted_indices.emplace(*sorted); + if (!cp.other->finalized) { migrate_data(); } + else + state_data.emplace( + *cp.other->finalized); +} + +void valid_dimension_aggregator( + const dataframe::any_aggregator_type &agg) +{ + using enum aggregator_type; + if (std::holds_alternative(agg)) { + switch (*std::get_if(&agg)) { + case sum: + case min: + case max: + case mean: + throw std::runtime_error( + "Dimension series cannot be aggregated by sum, min, " + "max or mean."); + default: return; + } + } + if (!std::holds_alternative(agg)) + throw std::runtime_error("Dimension series cannot be " + "aggregated by custom aggregator."); +} + +void valid_measure_aggregator( + const dataframe::any_aggregator_type &agg) +{ + if (std::holds_alternative(agg)) + throw std::runtime_error( + "Measure series must be aggregated."); + + if (!std::holds_alternative(agg)) { + if (Refl::enum_names.end() + != std::ranges::find(Refl::enum_names, + std::get_if(&agg)->get_name())) { + throw std::runtime_error( + "Custom aggregator name cannot be any of these: " + + std::string{ + Refl::enum_name_holder.data(), + Refl::enum_name_holder.size()}); + } + } +} + +std::string dataframe::set_aggregate(series_identifier series, + const any_aggregator_type &aggregator) & +{ + change_state_to(state_type::aggregating, + state_modification_reason::needs_series_type); + + auto &&ser = get_data_source().get_series(series); + switch (ser) { + using enum series_type; + default: throw std::runtime_error("Series does not exists."); + case dimension: valid_dimension_aggregator(aggregator); break; + case measure: valid_measure_aggregator(aggregator); break; + } + + change_state_to(state_type::aggregating, + state_modification_reason::needs_own_state); + auto &aggs = unsafe_get(state_data); + + const auto *agg = std::get_if(&aggregator); + if (ser == series_type::dimension && !agg) { + aggs.dims.emplace(unsafe_get(ser)); + return {}; + } + + return aggs.add_aggregated(std::move(ser), + agg ? aggregators[*agg] + : std::get(aggregator)); +} + +void dataframe::set_filter(std::function &&filt) & +{ + if (auto *bools = std::get_if>(&filter)) { + bools->assign(get_record_count(), false); + if (filt) { + visit( + [&filt, it = bools->begin()]( + record_type record) mutable + { + *it++ = filt(record); + }, + false); + } + } + else + filter = std::move(filt); +} + +void dataframe::set_sort(series_identifier series, + any_sort_type sort, + na_position na_pos) & +{ + change_state_to(state_type::sorting, + state_modification_reason::needs_series_type); + + const sort_type *sort_ptr = std::get_if(&sort); + auto ser = get_data_source().get_series(series); + + switch (ser) { + using enum series_type; + default: throw std::runtime_error("Series does not exists."); + case dimension: { + std::optional> indices; + if (const auto &dim = unsafe_get(ser).second; + std::ranges::is_sorted( + indices.emplace(dim.get_indices(sort))) + && (na_pos == dim.na_pos + || std::ranges::find(dim.values, data_source::nav) + == dim.values.end())) + break; + + if (source == source_type::copying) migrate_data(); + + auto &s = *unsafe_get(source); + const auto &[name, dim] = + unsafe_get(s.get_series(series)); + dim.sort_by(std::move(*indices), na_pos); + + sort_ptr = nullptr; + ser.emplace(name, dim); + break; + } + case measure: + if (!sort_ptr) + throw std::runtime_error( + "Measure series cannot be sorted by categories."); + switch (*sort_ptr) { + case sort_type::less: + case sort_type::greater: break; + case sort_type::natural_less: + case sort_type::natural_greater: + throw std::runtime_error( + "Measure series cannot be sorted by natural order."); + } + break; + } + + change_state_to(state_type::sorting, + state_modification_reason::needs_own_state); + + std::get_if(&state_data) + ->emplace_back(std::in_place_index<0>, + ser, + sort_ptr ? *sort_ptr : sort_type::less, + na_pos); +} + +void dataframe::set_custom_sort( + std::function + custom_sort) & +{ + if (!custom_sort) return; + + change_state_to(state_type::sorting, + state_modification_reason::needs_own_state); + + std::get_if(&state_data) + ->emplace_back(std::move(custom_sort)); +} + +void dataframe::add_dimension( + std::span dimension_categories, + std::span dimension_values, + const char *name, + adding_type adding_strategy, + std::span> info) & +{ + if (!name) + throw std::runtime_error("Series name cannot be null."); + + change_state_to(state_type::modifying, + state_modification_reason::needs_series_type); + + switch (get_data_source().get_series(name)) { + default: { + if (adding_strategy == adding_type::override_full + || adding_strategy + == adding_type::override_all_with_rotation) + throw std::runtime_error("override unknown dimension"); + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + unsafe_get(source)->add_new_dimension( + dimension_categories, + dimension_values, + name, + info); + break; + } + case series_type::dimension: { + if (adding_strategy == adding_type::create_or_throw) + throw std::runtime_error("create existing dimension"); + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + auto &dims = unsafe_get( + unsafe_get(source)->get_series(name)) + .second; + + switch (adding_strategy) { + case adding_type::create_or_throw: + case adding_type::create_or_add: { + dims.add_more_data(dimension_categories, + dimension_values); + break; + } + case adding_type::override_full: { + dims.categories.assign(dimension_categories.begin(), + dimension_categories.end()); + dims.values.assign(dimension_values.begin(), + dimension_values.end()); + dims.info = {info.begin(), info.end()}; + break; + } + case adding_type::override_all_with_rotation: { + dims.categories.assign(dimension_categories.begin(), + dimension_categories.end()); + for (auto i = std::size_t{}; i < dims.values.size(); ++i) + dims.values[i] = + dimension_values[i % dimension_values.size()]; + dims.info = {info.begin(), info.end()}; + break; + } + } + break; + } + case series_type::measure: + throw std::runtime_error("add dimension -> measure"); + } +} + +void dataframe::add_measure(std::span measure_values, + const char *name, + adding_type adding_strategy, + std::span> info) & +{ + if (!name) + throw std::runtime_error("Series name cannot be null."); + + change_state_to(state_type::modifying, + state_modification_reason::needs_series_type); + + switch (get_data_source().get_series(name)) { + default: { + if (adding_strategy == adding_type::override_full + || adding_strategy + == adding_type::override_all_with_rotation) + throw std::runtime_error("override unknown measure"); + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + unsafe_get(source)->add_new_measure( + measure_values, + name, + info); + break; + } + case series_type::measure: { + if (adding_strategy == adding_type::create_or_throw) + throw std::runtime_error("create existing measure"); + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + auto &meas = unsafe_get( + unsafe_get(source)->get_series(name)) + .second; + + switch (adding_strategy) { + case adding_type::create_or_throw: + case adding_type::create_or_add: { + meas.values.insert(meas.values.end(), + measure_values.begin(), + measure_values.end()); + break; + } + case adding_type::override_full: { + meas.values.assign(measure_values.begin(), + measure_values.end()); + meas.info = {info.begin(), info.end()}; + break; + } + case adding_type::override_all_with_rotation: { + for (auto i = std::size_t{}; i < meas.values.size(); ++i) + meas.values[i] = + measure_values[i % measure_values.size()]; + meas.info = {info.begin(), info.end()}; + break; + } + } + break; + } + case series_type::dimension: + throw std::runtime_error("add measure -> dimension"); + } +} + +void dataframe::add_series_by_other(series_identifier curr_series, + const char *name, + std::function + value_transform, + std::span> info) & +{ + if (!name) + throw std::runtime_error("Series name cannot be null."); + + change_state_to(state_type::modifying, + state_modification_reason::needs_series_type); + + const auto &pre = get_data_source(); + auto &&type = pre.get_series(curr_series); + if (type == series_type::unknown) + throw std::runtime_error("Reference series does not exists."); + if (pre.get_series(name) != series_type::unknown) + throw std::runtime_error("Series name already exists."); + + change_state_to(state_type::modifying, + state_modification_reason::needs_record_count); + + if (get_record_count() == 0) + throw std::runtime_error("Dataframe contains no records."); + + Refl::EnumVariant + v; + + auto add_val = [&v, &info](cell_value &&add_val) + { + using enum series_type; + const auto *d = std::get_if(&add_val); + switch (v) { + default: + if (d) + v.emplace(std::ranges::single_view(*d), + info); + else + v.emplace( + std::ranges::single_view( + *std::get_if(&add_val)), + std::ranges::single_view(0), + info); + break; + case measure: + if (!d) throw std::runtime_error("Mixed return types."); + unsafe_get(v).values.emplace_back(*d); + break; + case dimension: + if (d) throw std::runtime_error("Mixed return types."); + unsafe_get(v).add_element( + *std::get_if(&add_val)); + break; + } + }; + + change_state_to(state_type::modifying, + state_modification_reason::needs_sorted_records); + + switch (type) { + using enum series_type; + default: break; + case dimension: { + visit( + [&value_transform, + &add_val, + &dim = unsafe_get(type).second]( + record_type record) + { + add_val(value_transform(record, + dim.get(std::get(record.recordId)))); + }, + false); + break; + } + case measure: { + visit( + [&value_transform, + &add_val, + &mea = unsafe_get(type).second]( + record_type record) + { + add_val(value_transform(record, + mea.values[std::get( + record.recordId)])); + }, + false); + break; + } + } + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + auto &s = *unsafe_get(source); + + switch (v) { + using enum series_type; + default: break; + case measure: + s.add_new_measure(std::move(unsafe_get(v)), name); + break; + case dimension: + s.add_new_dimension(std::move(unsafe_get(v)), + name); + break; + } +} + +void dataframe::remove_series( + std::span names) & +{ + if (names.empty()) return; + + change_state_to(state_type::modifying, + state_modification_reason::needs_series_type); + + std::vector remove_dimensions; + std::vector remove_measures; + + for (auto &&s = get_data_source(); const auto &name : names) { + switch (auto ser = s.get_series(name)) { + using enum series_type; + default: throw std::runtime_error("Series does not exists."); + case dimension: { + auto ix = &unsafe_get(ser).second + - s.dimensions.data(); + remove_dimensions.insert( + std::ranges::lower_bound(remove_dimensions, ix), + ix); + break; + } + case measure: { + auto ix = + &unsafe_get(ser).second - s.measures.data(); + remove_measures.insert( + std::ranges::lower_bound(remove_measures, ix), + ix); + break; + } + } + } + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + unsafe_get(source)->remove_series( + remove_dimensions, + remove_measures); +} + +void dataframe::add_record(std::span values) & +{ + if (values.empty()) + throw std::runtime_error("Empty record cannot be added."); + + std::size_t dimensionIx{}; + std::size_t measureIx{}; + + for (const auto &v : values) + if (std::holds_alternative(v)) + ++measureIx; + else + ++dimensionIx; + + change_state_to(state_type::modifying, + state_modification_reason::needs_series_type); + + const auto &pre = get_data_source(); + if (measureIx != pre.measures.size()) + throw std::runtime_error("Measure count not match."); + if (dimensionIx != pre.dimensions.size()) + throw std::runtime_error("Dimension count not match."); + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + auto &s = *unsafe_get(source); + s.normalize_sizes(); + + measureIx = 0; + dimensionIx = 0; + for (const auto &v : values) { + if (const double *measure = std::get_if(&v)) + s.measures[measureIx++].values.emplace_back(*measure); + else { + s.dimensions[dimensionIx++].add_element( + *std::get_if(&v)); + } + } +} + +void dataframe::remove_records( + std::span record_ids) & +{ + + change_state_to(state_type::modifying, + state_modification_reason::needs_sorted_records); + + std::vector remove_ix; + for (auto &&s = get_data_source(); auto id : record_ids) { + if (std::holds_alternative(id)) + s.change_record_identifier_type(id); + + remove_ix.emplace_back(*std::get_if(&id)); + } + std::sort(remove_ix.begin(), remove_ix.end()); + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + unsafe_get(source)->remove_records( + remove_ix); +} + +void dataframe::remove_records( + std::function filter) & +{ + if (!filter) return; + + change_state_to(state_type::modifying, + state_modification_reason::needs_record_count); + + if (get_record_count() == 0) return; + + change_state_to(state_type::modifying, + state_modification_reason::needs_sorted_records); + + std::vector remove_ix; + visit( + [&remove_ix, &filter](const record_type &r) + { + if (filter(r)) + remove_ix.push_back( + std::get(r.recordId)); + }, + false); + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + unsafe_get(source)->remove_records( + remove_ix); +} + +void dataframe::change_data(record_identifier record_id, + series_identifier column, + cell_value value) & +{ + const double *d = std::get_if(&value); + + change_state_to(state_type::modifying, + state_modification_reason::needs_sorted_records); + + const auto &s = get_data_source(); + + if (std::holds_alternative(record_id)) + s.change_record_identifier_type(record_id); + + if (s.get_record_count() <= *std::get_if(&record_id)) + throw std::runtime_error("Record does not exists."); + + switch (s.get_series(column)) { + using enum series_type; + default: throw std::runtime_error("Series does not exists."); + case measure: + if (!d) throw std::runtime_error("Measure must be a number."); + break; + case dimension: + if (d) + throw std::runtime_error("Dimension must be a string."); + break; + } + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + switch (auto &&ser = + unsafe_get(source)->get_series( + column)) { + using enum series_type; + default: break; + case measure: + unsafe_get(ser) + .second.values[*std::get_if(&record_id)] = + *d; + break; + case dimension: + unsafe_get(ser).second.set( + *std::get_if(&record_id), + *std::get_if(&value)); + break; + } +} + +void dataframe::fill_na(series_identifier column, cell_value value) & +{ + const double *d = std::get_if(&value); + + change_state_to(state_type::modifying, + state_modification_reason::needs_series_type); + + switch (get_data_source().get_series(column)) { + using enum series_type; + default: throw std::runtime_error("Series does not exists."); + case measure: + if (!d) throw std::runtime_error("Measure must be a number."); + if (std::isnan(*d)) + throw std::runtime_error("Value must not be NaN."); + break; + case dimension: + if (d) + throw std::runtime_error("Dimension must be a string."); + break; + } + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + switch (auto &&ser = + unsafe_get(source)->get_series( + column)) { + using enum series_type; + default: break; + case measure: + for (auto &v : unsafe_get(ser).second.values) + if (std::isnan(v)) v = *d; + break; + case dimension: + unsafe_get(ser).second.set_nav( + *std::get_if(&value)); + break; + } +} + +void dataframe::finalize() & +{ + change_state_to(state_type::finalized, + state_modification_reason::needs_own_state); +} + +std::string dataframe::as_string() const & { return {}; } + +std::span dataframe::get_dimensions() const & +{ + return get_data_source().dimension_names; +} + +std::span dataframe::get_measures() const & +{ + return get_data_source().measure_names; +} + +std::span dataframe::get_categories( + series_identifier dimension) const & +{ + switch (auto &&ser = get_data_source().get_series(dimension)) { + using enum series_type; + default: throw std::runtime_error("Series does not exists."); + case measure: + throw std::runtime_error("Measure series has no categories."); + case dimension: + return unsafe_get(ser).second.categories; + } +} + +std::pair dataframe::get_min_max( + series_identifier measure) const & +{ + auto &&s = get_data_source(); + switch (auto &&meas = s.get_series(measure)) { + using enum series_type; + default: throw std::runtime_error("Series does not exists."); + case dimension: + throw std::runtime_error("Dimension series has no min/max."); + case measure: + return s.get_min_max(unsafe_get(meas).second); + } +} + +cell_value dataframe::get_data(record_identifier record_id, + series_identifier column) const & +{ + const auto &s = get_data_source(); + + if (std::holds_alternative(record_id)) + s.change_record_identifier_type(record_id); + + return s.get_data(*std::get_if(&record_id), column); +} + +void dataframe::visit(std::function function, + bool filtered) const & +{ + if (const auto *fun = std::get_if<0>(&filter); + fun && filtered && *fun) + throw std::runtime_error( + "Filtered dataframe is not finalized."); + + const auto *cp = get_if(&source); + + const auto *filt = filtered ? std::get_if<1>(&filter) : nullptr; + if (!filt && cp && cp->pre_remove) filt = &*cp->pre_remove; + visit(function, + cp && cp->sorted_indices ? &*cp->sorted_indices : nullptr, + filt); +} + +void dataframe::visit( + const std::function &function, + const std::vector *sort, + const std::vector *filt) const +{ + for (std::size_t i{}, max = get_record_count(); i < max; ++i) + if (auto ix = sort ? (*sort)[i] : i; !filt || !(*filt)[ix]) + function({this, ix}); +} + +void dataframe::migrate_data() +{ + auto *s = get_if(&source); + switch (state_data) { + using enum state_type; + case aggregating: { + source = std::make_shared( + std::move(*std::get_if( + &state_data)), + s && s->pre_remove ? &*s->pre_remove : nullptr, + get_record_count()); + + break; + } + case sorting: { + if (s) { + if (auto &&indices = s->other->get_sorted_indices(this, + std::exchange(unsafe_get(state_data), + {})); + !std::ranges::is_sorted(indices)) + s->sorted_indices = std::move(indices); + else + s->sorted_indices.reset(); + } + [[fallthrough]]; + } + default: + case modifying: + case finalized: { + if (!s) return; + source = std::make_shared(s->other, + std::exchange(s->pre_remove, {}), + std::exchange(s->sorted_indices, {})); + break; + } + } +} + +void dataframe::change_state_to(state_type new_state, + state_modification_reason reason) +{ + using reason_t = state_modification_reason; + if (state_data == new_state) return; + + switch (state_data) { + using enum state_type; + case modifying: + if (reason == reason_t::needs_series_type + || reason == reason_t::needs_sorted_records) + return; + if (auto *owned = get_if(&source)) + (*owned)->normalize_sizes(); + break; + case aggregating: migrate_data(); break; + case sorting: + if (reason == reason_t::needs_record_count + || reason == reason_t::needs_series_type) + return; + if (auto *ptr = std::get_if(&source)) { + if (auto &&indices = ptr->other->get_sorted_indices(this, + std::exchange(unsafe_get(state_data), + {})); + !std::ranges::is_sorted(indices)) + ptr->sorted_indices = std::move(indices); + } + else { + auto &owning = *unsafe_get(source); + if (auto &&indices = owning.get_sorted_indices(this, + std::exchange(unsafe_get(state_data), + {})); + !std::ranges::is_sorted(indices)) + owning.sort(std::move(indices)); + } + break; + default: + case finalized: + if (reason != reason_t::needs_own_state) return; + break; + } + + switch (new_state) { + using enum state_type; + case modifying: + migrate_data(); + state_data.emplace(); + break; + case aggregating: + if (auto *ptr = std::get_if(&source); + ptr && ptr->sorted_indices) + throw std::runtime_error( + "Dataframe cannot aggregate sorted data."); + state_data.emplace(); + break; + case sorting: + if (auto *ptr = std::get_if(&source); + ptr && ptr->sorted_indices) + throw std::runtime_error( + "Dataframe cannot sort already sorted data."); + + state_data.emplace(); + break; + case finalized: { + if (auto *ptr = get_if(&source)) + state_data.emplace((*ptr)->finalize()); + else + state_data.emplace( + *unsafe_get(source).other->finalized); + + auto the_filter = std::move(*std::get_if<0>(&this->filter)); + this->filter.emplace<1>(get_record_count()); + if (the_filter) set_filter(std::move(the_filter)); + break; + } + } +} + +const data_source &dataframe::get_data_source() const +{ + using enum source_type; + return source == owning ? *unsafe_get(source) + : *unsafe_get(source).other; +} +std::string_view dataframe::get_series_name( + const series_identifier &id) const & +{ + switch (auto &&ser = get_data_source().get_series(id)) { + using enum series_type; + default: return {}; + case measure: return unsafe_get(ser).first; + case dimension: return unsafe_get(ser).first; + } +} + +std::string_view dataframe::get_record_unique_id( + const record_identifier &id) const & +{ + if (const auto *ptr = std::get_if(&id)) + return *ptr; + + const auto *state = get_if(&state_data); + if (!state) + throw std::runtime_error("Dataframe is not finalized."); + + const data_source::final_info &info = state->get(); + auto ix = *std::get_if(&id); + return ix < info.record_unique_ids.size() + ? info.record_unique_ids[ix] + : std::string_view{}; +} + +} diff --git a/src/dataframe/impl/dataframe.h b/src/dataframe/impl/dataframe.h new file mode 100644 index 000000000..8c1916d6a --- /dev/null +++ b/src/dataframe/impl/dataframe.h @@ -0,0 +1,162 @@ +#ifndef VIZZU_DATAFRAME_DATAFRAME_H +#define VIZZU_DATAFRAME_DATAFRAME_H + +#include + +#include "../interface.h" + +#include "data_source.h" + +namespace Vizzu::dataframe +{ + +class dataframe final : public dataframe_interface +{ + enum class state_type { + modifying, + aggregating, + sorting, + finalized + }; + + enum class source_type { owning, copying }; + + enum class state_modification_reason { + needs_series_type, + needs_record_count, + needs_sorted_records, + needs_own_state + }; + + struct copy_source + { + std::shared_ptr other; + std::optional> sorted_indices; + std::optional> pre_remove; + }; + +public: + dataframe() noexcept = default; + dataframe(std::shared_ptr other, + std::vector const *filtered, + std::vector const *sorted); + + std::shared_ptr copy(bool remove_filtered, + bool inherit_sorting) const & final; + + std::string set_aggregate(series_identifier series, + const any_aggregator_type &aggregator) + & final; + + void set_filter(std::function &&filt) & final; + + void set_sort(series_identifier series, + any_sort_type sort, + na_position na_pos) + & final; + + void set_custom_sort( + std::function + custom_sort) + & final; + + void add_dimension( + std::span dimension_categories, + std::span dimension_values, + const char *name, + adding_type adding_strategy, + std::span> info) + & final; + + void add_measure(std::span measure_values, + const char *name, + adding_type adding_strategy, + std::span> info) + & final; + + void add_series_by_other(series_identifier curr_series, + const char *name, + std::function + value_transform, + std::span> info) + & final; + + void remove_series(std::span names) + & final; + + void add_record(std::span values) & final; + + void remove_records(std::span record_ids) + & final; + + void remove_records(std::function filter) + & final; + + void change_data(record_identifier record_id, + series_identifier column, + cell_value value) + & final; + + void fill_na(series_identifier column, cell_value value) & final; + + void finalize() & final; + + std::string as_string() const & final; + + std::span get_dimensions() const & final; + + std::span get_measures() const & final; + + std::span get_categories( + series_identifier dimension) const & final; + + std::pair get_min_max( + series_identifier measure) const & final; + + [[nodiscard]] std::string_view get_series_name( + const series_identifier &id) const & final; + + [[nodiscard]] std::string_view get_record_unique_id( + const record_identifier &id) const & final; + + cell_value get_data(record_identifier record_id, + series_identifier column) const & final; + + std::size_t get_record_count() const & final + { + return get_data_source().get_record_count(); + } + + void visit(std::function function, + bool filtered) const & final; + +private: + void migrate_data(); + void change_state_to(state_type new_state, + state_modification_reason reason); + + const data_source &get_data_source() const; + + void visit(const std::function &function, + const std::vector *sort, + const std::vector *filt) const; + + Refl::EnumVariant, + copy_source> + source{std::make_shared()}; + + Refl::EnumVariant> + state_data; + + std::variant, std::vector> + filter; +}; + +} + +#endif // VIZZU_DATAFRAME_DATAFRAME_H diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index 07aa51fde..4196a05c4 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -1,6 +1,6 @@ #include "../util/test.h" -// #include "dataframe/impl/dataframe.h" +#include "dataframe/impl/dataframe.h" #include "dataframe/interface.h" using bool_msg = test::expected_bool_t; @@ -25,8 +25,7 @@ struct setup std::vector> data{}; bool copied{}; std::shared_ptr _df{ - // std::make_shared() // TODO - }; + std::make_shared()}; explicit(false) operator interface *() { From 3ad316dff5340937fff522221c8275e96b1f851d Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Mon, 26 Feb 2024 17:30:55 +0100 Subject: [PATCH 059/253] Tooltip fix on incoming animation removes toltipped marker. --- CHANGELOG.md | 1 + src/apps/weblib/ts-api/plugins/tooltip.ts | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05ec0e92c..61dc32f84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Line chart draws was overwrite the event's settings. - Legend title outerRect was not properly calculated. - Fixed stacked empty min/max aggregated values. +- Fixed error when an animation triggered during tooptip activation which removed the corresopnding marker. ### Added diff --git a/src/apps/weblib/ts-api/plugins/tooltip.ts b/src/apps/weblib/ts-api/plugins/tooltip.ts index 8e10b4f52..b17ae53a7 100644 --- a/src/apps/weblib/ts-api/plugins/tooltip.ts +++ b/src/apps/weblib/ts-api/plugins/tooltip.ts @@ -1,5 +1,6 @@ +import { Snapshot } from '../module/cchart.js' import { Element, Marker, PointerEvent } from '../events.js' -import { Plugin } from '../plugins.js' +import { Plugin, type PluginHooks, type PrepareAnimationContext } from '../plugins.js' import Vizzu from '../vizzu.js' @@ -11,6 +12,24 @@ export class Tooltip implements Plugin { private _overedMarkerId: number | null = null private _lastMove = new Date().getTime() + get hooks(): PluginHooks { + return { + prepareAnimation: (ctx: PrepareAnimationContext, next: () => void): void => { + if (Array.isArray(ctx.target)) + ctx.target.forEach(({ target }) => { + if (target instanceof Snapshot) return + if (!target.config) target.config = {} + if (!('tooltip' in target.config)) { + target.config.tooltip = null + this._id++ + this._lastMarkerId = null + } + }) + next() + } + } + } + meta = { name: 'tooltip', depends: ['pointerEvents'] From b30f2cc096204959c2ca39a39d01a380e0873cdc Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Tue, 27 Feb 2024 18:57:57 +0100 Subject: [PATCH 060/253] Test: motion blur: test env incompatible part removed. --- .../web_content/cookbook/rendering/motion_blur.mjs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/e2e/test_cases/web_content/cookbook/rendering/motion_blur.mjs b/test/e2e/test_cases/web_content/cookbook/rendering/motion_blur.mjs index 34f3ac2ff..9a7ecd526 100755 --- a/test/e2e/test_cases/web_content/cookbook/rendering/motion_blur.mjs +++ b/test/e2e/test_cases/web_content/cookbook/rendering/motion_blur.mjs @@ -43,12 +43,6 @@ const testSteps = [ y: 'Joy factors', x: 'Value 2 (+)' }) - .then((chart) => { - const handle = setInterval(() => { - const ctx = chart.feature.htmlCanvas.element.getContext('2d') - if (!drawImages(ctx)) clearInterval(handle) - }, 40) - }) } ] From 792d4b54e8d9dec99db32e2a3574bb839dd12c1b Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Tue, 27 Feb 2024 19:26:21 +0100 Subject: [PATCH 061/253] fixed prettier --- .../web_content/cookbook/rendering/motion_blur.mjs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/e2e/test_cases/web_content/cookbook/rendering/motion_blur.mjs b/test/e2e/test_cases/web_content/cookbook/rendering/motion_blur.mjs index 9a7ecd526..2730b819c 100755 --- a/test/e2e/test_cases/web_content/cookbook/rendering/motion_blur.mjs +++ b/test/e2e/test_cases/web_content/cookbook/rendering/motion_blur.mjs @@ -37,12 +37,11 @@ const testSteps = [ chart.on('draw-complete', drawComplete) - return chart - .animate({ - coordSystem: 'polar', - y: 'Joy factors', - x: 'Value 2 (+)' - }) + return chart.animate({ + coordSystem: 'polar', + y: 'Joy factors', + x: 'Value 2 (+)' + }) } ] From d6a241bd07aebd83f8aa57e7d1f167e6ca79c01f Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Tue, 27 Feb 2024 20:54:49 +0100 Subject: [PATCH 062/253] test hash fixed --- test/e2e/test_cases/test_cases.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/test_cases/test_cases.json b/test/e2e/test_cases/test_cases.json index 4a00a75b0..8090ca7c1 100644 --- a/test/e2e/test_cases/test_cases.json +++ b/test/e2e/test_cases/test_cases.json @@ -3287,7 +3287,7 @@ "refs": ["2a18132"] }, "web_content/cookbook/rendering/motion_blur": { - "refs": ["be67912"] + "refs": ["3a48367"] }, "web_content/cookbook/style/colorfilter": { "refs": ["05406ff"] From 6b866660f133aab9c13e15bf2f2d272a3ef85e63 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 28 Feb 2024 18:02:07 +0100 Subject: [PATCH 063/253] rename setup --- test/unit/dataframe/interface_test.cpp | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index 4196a05c4..4e94ead27 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -18,7 +18,7 @@ using Vizzu::dataframe::sort_type; using interface = Vizzu::dataframe::dataframe_interface; using record_type = interface::record_type; -struct setup +struct if_setup { std::vector dims{}; std::vector meas{}; @@ -89,25 +89,25 @@ struct setup static inline const auto empty_input = input{[] { - return setup{}; + return if_setup{}; }, "empty input"}; static inline const auto empty_input_copied = input{[] { - return setup{.copied = true}; + return if_setup{.copied = true}; }, "empty input copied"}; static inline const auto one_one_empty = input{[] { - return setup{.dims = {"test_dim"}, .meas = {"test_meas"}}; + return if_setup{.dims = {"test_dim"}, .meas = {"test_meas"}}; }, "one one empty"}; static inline const auto one_one_empty_copied = input{[] { - return setup{.dims = {"test_dim"}, + return if_setup{.dims = {"test_dim"}, .meas = {"test_meas"}, .copied = true}; }, @@ -256,7 +256,7 @@ const static auto tests = } | "add_series_by_other" | - [](interface *df = setup{{"d1", "d2"}, + [](interface *df = if_setup{{"d1", "d2"}, {"m1"}, {{{"dm1", "dm2", 0.0}}, {{"dm1", "dmX", 1.0}}, @@ -310,7 +310,7 @@ const static auto tests = } | "remove_series" | - [](interface *df = setup{{"d1", "d2", "d3"}, + [](interface *df = if_setup{{"d1", "d2", "d3"}, {"m1", "m2", "m3"}, { {{"dm1", "dx2", "dm3", 0.0, 0.1, 0.2}}, @@ -329,7 +329,7 @@ const static auto tests = } | "remove_records" | - [](interface *df = setup{{"d1"}, + [](interface *df = if_setup{{"d1"}, {"m1"}, { {{"dm0", NAN}}, @@ -361,7 +361,7 @@ const static auto tests = } | "remove_records_filter" | - [](interface *df = setup{{"d1"}, + [](interface *df = if_setup{{"d1"}, {"m1"}, { {{"dm0", 5.3}}, @@ -393,7 +393,7 @@ const static auto tests = } | "change_data" | - [](interface *df = setup{{"d1"}, + [](interface *df = if_setup{{"d1"}, {"m1"}, {{{"dm0", 5.3}}, {{"dm1", 2.0}}, {{"dm2", 3.3}}}}) { @@ -423,7 +423,7 @@ const static auto tests = } | "fill_na" | - [](interface *df = setup{{"d1"}, + [](interface *df = if_setup{{"d1"}, {"m1"}, {{{"dm0", 5.3}}, {{std::string_view{nullptr, 0}, 2.0}}, @@ -444,7 +444,7 @@ const static auto tests = } | "aggregate types" | - [](interface *df = setup{{"d1"}, + [](interface *df = if_setup{{"d1"}, {"m1"}, { {{"dm0", 5.5}}, @@ -567,7 +567,7 @@ const static auto tests = } | "aggregate multiple dim" | - [](interface *df = setup{{"d1", "d2", "d3"}, + [](interface *df = if_setup{{"d1", "d2", "d3"}, {"m1"}, { {{"dx0", "dm0", "doa", 5.5}}, @@ -626,14 +626,14 @@ const static auto tests = } | "cannot finalize contains same dim" | - [](interface *df = setup{.dims = {"d1", "d2"}, + [](interface *df = if_setup{.dims = {"d1", "d2"}, .data = {{{"dx0", "dm0"}}, {{"dx0", "dm0"}}}}) { throw_(&interface::finalize, df); } | "sort dimension" | - [](interface *df = setup{{"d1", "d2"}, + [](interface *df = if_setup{{"d1", "d2"}, {"m1"}, { {{"dx2", "dm2", 5.5}}, @@ -701,7 +701,7 @@ const static auto tests = } | "another sort example" | - [](interface *df = setup{{"d1", "d2"}, + [](interface *df = if_setup{{"d1", "d2"}, {"m1"}, { {{"dx2", "dm2", 88.0}}, @@ -780,7 +780,7 @@ const static auto tests = } | "sort measure" | - [](interface *df = setup{{"d1"}, + [](interface *df = if_setup{{"d1"}, {"m1"}, { {{"dm0", 5.5}}, From af54de3ff7c2d4c40f2b3b17f8bfe65dd925d755 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 28 Feb 2024 18:04:52 +0100 Subject: [PATCH 064/253] Add Chart::Events testsuite --- src/apps/qutils/canvas.cpp | 9 - src/apps/qutils/canvas.h | 1 - src/apps/weblib/jscriptcanvas.cpp | 5 - src/apps/weblib/jscriptcanvas.h | 1 - src/base/gfx/canvas.h | 1 - src/base/util/eventdispatcher.h | 7 + src/chart/main/chart.h | 3 +- src/chart/ui/chart.h | 2 +- src/data/table/datatable.cpp | 2 +- src/data/table/datatable.h | 2 +- test/unit/chart/events.cpp | 322 ++++++++++++++++++++++++++++++ test/unit/util/condition.h | 21 ++ 12 files changed, 355 insertions(+), 21 deletions(-) create mode 100644 test/unit/chart/events.cpp diff --git a/src/apps/qutils/canvas.cpp b/src/apps/qutils/canvas.cpp index a4f1f6183..0679e12c1 100644 --- a/src/apps/qutils/canvas.cpp +++ b/src/apps/qutils/canvas.cpp @@ -135,15 +135,6 @@ void BaseCanvas::endPolygon() polygon = QPainterPath(); } -Geom::Rect BaseCanvas::getClipRect() const -{ - if (painter.hasClipping()) - return fromQRectF(painter.clipBoundingRect()); - return {Geom::Point(), - Geom::Size{static_cast(painter.device()->width()), - static_cast(painter.device()->height())}}; -} - void BaseCanvas::setClipRect(const Geom::Rect &rect) { painter.setClipping(true); diff --git a/src/apps/qutils/canvas.h b/src/apps/qutils/canvas.h index 4c3f5307e..d801d6302 100644 --- a/src/apps/qutils/canvas.h +++ b/src/apps/qutils/canvas.h @@ -19,7 +19,6 @@ class BaseCanvas : public Gfx::ICanvas, public Vizzu::Draw::Painter Gfx::ICanvas &getCanvas() override { return *this; } - [[nodiscard]] Geom::Rect getClipRect() const override; void setClipRect(const Geom::Rect &rect) override; void setClipCircle(const Geom::Circle &circle) override; void setClipPolygon() override; diff --git a/src/apps/weblib/jscriptcanvas.cpp b/src/apps/weblib/jscriptcanvas.cpp index 0b5a3c7ca..4d79f58d0 100644 --- a/src/apps/weblib/jscriptcanvas.cpp +++ b/src/apps/weblib/jscriptcanvas.cpp @@ -6,11 +6,6 @@ namespace Vizzu::Main { -Geom::Rect JScriptCanvas::getClipRect() const -{ - return clipRect ? *clipRect : Geom::Rect::CenteredMax(); -} - void JScriptCanvas::setClipRect(const Geom::Rect &rect) { if (!clipRect || *clipRect != rect) { diff --git a/src/apps/weblib/jscriptcanvas.h b/src/apps/weblib/jscriptcanvas.h index ccbd4d1bd..ccbfd8a70 100644 --- a/src/apps/weblib/jscriptcanvas.h +++ b/src/apps/weblib/jscriptcanvas.h @@ -16,7 +16,6 @@ class JScriptCanvas : public Gfx::ICanvas, public Draw::Painter JScriptCanvas() = default; ~JScriptCanvas() override = default; - [[nodiscard]] Geom::Rect getClipRect() const override; void setClipRect(const Geom::Rect &rect) override; void setClipCircle(const Geom::Circle &circle) override; void setClipPolygon() override; diff --git a/src/base/gfx/canvas.h b/src/base/gfx/canvas.h index fbc060db6..24efdd5cb 100644 --- a/src/base/gfx/canvas.h +++ b/src/base/gfx/canvas.h @@ -23,7 +23,6 @@ struct ICanvas { virtual ~ICanvas() = default; - [[nodiscard]] virtual Geom::Rect getClipRect() const = 0; virtual void setClipRect(const Geom::Rect &rect) = 0; virtual void setClipCircle(const Geom::Circle &circle) = 0; virtual void setClipPolygon() = 0; diff --git a/src/base/util/eventdispatcher.h b/src/base/util/eventdispatcher.h index 06e0dd931..b892bc5c5 100644 --- a/src/base/util/eventdispatcher.h +++ b/src/base/util/eventdispatcher.h @@ -69,6 +69,13 @@ class EventDispatcher bool destroyEvent(const event_ptr &event); + void registerOnEachEvent(const std::uint64_t &id, + const handler_fn &handler) const + { + for (const auto &event : eventRegistry) + event->attach(id, handler); + } + protected: struct EventPtrComp { diff --git a/src/chart/main/chart.h b/src/chart/main/chart.h index df33277d0..c5c5c2bbd 100644 --- a/src/chart/main/chart.h +++ b/src/chart/main/chart.h @@ -30,6 +30,7 @@ class Chart Event onChanged; Chart(); + Chart(Chart &&) noexcept = delete; void draw(Gfx::ICanvas &canvas); void setBoundRect(const Geom::Rect &rect); @@ -46,7 +47,7 @@ class Chart actStyles = styles; actStyles.setup(); } - Gen::Options getOptions() { return *nextOptions; } + Gen::Options &getOptions() { return *nextOptions; } void setOptions(const Gen::Options &options) { *nextOptions = options; diff --git a/src/chart/ui/chart.h b/src/chart/ui/chart.h index 0a7919b41..39042482e 100644 --- a/src/chart/ui/chart.h +++ b/src/chart/ui/chart.h @@ -14,7 +14,7 @@ namespace Vizzu::UI class ChartWidget : public GUI::Widget { public: - std::function doChange; + std::function doChange; std::function openUrl; explicit ChartWidget(); diff --git a/src/data/table/datatable.cpp b/src/data/table/datatable.cpp index e9c54d597..ac5cfd92b 100644 --- a/src/data/table/datatable.cpp +++ b/src/data/table/datatable.cpp @@ -91,7 +91,7 @@ DataTable::DataIndex DataTable::addColumn(const std::string &name, } DataTable::DataIndex DataTable::addColumn(const std::string &name, - const std::span &categories, + const std::span &categories, const std::span &values) { std::vector realValues(values.size()); diff --git a/src/data/table/datatable.h b/src/data/table/datatable.h index 57e91a713..3944e570e 100644 --- a/src/data/table/datatable.h +++ b/src/data/table/datatable.h @@ -50,7 +50,7 @@ class DataTable : public Table const std::string &unit, const std::span &values); DataIndex addColumn(const std::string &name, - const std::span &categories, + const std::span &categories, const std::span &values); void pushRow(const std::span &cells); diff --git a/test/unit/chart/events.cpp b/test/unit/chart/events.cpp new file mode 100644 index 000000000..c0181b31b --- /dev/null +++ b/test/unit/chart/events.cpp @@ -0,0 +1,322 @@ + +#include + +#include "../util/test.h" +#include "chart/ui/chart.h" + +using test::operator""_suite; +using test::assert; +using test::check; +using test::skip; +using test::operator""_is_true; + +Geom::Size Gfx::ICanvas::textBoundary(const Font &font, + const std::string &text) +{ + return {static_cast(text.size()) * font.size / 2.0, + font.size}; +} + +struct MyCanvas : Gfx::ICanvas, Vizzu::Draw::Painter +{ + MyCanvas() noexcept = default; + ~MyCanvas() final = default; + void setClipRect(const Geom::Rect &) final {} + void setClipCircle(const Geom::Circle &) final {} + void setClipPolygon() final {} + void setBrushColor(const Gfx::Color &) final {} + void setLineColor(const Gfx::Color &) final {} + void setTextColor(const Gfx::Color &) final {} + void setLineWidth(double) final {} + void setFont(const Gfx::Font &) final {} + void transform(const Geom::AffineTransform &) final {} + void save() final {} + void restore() final {} + void beginDropShadow() final {} + void setDropShadowBlur(double) final {} + void setDropShadowColor(const Gfx::Color &) final {} + void setDropShadowOffset(const Geom::Point &) final {} + void endDropShadow() final {} + void beginPolygon() final {} + void addPoint(const Geom::Point &) final {} + void addBezier(const Geom::Point &, + const Geom::Point &, + const Geom::Point &) final + {} + void endPolygon() final {} + void rectangle(const Geom::Rect &) final {} + void circle(const Geom::Circle &) final {} + void line(const Geom::Line &) final {} + void text(const Geom::Rect &, const std::string &) final {} + void setBrushGradient(const Geom::Line &, + const Gfx::ColorGradient &) final + {} + void frameBegin() final {} + void frameEnd() final {} + void *getPainter() final { return static_cast(this); } + ICanvas &getCanvas() final { return *this; } +}; + +struct chart_setup +{ + std::vector> + series; + Vizzu::Chart chart{}; + + explicit(false) operator Vizzu::Chart &() + { + auto &table = chart.getTable(); + table.addColumn("Dim5", + {{"A", "B", "C", "D", "E"}}, + {{0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4}}); + table.addColumn("Dim4", + {{"a", "b", "c", "d"}}, + {{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}}); + table.addColumn("Dim3", + {{"a", "b", "c"}}, + {{0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 0, 2, 2, 1, 0}}); + table.addColumn("Meas1", + "", + {{1, 2, 4, 3, 3, 4, 2, 1, 4, 3, 1, 2, 2, 1, 3, 4}}); + table.addColumn("Meas2", + "", + {{0, -1, 5, 6, 6, 5, -1, 0, 5, 6, 0, -1, -1, 0, 6, -5}}); + auto &&setter = chart.getSetter(); + for (auto &&[ch, name] : series) setter.addSeries(ch, name); + chart.setBoundRect(Geom::Rect(Geom::Point{}, {{640, 480}})); + return chart; + } +}; + +struct event_as +{ + std::string json; + const Util::EventTarget *target; + std::variant + drawn; +}; + +std::multimap> +get_events(Vizzu::Chart &chart, const double &position = 1.0) +{ + chart.getAnimOptions().control.position = position; + + bool ends{}; + chart.setKeyframe(); + chart.animate( + [&ends](bool b) + { + ends = b; + }); + + std::multimap> events; + chart.getEventDispatcher().registerOnEachEvent(0, + [&events](Util::EventDispatcher::Params ¶ms) + { + if (typeid(params) + == typeid(Util::EventDispatcher::Params) + || dynamic_cast( + ¶ms)) { + events.emplace(std::piecewise_construct, + std::tuple{params.eventName}, + std::tuple{params.toJSON(), params.target}); + } + else if (auto *p = dynamic_cast< + Vizzu::Events::OnTextDrawDetail *>( + ¶ms)) { + events.emplace(std::piecewise_construct, + std::tuple{params.eventName}, + std::tuple{params.toJSON(), params.target, *p}); + } + else if (auto *p = dynamic_cast< + Vizzu::Events::OnRectDrawEvent *>(¶ms)) { + events.emplace(std::piecewise_construct, + std::tuple{params.eventName}, + std::tuple{params.toJSON(), + params.target, + p->rect}); + } + else if (auto *p = dynamic_cast< + Vizzu::Events::OnLineDrawEvent *>(¶ms)) { + events.emplace(std::piecewise_construct, + std::tuple{params.eventName}, + std::tuple{params.toJSON(), + params.target, + p->line}); + } + else + skip->*false + == "Not all draw events type handled"_is_true; + }); + + using clock_t = std::chrono::steady_clock; + chart.getAnimControl().update(clock_t::now()); + chart.setBoundRect(chart.getLayout().boundary); + chart.draw(MyCanvas{}.getCanvas()); + chart.getAnimControl().update(clock_t::now()); + + skip->*ends == "finished"_is_true; + return events; +} + +using enum Vizzu::Gen::ChannelId; +using enum Vizzu::Gen::ShapeType; +using enum Vizzu::Gen::CoordSystem; +using std::ranges::subrange; +using std::ranges::views::values; + +const static auto tests = + "Chart::Events"_suite + + | "area" | + [](Vizzu::Chart &chart = + chart_setup{ + {{x, "Dim5"}, {y, "Meas1"}, {label, "Meas1"}}}) +{ + chart.getOptions().geometry = area; + + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 2u; + check->*events.count("plot-axis-label-draw") == 4u + 5u; + check->*events.count("plot-marker-label-draw") == 5u; + check->*events.count("plot-marker-draw") == 4u; + + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[v, t, d] : values(subrange(beg, end))) + check->*std::get(d).rect.bottom() == 0.0; +} + + | "bar stacked rectangle negative" | + [](Vizzu::Chart &chart = + chart_setup{{{x, "Dim5"}, {x, "Meas2"}, {y, "Dim3"}}}) +{ + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 2u; + check->*events.count("plot-axis-label-draw") == 3u + 3u; + check->*events.count("plot-marker-draw") == 10u; + + double xCenter{}; + using Axis = Vizzu::Events::Targets::Axis; + for (auto &&[beg, end] = events.equal_range("plot-axis-draw"); + const auto &[j, t, l] : values(subrange(beg, end))) + if (!dynamic_cast(*t).horizontal) + xCenter = std::get(l).line.begin.x; + + std::set zero_count{}; + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[j, t, r] : values(subrange(beg, end))) + if (auto rect = std::get(r).rect; + std::abs(rect.pos.x - xCenter) <= 0.000001 + || std::abs(rect.pos.x + rect.size.x - xCenter) + <= 0.000001) + zero_count.insert(rect.bottom()); + check->*zero_count.size() == 3u; +} + + | "dotplot circle" | + [](Vizzu::Chart &chart = chart_setup{{{x, "Dim5"}, {x, "Meas1"}}}) +{ + chart.getOptions().geometry = circle; + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 2u; + check->*events.count("plot-marker-draw") == 5u; + check->*events.count("plot-marker-guide-draw") == 5u; + + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[j, t, r] : values(subrange(beg, end))) { + auto rect = std::get(r).rect; + check->*std::abs(rect.center().y - 0.5) <= 0.000001; + } +} + + | "Marimekko chart" | + [](Vizzu::Chart &chart = + chart_setup{{{x, "Dim5"}, {x, "Meas1"}, {y, "Meas1"}}}) +{ + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 2u; + check->*events.count("plot-marker-draw") == 5u; + + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[j, t, r] : values(subrange(beg, end))) + check->*std::get_if(&r)->rect.bottom() + == 0.0; +} + + | "spiderweb area" | + [](Vizzu::Chart &chart = + chart_setup{{{x, "Dim5"}, {y, "Dim3"}, {y, "Meas1"}}}) +{ + chart.getOptions().geometry = area; + chart.getOptions().coordSystem = polar; + + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 2u; + check->*events.count("plot-axis-label-draw") == 4u + 5u; + check->*events.count("plot-marker-draw") == 13u; + + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[v, t, d] : values(subrange(beg, end))) + check->*std::get_if(&d) != nullptr; +} + + | "coxcomb" | + [](Vizzu::Chart &chart = + chart_setup{{{x, "Dim5"}, {y, "Meas1"}, {color, "Dim5"}}}) +{ + chart.getOptions().coordSystem = polar; + chart.getStyles().plot.marker.rectangleSpacing = 0; + + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 2u; + check->*events.count("plot-axis-label-draw") == 4u + 5u; + check->*events.count("plot-marker-draw") == 5u; + + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[v, t, d] : values(subrange(beg, end))) + check->*std::get_if(&d)->rect.bottom() + == 0.0; +} + + | "bubble chart" | + [](Vizzu::Chart &chart = + chart_setup{{{color, "Dim5"}, {size, "Meas1"}}}) +{ + chart.getOptions().geometry = circle; + + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 0u; + check->*events.count("plot-marker-draw") == 5u; + + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[v, t, d] : values(subrange(beg, end))) { + auto rect = std::get(d).rect; + check->*std::abs(rect.size.x - rect.size.y) <= 0.000001; + } +} + + | "stacked tree map" | + [](Vizzu::Chart &chart = chart_setup{{{color, "Dim5"}, + {size, "Meas1"}, + {size, "Dim3"}, + {label, "Meas1"}}}) +{ + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 0u; + check->*events.count("plot-marker-draw") == 10u; + + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[v, t, d] : values(subrange(beg, end))) + check->*std::get_if(&d) != nullptr; +}; \ No newline at end of file diff --git a/test/unit/util/condition.h b/test/unit/util/condition.h index edc1672f1..59d091be9 100644 --- a/test/unit/util/condition.h +++ b/test/unit/util/condition.h @@ -40,6 +40,27 @@ template struct decomposer } } + template void operator!=(const U &ref) const + { + if constexpr (requires { ref != value; }) { + return evaluate(value != ref, "!=", ref); + } + else if constexpr (std::ranges::range) { + if (std::ranges::size(value) != std::ranges::size(ref)) + return; + auto &&[lhs, rhs] = std::ranges::mismatch(value, ref); + return evaluate(lhs != std::end(value), "!=", ref); + } + else if constexpr (std::is_convertible_v) { + return *this != static_cast(ref); + } + else { + static_assert( + requires { ref != value; }, + "Cannot compare types"); + } + } + void operator<=(const auto &ref) const { return evaluate(value <= ref, "<=", ref); From a4f7a8a33eb4d560cb394efdb754d99de64dfc4d Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 29 Feb 2024 09:40:25 +0100 Subject: [PATCH 065/253] fix emscripten build. --- test/unit/chart/events.cpp | 68 +++++++++++++++++++------------------ test/unit/util/case.h | 11 ++++-- test/unit/util/collection.h | 15 +++++--- test/unit/util/condition.h | 8 ++--- 4 files changed, 58 insertions(+), 44 deletions(-) diff --git a/test/unit/chart/events.cpp b/test/unit/chart/events.cpp index c0181b31b..1cf1ae293 100644 --- a/test/unit/chart/events.cpp +++ b/test/unit/chart/events.cpp @@ -9,6 +9,7 @@ using test::assert; using test::check; using test::skip; using test::operator""_is_true; +using test::operator""_is_false; Geom::Size Gfx::ICanvas::textBoundary(const Font &font, const std::string &text) @@ -17,7 +18,7 @@ Geom::Size Gfx::ICanvas::textBoundary(const Font &font, font.size}; } -struct MyCanvas : Gfx::ICanvas, Vizzu::Draw::Painter +struct MyCanvas final : Gfx::ICanvas, Vizzu::Draw::Painter { MyCanvas() noexcept = default; ~MyCanvas() final = default; @@ -61,6 +62,17 @@ struct chart_setup { std::vector> series; + bool is_emscripten{[] + { + bool is_emscripten{ +#ifdef __EMSCRIPTEN__ + true +#endif + }; + + skip->*is_emscripten == "Emscripten build"_is_false; + return is_emscripten; + }()}; Vizzu::Chart chart{}; explicit(false) operator Vizzu::Chart &() @@ -92,17 +104,14 @@ struct event_as { std::string json; const Util::EventTarget *target; - std::variant + std::variant drawn; }; -std::multimap> -get_events(Vizzu::Chart &chart, const double &position = 1.0) +std::multimap> get_events( + Vizzu::Chart &chart) { - chart.getAnimOptions().control.position = position; + chart.getAnimOptions().control.position = 1.0; bool ends{}; chart.setKeyframe(); @@ -113,43 +122,36 @@ get_events(Vizzu::Chart &chart, const double &position = 1.0) }); std::multimap> events; + + auto line = + chart.getOptions().geometry == Vizzu::Gen::ShapeType::line; chart.getEventDispatcher().registerOnEachEvent(0, - [&events](Util::EventDispatcher::Params ¶ms) + [&events, &line](Util::EventDispatcher::Params ¶ms) { - if (typeid(params) - == typeid(Util::EventDispatcher::Params) - || dynamic_cast( - ¶ms)) { - events.emplace(std::piecewise_construct, - std::tuple{params.eventName}, - std::tuple{params.toJSON(), params.target}); - } - else if (auto *p = dynamic_cast< - Vizzu::Events::OnTextDrawDetail *>( - ¶ms)) { - events.emplace(std::piecewise_construct, - std::tuple{params.eventName}, - std::tuple{params.toJSON(), params.target, *p}); - } - else if (auto *p = dynamic_cast< - Vizzu::Events::OnRectDrawEvent *>(¶ms)) { + auto marker = params.eventName == "plot-marker-draw"; + if ((marker && line) + || params.eventName == "plot-axis-draw") { events.emplace(std::piecewise_construct, std::tuple{params.eventName}, std::tuple{params.toJSON(), params.target, - p->rect}); + static_cast( + params) + .line}); } - else if (auto *p = dynamic_cast< - Vizzu::Events::OnLineDrawEvent *>(¶ms)) { + else if (marker) { events.emplace(std::piecewise_construct, std::tuple{params.eventName}, std::tuple{params.toJSON(), params.target, - p->line}); + static_cast( + params) + .rect}); } else - skip->*false - == "Not all draw events type handled"_is_true; + events.emplace(std::piecewise_construct, + std::tuple{params.eventName}, + std::tuple{params.toJSON(), params.target}); }); using clock_t = std::chrono::steady_clock; @@ -204,7 +206,7 @@ const static auto tests = using Axis = Vizzu::Events::Targets::Axis; for (auto &&[beg, end] = events.equal_range("plot-axis-draw"); const auto &[j, t, l] : values(subrange(beg, end))) - if (!dynamic_cast(*t).horizontal) + if (!static_cast(*t).horizontal) xCenter = std::get(l).line.begin.x; std::set zero_count{}; diff --git a/test/unit/util/case.h b/test/unit/util/case.h index 00e68037f..965f9374a 100644 --- a/test/unit/util/case.h +++ b/test/unit/util/case.h @@ -40,6 +40,8 @@ struct skip_error : std::runtime_error class case_type { public: + enum class result_t { OK, SKIP, FAIL }; + case_type(std::string_view suite_name, std::string_view case_name, runnable runner, @@ -50,7 +52,7 @@ class case_type location(location) {} - void operator()() + result_t operator()() { using std::chrono::steady_clock; @@ -61,6 +63,8 @@ class case_type auto duration = steady_clock::now() - start; print_summary(duration); + + return static_cast(*this); } void fail(const src_location &location, @@ -82,9 +86,10 @@ class case_type return location.get_file_name(); } - explicit operator bool() const + explicit operator result_t() const { - return skip || error_messages.empty(); + using enum result_t; + return skip ? SKIP : error_messages.empty() ? OK : FAIL; } void set_latest_location(const src_location &loc) diff --git a/test/unit/util/collection.h b/test/unit/util/collection.h index 7a9bce4a2..b2c0917e4 100644 --- a/test/unit/util/collection.h +++ b/test/unit/util/collection.h @@ -76,6 +76,7 @@ class collection : public case_registry { std::size_t run{}; std::size_t failed{}; + std::size_t skipped{}; statistics() = default; }; @@ -88,10 +89,13 @@ class collection : public case_registry for (auto &act_case : cases) { if (condition(act_case)) { - stats.run++; running_case = &act_case; - act_case(); - if (!act_case) stats.failed++; + switch (act_case()) { + using enum case_type::result_t; + case FAIL: ++stats.failed; [[fallthrough]]; + case OK: ++stats.run; break; + case SKIP: ++stats.skipped; break; + } } } return stats; @@ -103,10 +107,13 @@ class collection : public case_registry << "all tests: " << cases.size() << "\n" << "tests run: " << stats.run << "\n" << "tests failed: " << stats.failed << "\n"; + if (stats.skipped > 0) + std::cout << "test skipped: " << stats.skipped << "\n"; if (stats.failed > 0) for (auto &act_case : cases) - if (!act_case) + if (static_cast(act_case) + == case_type::result_t::FAIL) std::cout << "\t" << act_case.full_name() << "\n"; std::cout << "\n"; diff --git a/test/unit/util/condition.h b/test/unit/util/condition.h index 59d091be9..8308767d8 100644 --- a/test/unit/util/condition.h +++ b/test/unit/util/condition.h @@ -214,15 +214,15 @@ struct bool_check_t : check_t if (throw_error) { throw_error(std::string("expectation failed\n") + "\t\"" + std::string{exp.msg} - + "\" is not " - + (exp.value ? "true" : "false"), + + "\" is " + + (exp.value ? "false" : "true"), location); } collection::instance().running_test()->fail(location, std::string("Check expectation failed\n") + "\t\"" - + std::string{exp.msg} + "\" is not " - + (exp.value ? "true" : "false")); + + std::string{exp.msg} + "\" is " + + (exp.value ? "false" : "true")); } } }; From c4c1db05a5cec4a5465cf06c35468fc44e55c669 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 29 Feb 2024 11:05:34 +0100 Subject: [PATCH 066/253] dummy --- project/cmake/common.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/cmake/common.txt b/project/cmake/common.txt index 0346d0712..d505b24a0 100644 --- a/project/cmake/common.txt +++ b/project/cmake/common.txt @@ -14,11 +14,11 @@ option(cppcheck "Run cppcheck analyzis" OFF) option(cpplint "Run cpplint analyzis" OFF) if(clangtidy) - set(CMAKE_CXX_CLANG_TIDY clang-tidy) + set(CMAKE_CXX_CLANG_TIDY clang-tidy-16) endif() if (clangformat) - set(CLANG_FORMAT_EXE clang-format) + set(CLANG_FORMAT_EXE clang-format-16) get_property(_allTargets GLOBAL PROPERTY GlobalTargetList) if (NOT _allTargets) From eed3e435ab9df94b8c84ffed1e6247609c3fa459 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Thu, 29 Feb 2024 12:41:51 +0100 Subject: [PATCH 067/253] Tooltip on marker labels. --- CHANGELOG.md | 1 + src/apps/weblib/ts-api/plugins/tooltip.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61dc32f84..ddf47aa73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ - When X axis dimension labels are close to each other, they are rotated to avoid overlapping. - The event handler registration order changed. Now the handlers are called in the opposite order of the registration. - Added the padded rectangle, the bounding rectangle and the align parameter to the draw text event object. +- Tooltip works on marker labels too. ## [0.9.3] - 2023-12-20 diff --git a/src/apps/weblib/ts-api/plugins/tooltip.ts b/src/apps/weblib/ts-api/plugins/tooltip.ts index b17ae53a7..93305ed1b 100644 --- a/src/apps/weblib/ts-api/plugins/tooltip.ts +++ b/src/apps/weblib/ts-api/plugins/tooltip.ts @@ -1,5 +1,5 @@ import { Snapshot } from '../module/cchart.js' -import { Element, Marker, PointerEvent } from '../events.js' +import { Element, Marker, MarkerLabel, PointerEvent } from '../events.js' import { Plugin, type PluginHooks, type PrepareAnimationContext } from '../plugins.js' import Vizzu from '../vizzu.js' @@ -71,8 +71,8 @@ export class Tooltip implements Plugin { _mouseon(param: PointerEvent): void { this._id++ const id = this._id - if (param.target && this._isMarker(param.target)) { - const markerId = param.target.index + const markerId = this._getMarkerId(param.target) + if (markerId !== null) { setTimeout(() => { this._in(id, markerId) }, 0) @@ -86,6 +86,8 @@ export class Tooltip implements Plugin { _getMarkerId(target: Element | null): number | null { if (target && this._isMarker(target)) { return target.index + } else if (target && this._isMarkerLabel(target)) { + return target.parent.index } else { return null } @@ -95,6 +97,10 @@ export class Tooltip implements Plugin { return target.tagName === 'plot-marker' } + _isMarkerLabel(target: Element): target is MarkerLabel { + return target.tagName === 'plot-marker-label' + } + _in(id: number, markerId: number): void { if (this._id === id) { if (!this._animating) { From 02ea0939981d1df02734da307c830a007c60c43a Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 29 Feb 2024 12:51:26 +0100 Subject: [PATCH 068/253] Add get_series_info --- src/dataframe/impl/dataframe.cpp | 20 ++++++++++++++++++++ src/dataframe/impl/dataframe.h | 4 ++++ src/dataframe/interface.h | 4 ++++ 3 files changed, 28 insertions(+) diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp index 4bd0d2680..5b953aa3b 100644 --- a/src/dataframe/impl/dataframe.cpp +++ b/src/dataframe/impl/dataframe.cpp @@ -917,4 +917,24 @@ std::string_view dataframe::get_record_unique_id( : std::string_view{}; } +std::string_view dataframe::get_series_info( + const series_identifier &id, + const char *key) const & +{ + switch (auto &&ser = get_data_source().get_series(id)) { + using enum series_type; + default: return {}; + case measure: { + auto &info = unsafe_get(ser).second.info; + auto it = info.find(key); + return it == info.end() ? std::string_view{} : it->second; + } + case dimension: { + auto &info = unsafe_get(ser).second.info; + auto it = info.find(key); + return it == info.end() ? std::string_view{} : it->second; + } + } +} + } diff --git a/src/dataframe/impl/dataframe.h b/src/dataframe/impl/dataframe.h index 8c1916d6a..475b3aea4 100644 --- a/src/dataframe/impl/dataframe.h +++ b/src/dataframe/impl/dataframe.h @@ -119,6 +119,10 @@ class dataframe final : public dataframe_interface [[nodiscard]] std::string_view get_record_unique_id( const record_identifier &id) const & final; + [[nodiscard]] std::string_view get_series_info( + const series_identifier &id, + const char *key) const & final; + cell_value get_data(record_identifier record_id, series_identifier column) const & final; diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 023269bd2..94dc25c18 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -166,6 +166,10 @@ class dataframe_interface : [[nodiscard]] virtual std::size_t get_record_count() const & = 0; + [[nodiscard]] virtual std::string_view get_series_info( + const series_identifier &id, + const char *key) const & = 0; + virtual void visit(std::function function, bool filtered) const & = 0; }; From e96d46bd8b8f7701dedd915537bbdf941ea86abb Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 29 Feb 2024 18:29:14 +0100 Subject: [PATCH 069/253] remove test function --- src/base/util/eventdispatcher.h | 7 --- test/unit/chart/events.cpp | 90 +++++++++++++++++++++++---------- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/src/base/util/eventdispatcher.h b/src/base/util/eventdispatcher.h index b892bc5c5..06e0dd931 100644 --- a/src/base/util/eventdispatcher.h +++ b/src/base/util/eventdispatcher.h @@ -69,13 +69,6 @@ class EventDispatcher bool destroyEvent(const event_ptr &event); - void registerOnEachEvent(const std::uint64_t &id, - const handler_fn &handler) const - { - for (const auto &event : eventRegistry) - event->attach(id, handler); - } - protected: struct EventPtrComp { diff --git a/test/unit/chart/events.cpp b/test/unit/chart/events.cpp index 1cf1ae293..264a9b582 100644 --- a/test/unit/chart/events.cpp +++ b/test/unit/chart/events.cpp @@ -123,36 +123,70 @@ std::multimap> get_events( std::multimap> events; + static std::vector eventNames = [] + { + std::vector names; + Refl::visit( + [&names]( + Util::EventDispatcher::event_ptr &(*)(const T &), + const std::initializer_list &sv = + {}) + { + std::span s{sv}; + if (s.back() == "begin" || s.back() == "complete") { + return names.emplace_back( + "draw-" + std::string{s.back()}); + } + + if (s.back() == "base") s = {s.begin(), s.end() - 1}; + + std::string name; + for (auto name_part : s) { + name += name_part; + name += '-'; + } + name += "draw"; + return names.emplace_back(name); + }); + return names; + }(); + auto line = chart.getOptions().geometry == Vizzu::Gen::ShapeType::line; - chart.getEventDispatcher().registerOnEachEvent(0, - [&events, &line](Util::EventDispatcher::Params ¶ms) - { - auto marker = params.eventName == "plot-marker-draw"; - if ((marker && line) - || params.eventName == "plot-axis-draw") { - events.emplace(std::piecewise_construct, - std::tuple{params.eventName}, - std::tuple{params.toJSON(), - params.target, - static_cast( - params) - .line}); - } - else if (marker) { - events.emplace(std::piecewise_construct, - std::tuple{params.eventName}, - std::tuple{params.toJSON(), - params.target, - static_cast( - params) - .rect}); - } - else - events.emplace(std::piecewise_construct, - std::tuple{params.eventName}, - std::tuple{params.toJSON(), params.target}); - }); + auto event_handler = [&events, &line]( + Util::EventDispatcher::Params ¶ms) + { + auto marker = params.eventName == "plot-marker-draw"; + if ((marker && line) + || params.eventName == "plot-axis-draw") { + events.emplace(std::piecewise_construct, + std::tuple{params.eventName}, + std::tuple{params.toJSON(), + params.target, + static_cast( + params) + .line}); + } + else if (marker) { + events.emplace(std::piecewise_construct, + std::tuple{params.eventName}, + std::tuple{params.toJSON(), + params.target, + static_cast( + params) + .rect}); + } + else + events.emplace(std::piecewise_construct, + std::tuple{params.eventName}, + std::tuple{params.toJSON(), params.target}); + }; + + for (const auto &name : eventNames) { + auto n = chart.getEventDispatcher().getEvent(name); + skip->*n != nullptr; + n->attach(0, event_handler); + } using clock_t = std::chrono::steady_clock; chart.getAnimControl().update(clock_t::now()); From 07837815e815ee22b7bd96ad038ef84ad6be5c32 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 29 Feb 2024 18:59:39 +0100 Subject: [PATCH 070/253] Rename DataTable to DataTableOld. --- src/apps/weblib/interface.cpp | 2 +- src/chart/generator/marker.cpp | 6 ++++-- src/chart/options/optionssetter.h | 5 ----- src/data/datacube/datacube.cpp | 11 ++++++----- src/data/datacube/datacube.h | 3 +-- src/data/datacube/datastat.cpp | 5 +++-- src/data/datacube/datastat.h | 4 +--- src/data/table/columninfo.cpp | 4 +--- src/data/table/columninfo.h | 5 ++--- src/data/table/datatable.cpp | 32 ++++++++++++++++++++----------- src/data/table/datatable.h | 8 +++++--- 11 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/apps/weblib/interface.cpp b/src/apps/weblib/interface.cpp index f4af73622..23e5d5e84 100644 --- a/src/apps/weblib/interface.cpp +++ b/src/apps/weblib/interface.cpp @@ -322,7 +322,7 @@ void Interface::addRecord(ObjectRegistry::Handle chart, const char *Interface::dataMetaInfo(ObjectRegistry::Handle chart) { thread_local std::string res; - res = Conv::toJSON(getChart(chart)->getTable().getInfos()); + res = getChart(chart)->getTable().getInfos(); return res.c_str(); } diff --git a/src/chart/generator/marker.cpp b/src/chart/generator/marker.cpp index 3d7173a34..e01655dc3 100644 --- a/src/chart/generator/marker.cpp +++ b/src/chart/generator/marker.cpp @@ -158,7 +158,8 @@ Conv::JSONObj &&Marker::appendToJSON(Conv::JSONObj &&jsonObj) const std::ranges::views::transform(cellInfo.categories, [this](const auto &pair) { - return std::make_pair(pair.first.toString(table), + return std::make_pair( + pair.first.toString(table.get()), table.get() .getInfo(pair.first.getColIndex().value()) .categories()[pair.second]); @@ -166,7 +167,8 @@ Conv::JSONObj &&Marker::appendToJSON(Conv::JSONObj &&jsonObj) const std::ranges::views::transform(cellInfo.values, [this](const auto &pair) { - return std::make_pair(pair.first.toString(table), + return std::make_pair( + pair.first.toString(table.get()), pair.second); }))("index", idx); } diff --git a/src/chart/options/optionssetter.h b/src/chart/options/optionssetter.h index d6b5f8ac0..f0f04f777 100644 --- a/src/chart/options/optionssetter.h +++ b/src/chart/options/optionssetter.h @@ -8,11 +8,6 @@ namespace Vizzu { -namespace Data -{ -class DataTable; -} - namespace Gen { diff --git a/src/data/datacube/datacube.cpp b/src/data/datacube/datacube.cpp index 2db64e597..28cc72a86 100644 --- a/src/data/datacube/datacube.cpp +++ b/src/data/datacube/datacube.cpp @@ -23,7 +23,8 @@ DataCube::DataCube(const DataTable &table, auto size = idx.getType().isReal() ? table.getInfo(idx.getColIndex().value()) - .dimensionValueCnt() + .categories() + .size() : idx.getType() == SeriesType::Index ? table.getRowCount() : throw std::logic_error("internal error: cannot " @@ -63,7 +64,7 @@ DataCube::DataCube(const DataTable &table, } } -MultiIndex DataCube::getIndex(const TableRow &row, +MultiIndex DataCube::getIndex(const DataTable::Row &row, const std::vector &indices, size_t rowIndex) { @@ -238,9 +239,9 @@ MultiDim::SubSliceIndex DataCube::subSliceIndex( for (const auto &pair : stringMarkerId) { auto colIdx = table->getColumn(pair.first); auto seriesIdx = table->getIndex(colIdx); - const auto &values = - table->getInfo(colIdx).dimensionValueIndexes(); - auto valIdx = values.at(pair.second); + auto valIdx = + table->getInfo(colIdx).dimensionValueIndexes().at( + pair.second); auto dimIdx = getDimBySeries(SeriesIndex(seriesIdx)); index.push_back( MultiDim::SliceIndex{dimIdx, MultiDim::Index{valIdx}}); diff --git a/src/data/datacube/datacube.h b/src/data/datacube/datacube.h index de33f191e..1f2275d45 100644 --- a/src/data/datacube/datacube.h +++ b/src/data/datacube/datacube.h @@ -15,7 +15,6 @@ namespace Vizzu::Data { -class DataTable; template class TableRow; using SubCellIndex = @@ -101,7 +100,7 @@ class DataCube std::map subIndexBySeries; std::vector seriesBySubIndex; - static MultiDim::MultiIndex getIndex(const TableRow &row, + static MultiDim::MultiIndex getIndex(const DataTable::Row &row, const std::vector &indices, size_t rowIndex); diff --git a/src/data/datacube/datastat.cpp b/src/data/datacube/datastat.cpp index 014e3b151..dd822b994 100644 --- a/src/data/datacube/datastat.cpp +++ b/src/data/datacube/datastat.cpp @@ -11,7 +11,8 @@ DataStat::DataStat(const DataTable &table, for (const auto &idx : indices) { if (idx.getType().isReal()) { auto valueCnt = table.getInfo(idx.getColIndex().value()) - .dimensionValueCnt(); + .categories() + .size(); usedColumnIDs.insert( {static_cast(idx.getColIndex().value()), usedValues.size()}); @@ -37,7 +38,7 @@ size_t DataStat::usedValueCntOf(const SeriesIndex &index) const return 0; } -void DataStat::trackIndex(const TableRow &row, +void DataStat::trackIndex(const DataTable::Row &row, const std::vector &indices) { for (auto i = 0U; i < indices.size(); ++i) { diff --git a/src/data/datacube/datastat.h b/src/data/datacube/datastat.h index a86fc1af3..54d704118 100644 --- a/src/data/datacube/datastat.h +++ b/src/data/datacube/datastat.h @@ -9,8 +9,6 @@ namespace Vizzu::Data { -class DataTable; - class DataStat { public: @@ -21,7 +19,7 @@ class DataStat size_t usedValueCntOf(const SeriesIndex &index) const; private: - void trackIndex(const TableRow &row, + void trackIndex(const DataTable::Row &row, const std::vector &indices); void countValues(); diff --git a/src/data/table/columninfo.cpp b/src/data/table/columninfo.cpp index a9a521aa5..9b05377b2 100644 --- a/src/data/table/columninfo.cpp +++ b/src/data/table/columninfo.cpp @@ -71,11 +71,9 @@ const ColumnInfo::Values &ColumnInfo::categories() const return values; } -size_t ColumnInfo::dimensionValueCnt() const { return values.size(); } - std::string ColumnInfo::getName() const { return name; } -std::string ColumnInfo::getUnit() const { return unit; } +const std::string &ColumnInfo::getUnit() const { return unit; } Math::Range ColumnInfo::getRange() const { return range; } diff --git a/src/data/table/columninfo.h b/src/data/table/columninfo.h index c1f155e62..ade1161b8 100644 --- a/src/data/table/columninfo.h +++ b/src/data/table/columninfo.h @@ -10,7 +10,7 @@ namespace Vizzu::Data { -enum TextType : uint32_t { Number, String }; +enum class TextType : uint32_t { Number, String }; class ColumnInfo { @@ -34,10 +34,9 @@ class ColumnInfo [[nodiscard]] ContiType getContiType() const; [[nodiscard]] const ValueIndexes &dimensionValueIndexes() const; [[nodiscard]] const Values &categories() const; - [[nodiscard]] size_t dimensionValueCnt() const; [[nodiscard]] std::string getName() const; - [[nodiscard]] std::string getUnit() const; + [[nodiscard]] const std::string &getUnit() const; [[nodiscard]] Math::Range getRange() const; [[nodiscard]] std::string toString() const; [[nodiscard]] size_t minByteWidth() const; diff --git a/src/data/table/datatable.cpp b/src/data/table/datatable.cpp index e9c54d597..c56a312d3 100644 --- a/src/data/table/datatable.cpp +++ b/src/data/table/datatable.cpp @@ -1,17 +1,19 @@ #include "datatable.h" +#include "base/conv/auto_json.h" + namespace Vizzu::Data { -DataTable::DataTable() = default; +DataTableOld::DataTableOld() = default; -void DataTable::pushRow(const std::span &cells) +void DataTableOld::pushRow(const std::span &cells) { std::vector strCells(cells.begin(), cells.end()); pushRow(TableRow(std::move(strCells))); } -void DataTable::pushRow(const TableRow &textRow) +void DataTableOld::pushRow(const TableRow &textRow) { Row row; for (auto i = 0U; i < getColumnCount(); ++i) { @@ -23,7 +25,7 @@ void DataTable::pushRow(const TableRow &textRow) } template -DataTable::DataIndex DataTable::addTypedColumn( +DataTableOld::DataIndex DataTableOld::addTypedColumn( const std::string &name, const std::string &unit, const std::span &values) @@ -83,14 +85,16 @@ DataTable::DataIndex DataTable::addTypedColumn( return getIndex(ColumnIndex(colIndex)); } -DataTable::DataIndex DataTable::addColumn(const std::string &name, +DataTableOld::DataIndex DataTableOld::addColumn( + const std::string &name, const std::string &unit, const std::span &values) { return addTypedColumn(name, unit, values); } -DataTable::DataIndex DataTable::addColumn(const std::string &name, +DataTableOld::DataIndex DataTableOld::addColumn( + const std::string &name, const std::span &categories, const std::span &values) { @@ -101,29 +105,35 @@ DataTable::DataIndex DataTable::addColumn(const std::string &name, return addTypedColumn(name, {}, realValues); } -const ColumnInfo &DataTable::getInfo(ColumnIndex index) const +const ColumnInfo &DataTableOld::getInfo(ColumnIndex index) const { return infos[index]; } -DataTable::DataIndex DataTable::getIndex(ColumnIndex index) const +DataTableOld::DataIndex DataTableOld::getIndex( + ColumnIndex index) const { return {index, infos[index].getType()}; } -ColumnIndex DataTable::getColumn(const std::string &name) const +ColumnIndex DataTableOld::getColumn(const std::string &name) const { auto it = indexByName.find(name); if (it != indexByName.end()) return it->second; throw std::logic_error("No column name exists: " + name); } -DataTable::DataIndex DataTable::getIndex( +DataTableOld::DataIndex DataTableOld::getIndex( const std::string &name) const { return getIndex(getColumn(name)); } -size_t DataTable::columnCount() const { return infos.size(); } +size_t DataTableOld::columnCount() const { return infos.size(); } + +std::string DataTableOld::getInfos() const +{ + return Conv::toJSON(infos); +} } \ No newline at end of file diff --git a/src/data/table/datatable.h b/src/data/table/datatable.h index 57e91a713..c7e4924a6 100644 --- a/src/data/table/datatable.h +++ b/src/data/table/datatable.h @@ -16,7 +16,7 @@ enum class SortType : uint8_t { AsSeen, Natural }; class DataCube; -class DataTable : public Table +class DataTableOld : public Table { using Infos = std::vector; @@ -39,7 +39,7 @@ class DataTable : public Table } }; - DataTable(); + DataTableOld(); [[nodiscard]] const ColumnInfo &getInfo(ColumnIndex index) const; [[nodiscard]] DataIndex getIndex(ColumnIndex index) const; [[nodiscard]] ColumnIndex getColumn( @@ -58,7 +58,7 @@ class DataTable : public Table [[nodiscard]] size_t columnCount() const; - [[nodiscard]] const Infos &getInfos() const { return infos; } + [[nodiscard]] std::string getInfos() const; private: std::map indexByName; @@ -70,6 +70,8 @@ class DataTable : public Table const std::span &values); }; +using DataTable = DataTableOld; + class CellWrapper { public: From 4d334e90ce06a87b4ee9cb0cc7f67a4b9ab0ddd6 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 29 Feb 2024 19:38:28 +0100 Subject: [PATCH 071/253] Fix clang-tidy --- src/chart/options/optionssetter.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/chart/options/optionssetter.h b/src/chart/options/optionssetter.h index f0f04f777..a6dfa97dc 100644 --- a/src/chart/options/optionssetter.h +++ b/src/chart/options/optionssetter.h @@ -5,10 +5,7 @@ #include "options.h" -namespace Vizzu -{ - -namespace Gen +namespace Vizzu::Gen { class OptionsSetter @@ -54,7 +51,6 @@ class OptionsSetter const Data::DataTable *table{}; }; -} } #endif From 3f79a193644bf9ac7ffbb675d74d0ec955af1e42 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Fri, 1 Mar 2024 13:40:31 +0100 Subject: [PATCH 072/253] Render class splitted to Rendering and CanvasRenderer --- .../ts-api/{render.ts => canvasrenderer.ts} | 75 ++++--------------- src/apps/weblib/ts-api/chart.ts | 2 +- src/apps/weblib/ts-api/index.ts | 2 +- src/apps/weblib/ts-api/module/ccanvas.ts | 35 ++++++++- src/apps/weblib/ts-api/module/module.ts | 13 ++-- src/apps/weblib/ts-api/rendering.ts | 53 +++++++++++++ 6 files changed, 109 insertions(+), 71 deletions(-) rename src/apps/weblib/ts-api/{render.ts => canvasrenderer.ts} (69%) create mode 100644 src/apps/weblib/ts-api/rendering.ts diff --git a/src/apps/weblib/ts-api/render.ts b/src/apps/weblib/ts-api/canvasrenderer.ts similarity index 69% rename from src/apps/weblib/ts-api/render.ts rename to src/apps/weblib/ts-api/canvasrenderer.ts index 3cc12c8d1..642566ea9 100644 --- a/src/apps/weblib/ts-api/render.ts +++ b/src/apps/weblib/ts-api/canvasrenderer.ts @@ -1,61 +1,23 @@ -import { CString, CColorGradientPtr } from './cvizzu.types' -import { Plugin, PluginApi } from './plugins.js' +import { CColorGradient } from './module/ccolorgradient.js' +import { CEnv } from './module/cenv.js' +import { CPointerClosure } from './module/objregistry.js' import { Canvas } from './module/canvas.js' -import { Module } from './module/module.js' import { CCanvas } from './module/ccanvas.js' -import { CChart } from './module/cchart.js' import { HtmlCanvas } from './htmlcanvas.js' -export interface RenderingApi extends PluginApi { - /** Re-renders the chart. */ - update(): void -} - -export class Render implements Plugin, Canvas { +export class CanvasRenderer extends CCanvas implements Canvas { private _canvas: HtmlCanvas - private _ccanvas: CCanvas - private _enabled: boolean - private _cchart: CChart - private _polygonInProgress: boolean + private _polygonInProgress: boolean = false private _currentLineWidth: number = 1 - meta = { name: 'rendering' } - - get api(): RenderingApi { - return { - update: (): void => { - this.updateFrame(true) - } - } - } - - enable(enabled: boolean): void { - this._enabled = enabled - } - - constructor(module: Module, cchart: CChart, canvas: HtmlCanvas) { + constructor(env: CEnv, getId: CPointerClosure, canvas: HtmlCanvas) { + super(env, getId) this._canvas = canvas - this._canvas.onchange = (): void => { - this.updateFrame(true) - } - this._enabled = true - this._polygonInProgress = false - this._cchart = cchart - this._ccanvas = module.createCanvas() - module.registerRenderer(this._ccanvas, this) - } - - updateFrame(force: boolean = false): void { - const size = this._canvas.calcSize() - if (size.x >= 1 && size.y >= 1) { - const time = performance.now() - const renderControl = !this._enabled ? 2 : force ? 1 : 0 - this._cchart.update(this._ccanvas, size.x, size.y, time, renderControl) - } } frameBegin(): void { this._currentLineWidth = 1 + this._polygonInProgress = false this._canvas.frameBegin() } @@ -100,9 +62,9 @@ export class Render implements Plugin, Canvas { this._currentLineWidth = width } - setFont(font: CString): void { + setFontStyle(font: string): void { const dc = this._canvas.context - dc.font = this._ccanvas.getString(font) + dc.font = font } beginDropShadow(): void {} @@ -180,28 +142,19 @@ export class Render implements Plugin, Canvas { if (this._currentLineWidth !== 0) dc.stroke() } - text(x: number, y: number, sizex: number, sizey: number, text: CString): void { + drawText(x: number, y: number, sizex: number, sizey: number, text: string): void { const dc = this._canvas.context dc.textAlign = 'left' dc.textBaseline = 'top' x = x + (sizex < 0 ? -sizex : 0) y = y + (sizey < 0 ? -sizey : 0) - dc.fillText(this._ccanvas.getString(text), x, y) + dc.fillText(text, x, y) } - setBrushGradient( - x1: number, - y1: number, - x2: number, - y2: number, - stopCount: number, - stopsPtr: CColorGradientPtr - ): void { + setGradient(x1: number, y1: number, x2: number, y2: number, gradient: CColorGradient): void { const dc = this._canvas.context const grd = dc.createLinearGradient(x1, y1, x2, y2) - this._ccanvas - .getColorGradient(stopsPtr, stopCount) - .stops.forEach((g) => grd.addColorStop(g.offset, g.color)) + gradient.stops.forEach((g) => grd.addColorStop(g.offset, g.color)) dc.fillStyle = grd } diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index 92f55f5b3..71b598092 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -6,7 +6,7 @@ import { Module } from './module/module.js' import { CChart, Snapshot } from './module/cchart.js' import { CAnimControl, CAnimation } from './module/canimctrl.js' import { CData } from './module/cdata.js' -import { Render } from './render.js' +import { Render } from './rendering.js' import { Data } from './data.js' import { Events, EventType, EventHandler, EventMap } from './events.js' import { PluginRegistry, Hooks } from './plugins.js' diff --git a/src/apps/weblib/ts-api/index.ts b/src/apps/weblib/ts-api/index.ts index 61c624ee5..3faaf4f69 100644 --- a/src/apps/weblib/ts-api/index.ts +++ b/src/apps/weblib/ts-api/index.ts @@ -34,7 +34,7 @@ export * as Events from './events.js' export * as Geom from './geom.js' export * from './htmlcanvas.js' export * as Plugins from './plugins.js' -export * from './render.js' +export * from './rendering.js' export * from './vizzu.js' export { default as Vizzu } from './vizzu.js' diff --git a/src/apps/weblib/ts-api/module/ccanvas.ts b/src/apps/weblib/ts-api/module/ccanvas.ts index deec7aeba..d1dd30eb1 100644 --- a/src/apps/weblib/ts-api/module/ccanvas.ts +++ b/src/apps/weblib/ts-api/module/ccanvas.ts @@ -3,16 +3,45 @@ import { CPointerClosure } from './objregistry.js' import { CColorGradient } from './ccolorgradient.js' import { CString, CColorGradientPtr } from '../cvizzu.types' -export class CCanvas extends CObject { +export abstract class CCanvas extends CObject { constructor(env: CEnv, getId: CPointerClosure) { super(getId, env) } - getColorGradient(stops: CColorGradientPtr, stopCount: number): CColorGradient { + abstract setFontStyle(font: string): void + abstract drawText(x: number, y: number, sizex: number, sizey: number, text: string): void + abstract setGradient( + x1: number, + y1: number, + x2: number, + y2: number, + gradient: CColorGradient + ): void + + setFont(font: CString): void { + this.setFontStyle(this._getString(font)) + } + + text(x: number, y: number, sizex: number, sizey: number, text: CString): void { + this.drawText(x, y, sizex, sizey, this._getString(text)) + } + + setBrushGradient( + x1: number, + y1: number, + x2: number, + y2: number, + stopCount: number, + stops: CColorGradientPtr + ): void { + this.setGradient(x1, y1, x2, y2, this._getColorGradient(stops, stopCount)) + } + + private _getColorGradient(stops: CColorGradientPtr, stopCount: number): CColorGradient { return new CColorGradient(this, stops, stopCount) } - getString(text: CString): string { + private _getString(text: CString): string { return this._wasm.UTF8ToString(text) } } diff --git a/src/apps/weblib/ts-api/module/module.ts b/src/apps/weblib/ts-api/module/module.ts index 35793df2a..ea56f476a 100644 --- a/src/apps/weblib/ts-api/module/module.ts +++ b/src/apps/weblib/ts-api/module/module.ts @@ -1,6 +1,6 @@ import { CVizzu } from '../cvizzu.types' -import { ObjectRegistry } from './objregistry.js' +import { CPointerClosure, ObjectRegistry } from './objregistry.js' import { CEnv } from './cenv.js' import { CData } from './cdata.js' import { CChart } from './cchart.js' @@ -19,8 +19,8 @@ export class Module extends CEnv { this.setLogging(false) } - registerRenderer(cCanvas: CCanvas, canvas: Canvas): void { - this._wasm.canvases[cCanvas.getId()] = canvas + registerRenderer(cCanvas: CCanvas & Canvas): void { + this._wasm.canvases[cCanvas.getId()] = cCanvas } version(): string { @@ -47,7 +47,10 @@ export class Module extends CEnv { return new CChart(this, this._getStatic(this._wasm._vizzu_createChart)) } - createCanvas(): CCanvas { - return new CCanvas(this, this._getStatic(this._wasm._vizzu_createCanvas)) + createCanvas( + ctor: new (env: CEnv, getId: CPointerClosure, ...args: Args[]) => T, + ...args: Args[] + ): T { + return new ctor(this, this._getStatic(this._wasm._vizzu_createCanvas), ...args) } } diff --git a/src/apps/weblib/ts-api/rendering.ts b/src/apps/weblib/ts-api/rendering.ts new file mode 100644 index 000000000..01cb0ff9e --- /dev/null +++ b/src/apps/weblib/ts-api/rendering.ts @@ -0,0 +1,53 @@ +import { Plugin, PluginApi } from './plugins.js' +import { Module } from './module/module.js' +import { CCanvas } from './module/ccanvas.js' +import { Canvas } from './module/canvas.js' +import { CChart } from './module/cchart.js' +import { HtmlCanvas } from './htmlcanvas.js' +import { CanvasRenderer } from './canvasrenderer.js' + +export interface RenderingApi extends PluginApi { + /** Re-renders the chart. */ + update(): void +} + +export class Render implements Plugin { + private _canvas: HtmlCanvas + private _ccanvas: CCanvas & Canvas + private _enabled: boolean + private _cchart: CChart + + meta = { name: 'rendering' } + + get api(): RenderingApi { + return { + update: (): void => { + this.updateFrame(true) + } + } + } + + enable(enabled: boolean): void { + this._enabled = enabled + } + + constructor(module: Module, cchart: CChart, canvas: HtmlCanvas) { + this._canvas = canvas + this._canvas.onchange = (): void => { + this.updateFrame(true) + } + this._enabled = true + this._cchart = cchart + this._ccanvas = module.createCanvas(CanvasRenderer, canvas) + module.registerRenderer(this._ccanvas) + } + + updateFrame(force: boolean = false): void { + const size = this._canvas.calcSize() + if (size.x >= 1 && size.y >= 1) { + const time = performance.now() + const renderControl = !this._enabled ? 2 : force ? 1 : 0 + this._cchart.update(this._ccanvas, size.x, size.y, time, renderControl) + } + } +} From e3115ed9af05f23af4c3f650a0cd4e58cc2ecac0 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Fri, 1 Mar 2024 13:48:55 +0100 Subject: [PATCH 073/253] htmlcanvas and canvasrendering moved to plugins dir --- src/apps/weblib/ts-api/chart.ts | 2 +- src/apps/weblib/ts-api/events.ts | 2 +- src/apps/weblib/ts-api/index.ts | 2 +- src/apps/weblib/ts-api/{ => plugins}/canvasrenderer.ts | 10 +++++----- src/apps/weblib/ts-api/{ => plugins}/htmlcanvas.ts | 4 ++-- src/apps/weblib/ts-api/plugins/pointerevents.ts | 2 +- src/apps/weblib/ts-api/rendering.ts | 4 ++-- src/apps/weblib/ts-api/vizzu.ts | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) rename src/apps/weblib/ts-api/{ => plugins}/canvasrenderer.ts (94%) rename src/apps/weblib/ts-api/{ => plugins}/htmlcanvas.ts (98%) diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index 71b598092..d4f43abf4 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -17,7 +17,7 @@ import { Tooltip } from './plugins/tooltip.js' import { PointerEvents } from './plugins/pointerevents.js' import { CSSProperties } from './plugins/cssproperties.js' import { Mirrored } from './tsutils.js' -import { HtmlCanvas } from './htmlcanvas.js' +import { HtmlCanvas } from './plugins/htmlcanvas.js' import { VizzuOptions } from './vizzu.js' import { AnimControl } from './animcontrol.js' import { CoordSystem } from './plugins/coordsys.js' diff --git a/src/apps/weblib/ts-api/events.ts b/src/apps/weblib/ts-api/events.ts index ccfebf120..8eb086a45 100644 --- a/src/apps/weblib/ts-api/events.ts +++ b/src/apps/weblib/ts-api/events.ts @@ -2,7 +2,7 @@ import { CFunction } from './cvizzu.types' import { PluginListeners } from './plugins.js' import { CChart, CEvent } from './module/cchart.js' -import { HtmlCanvas, HtmlCanvasContext } from './htmlcanvas.js' +import { HtmlCanvas, HtmlCanvasContext } from './plugins/htmlcanvas.js' import * as Data from './types/data.js' import * as Anim from './types/anim.js' diff --git a/src/apps/weblib/ts-api/index.ts b/src/apps/weblib/ts-api/index.ts index 3faaf4f69..894f9c539 100644 --- a/src/apps/weblib/ts-api/index.ts +++ b/src/apps/weblib/ts-api/index.ts @@ -32,7 +32,7 @@ export * from './datarecord.js' export * from './errors.js' export * as Events from './events.js' export * as Geom from './geom.js' -export * from './htmlcanvas.js' +export * from './plugins/htmlcanvas.js' export * as Plugins from './plugins.js' export * from './rendering.js' diff --git a/src/apps/weblib/ts-api/canvasrenderer.ts b/src/apps/weblib/ts-api/plugins/canvasrenderer.ts similarity index 94% rename from src/apps/weblib/ts-api/canvasrenderer.ts rename to src/apps/weblib/ts-api/plugins/canvasrenderer.ts index 642566ea9..e7fc70620 100644 --- a/src/apps/weblib/ts-api/canvasrenderer.ts +++ b/src/apps/weblib/ts-api/plugins/canvasrenderer.ts @@ -1,8 +1,8 @@ -import { CColorGradient } from './module/ccolorgradient.js' -import { CEnv } from './module/cenv.js' -import { CPointerClosure } from './module/objregistry.js' -import { Canvas } from './module/canvas.js' -import { CCanvas } from './module/ccanvas.js' +import { CColorGradient } from '../module/ccolorgradient.js' +import { CEnv } from '../module/cenv.js' +import { CPointerClosure } from '../module/objregistry.js' +import { Canvas } from '../module/canvas.js' +import { CCanvas } from '../module/ccanvas.js' import { HtmlCanvas } from './htmlcanvas.js' export class CanvasRenderer extends CCanvas implements Canvas { diff --git a/src/apps/weblib/ts-api/htmlcanvas.ts b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts similarity index 98% rename from src/apps/weblib/ts-api/htmlcanvas.ts rename to src/apps/weblib/ts-api/plugins/htmlcanvas.ts index 80a46b8aa..3f0ed9af8 100644 --- a/src/apps/weblib/ts-api/htmlcanvas.ts +++ b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts @@ -1,5 +1,5 @@ -import * as Geom from './geom.js' -import { Plugin, PluginApi } from './plugins.js' +import * as Geom from '../geom.js' +import { Plugin, PluginApi } from '../plugins.js' export interface CanvasOptions { element: HTMLElement diff --git a/src/apps/weblib/ts-api/plugins/pointerevents.ts b/src/apps/weblib/ts-api/plugins/pointerevents.ts index 23e6b8c93..309306bcc 100644 --- a/src/apps/weblib/ts-api/plugins/pointerevents.ts +++ b/src/apps/weblib/ts-api/plugins/pointerevents.ts @@ -2,7 +2,7 @@ import Vizzu from '../vizzu.js' import { Plugin } from '../plugins.js' import { CChart } from '../module/cchart.js' import { NotInitializedError } from '../errors.js' -import { HtmlCanvasApi } from '../htmlcanvas.js' +import { HtmlCanvasApi } from './htmlcanvas.js' interface Handlers { pointermove: (event: PointerEvent) => void diff --git a/src/apps/weblib/ts-api/rendering.ts b/src/apps/weblib/ts-api/rendering.ts index 01cb0ff9e..761adc661 100644 --- a/src/apps/weblib/ts-api/rendering.ts +++ b/src/apps/weblib/ts-api/rendering.ts @@ -3,8 +3,8 @@ import { Module } from './module/module.js' import { CCanvas } from './module/ccanvas.js' import { Canvas } from './module/canvas.js' import { CChart } from './module/cchart.js' -import { HtmlCanvas } from './htmlcanvas.js' -import { CanvasRenderer } from './canvasrenderer.js' +import { HtmlCanvas } from './plugins/htmlcanvas.js' +import { CanvasRenderer } from './plugins/canvasrenderer.js' export interface RenderingApi extends PluginApi { /** Re-renders the chart. */ diff --git a/src/apps/weblib/ts-api/vizzu.ts b/src/apps/weblib/ts-api/vizzu.ts index efb22346d..108148641 100644 --- a/src/apps/weblib/ts-api/vizzu.ts +++ b/src/apps/weblib/ts-api/vizzu.ts @@ -14,7 +14,7 @@ import { Mirrored } from './tsutils.js' import { NotInitializedError, CancelError } from './errors.js' import { Plugin, PluginApi, PluginRegistry, Hooks } from './plugins.js' import Presets from './plugins/presets.js' -import { LazyCanvasOptions, HtmlCanvasApi } from './htmlcanvas.js' +import { LazyCanvasOptions, HtmlCanvasApi } from './plugins/htmlcanvas.js' import { CoordSystemApi } from './plugins/coordsys.js' /** Options for the library. */ From e3d2f4d285c6cfe5fb5dd85bf2a68192dd273ed9 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Fri, 1 Mar 2024 13:53:22 +0100 Subject: [PATCH 074/253] Scheduler plugin added. --- CHANGELOG.md | 2 + src/apps/weblib/ts-api/chart.ts | 13 +++--- src/apps/weblib/ts-api/plugins.ts | 7 ++++ src/apps/weblib/ts-api/plugins/scheduler.ts | 46 +++++++++++++++++++++ 4 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 src/apps/weblib/ts-api/plugins/scheduler.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ddf47aa73..5a06b0089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ - The event handler registration order changed. Now the handlers are called in the opposite order of the registration. - Added the padded rectangle, the bounding rectangle and the align parameter to the draw text event object. - Tooltip works on marker labels too. +- New plugins and plugin hooks introduced: + - scheduler/startScheduler - plugin resposible for scheduling the rendering ## [0.9.3] - 2023-12-20 diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index d4f43abf4..e2fbafd52 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -21,6 +21,7 @@ import { HtmlCanvas } from './plugins/htmlcanvas.js' import { VizzuOptions } from './vizzu.js' import { AnimControl } from './animcontrol.js' import { CoordSystem } from './plugins/coordsys.js' +import { Scheduler } from './plugins/scheduler.js' export class Chart { private _cChart: CChart @@ -31,7 +32,6 @@ export class Chart { private _data: Data private _events: Events private _plugins: PluginRegistry - private _updateInterval?: ReturnType constructor(module: Module, options: VizzuOptions, plugins: PluginRegistry) { this._plugins = plugins @@ -51,6 +51,7 @@ export class Chart { registerBuiltins(): void { this._plugins.register(new Logging(this._module.setLogging.bind(this._module)), false) this._plugins.register(this._canvas, true) + this._plugins.register(new Scheduler(), true) this._plugins.register(this._render, true) this._plugins.register(new CoordSystem(this._module.getCoordSystem(this._cChart)), true) this._plugins.register(new CSSProperties(), false) @@ -61,12 +62,10 @@ export class Chart { } start(): void { - if (!this._updateInterval) { - this._render.updateFrame() - this._updateInterval = setInterval(() => { - this._render.updateFrame() - }, 25) + const ctx = { + update: () => this._render.updateFrame() } + this._plugins.hook(Hooks.startScheduler, ctx).default(() => {}) } async prepareAnimation( @@ -126,8 +125,6 @@ export class Chart { destruct(): void { this._canvas.destruct() - if (this._updateInterval) clearInterval(this._updateInterval) - delete this._updateInterval } version(): string { diff --git a/src/apps/weblib/ts-api/plugins.ts b/src/apps/weblib/ts-api/plugins.ts index 1b485ec12..ff43abbd1 100644 --- a/src/apps/weblib/ts-api/plugins.ts +++ b/src/apps/weblib/ts-api/plugins.ts @@ -5,6 +5,8 @@ import { AnimCompleting } from './animcompleting.js' /** Available hooks for plugins in Vizzu. */ export enum Hooks { + /** Called once on startup for start the scheduler for rendering. */ + startScheduler = 'startScheduler', /** Called when the animate() parameters gets set in the library to prepare the animation. */ prepareAnimation = 'prepareAnimation', @@ -27,6 +29,10 @@ export interface PluginMeta { depends?: string[] } +export interface StartSchedulerContext { + update: (time: number) => void +} + export interface PrepareAnimationContext { target: Anim.AnimTarget options?: Anim.ControlOptions @@ -43,6 +49,7 @@ export interface RunAnimationContext { } export interface HookContexts { + [Hooks.startScheduler]: StartSchedulerContext [Hooks.prepareAnimation]: PrepareAnimationContext [Hooks.registerAnimation]: RegisterAnimationContext [Hooks.runAnimation]: RunAnimationContext diff --git a/src/apps/weblib/ts-api/plugins/scheduler.ts b/src/apps/weblib/ts-api/plugins/scheduler.ts new file mode 100644 index 000000000..bbf18088d --- /dev/null +++ b/src/apps/weblib/ts-api/plugins/scheduler.ts @@ -0,0 +1,46 @@ +import { Plugin, PluginHooks, StartSchedulerContext } from '../plugins.js' + +export class Scheduler implements Plugin { + private _updateInterval?: ReturnType + private _update: (time: number) => void = () => {} + private _enabled = false + + meta = { name: 'scheduler' } + + get hooks(): PluginHooks { + const hooks = { + startScheduler: (ctx: StartSchedulerContext, next: () => void): void => { + this._update = ctx.update + this._start() + next() + } + } + return hooks + } + + enable(enabled: boolean): void { + this._enabled = enabled + if (this._enabled) this._start() + else this._stop() + } + + unregister(): void { + this._stop() + } + + private _start(): void { + if (!this._updateInterval) { + this._update(performance.now()) + this._updateInterval = setInterval(() => { + if (this._enabled) { + this._update(performance.now()) + } + }, 25) + } + } + + private _stop(): void { + if (this._updateInterval) clearInterval(this._updateInterval) + delete this._updateInterval + } +} From b0c3c4d80e8c7c3900108952cc58498ae74fead3 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Fri, 1 Mar 2024 16:44:31 +0100 Subject: [PATCH 075/253] API refactor: - rendering plugin changed to renderControl, update moved to char - clock plugin added --- CHANGELOG.md | 5 +- src/apps/weblib/ts-api/chart.ts | 59 +++++++++++++++---- src/apps/weblib/ts-api/index.ts | 4 +- src/apps/weblib/ts-api/plugins.ts | 19 ++++-- src/apps/weblib/ts-api/plugins/clock.ts | 30 ++++++++++ .../weblib/ts-api/plugins/rendercontrol.ts | 46 +++++++++++++++ src/apps/weblib/ts-api/plugins/scheduler.ts | 10 ++-- src/apps/weblib/ts-api/rendering.ts | 53 ----------------- 8 files changed, 149 insertions(+), 77 deletions(-) create mode 100644 src/apps/weblib/ts-api/plugins/clock.ts create mode 100644 src/apps/weblib/ts-api/plugins/rendercontrol.ts delete mode 100644 src/apps/weblib/ts-api/rendering.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a06b0089..0beea0fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,10 @@ - Added the padded rectangle, the bounding rectangle and the align parameter to the draw text event object. - Tooltip works on marker labels too. - New plugins and plugin hooks introduced: - - scheduler/startScheduler - plugin resposible for scheduling the rendering + - plugin: scheduler - plugin resposible for scheduling the rendering + - plugin: clock - supplying the current time for the animation + - hook: start - hook for starting the rendering loop + - hook: render - hook for rendering the chart ## [0.9.3] - 2023-12-20 diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index e2fbafd52..e16f3c016 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -3,12 +3,16 @@ import * as Config from './types/config.js' import * as Styles from './types/styles.js' import * as D from './types/data.js' import { Module } from './module/module.js' +import { type Canvas } from './module/canvas.js' import { CChart, Snapshot } from './module/cchart.js' +import { CCanvas } from './module/ccanvas.js' import { CAnimControl, CAnimation } from './module/canimctrl.js' import { CData } from './module/cdata.js' -import { Render } from './rendering.js' import { Data } from './data.js' import { Events, EventType, EventHandler, EventMap } from './events.js' +import { Mirrored } from './tsutils.js' +import { VizzuOptions } from './vizzu.js' +import { AnimControl } from './animcontrol.js' import { PluginRegistry, Hooks } from './plugins.js' import { Logging } from './plugins/logging.js' import { Shorthands } from './plugins/shorthands.js' @@ -16,18 +20,18 @@ import { PivotData } from './plugins/pivotdata.js' import { Tooltip } from './plugins/tooltip.js' import { PointerEvents } from './plugins/pointerevents.js' import { CSSProperties } from './plugins/cssproperties.js' -import { Mirrored } from './tsutils.js' +import { CanvasRenderer } from './plugins/canvasrenderer.js' import { HtmlCanvas } from './plugins/htmlcanvas.js' -import { VizzuOptions } from './vizzu.js' -import { AnimControl } from './animcontrol.js' import { CoordSystem } from './plugins/coordsys.js' +import { Clock } from './plugins/clock.js' import { Scheduler } from './plugins/scheduler.js' +import { RenderControl } from './plugins/rendercontrol.js' export class Chart { private _cChart: CChart - private _render: Render private _module: Module private _canvas: HtmlCanvas + private _ccanvas: CCanvas & Canvas private _cData: CData private _data: Data private _events: Events @@ -37,13 +41,20 @@ export class Chart { this._plugins = plugins this._module = module - this._canvas = new HtmlCanvas(HtmlCanvas.extractOptions(options)) - this._cChart = this._module.createChart() this._cData = this._module.getData(this._cChart) this._data = new Data(this._cData) - this._render = new Render(this._module, this._cChart, this._canvas) + this._canvas = new HtmlCanvas(HtmlCanvas.extractOptions(options)) + this._canvas.onchange = (): void => { + this.updateFrame(true) + } + this._ccanvas = this._module.createCanvas( + CanvasRenderer, + this._canvas + ) + this._module.registerRenderer(this._ccanvas) + this._events = new Events(this._cChart, this._canvas) this._plugins.init(this._events) } @@ -51,8 +62,9 @@ export class Chart { registerBuiltins(): void { this._plugins.register(new Logging(this._module.setLogging.bind(this._module)), false) this._plugins.register(this._canvas, true) + this._plugins.register(new Clock(), true) this._plugins.register(new Scheduler(), true) - this._plugins.register(this._render, true) + this._plugins.register(new RenderControl(), true) this._plugins.register(new CoordSystem(this._module.getCoordSystem(this._cChart)), true) this._plugins.register(new CSSProperties(), false) this._plugins.register(new Shorthands(), true) @@ -63,9 +75,34 @@ export class Chart { start(): void { const ctx = { - update: () => this._render.updateFrame() + update: () => this.updateFrame() + } + this._plugins.hook(Hooks.start, ctx).default(() => { + this.updateFrame() + }) + } + + updateFrame(_force: boolean = false): void { + const size = this._canvas.calcSize() + if (size.x >= 1 && size.y >= 1) { + const ctx = { + timeInMSecs: null, + force: _force, + enable: true + } + this._plugins.hook(Hooks.render, ctx).default((ctx) => { + if (ctx.timeInMSecs !== null) { + const renderControl = !ctx.enable ? 2 : ctx.force ? 1 : 0 + this._cChart.update( + this._ccanvas, + size.x, + size.y, + ctx.timeInMSecs, + renderControl + ) + } + }) } - this._plugins.hook(Hooks.startScheduler, ctx).default(() => {}) } async prepareAnimation( diff --git a/src/apps/weblib/ts-api/index.ts b/src/apps/weblib/ts-api/index.ts index 894f9c539..cd177df89 100644 --- a/src/apps/weblib/ts-api/index.ts +++ b/src/apps/weblib/ts-api/index.ts @@ -17,6 +17,8 @@ export * from './plugins/pointerevents.js' export * from './plugins/shorthands-augmentation.js' export * from './plugins/shorthands.js' export * from './plugins/tooltip.js' +export * from './plugins/htmlcanvas.js' +export * from './plugins/rendercontrol.js' export * as Anim from './types/anim.js' export * as Config from './types/config.js' @@ -32,9 +34,7 @@ export * from './datarecord.js' export * from './errors.js' export * as Events from './events.js' export * as Geom from './geom.js' -export * from './plugins/htmlcanvas.js' export * as Plugins from './plugins.js' -export * from './rendering.js' export * from './vizzu.js' export { default as Vizzu } from './vizzu.js' diff --git a/src/apps/weblib/ts-api/plugins.ts b/src/apps/weblib/ts-api/plugins.ts index ff43abbd1..8bf4c2ca8 100644 --- a/src/apps/weblib/ts-api/plugins.ts +++ b/src/apps/weblib/ts-api/plugins.ts @@ -5,8 +5,10 @@ import { AnimCompleting } from './animcompleting.js' /** Available hooks for plugins in Vizzu. */ export enum Hooks { - /** Called once on startup for start the scheduler for rendering. */ - startScheduler = 'startScheduler', + /** Called once on startup for start the rendering loop. */ + start = 'start', + /** Called on rendering. */ + render = 'render', /** Called when the animate() parameters gets set in the library to prepare the animation. */ prepareAnimation = 'prepareAnimation', @@ -29,8 +31,14 @@ export interface PluginMeta { depends?: string[] } -export interface StartSchedulerContext { - update: (time: number) => void +export interface StartContext { + update: () => void +} + +export interface RenderContext { + timeInMSecs: number | null + force: boolean + enable: boolean } export interface PrepareAnimationContext { @@ -49,7 +57,8 @@ export interface RunAnimationContext { } export interface HookContexts { - [Hooks.startScheduler]: StartSchedulerContext + [Hooks.start]: StartContext + [Hooks.render]: RenderContext [Hooks.prepareAnimation]: PrepareAnimationContext [Hooks.registerAnimation]: RegisterAnimationContext [Hooks.runAnimation]: RunAnimationContext diff --git a/src/apps/weblib/ts-api/plugins/clock.ts b/src/apps/weblib/ts-api/plugins/clock.ts new file mode 100644 index 000000000..1fba61f54 --- /dev/null +++ b/src/apps/weblib/ts-api/plugins/clock.ts @@ -0,0 +1,30 @@ +import { Plugin, PluginApi, PluginHooks, RenderContext } from '../plugins.js' + +export interface ClockApi extends PluginApi { + /** Returns the actual time in miliseconds. */ + now(): number +} + +export class Clock implements Plugin { + meta = { name: 'clock' } + + get api(): ClockApi { + return { + now: (): number => this._now() + } + } + + get hooks(): PluginHooks { + const hooks = { + render: (ctx: RenderContext, next: () => void): void => { + if (ctx.timeInMSecs === null) ctx.timeInMSecs = this._now() + next() + } + } + return hooks + } + + private _now(): number { + return performance.now() + } +} diff --git a/src/apps/weblib/ts-api/plugins/rendercontrol.ts b/src/apps/weblib/ts-api/plugins/rendercontrol.ts new file mode 100644 index 000000000..fa78b2477 --- /dev/null +++ b/src/apps/weblib/ts-api/plugins/rendercontrol.ts @@ -0,0 +1,46 @@ +import { Plugin, PluginApi, PluginHooks, RenderContext, StartContext } from '../plugins.js' + +export interface RenderControlApi extends PluginApi { + /** Re-renders the chart. */ + update(timeInMSecs: number | null): void +} + +export class RenderControl implements Plugin { + private _update: () => void = () => {} + private _timeInMSecs: number | null = null + private _enabled: boolean = true + + meta = { name: 'rendering' } + + get api(): RenderControlApi { + return { + update: (timeInMSecs: number | null = null): void => { + this._timeInMSecs = timeInMSecs + this._update() + } + } + } + + get hooks(): PluginHooks { + const hooks = { + start: (ctx: StartContext, next: () => void): void => { + this._update = ctx.update + next() + }, + render: (ctx: RenderContext, next: () => void): void => { + if (this._timeInMSecs !== null) { + ctx.timeInMSecs = this._timeInMSecs + ctx.force = true + this._timeInMSecs = null + } + ctx.enable = this._enabled + next() + } + } + return hooks + } + + enable(enabled: boolean): void { + this._enabled = enabled + } +} diff --git a/src/apps/weblib/ts-api/plugins/scheduler.ts b/src/apps/weblib/ts-api/plugins/scheduler.ts index bbf18088d..7dd49c08f 100644 --- a/src/apps/weblib/ts-api/plugins/scheduler.ts +++ b/src/apps/weblib/ts-api/plugins/scheduler.ts @@ -1,15 +1,15 @@ -import { Plugin, PluginHooks, StartSchedulerContext } from '../plugins.js' +import { Plugin, PluginHooks, StartContext } from '../plugins.js' export class Scheduler implements Plugin { private _updateInterval?: ReturnType - private _update: (time: number) => void = () => {} + private _update: () => void = () => {} private _enabled = false meta = { name: 'scheduler' } get hooks(): PluginHooks { const hooks = { - startScheduler: (ctx: StartSchedulerContext, next: () => void): void => { + start: (ctx: StartContext, next: () => void): void => { this._update = ctx.update this._start() next() @@ -30,10 +30,10 @@ export class Scheduler implements Plugin { private _start(): void { if (!this._updateInterval) { - this._update(performance.now()) + this._update() this._updateInterval = setInterval(() => { if (this._enabled) { - this._update(performance.now()) + this._update() } }, 25) } diff --git a/src/apps/weblib/ts-api/rendering.ts b/src/apps/weblib/ts-api/rendering.ts deleted file mode 100644 index 761adc661..000000000 --- a/src/apps/weblib/ts-api/rendering.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Plugin, PluginApi } from './plugins.js' -import { Module } from './module/module.js' -import { CCanvas } from './module/ccanvas.js' -import { Canvas } from './module/canvas.js' -import { CChart } from './module/cchart.js' -import { HtmlCanvas } from './plugins/htmlcanvas.js' -import { CanvasRenderer } from './plugins/canvasrenderer.js' - -export interface RenderingApi extends PluginApi { - /** Re-renders the chart. */ - update(): void -} - -export class Render implements Plugin { - private _canvas: HtmlCanvas - private _ccanvas: CCanvas & Canvas - private _enabled: boolean - private _cchart: CChart - - meta = { name: 'rendering' } - - get api(): RenderingApi { - return { - update: (): void => { - this.updateFrame(true) - } - } - } - - enable(enabled: boolean): void { - this._enabled = enabled - } - - constructor(module: Module, cchart: CChart, canvas: HtmlCanvas) { - this._canvas = canvas - this._canvas.onchange = (): void => { - this.updateFrame(true) - } - this._enabled = true - this._cchart = cchart - this._ccanvas = module.createCanvas(CanvasRenderer, canvas) - module.registerRenderer(this._ccanvas) - } - - updateFrame(force: boolean = false): void { - const size = this._canvas.calcSize() - if (size.x >= 1 && size.y >= 1) { - const time = performance.now() - const renderControl = !this._enabled ? 2 : force ? 1 : 0 - this._cchart.update(this._ccanvas, size.x, size.y, time, renderControl) - } - } -} From 5242e52bbc0fed075ddc35d9c810c81cfd7be457 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Fri, 1 Mar 2024 17:39:11 +0100 Subject: [PATCH 076/253] Canvas onchange moved inside HtmlCanvas --- src/apps/weblib/ts-api/chart.ts | 10 +++------- src/apps/weblib/ts-api/plugins.ts | 3 +-- src/apps/weblib/ts-api/plugins/htmlcanvas.ts | 18 ++++++++++++++---- .../weblib/ts-api/plugins/rendercontrol.ts | 5 ++--- src/apps/weblib/ts-api/plugins/scheduler.ts | 6 +++--- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index e16f3c016..b8592c9b9 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -46,9 +46,6 @@ export class Chart { this._data = new Data(this._cData) this._canvas = new HtmlCanvas(HtmlCanvas.extractOptions(options)) - this._canvas.onchange = (): void => { - this.updateFrame(true) - } this._ccanvas = this._module.createCanvas( CanvasRenderer, this._canvas @@ -75,24 +72,23 @@ export class Chart { start(): void { const ctx = { - update: () => this.updateFrame() + update: (force: boolean): void => this.updateFrame(force) } this._plugins.hook(Hooks.start, ctx).default(() => { this.updateFrame() }) } - updateFrame(_force: boolean = false): void { + updateFrame(force: boolean = false): void { const size = this._canvas.calcSize() if (size.x >= 1 && size.y >= 1) { const ctx = { timeInMSecs: null, - force: _force, enable: true } this._plugins.hook(Hooks.render, ctx).default((ctx) => { if (ctx.timeInMSecs !== null) { - const renderControl = !ctx.enable ? 2 : ctx.force ? 1 : 0 + const renderControl = !ctx.enable ? 2 : force ? 1 : 0 this._cChart.update( this._ccanvas, size.x, diff --git a/src/apps/weblib/ts-api/plugins.ts b/src/apps/weblib/ts-api/plugins.ts index 8bf4c2ca8..435eb27b6 100644 --- a/src/apps/weblib/ts-api/plugins.ts +++ b/src/apps/weblib/ts-api/plugins.ts @@ -32,12 +32,11 @@ export interface PluginMeta { } export interface StartContext { - update: () => void + update: (force: boolean) => void } export interface RenderContext { timeInMSecs: number | null - force: boolean enable: boolean } diff --git a/src/apps/weblib/ts-api/plugins/htmlcanvas.ts b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts index 3f0ed9af8..098f96fa9 100644 --- a/src/apps/weblib/ts-api/plugins/htmlcanvas.ts +++ b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts @@ -1,5 +1,5 @@ import * as Geom from '../geom.js' -import { Plugin, PluginApi } from '../plugins.js' +import { Plugin, PluginApi, PluginHooks, StartContext } from '../plugins.js' export interface CanvasOptions { element: HTMLElement @@ -17,7 +17,7 @@ export interface HtmlCanvasApi extends PluginApi { } export class HtmlCanvas implements Plugin { - onchange: () => void = () => {} + private _update: (force: boolean) => void = () => {} private _container?: HTMLElement private _offscreenCanvas: HTMLCanvasElement private _offscreenContext: CanvasRenderingContext2D @@ -40,6 +40,16 @@ export class HtmlCanvas implements Plugin { } } + get hooks(): PluginHooks { + const hooks = { + start: (ctx: StartContext, next: () => void): void => { + this._update = ctx.update + next() + } + } + return hooks + } + static extractOptions(options: unknown): CanvasOptions { const opts = typeof options !== 'object' || options instanceof HTMLElement @@ -75,7 +85,7 @@ export class HtmlCanvas implements Plugin { this.calcSize() this._resizeObserver = this._createResizeObserverFor(this._mainCanvas) this._resizeHandler = (): void => { - this.onchange() + this._update(true) } window.addEventListener('resize', this._resizeHandler) } @@ -165,7 +175,7 @@ export class HtmlCanvas implements Plugin { private _createResizeObserverFor(canvas: HTMLCanvasElement): ResizeObserver { const resizeObserver = new ResizeObserver(() => { - this.onchange() + this._update(true) }) resizeObserver.observe(canvas) return resizeObserver diff --git a/src/apps/weblib/ts-api/plugins/rendercontrol.ts b/src/apps/weblib/ts-api/plugins/rendercontrol.ts index fa78b2477..bd0a2bee1 100644 --- a/src/apps/weblib/ts-api/plugins/rendercontrol.ts +++ b/src/apps/weblib/ts-api/plugins/rendercontrol.ts @@ -6,7 +6,7 @@ export interface RenderControlApi extends PluginApi { } export class RenderControl implements Plugin { - private _update: () => void = () => {} + private _update: (force: boolean) => void = () => {} private _timeInMSecs: number | null = null private _enabled: boolean = true @@ -16,7 +16,7 @@ export class RenderControl implements Plugin { return { update: (timeInMSecs: number | null = null): void => { this._timeInMSecs = timeInMSecs - this._update() + this._update(true) } } } @@ -30,7 +30,6 @@ export class RenderControl implements Plugin { render: (ctx: RenderContext, next: () => void): void => { if (this._timeInMSecs !== null) { ctx.timeInMSecs = this._timeInMSecs - ctx.force = true this._timeInMSecs = null } ctx.enable = this._enabled diff --git a/src/apps/weblib/ts-api/plugins/scheduler.ts b/src/apps/weblib/ts-api/plugins/scheduler.ts index 7dd49c08f..a1452cbd5 100644 --- a/src/apps/weblib/ts-api/plugins/scheduler.ts +++ b/src/apps/weblib/ts-api/plugins/scheduler.ts @@ -2,7 +2,7 @@ import { Plugin, PluginHooks, StartContext } from '../plugins.js' export class Scheduler implements Plugin { private _updateInterval?: ReturnType - private _update: () => void = () => {} + private _update: (force: boolean) => void = () => {} private _enabled = false meta = { name: 'scheduler' } @@ -30,10 +30,10 @@ export class Scheduler implements Plugin { private _start(): void { if (!this._updateInterval) { - this._update() + this._update(false) this._updateInterval = setInterval(() => { if (this._enabled) { - this._update() + this._update(false) } }, 25) } From 6681c54262f1f4728ac98d24abfd9ac2bee87cb5 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Fri, 1 Mar 2024 18:12:39 +0100 Subject: [PATCH 077/253] HtmlCanvas.calcSize() made private --- src/apps/weblib/ts-api/chart.ts | 34 +++++++++----------- src/apps/weblib/ts-api/module/module.ts | 2 +- src/apps/weblib/ts-api/plugins.ts | 2 ++ src/apps/weblib/ts-api/plugins/htmlcanvas.ts | 10 ++++-- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index b8592c9b9..8f8f6b6df 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -80,25 +80,23 @@ export class Chart { } updateFrame(force: boolean = false): void { - const size = this._canvas.calcSize() - if (size.x >= 1 && size.y >= 1) { - const ctx = { - timeInMSecs: null, - enable: true - } - this._plugins.hook(Hooks.render, ctx).default((ctx) => { - if (ctx.timeInMSecs !== null) { - const renderControl = !ctx.enable ? 2 : force ? 1 : 0 - this._cChart.update( - this._ccanvas, - size.x, - size.y, - ctx.timeInMSecs, - renderControl - ) - } - }) + const ctx = { + timeInMSecs: null, + enable: true, + size: { x: 0, y: 0 } } + this._plugins.hook(Hooks.render, ctx).default((ctx) => { + if (ctx.size.x >= 1 && ctx.size.y >= 1 && ctx.timeInMSecs !== null) { + const renderControl = !ctx.enable ? 2 : force ? 1 : 0 + this._cChart.update( + this._ccanvas, + ctx.size.x, + ctx.size.y, + ctx.timeInMSecs, + renderControl + ) + } + }) } async prepareAnimation( diff --git a/src/apps/weblib/ts-api/module/module.ts b/src/apps/weblib/ts-api/module/module.ts index ea56f476a..b8bdb4377 100644 --- a/src/apps/weblib/ts-api/module/module.ts +++ b/src/apps/weblib/ts-api/module/module.ts @@ -7,7 +7,7 @@ import { CChart } from './cchart.js' import { CCanvas } from './ccanvas.js' import { CAnimControl } from './canimctrl.js' import { CCoordSystem } from './ccoordsys.js' -import { Canvas } from './canvas' +import { Canvas } from './canvas.js' export class Module extends CEnv { constructor(wasm: CVizzu) { diff --git a/src/apps/weblib/ts-api/plugins.ts b/src/apps/weblib/ts-api/plugins.ts index 435eb27b6..73c4c7764 100644 --- a/src/apps/weblib/ts-api/plugins.ts +++ b/src/apps/weblib/ts-api/plugins.ts @@ -2,6 +2,7 @@ import Vizzu from './vizzu.js' import * as Anim from './types/anim.js' import { Events, EventType, EventHandler, EventMap } from './events.js' import { AnimCompleting } from './animcompleting.js' +import { Point } from './geom.js' /** Available hooks for plugins in Vizzu. */ export enum Hooks { @@ -38,6 +39,7 @@ export interface StartContext { export interface RenderContext { timeInMSecs: number | null enable: boolean + size: Point } export interface PrepareAnimationContext { diff --git a/src/apps/weblib/ts-api/plugins/htmlcanvas.ts b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts index 098f96fa9..0b68c0647 100644 --- a/src/apps/weblib/ts-api/plugins/htmlcanvas.ts +++ b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts @@ -1,5 +1,5 @@ import * as Geom from '../geom.js' -import { Plugin, PluginApi, PluginHooks, StartContext } from '../plugins.js' +import { Plugin, PluginApi, PluginHooks, RenderContext, StartContext } from '../plugins.js' export interface CanvasOptions { element: HTMLElement @@ -45,6 +45,10 @@ export class HtmlCanvas implements Plugin { start: (ctx: StartContext, next: () => void): void => { this._update = ctx.update next() + }, + render: (ctx: RenderContext, next: () => void): void => { + ctx.size = this._calcSize() + next() } } return hooks @@ -82,7 +86,7 @@ export class HtmlCanvas implements Plugin { const ctx = this._mainCanvas.getContext('2d') if (!ctx) throw Error('Cannot get rendering context of canvas') this._context = ctx - this.calcSize() + this._calcSize() this._resizeObserver = this._createResizeObserverFor(this._mainCanvas) this._resizeHandler = (): void => { this._update(true) @@ -104,7 +108,7 @@ export class HtmlCanvas implements Plugin { return this._offscreenContext } - calcSize(): Geom.Point { + private _calcSize(): Geom.Point { this._scaleFactor = window.devicePixelRatio this._cssWidth = +getComputedStyle(this._mainCanvas).width.slice(0, -2) this._cssHeight = +getComputedStyle(this._mainCanvas).height.slice(0, -2) From 18da664915b522d2361fd05925d87a3d445e5b34 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Fri, 1 Mar 2024 18:19:56 +0100 Subject: [PATCH 078/253] HtmlCanvas desctructed through plugin infra --- src/apps/weblib/ts-api/chart.ts | 4 ---- src/apps/weblib/ts-api/plugins/htmlcanvas.ts | 2 +- src/apps/weblib/ts-api/vizzu.ts | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index 8f8f6b6df..ed5635e2d 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -154,10 +154,6 @@ export class Chart { }) } - destruct(): void { - this._canvas.destruct() - } - version(): string { return this._module.version() } diff --git a/src/apps/weblib/ts-api/plugins/htmlcanvas.ts b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts index 0b68c0647..28f185e77 100644 --- a/src/apps/weblib/ts-api/plugins/htmlcanvas.ts +++ b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts @@ -94,7 +94,7 @@ export class HtmlCanvas implements Plugin { window.addEventListener('resize', this._resizeHandler) } - destruct(): void { + unregister(): void { window.removeEventListener('resize', this._resizeHandler) this._resizeObserver.disconnect() if (this._container) this._container.removeChild(this._mainCanvas) diff --git a/src/apps/weblib/ts-api/vizzu.ts b/src/apps/weblib/ts-api/vizzu.ts index 108148641..feeb6947f 100644 --- a/src/apps/weblib/ts-api/vizzu.ts +++ b/src/apps/weblib/ts-api/vizzu.ts @@ -286,8 +286,8 @@ export default class Vizzu { detach(): void { try { this._plugins.destruct() - } finally { - this._chart?.destruct() + } catch (e) { + console.error(`Error during plugin destruct: ${e}`) } } } From 0c93cc75e13baee308e14e3e123918b1f927b459 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Sun, 3 Mar 2024 21:36:12 +0100 Subject: [PATCH 079/253] Adding canvas context to event moved from Event to HTMLCanvas. --- src/apps/weblib/ts-api/chart.ts | 2 +- src/apps/weblib/ts-api/events.ts | 13 ++------ src/apps/weblib/ts-api/index.ts | 1 + src/apps/weblib/ts-api/plugins/htmlcanvas.ts | 35 ++++++++++++++++++-- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index ed5635e2d..6cc4dfb98 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -52,7 +52,7 @@ export class Chart { ) this._module.registerRenderer(this._ccanvas) - this._events = new Events(this._cChart, this._canvas) + this._events = new Events(this._cChart) this._plugins.init(this._events) } diff --git a/src/apps/weblib/ts-api/events.ts b/src/apps/weblib/ts-api/events.ts index 8eb086a45..edb2b389d 100644 --- a/src/apps/weblib/ts-api/events.ts +++ b/src/apps/weblib/ts-api/events.ts @@ -2,7 +2,6 @@ import { CFunction } from './cvizzu.types' import { PluginListeners } from './plugins.js' import { CChart, CEvent } from './module/cchart.js' -import { HtmlCanvas, HtmlCanvasContext } from './plugins/htmlcanvas.js' import * as Data from './types/data.js' import * as Anim from './types/anim.js' @@ -218,6 +217,7 @@ export interface LegendBar extends Element { tagName: 'legend-bar' parent: Legend } + /** The interface of the event object is passed to event handlers by the library. Detail properties will vary by event type. */ export interface Event { @@ -226,10 +226,8 @@ export interface Event { target: T | null /** If called, the default action of the event will be canceled. */ preventDefault(): void - /** For drawing events the rendering context of the underlying - canvas set up for drawing the element. */ - renderingContext?: HtmlCanvasContext } + export interface PointerDetail { pointerId: number | null position: Point @@ -309,12 +307,10 @@ type EventHandlers = { export class Events { private _cChart: CChart - private _canvas: HtmlCanvas private _eventHandlers: EventHandlers = {} - constructor(cChart: CChart, canvas: HtmlCanvas) { + constructor(cChart: CChart) { this._cChart = cChart - this._canvas = canvas } add(eventName: T, handler: EventHandler): void { @@ -417,9 +413,6 @@ export class Events { cEvent.preventDefault() state.canceled = true } - if (param.type.endsWith('-draw') || param.type.startsWith('draw-')) { - param.renderingContext = this._canvas.context - } return param } } diff --git a/src/apps/weblib/ts-api/index.ts b/src/apps/weblib/ts-api/index.ts index cd177df89..4b2b4a1be 100644 --- a/src/apps/weblib/ts-api/index.ts +++ b/src/apps/weblib/ts-api/index.ts @@ -9,6 +9,7 @@ export * from './module/loader.js' export * from './module/module.js' export * from './module/objregistry.js' +export * from './plugins/clock.js' export * from './plugins/coordsys.js' export * from './plugins/cssproperties.js' export * from './plugins/logging.js' diff --git a/src/apps/weblib/ts-api/plugins/htmlcanvas.ts b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts index 28f185e77..643a37822 100644 --- a/src/apps/weblib/ts-api/plugins/htmlcanvas.ts +++ b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts @@ -1,5 +1,13 @@ import * as Geom from '../geom.js' -import { Plugin, PluginApi, PluginHooks, RenderContext, StartContext } from '../plugins.js' +import * as Events from '../events.js' +import { + Plugin, + PluginApi, + PluginHooks, + PluginListeners, + RenderContext, + StartContext +} from '../plugins.js' export interface CanvasOptions { element: HTMLElement @@ -7,11 +15,17 @@ export interface CanvasOptions { export type LazyCanvasOptions = string | HTMLElement | CanvasOptions -export type HtmlCanvasContext = CanvasRenderingContext2D +export type Event = Events.Event & { + /** For drawing events the rendering context of the underlying + canvas set up for drawing the element. */ + renderingContext?: CanvasRenderingContext2D +} export interface HtmlCanvasApi extends PluginApi { /** Returns the underlying canvas element. */ get element(): HTMLCanvasElement + /** Returns the actual canvas context */ + get context(): CanvasRenderingContext2D clientToCanvas(clientPos: Geom.Point): Geom.Point canvasToClient(renderPos: Geom.Point): Geom.Point } @@ -35,6 +49,7 @@ export class HtmlCanvas implements Plugin { get api(): HtmlCanvasApi { return { element: this.element, + context: this.context, clientToCanvas: this._clientToCanvas.bind(this), canvasToClient: this._canvasToClient.bind(this) } @@ -54,6 +69,22 @@ export class HtmlCanvas implements Plugin { return hooks } + get listeners(): PluginListeners { + return Object.fromEntries( + Object.values(Events.EventType) + .filter( + (type: string): boolean => type.endsWith('-draw') || type.startsWith('draw-') + ) + .map((type: string) => [type as Events.EventType, this._extendEvent.bind(this)]) + ) + } + + private _extendEvent(param: Event): void { + if (param.type.endsWith('-draw') || param.type.startsWith('draw-')) { + param.renderingContext = this.context + } + } + static extractOptions(options: unknown): CanvasOptions { const opts = typeof options !== 'object' || options instanceof HTMLElement From a445681b19e07fc3b01d62cad3e48f92a2090613 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 4 Mar 2024 16:12:19 +0100 Subject: [PATCH 080/253] create_or_override, store series order, no copy on not finalized. add_record take into account the series inserted order. No throw on wrong record id. --- project/cmake/common.txt | 4 +- src/dataframe/impl/data_source.cpp | 5 +- src/dataframe/impl/dataframe.cpp | 129 ++++++++++++++++++++++++----- src/dataframe/impl/dataframe.h | 2 +- src/dataframe/interface.h | 1 + 5 files changed, 113 insertions(+), 28 deletions(-) diff --git a/project/cmake/common.txt b/project/cmake/common.txt index d505b24a0..0346d0712 100644 --- a/project/cmake/common.txt +++ b/project/cmake/common.txt @@ -14,11 +14,11 @@ option(cppcheck "Run cppcheck analyzis" OFF) option(cpplint "Run cpplint analyzis" OFF) if(clangtidy) - set(CMAKE_CXX_CLANG_TIDY clang-tidy-16) + set(CMAKE_CXX_CLANG_TIDY clang-tidy) endif() if (clangformat) - set(CLANG_FORMAT_EXE clang-format-16) + set(CLANG_FORMAT_EXE clang-format) get_property(_allTargets GLOBAL PROPERTY GlobalTargetList) if (NOT _allTargets) diff --git a/src/dataframe/impl/data_source.cpp b/src/dataframe/impl/data_source.cpp index 40480170d..5da80b8ac 100644 --- a/src/dataframe/impl/data_source.cpp +++ b/src/dataframe/impl/data_source.cpp @@ -256,13 +256,12 @@ cell_value data_source::get_data(std::size_t record_id, case dimension: { const auto &dims = unsafe_get(series).second; if (record_id >= dims.values.size()) - throw std::runtime_error("unknown record"); + return std::string_view{}; return dims.get(record_id); } case measure: { const auto &meas = unsafe_get(series).second; - if (record_id >= meas.values.size()) - throw std::runtime_error("unknown record"); + if (record_id >= meas.values.size()) return nan; return meas.values[record_id]; } diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp index 5b953aa3b..41e5cc3e2 100644 --- a/src/dataframe/impl/dataframe.cpp +++ b/src/dataframe/impl/dataframe.cpp @@ -1,5 +1,6 @@ #include "dataframe.h" +#include #include #include #include @@ -40,8 +41,7 @@ dataframe::dataframe(std::shared_ptr other, auto &cp = unsafe_get(source); if (filtered) cp.pre_remove.emplace(*filtered); if (sorted) cp.sorted_indices.emplace(*sorted); - if (!cp.other->finalized) { migrate_data(); } - else + if (cp.other->finalized) state_data.emplace( *cp.other->finalized); } @@ -229,6 +229,9 @@ void dataframe::add_dimension( change_state_to(state_type::modifying, state_modification_reason::needs_own_state); + unsafe_get(state_data) + .emplace_back(name); + unsafe_get(source)->add_new_dimension( dimension_categories, dimension_values, @@ -254,6 +257,7 @@ void dataframe::add_dimension( dimension_values); break; } + case adding_type::create_or_override: case adding_type::override_full: { dims.categories.assign(dimension_categories.begin(), dimension_categories.end()); @@ -300,6 +304,9 @@ void dataframe::add_measure(std::span measure_values, change_state_to(state_type::modifying, state_modification_reason::needs_own_state); + unsafe_get(state_data) + .emplace_back(name); + unsafe_get(source)->add_new_measure( measure_values, name, @@ -325,6 +332,7 @@ void dataframe::add_measure(std::span measure_values, measure_values.end()); break; } + case adding_type::create_or_override: case adding_type::override_full: { meas.values.assign(measure_values.begin(), measure_values.end()); @@ -443,6 +451,8 @@ void dataframe::add_series_by_other(series_identifier curr_series, change_state_to(state_type::modifying, state_modification_reason::needs_own_state); + unsafe_get(state_data).emplace_back(name); + auto &s = *unsafe_get(source); switch (v) { @@ -505,33 +515,74 @@ void dataframe::add_record(std::span values) & if (values.empty()) throw std::runtime_error("Empty record cannot be added."); - std::size_t dimensionIx{}; - std::size_t measureIx{}; - - for (const auto &v : values) - if (std::holds_alternative(v)) - ++measureIx; - else - ++dimensionIx; - change_state_to(state_type::modifying, state_modification_reason::needs_series_type); - const auto &pre = get_data_source(); - if (measureIx != pre.measures.size()) - throw std::runtime_error("Measure count not match."); - if (dimensionIx != pre.dimensions.size()) - throw std::runtime_error("Dimension count not match."); + std::vector reorder; + if (auto *vec = get_if(&state_data); + vec && !vec->empty() + && std::ranges::all_of(values, + [](const cell_value &c) + { + return std::holds_alternative(c); + })) { + if (vec->size() != values.size()) + throw std::runtime_error("Record size not match."); + reorder.resize(vec->size()); + const auto &s = get_data_source(); + auto dims = s.dimensions.size(); + for (auto it = values.data(); auto col : *vec) { + const auto &sv = *std::get_if(it); + switch (auto &&ser = s.get_series(col)) { + using enum series_type; + default: throw std::runtime_error("FATAL_ERROR"); + case dimension: + reorder[&unsafe_get(ser).second + - s.dimensions.data()] = sv; + break; + case measure: + char *eof{}; + reorder[&unsafe_get(ser).second + - s.measures.data() + dims] = + std::strtod(sv.data(), &eof); + if (eof == sv.data()) + throw std::runtime_error( + "cell should be numeric: " + std::string{sv}); + break; + } + ++it; + } - change_state_to(state_type::modifying, - state_modification_reason::needs_own_state); + values = reorder; + } + else { + std::size_t dimensionIx{}; + std::size_t measureIx{}; + + for (const auto &v : values) + if (std::holds_alternative(v)) + ++measureIx; + else + ++dimensionIx; + + change_state_to(state_type::modifying, + state_modification_reason::needs_series_type); + + const auto &pre = get_data_source(); + if (measureIx != pre.measures.size()) + throw std::runtime_error("Measure count not match."); + if (dimensionIx != pre.dimensions.size()) + throw std::runtime_error("Dimension count not match."); + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + } auto &s = *unsafe_get(source); s.normalize_sizes(); - measureIx = 0; - dimensionIx = 0; - for (const auto &v : values) { + for (std::size_t measureIx{}, dimensionIx{}; + const auto &v : values) { if (const double *measure = std::get_if(&v)) s.measures[measureIx++].values.emplace_back(*measure); else { @@ -690,7 +741,41 @@ void dataframe::finalize() & state_modification_reason::needs_own_state); } -std::string dataframe::as_string() const & { return {}; } +std::string dataframe::as_string() const & +{ + const auto *vec = get_if(&state_data); + if (!vec || vec->empty()) + throw std::runtime_error( + "Only raw dataframe can get the state"); + + std::string res{'['}; + bool first = true; + for (const auto s = get_data_source(); auto name : *vec) { + if (!std::exchange(first, false)) res += ','; + Conv::JSONObj obj{res}; + switch (auto &&ser = s.get_series(name)) { + using enum series_type; + default: throw std::runtime_error("Series does not exists."); + case dimension: { + const auto &[name, dim] = unsafe_get(ser); + obj("name", name)("type", "dimension")("unit", + "")("length", dim.values.size())("categories", + dim.categories); + break; + } + case measure: { + const auto &[name, mea] = unsafe_get(ser); + auto &&[min, max] = mea.get_min_max(); + obj("name", name)("type", "measure")("unit", + mea.info.at("unit"))("length", mea.values.size()) + .nested("range")("min", min)("max", max); + break; + } + } + } + res += ']'; + return res; +} std::span dataframe::get_dimensions() const & { diff --git a/src/dataframe/impl/dataframe.h b/src/dataframe/impl/dataframe.h index 475b3aea4..59a38a8c5 100644 --- a/src/dataframe/impl/dataframe.h +++ b/src/dataframe/impl/dataframe.h @@ -151,7 +151,7 @@ class dataframe final : public dataframe_interface source{std::make_shared()}; Refl::EnumVariant, data_source::aggregating_type, data_source::sorting_type, std::reference_wrapper> diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 94dc25c18..a147f3d55 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -30,6 +30,7 @@ enum class na_position { last, first }; enum class adding_type { create_or_add, create_or_throw, + create_or_override, override_full, override_all_with_rotation }; From 38a8b6beb52bf9165e82ea1a617c4bacc30e1f5a Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Tue, 5 Mar 2024 00:49:13 +0100 Subject: [PATCH 081/253] Added missing files to index.ts --- src/apps/weblib/ts-api/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/apps/weblib/ts-api/index.ts b/src/apps/weblib/ts-api/index.ts index 4b2b4a1be..639c32d06 100644 --- a/src/apps/weblib/ts-api/index.ts +++ b/src/apps/weblib/ts-api/index.ts @@ -1,6 +1,9 @@ export * from './module/canimctrl.js' +export * from './module/canvas.js' export * from './module/ccanvas.js' export * from './module/cchart.js' +export * from './module/ccolorgradient.js' +export * from './module/ccoordsys.js' export * from './module/cdata.js' export * from './module/cenv.js' export * from './module/cerror.js' @@ -9,17 +12,20 @@ export * from './module/loader.js' export * from './module/module.js' export * from './module/objregistry.js' +export * from './plugins/canvasrenderer.js' export * from './plugins/clock.js' export * from './plugins/coordsys.js' export * from './plugins/cssproperties.js' +export * from './plugins/htmlcanvas.js' export * from './plugins/logging.js' export * from './plugins/pivotdata.js' export * from './plugins/pointerevents.js' +export * from './plugins/presets.js' +export * from './plugins/rendercontrol.js' +export * from './plugins/scheduler.js' export * from './plugins/shorthands-augmentation.js' export * from './plugins/shorthands.js' export * from './plugins/tooltip.js' -export * from './plugins/htmlcanvas.js' -export * from './plugins/rendercontrol.js' export * as Anim from './types/anim.js' export * as Config from './types/config.js' From bbd90a23dbb498484a135b1aeae14541b71366e7 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Tue, 5 Mar 2024 01:30:47 +0100 Subject: [PATCH 082/253] htmlcanvas and canvas renderer made into pure plugin --- CHANGELOG.md | 1 + src/apps/weblib/ts-api/chart.ts | 23 ++--- src/apps/weblib/ts-api/module/module.ts | 11 ++- src/apps/weblib/ts-api/plugins.ts | 3 + .../weblib/ts-api/plugins/canvasrenderer.ts | 93 +++++++++++++------ src/apps/weblib/ts-api/plugins/htmlcanvas.ts | 17 +--- 6 files changed, 89 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0beea0fa9..a672aac5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - New plugins and plugin hooks introduced: - plugin: scheduler - plugin resposible for scheduling the rendering - plugin: clock - supplying the current time for the animation + - plugin: canvasRenderer - plugin for rendering the chart on a htmlcanvas compatible canvas - hook: start - hook for starting the rendering loop - hook: render - hook for rendering the chart diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index 6cc4dfb98..04131e7df 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -3,9 +3,7 @@ import * as Config from './types/config.js' import * as Styles from './types/styles.js' import * as D from './types/data.js' import { Module } from './module/module.js' -import { type Canvas } from './module/canvas.js' import { CChart, Snapshot } from './module/cchart.js' -import { CCanvas } from './module/ccanvas.js' import { CAnimControl, CAnimation } from './module/canimctrl.js' import { CData } from './module/cdata.js' import { Data } from './data.js' @@ -28,16 +26,16 @@ import { Scheduler } from './plugins/scheduler.js' import { RenderControl } from './plugins/rendercontrol.js' export class Chart { + private _options: VizzuOptions private _cChart: CChart private _module: Module - private _canvas: HtmlCanvas - private _ccanvas: CCanvas & Canvas private _cData: CData private _data: Data private _events: Events private _plugins: PluginRegistry constructor(module: Module, options: VizzuOptions, plugins: PluginRegistry) { + this._options = options this._plugins = plugins this._module = module @@ -45,20 +43,14 @@ export class Chart { this._cData = this._module.getData(this._cChart) this._data = new Data(this._cData) - this._canvas = new HtmlCanvas(HtmlCanvas.extractOptions(options)) - this._ccanvas = this._module.createCanvas( - CanvasRenderer, - this._canvas - ) - this._module.registerRenderer(this._ccanvas) - this._events = new Events(this._cChart) this._plugins.init(this._events) } registerBuiltins(): void { this._plugins.register(new Logging(this._module.setLogging.bind(this._module)), false) - this._plugins.register(this._canvas, true) + this._plugins.register(new HtmlCanvas(HtmlCanvas.extractOptions(this._options)), true) + this._plugins.register(this._module.createCanvas(CanvasRenderer), true) this._plugins.register(new Clock(), true) this._plugins.register(new Scheduler(), true) this._plugins.register(new RenderControl(), true) @@ -81,20 +73,23 @@ export class Chart { updateFrame(force: boolean = false): void { const ctx = { + renderer: null, timeInMSecs: null, enable: true, size: { x: 0, y: 0 } } this._plugins.hook(Hooks.render, ctx).default((ctx) => { - if (ctx.size.x >= 1 && ctx.size.y >= 1 && ctx.timeInMSecs !== null) { + if (ctx.size.x >= 1 && ctx.size.y >= 1 && ctx.timeInMSecs !== null && ctx.renderer) { const renderControl = !ctx.enable ? 2 : force ? 1 : 0 + this._module.registerRenderer(ctx.renderer) this._cChart.update( - this._ccanvas, + ctx.renderer, ctx.size.x, ctx.size.y, ctx.timeInMSecs, renderControl ) + this._module.unregisterRenderer(ctx.renderer) } }) } diff --git a/src/apps/weblib/ts-api/module/module.ts b/src/apps/weblib/ts-api/module/module.ts index b8bdb4377..1802d969d 100644 --- a/src/apps/weblib/ts-api/module/module.ts +++ b/src/apps/weblib/ts-api/module/module.ts @@ -23,6 +23,10 @@ export class Module extends CEnv { this._wasm.canvases[cCanvas.getId()] = cCanvas } + unregisterRenderer(cCanvas: CCanvas & Canvas): void { + delete this._wasm.canvases[cCanvas.getId()] + } + version(): string { return this._wasm.UTF8ToString(this._wasm._vizzu_version()) } @@ -47,10 +51,7 @@ export class Module extends CEnv { return new CChart(this, this._getStatic(this._wasm._vizzu_createChart)) } - createCanvas( - ctor: new (env: CEnv, getId: CPointerClosure, ...args: Args[]) => T, - ...args: Args[] - ): T { - return new ctor(this, this._getStatic(this._wasm._vizzu_createCanvas), ...args) + createCanvas(ctor: new (env: CEnv, getId: CPointerClosure) => T): T { + return new ctor(this, this._getStatic(this._wasm._vizzu_createCanvas)) } } diff --git a/src/apps/weblib/ts-api/plugins.ts b/src/apps/weblib/ts-api/plugins.ts index 73c4c7764..f6a383010 100644 --- a/src/apps/weblib/ts-api/plugins.ts +++ b/src/apps/weblib/ts-api/plugins.ts @@ -3,6 +3,8 @@ import * as Anim from './types/anim.js' import { Events, EventType, EventHandler, EventMap } from './events.js' import { AnimCompleting } from './animcompleting.js' import { Point } from './geom.js' +import { CCanvas } from './module/ccanvas.js' +import { Canvas } from './module/canvas.js' /** Available hooks for plugins in Vizzu. */ export enum Hooks { @@ -37,6 +39,7 @@ export interface StartContext { } export interface RenderContext { + renderer: (CCanvas & Canvas) | null timeInMSecs: number | null enable: boolean size: Point diff --git a/src/apps/weblib/ts-api/plugins/canvasrenderer.ts b/src/apps/weblib/ts-api/plugins/canvasrenderer.ts index e7fc70620..7c4898f86 100644 --- a/src/apps/weblib/ts-api/plugins/canvasrenderer.ts +++ b/src/apps/weblib/ts-api/plugins/canvasrenderer.ts @@ -3,16 +3,53 @@ import { CEnv } from '../module/cenv.js' import { CPointerClosure } from '../module/objregistry.js' import { Canvas } from '../module/canvas.js' import { CCanvas } from '../module/ccanvas.js' -import { HtmlCanvas } from './htmlcanvas.js' +import { Plugin, PluginHooks, RenderContext as Ctx } from '../plugins.js' -export class CanvasRenderer extends CCanvas implements Canvas { - private _canvas: HtmlCanvas +export class CanvasError extends Error { + constructor() { + super('Canvas is not initialized') + this.name = 'CanvasError' + } +} + +export interface HtmlCanvasAlternative { + frameBegin(): void + frameEnd(): void + context: CanvasRenderingContext2D +} + +export type RenderContext = Ctx & { htmlCanvas?: HtmlCanvasAlternative } + +export class CanvasRenderer extends CCanvas implements Canvas, Plugin { + private _htmlCanvas?: HtmlCanvasAlternative private _polygonInProgress: boolean = false private _currentLineWidth: number = 1 - constructor(env: CEnv, getId: CPointerClosure, canvas: HtmlCanvas) { + constructor(env: CEnv, getId: CPointerClosure) { super(env, getId) - this._canvas = canvas + } + + get hooks(): PluginHooks { + const hooks = { + render: (ctx: RenderContext, next: () => void): void => { + if ('htmlCanvas' in ctx) { + this._htmlCanvas = ctx.htmlCanvas + ctx.renderer = this + } + next() + } + } + return hooks + } + + private get _canvas(): HtmlCanvasAlternative { + if (!this._htmlCanvas) throw new CanvasError() + return this._htmlCanvas + } + + private get _context(): CanvasRenderingContext2D { + if (!this._htmlCanvas) throw new CanvasError() + return this._htmlCanvas.context } frameBegin(): void { @@ -26,67 +63,67 @@ export class CanvasRenderer extends CCanvas implements Canvas { } setClipRect(x: number, y: number, sizex: number, sizey: number): void { - const dc = this._canvas.context + const dc = this._context dc.beginPath() dc.rect(x, y, sizex, sizey) dc.clip() } setClipCircle(x: number, y: number, radius: number): void { - const dc = this._canvas.context + const dc = this._context dc.beginPath() dc.arc(x, y, radius, 0, 6.28318530718) dc.clip() } setClipPolygon(): void { - const dc = this._canvas.context + const dc = this._context dc.closePath() dc.clip() this._polygonInProgress = false } setBrushColor(r: number, g: number, b: number, a: number): void { - const dc = this._canvas.context + const dc = this._context dc.fillStyle = 'rgba(' + r * 255 + ',' + g * 255 + ',' + b * 255 + ',' + a + ')' } setLineColor(r: number, g: number, b: number, a: number): void { - const dc = this._canvas.context + const dc = this._context dc.strokeStyle = 'rgba(' + r * 255 + ',' + g * 255 + ',' + b * 255 + ',' + a + ')' } setLineWidth(width: number): void { - const dc = this._canvas.context + const dc = this._context dc.lineWidth = width this._currentLineWidth = width } setFontStyle(font: string): void { - const dc = this._canvas.context + const dc = this._context dc.font = font } beginDropShadow(): void {} setDropShadowBlur(radius: number): void { - const dc = this._canvas.context + const dc = this._context dc.shadowBlur = radius } setDropShadowColor(r: number, g: number, b: number, a: number): void { - const dc = this._canvas.context + const dc = this._context dc.shadowColor = 'rgba(' + r * 255 + ',' + g * 255 + ',' + b * 255 + ',' + a + ')' } setDropShadowOffset(x: number, y: number): void { - const dc = this._canvas.context + const dc = this._context dc.shadowOffsetX = x dc.shadowOffsetY = y } endDropShadow(): void { - const dc = this._canvas.context + const dc = this._context dc.shadowBlur = 0 dc.shadowOffsetX = 0 dc.shadowOffsetY = 0 @@ -94,24 +131,24 @@ export class CanvasRenderer extends CCanvas implements Canvas { } beginPolygon(): void { - const dc = this._canvas.context + const dc = this._context dc.beginPath() } addPoint(x: number, y: number): void { - const dc = this._canvas.context + const dc = this._context if (!this._polygonInProgress) dc.moveTo(x, y) else dc.lineTo(x, y) this._polygonInProgress = true } addBezier(c0x: number, c0y: number, c1x: number, c1y: number, x: number, y: number): void { - const dc = this._canvas.context + const dc = this._context dc.bezierCurveTo(c0x, c0y, c1x, c1y, x, y) } endPolygon(): void { - const dc = this._canvas.context + const dc = this._context dc.closePath() dc.fill() if (this._currentLineWidth !== 0) dc.stroke() @@ -119,7 +156,7 @@ export class CanvasRenderer extends CCanvas implements Canvas { } rectangle(x: number, y: number, sizex: number, sizey: number): void { - const dc = this._canvas.context + const dc = this._context dc.beginPath() dc.rect(x, y, sizex, sizey) dc.fill() @@ -127,7 +164,7 @@ export class CanvasRenderer extends CCanvas implements Canvas { } circle(x: number, y: number, radius: number): void { - const dc = this._canvas.context + const dc = this._context dc.beginPath() dc.arc(x, y, radius, 0, 6.28318530718) dc.fill() @@ -135,7 +172,7 @@ export class CanvasRenderer extends CCanvas implements Canvas { } line(x1: number, y1: number, x2: number, y2: number): void { - const dc = this._canvas.context + const dc = this._context dc.beginPath() dc.moveTo(x1, y1) dc.lineTo(x2, y2) @@ -143,7 +180,7 @@ export class CanvasRenderer extends CCanvas implements Canvas { } drawText(x: number, y: number, sizex: number, sizey: number, text: string): void { - const dc = this._canvas.context + const dc = this._context dc.textAlign = 'left' dc.textBaseline = 'top' x = x + (sizex < 0 ? -sizex : 0) @@ -152,24 +189,24 @@ export class CanvasRenderer extends CCanvas implements Canvas { } setGradient(x1: number, y1: number, x2: number, y2: number, gradient: CColorGradient): void { - const dc = this._canvas.context + const dc = this._context const grd = dc.createLinearGradient(x1, y1, x2, y2) gradient.stops.forEach((g) => grd.addColorStop(g.offset, g.color)) dc.fillStyle = grd } transform(a: number, b: number, c: number, d: number, e: number, f: number): void { - const dc = this._canvas.context + const dc = this._context dc.transform(a, b, c, d, e, f) } save(): void { - const dc = this._canvas.context + const dc = this._context dc.save() } restore(): void { - const dc = this._canvas.context + const dc = this._context dc.restore() } } diff --git a/src/apps/weblib/ts-api/plugins/htmlcanvas.ts b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts index 643a37822..7159d6d64 100644 --- a/src/apps/weblib/ts-api/plugins/htmlcanvas.ts +++ b/src/apps/weblib/ts-api/plugins/htmlcanvas.ts @@ -1,13 +1,7 @@ import * as Geom from '../geom.js' import * as Events from '../events.js' -import { - Plugin, - PluginApi, - PluginHooks, - PluginListeners, - RenderContext, - StartContext -} from '../plugins.js' +import { Plugin, PluginApi, PluginHooks, PluginListeners, StartContext } from '../plugins.js' +import type { HtmlCanvasAlternative, RenderContext } from './canvasrenderer.js' export interface CanvasOptions { element: HTMLElement @@ -30,7 +24,7 @@ export interface HtmlCanvasApi extends PluginApi { canvasToClient(renderPos: Geom.Point): Geom.Point } -export class HtmlCanvas implements Plugin { +export class HtmlCanvas implements Plugin, HtmlCanvasAlternative { private _update: (force: boolean) => void = () => {} private _container?: HTMLElement private _offscreenCanvas: HTMLCanvasElement @@ -62,6 +56,7 @@ export class HtmlCanvas implements Plugin { next() }, render: (ctx: RenderContext, next: () => void): void => { + ctx.htmlCanvas = this ctx.size = this._calcSize() next() } @@ -80,9 +75,7 @@ export class HtmlCanvas implements Plugin { } private _extendEvent(param: Event): void { - if (param.type.endsWith('-draw') || param.type.startsWith('draw-')) { - param.renderingContext = this.context - } + param.renderingContext = this.context } static extractOptions(options: unknown): CanvasOptions { From 7c24e7726adf08d05b6d0a043799a7fee81177bf Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 7 Mar 2024 16:13:00 +0100 Subject: [PATCH 083/253] Remove optionssetter, rename CellWrapper to CellWrapperOld --- src/apps/weblib/cinterface.cpp | 4 +- src/apps/weblib/interface.cpp | 5 +- src/apps/weblib/jsfunctionwrapper.h | 5 +- src/chart/generator/channelstats.cpp | 6 +- src/chart/main/chart.cpp | 8 +- src/chart/main/chart.h | 1 - src/chart/options/config.cpp | 60 ++++++------- src/chart/options/config.h | 18 ++-- src/chart/options/options.cpp | 20 +++++ src/chart/options/options.h | 2 + src/chart/options/optionssetter.cpp | 125 --------------------------- src/chart/options/optionssetter.h | 56 ------------ src/data/datacube/datacube.cpp | 3 +- src/data/table/datatable.h | 8 +- test/qtest/chart.cpp | 114 ++++++++++++------------ 15 files changed, 137 insertions(+), 298 deletions(-) delete mode 100644 src/chart/options/optionssetter.cpp delete mode 100644 src/chart/options/optionssetter.h diff --git a/src/apps/weblib/cinterface.cpp b/src/apps/weblib/cinterface.cpp index bb8e54924..d5dd8ef47 100644 --- a/src/apps/weblib/cinterface.cpp +++ b/src/apps/weblib/cinterface.cpp @@ -263,9 +263,7 @@ void chart_setFilter(APIHandles::Chart chart, Vizzu::JsFunctionWrapper{{filter, deleter}}); - return Interface::getInstance().setChartFilter(chart, - Vizzu::JsFunctionWrapper{}); + return Interface::getInstance().setChartFilter(chart, {}); } const Value *record_getValue(const Vizzu::Data::RowWrapper *record, diff --git a/src/apps/weblib/interface.cpp b/src/apps/weblib/interface.cpp index 23e5d5e84..57d573d72 100644 --- a/src/apps/weblib/interface.cpp +++ b/src/apps/weblib/interface.cpp @@ -167,9 +167,8 @@ void Interface::setChartFilter(ObjectRegistry::Handle chart, JsFunctionWrapper &&filter) { const auto hash = filter.hash(); - getChart(chart)->getConfig().setFilter( - Data::Filter::Function{std::move(filter)}, - hash); + getChart(chart)->getOptions().dataFilter = {std::move(filter), + hash}; } std::variant Interface::getRecordValue( diff --git a/src/apps/weblib/jsfunctionwrapper.h b/src/apps/weblib/jsfunctionwrapper.h index 91ede8aa4..75a9d59a6 100644 --- a/src/apps/weblib/jsfunctionwrapper.h +++ b/src/apps/weblib/jsfunctionwrapper.h @@ -10,12 +10,13 @@ namespace Vizzu template class JsFunctionWrapper { -private: using JsFun = R(std::remove_reference_t *...); public: + constexpr JsFunctionWrapper() noexcept = default; + constexpr explicit JsFunctionWrapper( - std::shared_ptr &&fun = {}) : + std::shared_ptr &&fun) noexcept : wrapper{std::move(fun)} {} diff --git a/src/chart/generator/channelstats.cpp b/src/chart/generator/channelstats.cpp index 6ae22d441..2ae2d6326 100644 --- a/src/chart/generator/channelstats.cpp +++ b/src/chart/generator/channelstats.cpp @@ -8,9 +8,9 @@ ChannelStats::ChannelStats(const Channel &channel, isDimension(channel.isDimension()) { if (isDimension) - usedIndices = std::vector( - cube.combinedSizeOf(channel.dimensionIds), - Data::MultiDim::SubSliceIndex()); + usedIndices = + std::vector(cube.combinedSizeOf(channel.dimensionIds), + Data::MultiDim::SubSliceIndex()); } void ChannelStats::track(double value) diff --git a/src/chart/main/chart.cpp b/src/chart/main/chart.cpp index 65090b573..64656e2de 100644 --- a/src/chart/main/chart.cpp +++ b/src/chart/main/chart.cpp @@ -86,13 +86,9 @@ void Chart::setAnimation(const Anim::AnimationPtr &animation) animator->setAnimation(animation); } -Gen::Config Chart::getConfig() { return Gen::Config{getSetter()}; } - -Gen::OptionsSetter Chart::getSetter() +Gen::Config Chart::getConfig() { - Gen::OptionsSetter setter(*nextOptions); - setter.setTable(&table); - return setter; + return Gen::Config{getOptions(), table}; } void Chart::draw(Gfx::ICanvas &canvas) diff --git a/src/chart/main/chart.h b/src/chart/main/chart.h index c5c5c2bbd..0ba85f6bf 100644 --- a/src/chart/main/chart.h +++ b/src/chart/main/chart.h @@ -35,7 +35,6 @@ class Chart void setBoundRect(const Geom::Rect &rect); Data::DataTable &getTable() { return table; } - Gen::OptionsSetter getSetter(); Styles::Sheet &getStylesheet() { return stylesheet; } Styles::Chart &getStyles() { return actStyles; } [[nodiscard]] const Styles::Chart &getComputedStyles() const diff --git a/src/chart/options/config.cpp b/src/chart/options/config.cpp index 054ef9bf3..617a940e8 100644 --- a/src/chart/options/config.cpp +++ b/src/chart/options/config.cpp @@ -48,9 +48,9 @@ inline constexpr std::pair ExtractType{}(std::invoke(Mptr, options))); }, .set = - [](OptionsSetter &setter, const std::string &value) + [](Options &options, const std::string &value) { - std::invoke(Mptr, setter.getOptions()) = + std::invoke(Mptr, options) = Conv::parse::type>( value); }}}; @@ -77,10 +77,9 @@ const Config::Accessors &Config::getAccessors() return Conv::toString(options.tooltip); }, .set = - [](OptionsSetter &setter, - const std::string &value) + [](Options &options, const std::string &value) { - setter.showTooltip( + options.showTooltip( Conv::parse>(value)); }}}}; @@ -99,12 +98,11 @@ const std::pair ExtractType{}(std::invoke(Mptr, channel))); }, .set = - [](OptionsSetter &setter, + [](Options &options, const ChannelId &channel, const std::string &value) { - std::invoke(Mptr, - setter.getOptions().getChannels().at(channel)) = + std::invoke(Mptr, options.getChannels().at(channel)) = Conv::parse::type>( value); }}}; @@ -122,14 +120,11 @@ const Config::ChannelAccessors &Config::getChannelAccessors() return Conv::toString(channel.range.min); }, .set = - [](OptionsSetter &setter, + [](Options &options, const ChannelId &id, const std::string &value) { - setter.getOptions() - .getChannels() - .at(id) - .range.min = + options.getChannels().at(id).range.min = Conv::parse(value); }}}, {"range.max", @@ -139,14 +134,11 @@ const Config::ChannelAccessors &Config::getChannelAccessors() return Conv::toString(channel.range.max); }, .set = - [](OptionsSetter &setter, + [](Options &options, const ChannelId &id, const std::string &value) { - setter.getOptions() - .getChannels() - .at(id) - .range.max = + options.getChannels().at(id).range.max = Conv::parse(value); }}}, channel_accessor<&Channel::labelLevel>, @@ -188,7 +180,7 @@ void Config::setParam(const std::string &path, if (it == getAccessors().end()) throw std::logic_error( path + "/" + value + ": invalid config parameter"); - it->second.set(setter, value); + it->second.set(options, value); } } @@ -198,37 +190,37 @@ std::string Config::getParam(const std::string &path) const if (auto it = getAccessors().find(path); it != getAccessors().end()) - return it->second.get(setter.getOptions()); + return it->second.get(options); throw std::logic_error(path + ": invalid config parameter"); } -void Config::setFilter(Data::Filter::Function &&func, uint64_t hash) -{ - setter.setFilter(Data::Filter(std::move(func), hash)); -} - void Config::setChannelParam(const std::string &path, const std::string &value) { auto parts = Text::SmartString::split(path, '.'); auto id = Conv::parse(parts.at(1)); auto property = parts.at(2); + Options &options = this->options; if (property == "attach") { - setter.addSeries(id, value); + options.markersInfo.clear(); + options.getChannels().addSeries(id, {value, table}); return; } if (property == "detach") { - setter.deleteSeries(id, value); + options.markersInfo.clear(); + options.getChannels().removeSeries(id, {value, table}); return; } if (property == "set") { + options.markersInfo.clear(); if (parts.size() == 3 && value == "null") - setter.clearSeries(id); + options.getChannels().clearSeries(id); else { - if (std::stoi(parts.at(3)) == 0) setter.clearSeries(id); - setter.addSeries(id, value); + if (std::stoi(parts.at(3)) == 0) + options.getChannels().clearSeries(id); + options.getChannels().addSeries(id, {value, table}); } return; } @@ -237,7 +229,7 @@ void Config::setChannelParam(const std::string &path, const auto &accessors = getChannelAccessors(); if (auto it = accessors.find(property); it != accessors.end()) { - return it->second.set(setter, id, value); + return it->second.set(options, id, value); } throw std::logic_error( @@ -251,11 +243,11 @@ std::string Config::getChannelParam(const std::string &path) const auto id = Conv::parse(parts.at(1)); auto property = parts.at(2); - const auto &channel = setter.getOptions().getChannels().at(id); + const auto &channel = options.get().getChannels().at(id); if (property == "set") { - auto list = channel.dimensionNames(*setter.getTable()); - auto measure = channel.measureName(*setter.getTable()); + auto list = channel.dimensionNames(table); + auto measure = channel.measureName(table); if (!measure.empty()) list.push_front(measure); return Conv::toJSON(list); } diff --git a/src/chart/options/config.h b/src/chart/options/config.h index 12fe0aeb3..3ef8d04a7 100644 --- a/src/chart/options/config.h +++ b/src/chart/options/config.h @@ -7,7 +7,7 @@ #include #include -#include "optionssetter.h" +#include "options.h" namespace Vizzu::Gen { @@ -18,22 +18,23 @@ class Config static std::list listParams(); [[nodiscard]] std::string getParam(const std::string &path) const; void setParam(const std::string &path, const std::string &value); - void setFilter(Data::Filter::Function &&func, uint64_t hash); - explicit Config(const OptionsSetter &setter) : setter(setter) {} + explicit Config(Options &options, Data::DataTable &table) : + options(options), + table(table) + {} private: struct Accessor { std::string (*get)(const Options &); - void (*set)(OptionsSetter &, const std::string &); + void (*set)(Options &, const std::string &); }; struct ChannelAccessor { std::string (*get)(const Channel &); - void (*set)(OptionsSetter &, - const ChannelId &, - const std::string &); + void ( + *set)(Options &, const ChannelId &, const std::string &); }; template @@ -51,7 +52,8 @@ class Config static const Accessors &getAccessors(); static const ChannelAccessors &getChannelAccessors(); - OptionsSetter setter; + std::reference_wrapper options; + std::reference_wrapper table; void setChannelParam(const std::string &path, const std::string &value); diff --git a/src/chart/options/options.cpp b/src/chart/options/options.cpp index 303843ea8..93be05792 100644 --- a/src/chart/options/options.cpp +++ b/src/chart/options/options.cpp @@ -378,6 +378,26 @@ bool Options::labelsShownFor(const Data::SeriesIndex &series) const == series); } +void Options::showTooltip(std::optional marker) +{ + if (!marker && tooltip) { + if (auto miid = getMarkerInfoId(*tooltip)) + markersInfo.erase(*miid); + tooltip.reset(); + } + else if (marker && !tooltip) { + if (!getMarkerInfoId(*marker)) + markersInfo.insert({generateMarkerInfoId(), *marker}); + tooltip = marker; + } + else if (marker && tooltip && marker != tooltip) { + if (auto idFrom = getMarkerInfoId(*tooltip); + idFrom && !getMarkerInfoId(*marker)) + markersInfo.find(*idFrom)->second = *marker; + tooltip = marker; + } +} + [[nodiscard]] ChannelId Options::toChannel(const Options::LegendId &l) { return static_cast(l); diff --git a/src/chart/options/options.h b/src/chart/options/options.h index 16f0f7ab2..611407a2b 100644 --- a/src/chart/options/options.h +++ b/src/chart/options/options.h @@ -173,6 +173,8 @@ class Options [[nodiscard]] bool labelsShownFor( const Data::SeriesIndex &series) const; + void showTooltip(std::optional marker); + private: Channels channels; diff --git a/src/chart/options/optionssetter.cpp b/src/chart/options/optionssetter.cpp deleted file mode 100644 index bb1b33a1d..000000000 --- a/src/chart/options/optionssetter.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "optionssetter.h" - -#include -#include - -#include "base/text/smartstring.h" -#include "data/table/datatable.h" - -namespace Vizzu::Gen -{ - -OptionsSetter::OptionsSetter(Options &options) : options(options) {} - -void OptionsSetter::setTable(const Data::DataTable *table) -{ - this->table = table; -} - -OptionsSetter &OptionsSetter::addSeries(const ChannelId &channelId, - const std::string &seriesName, - std::optional pos) -{ - if (table) { - auto index = Data::SeriesIndex(seriesName, *table); - addSeries(channelId, index, pos); - } - else - throw std::logic_error("no table set for options"); - - return *this; -} - -OptionsSetter &OptionsSetter::deleteSeries(const ChannelId &channelId, - const std::string &seriesName) -{ - if (table) { - auto index = Data::SeriesIndex(seriesName, *table); - deleteSeries(channelId, index); - } - else - throw std::logic_error("no table set for options"); - - return *this; -} - -OptionsSetter &OptionsSetter::addSeries(const ChannelId &channelId, - const Data::SeriesIndex &index, - std::optional pos) -{ - options.markersInfo.clear(); - options.getChannels().addSeries(channelId, index, pos); - return *this; -} - -OptionsSetter &OptionsSetter::deleteSeries(const ChannelId &channelId, - const Data::SeriesIndex &index) -{ - options.markersInfo.clear(); - options.getChannels().removeSeries(channelId, index); - return *this; -} - -OptionsSetter &OptionsSetter::clearSeries(const ChannelId &channelId) -{ - options.markersInfo.clear(); - options.getChannels().clearSeries(channelId); - return *this; -} - -OptionsSetter &OptionsSetter::setFilter(const Data::Filter &filter) -{ - options.dataFilter = filter; - return *this; -} - -OptionsSetter &OptionsSetter::addMarkerInfo(Options::MarkerId marker) -{ - if (!options.getMarkerInfoId(marker).has_value()) { - auto miid = Options::generateMarkerInfoId(); - options.markersInfo.insert(std::make_pair(miid, marker)); - } - return *this; -} - -OptionsSetter &OptionsSetter::moveMarkerInfo(Options::MarkerId from, - Options::MarkerId to) -{ - auto idTo = options.getMarkerInfoId(to); - auto idFrom = options.getMarkerInfoId(from); - if (idFrom.has_value() && !idTo.has_value()) { - auto iter = options.markersInfo.find(*idFrom); - iter->second = to; - } - return *this; -} - -OptionsSetter &OptionsSetter::deleteMarkerInfo( - Options::MarkerId marker) -{ - auto miid = options.getMarkerInfoId(marker); - if (miid.has_value()) options.markersInfo.erase(*miid); - return *this; -} - -OptionsSetter &OptionsSetter::showTooltip( - std::optional marker) -{ - auto current = options.tooltip; - if (!marker.has_value() && current.has_value()) { - deleteMarkerInfo(*current); - options.tooltip.reset(); - } - else if (marker.has_value() && !current.has_value()) { - addMarkerInfo(*marker); - options.tooltip = marker; - } - else if (marker.has_value() && current.has_value() - && marker != current) { - moveMarkerInfo(*current, *marker); - options.tooltip = marker; - } - return *this; -} - -} \ No newline at end of file diff --git a/src/chart/options/optionssetter.h b/src/chart/options/optionssetter.h deleted file mode 100644 index a6dfa97dc..000000000 --- a/src/chart/options/optionssetter.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef OPTIONSSETTER_H -#define OPTIONSSETTER_H - -#include "base/util/event.h" - -#include "options.h" - -namespace Vizzu::Gen -{ - -class OptionsSetter -{ -public: - explicit OptionsSetter(Options &options); - - OptionsSetter &clearSeries(const ChannelId &channelId); - - OptionsSetter &addSeries(const ChannelId &channelId, - const std::string &seriesName, - std::optional pos = std::nullopt); - - OptionsSetter &deleteSeries(const ChannelId &channelId, - const std::string &seriesName); - - OptionsSetter &addSeries(const ChannelId &channelId, - const Data::SeriesIndex &index, - std::optional pos); - OptionsSetter &deleteSeries(const ChannelId &channelId, - const Data::SeriesIndex &index); - OptionsSetter &setFilter(const Data::Filter &filter); - OptionsSetter &addMarkerInfo(Options::MarkerId marker); - OptionsSetter &moveMarkerInfo(Options::MarkerId from, - Options::MarkerId to); - OptionsSetter &deleteMarkerInfo(Options::MarkerId marker); - OptionsSetter &showTooltip( - std::optional marker); - - [[nodiscard]] const Options &getOptions() const - { - return options; - } - Options &getOptions() { return options; } - void setTable(const Data::DataTable *table); - [[nodiscard]] const Data::DataTable *getTable() const - { - return table; - } - -protected: - Options &options; - const Data::DataTable *table{}; -}; - -} - -#endif diff --git a/src/data/datacube/datacube.cpp b/src/data/datacube/datacube.cpp index 28cc72a86..115f60f3e 100644 --- a/src/data/datacube/datacube.cpp +++ b/src/data/datacube/datacube.cpp @@ -59,7 +59,8 @@ DataCube::DataCube(const DataTable &table, : 0.0; if (filter.match(RowWrapper(table, row))) - data.at(index).subCells[idx].add(value); + data.at(index).subCells[idx].add( + static_cast(value)); } } } diff --git a/src/data/table/datatable.h b/src/data/table/datatable.h index bf2d56aba..992d1198d 100644 --- a/src/data/table/datatable.h +++ b/src/data/table/datatable.h @@ -72,10 +72,10 @@ class DataTableOld : public Table using DataTable = DataTableOld; -class CellWrapper +class CellWrapperOld { public: - CellWrapper(const double &value, const ColumnInfo &info) : + CellWrapperOld(const double &value, const ColumnInfo &info) : value(value), info(info) {} @@ -110,6 +110,8 @@ class CellWrapper const ColumnInfo &info; }; +using CellWrapper = CellWrapperOld; + class RowWrapper { public: @@ -121,7 +123,7 @@ class RowWrapper CellWrapper operator[](const std::string &columnName) const { auto colIndex = table.getColumn(columnName); - return this->operator[](colIndex); + return {row[colIndex], table.getInfo(colIndex)}; } CellWrapper operator[](ColumnIndex colIndex) const diff --git a/test/qtest/chart.cpp b/test/qtest/chart.cpp index 698d9af06..a42821a6b 100644 --- a/test/qtest/chart.cpp +++ b/test/qtest/chart.cpp @@ -7,7 +7,7 @@ #include "chart/ui/events.h" #include "data/datacube/datacube.h" -TestChart::TestChart() : chart() {} +TestChart::TestChart() {} void TestChart::prepareData() { @@ -50,7 +50,7 @@ void TestChart::operator()(Util::EventDispatcher::Params ¶ms) marker = &c->parent; if (marker) markerId.emplace(marker->marker.idx); - chart.getChart().getSetter().showTooltip(markerId); + chart.getChart().getOptions().showTooltip(markerId); chart.getChart().setKeyframe(); chart.getChart().animate(); } @@ -69,9 +69,9 @@ void TestChart::run() auto step6 = [end, this](bool) { IO::log() << "step 6"; - auto setter = chart.getChart().getSetter(); - setter.getOptions().title = "VIZZU Chart - Phase 6"; - setter.deleteMarkerInfo(5); + auto &options = chart.getChart().getOptions(); + options.title = "VIZZU Chart - Phase 6"; + options.showTooltip({}); chart.getChart().setKeyframe(); chart.getChart().animate(end); }; @@ -79,9 +79,9 @@ void TestChart::run() auto step5 = [step6, this](bool) { IO::log() << "step 5"; - auto setter = chart.getChart().getSetter(); - setter.getOptions().title = "VIZZU Chart - Phase 5"; - setter.moveMarkerInfo(4, 5); + auto &options = chart.getChart().getOptions(); + options.title = "VIZZU Chart - Phase 5"; + options.showTooltip(5); chart.getChart().setKeyframe(); chart.getChart().animate(step6); }; @@ -89,9 +89,9 @@ void TestChart::run() auto step4 = [step5, this](bool) { IO::log() << "step 4"; - auto setter = chart.getChart().getSetter(); - setter.getOptions().title = "VIZZU Chart - Phase 4"; - setter.addMarkerInfo(4); + auto &options = chart.getChart().getOptions(); + options.title = "VIZZU Chart - Phase 4"; + options.showTooltip(4); chart.getChart().setKeyframe(); chart.getChart().animate(step5); }; @@ -99,10 +99,13 @@ void TestChart::run() auto step3 = [step4, this](bool) { IO::log() << "step 3"; - auto setter = chart.getChart().getSetter(); - setter.deleteSeries(ChannelId::y, "Cat2"); - setter.addSeries(ChannelId::x, "Cat2"); - setter.getOptions().title = "VIZZU Chart - Phase 3"; + auto &options = chart.getChart().getOptions(); + auto &channels = options.getChannels(); + auto &table = chart.getChart().getTable(); + + channels.removeSeries(ChannelId::y, {"Cat2", table}); + channels.addSeries(ChannelId::x, {"Cat2", table}); + options.title = "VIZZU Chart - Phase 3"; chart.getChart().getStyles().title.textAlign = ::Anim::Interpolated( Vizzu::Styles::Text::TextAlign::right); @@ -113,19 +116,22 @@ void TestChart::run() auto step2 = [step3, this](bool) { IO::log() << "step 2"; - auto setter = chart.getChart().getSetter(); - setter.setFilter(Vizzu::Data::Filter()); - setter.addSeries(ChannelId::y, "Cat2"); - setter.addSeries(ChannelId::color, "Cat2"); - setter.getOptions().coordSystem = - Vizzu::Gen::CoordSystem::polar; - setter.getOptions().title = "VIZZU Chart - Phase 2"; - chart.getChart().getStyles().title.fontSize = Gfx::Length{10}; - chart.getChart().getStyles().legend.marker.type = + auto &options = chart.getChart().getOptions(); + auto &styles = chart.getChart().getStyles(); + auto &table = chart.getChart().getTable(); + auto &channels = options.getChannels(); + + options.dataFilter = Vizzu::Data::Filter{}; + + channels.addSeries(ChannelId::y, {"Cat2", table}); + channels.addSeries(ChannelId::color, {"Cat2", table}); + options.coordSystem = Vizzu::Gen::CoordSystem::polar; + options.title = "VIZZU Chart - Phase 2"; + styles.title.fontSize = Gfx::Length{10}; + styles.legend.marker.type = Vizzu::Styles::Legend::Marker::Type::square; - chart.getChart().getStyles().title.textAlign = - ::Anim::Interpolated( - Vizzu::Styles::Text::TextAlign::center); + styles.title.textAlign = ::Anim::Interpolated( + Vizzu::Styles::Text::TextAlign::center); chart.getChart().setKeyframe(); chart.getChart().animate(step3); }; @@ -134,21 +140,21 @@ void TestChart::run() { try { IO::log() << "step 1b"; - auto setter = chart.getChart().getSetter(); - setter.setFilter(Vizzu::Data::Filter( - [&](const auto &row) + auto &options = chart.getChart().getOptions(); + auto &styles = chart.getChart().getStyles(); + options.dataFilter = {[&](const auto &row) { - return *row["Cat1"] == row["Cat1"]["A"] - || static_cast(row["Cat2"]) + return std::string{row["Cat1"].dimensionValue()} + == "A" + || std::string{row["Cat2"].dimensionValue()} == "b"; }, - 0)); - setter.getOptions().title = "VIZZU Chart - Phase 1b"; - chart.getChart().getStyles().legend.marker.type = + 0}; + options.title = "VIZZU Chart - Phase 1b"; + styles.legend.marker.type = Vizzu::Styles::Legend::Marker::Type::circle; - chart.getChart().getStyles().title.textAlign = - ::Anim::Interpolated( - Vizzu::Styles::Text::TextAlign::right); + styles.title.textAlign = ::Anim::Interpolated( + Vizzu::Styles::Text::TextAlign::right); chart.getChart().setKeyframe(); chart.getChart().animate(step2); } @@ -160,24 +166,26 @@ void TestChart::run() auto step1 = [step1b, this](bool) { IO::log() << "step 1"; - auto setter = chart.getChart().getSetter(); - setter.addSeries(ChannelId::x, "Cat1"); - setter.addSeries(ChannelId::x, "exists()"); - setter.addSeries(ChannelId::y, "Val"); - setter.addSeries(ChannelId::label, "Val"); - setter.addSeries(ChannelId::x, "Val"); - setter.addSeries(ChannelId::y, "Cat2"); - setter.addSeries(ChannelId::color, "Cat2"); - chart.getChart().getStyles().plot.marker.label.filter = + auto &options = chart.getChart().getOptions(); + auto &styles = chart.getChart().getStyles(); + auto &table = chart.getChart().getTable(); + auto &channels = options.getChannels(); + channels.addSeries(ChannelId::x, {"Cat1", table}); + channels.addSeries(ChannelId::x, {"exists()", table}); + channels.addSeries(ChannelId::y, {"Val", table}); + channels.addSeries(ChannelId::label, {"Val", table}); + channels.addSeries(ChannelId::x, {"Val", table}); + channels.addSeries(ChannelId::y, {"Cat2", table}); + channels.addSeries(ChannelId::color, {"Cat2", table}); + styles.plot.marker.label.filter = Gfx::ColorTransform::Lightness(0.5); - chart.getChart().getStyles().plot.marker.label.position = + styles.plot.marker.label.position = Vizzu::Styles::MarkerLabel::Position::center; - chart.getChart().getStyles().legend.marker.type = + styles.legend.marker.type = Vizzu::Styles::Legend::Marker::Type::square; - chart.getChart().getStyles().title.textAlign = - ::Anim::Interpolated( - Vizzu::Styles::Text::TextAlign::left); - setter.getOptions().title = "Example VIZZU Chart"; + styles.title.textAlign = ::Anim::Interpolated( + Vizzu::Styles::Text::TextAlign::left); + options.title = "Example VIZZU Chart"; chart.getChart().setKeyframe(); chart.getChart().animate(step1b); }; From 984fecac301805e1e5fe563aa8a520a8961e70d7 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 7 Mar 2024 16:16:10 +0100 Subject: [PATCH 084/253] Fix unittest --- test/unit/chart/events.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit/chart/events.cpp b/test/unit/chart/events.cpp index 264a9b582..4adf773e4 100644 --- a/test/unit/chart/events.cpp +++ b/test/unit/chart/events.cpp @@ -93,8 +93,9 @@ struct chart_setup table.addColumn("Meas2", "", {{0, -1, 5, 6, 6, 5, -1, 0, 5, 6, 0, -1, -1, 0, 6, -5}}); - auto &&setter = chart.getSetter(); - for (auto &&[ch, name] : series) setter.addSeries(ch, name); + auto &channels = chart.getOptions().getChannels(); + for (auto &&[ch, name] : series) + channels.addSeries(ch, {name, table}); chart.setBoundRect(Geom::Rect(Geom::Point{}, {{640, 480}})); return chart; } From 4023310fb5b112f560d4e5978093b92bcfa1dc29 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 11 Mar 2024 10:12:06 +0100 Subject: [PATCH 085/253] Fix aggregators, distinct works only on dimension --- src/dataframe/impl/aggregators.h | 50 ++++++++++++++++++-------- src/dataframe/impl/data_source.cpp | 14 +++----- src/dataframe/impl/dataframe.cpp | 4 +++ src/dataframe/interface.h | 2 +- test/unit/dataframe/interface_test.cpp | 9 ++--- 5 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/dataframe/impl/aggregators.h b/src/dataframe/impl/aggregators.h index 464ad2470..8c39f6fce 100644 --- a/src/dataframe/impl/aggregators.h +++ b/src/dataframe/impl/aggregators.h @@ -11,6 +11,14 @@ namespace Vizzu::dataframe { +inline bool is_valid(cell_value const &value) +{ + const double *d = std::get_if(&value); + return d ? !std::isnan(*d) + : std::get_if(&value)->data() + != nullptr; +} + // NOLINTNEXTLINE(fuchsia-statically-constructed-objects) constinit const static inline Refl::EnumArray @@ -20,9 +28,11 @@ constinit const static inline Refl::EnumArray double + cell_value const &cell) -> double { auto &ref = *std::any_cast(&id); + const double &value = + *std::get_if(&cell); if (!std::isnan(value) && !std::isinf(value)) ref += value; @@ -34,9 +44,11 @@ constinit const static inline Refl::EnumArray::quiet_NaN(); }, - [](custom_aggregator::id_type &id, double value) -> double + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double { auto &ref = *std::any_cast(&id); + const auto &value = *std::get_if(&cell); if (!std::isnan(value) && !std::isinf(value) && !(value >= ref)) ref = value; @@ -48,9 +60,11 @@ constinit const static inline Refl::EnumArray::quiet_NaN(); }, - [](custom_aggregator::id_type &id, double value) -> double + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double { auto &ref = *std::any_cast(&id); + const auto &value = *std::get_if(&cell); if (!std::isnan(value) && !std::isinf(value) && !(value <= ref)) ref = value; @@ -62,11 +76,13 @@ constinit const static inline Refl::EnumArray(0.0, 0); }, - [](custom_aggregator::id_type &id, double value) -> double + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double { auto &[sum, count] = *std::any_cast>( &id); + const auto &value = *std::get_if(&cell); if (!std::isnan(value) && !std::isinf(value)) { sum += value; ++count; @@ -79,22 +95,26 @@ constinit const static inline Refl::EnumArray double + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double { auto &s = *std::any_cast(&id); - if (!std::isnan(value) && !std::isinf(value)) s += 1; + if (is_valid(cell)) s += 1; return static_cast(s); }}, {std::string_view{"distinct"}, []() -> custom_aggregator::id_type { - return std::set{}; + return std::set{}; }, - [](custom_aggregator::id_type &id, double value) -> double + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double { - auto &set = *std::any_cast>(&id); - if (!std::isnan(value) && !std::isinf(value)) - set.insert(value); + auto &set = + *std::any_cast>(&id); + if (const char *v = + std::get_if(&cell)->data()) + set.insert(v); return static_cast(set.size()); }}, {std::string_view{"exists"}, @@ -102,12 +122,12 @@ constinit const static inline Refl::EnumArray double + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double { auto &b = *std::any_cast(&id); - if (!std::isnan(value) && !std::isinf(value)) - b = true; - return b; + if (is_valid(cell)) b = true; + return static_cast(b); }}}}}; } diff --git a/src/dataframe/impl/data_source.cpp b/src/dataframe/impl/data_source.cpp index 5da80b8ac..f74fb5c05 100644 --- a/src/dataframe/impl/data_source.cpp +++ b/src/dataframe/impl/data_source.cpp @@ -472,17 +472,13 @@ data_source::data_source(aggregating_type &&aggregating, for (std::size_t ix{}; const auto &[name, mea] : meas) { auto &[data, agg] = mea; - double val{}; - if (data == series_type::measure) { + cell_value val{}; + if (data == series_type::measure) val = unsafe_get(data) .second.values[i]; - } - else { - auto aval = unsafe_get(data) - .second.values[i]; - val = aval == data_source::nav ? data_source::nan - : aval; - } + else + val = unsafe_get(data) + .second.get(i); measures[ix].values[index] = agg.add(aggregators[ix], val); ++ix; diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp index 41e5cc3e2..84027d208 100644 --- a/src/dataframe/impl/dataframe.cpp +++ b/src/dataframe/impl/dataframe.cpp @@ -85,6 +85,10 @@ void valid_measure_aggregator( Refl::enum_name_holder.size()}); } } + else if (*std::get_if(&agg) + == aggregator_type::distinct) + throw std::runtime_error( + "Measure series cannot be aggregated by distinct."); } std::string dataframe::set_aggregate(series_identifier series, diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index a147f3d55..113c89a93 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -40,7 +40,7 @@ struct custom_aggregator std::variant name; using id_type = std::any; id_type (*create)(); - double (*add)(id_type &, double); + double (*add)(id_type &, cell_value const &); [[nodiscard]] std::string_view get_name() const { diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index 4e94ead27..47350a18a 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -470,7 +470,6 @@ const static auto tests = auto &&m1ma = df->set_aggregate("m1", max); auto &&m1me = df->set_aggregate("m1", mean); auto &&m1c = df->set_aggregate("m1", count); - auto &&m1d = df->set_aggregate("m1", distinct); auto &&m1e = df->set_aggregate("m1", exists); auto &&m1t = df->set_aggregate("m1", @@ -482,10 +481,11 @@ const static auto tests = std::numeric_limits::max()}; }, [](Vizzu::dataframe::custom_aggregator::id_type &id, - double v) -> double + cell_value const &cell) -> double { auto &[min, min2] = std::any_cast &>(id); + const double &v = *std::get_if(&cell); if (v < min) min2 = std::exchange(min, v); else if (v < min2) @@ -501,7 +501,6 @@ const static auto tests = == std::array{d1c, m1c, d1d, - m1d, d1e, m1e, m1ma, @@ -515,7 +514,6 @@ const static auto tests = check->*df->get_data(std::size_t{0}, d1c) == 5.0; check->*df->get_data(std::size_t{0}, m1c) == 5.0; check->*df->get_data(std::size_t{0}, d1d) == 1.0; - check->*df->get_data(std::size_t{0}, m1d) == 5.0; check->*df->get_data(std::size_t{0}, d1e) == 1.0; check->*df->get_data(std::size_t{0}, m1e) == 1.0; check->*df->get_data(std::size_t{0}, m1ma) == 88.0; @@ -527,7 +525,6 @@ const static auto tests = check->*df->get_data(std::size_t{1}, d1c) == 4.0; check->*df->get_data(std::size_t{1}, m1c) == 3.0; check->*df->get_data(std::size_t{1}, d1d) == 1.0; - check->*df->get_data(std::size_t{1}, m1d) == 3.0; check->*df->get_data(std::size_t{1}, d1e) == 1.0; check->*df->get_data(std::size_t{1}, m1e) == 1.0; check->*df->get_data(std::size_t{1}, m1ma) == 7.25; @@ -539,7 +536,6 @@ const static auto tests = check->*df->get_data(std::size_t{2}, d1c) == 1.0; check->*df->get_data(std::size_t{2}, m1c) == 0.0; check->*df->get_data(std::size_t{2}, d1d) == 1.0; - check->*df->get_data(std::size_t{2}, m1d) == 0.0; check->*df->get_data(std::size_t{2}, d1e) == 1.0; check->*df->get_data(std::size_t{2}, m1e) == 0.0; check @@ -561,7 +557,6 @@ const static auto tests = check->*df->get_data(std::size_t{3}, d1c) == 0.0; check->*df->get_data(std::size_t{3}, m1c) == 1.0; check->*df->get_data(std::size_t{3}, d1d) == 0.0; - check->*df->get_data(std::size_t{3}, m1d) == 1.0; check->*df->get_data(std::size_t{3}, d1e) == 0.0; check->*df->get_data(std::size_t{3}, m1e) == 1.0; } From 2e3b1beae7f9b8f846a1c781695fbf8bd12f4296 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Mon, 11 Mar 2024 13:10:55 +0100 Subject: [PATCH 086/253] Set version to 0.10.0 --- .../workflows/docker-vizzu-dev-desktop.yml | 2 +- .github/workflows/docker-vizzu-dev-wasm.yml | 2 +- CHANGELOG.md | 2 ++ CONTRIBUTING.md | 4 +-- src/chart/main/version.cpp | 4 +-- tools/ci/gcp/cloudbuild/cloudbuild.yaml | 26 +++++++++---------- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.github/workflows/docker-vizzu-dev-desktop.yml b/.github/workflows/docker-vizzu-dev-desktop.yml index ea044f996..ac272cfc1 100644 --- a/.github/workflows/docker-vizzu-dev-desktop.yml +++ b/.github/workflows/docker-vizzu-dev-desktop.yml @@ -24,6 +24,6 @@ jobs: - name: Build and Publish run: | IMAGE="vizzu-dev-desktop" - IMAGE_NAME="vizzu/$IMAGE:0.9" + IMAGE_NAME="vizzu/$IMAGE:0.10" docker build -t $IMAGE_NAME -f tools/ci/docker/$IMAGE . docker push $IMAGE_NAME diff --git a/.github/workflows/docker-vizzu-dev-wasm.yml b/.github/workflows/docker-vizzu-dev-wasm.yml index 2be21bdd2..b96cc668d 100644 --- a/.github/workflows/docker-vizzu-dev-wasm.yml +++ b/.github/workflows/docker-vizzu-dev-wasm.yml @@ -24,6 +24,6 @@ jobs: - name: Build and Publish run: | IMAGE="vizzu-dev-wasm" - IMAGE_NAME="vizzu/$IMAGE:0.9" + IMAGE_NAME="vizzu/$IMAGE:0.10" docker build -t $IMAGE_NAME -f tools/ci/docker/$IMAGE . docker push $IMAGE_NAME diff --git a/CHANGELOG.md b/CHANGELOG.md index ddf47aa73..fd98607d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +## [0.10.0] - 2024-03-11 + ### Fixed - Json serializer control character escape fixed. Some unicode characters diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e999c4145..76da1aee6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ docker run -i -t -v .:/workspace vizzu/vizzu-dev-desktop bash or you can use a specific version of the prebuilt image: ```sh -docker run -i -t -v .:/workspace vizzu/vizzu-dev-desktop:0.9 bash +docker run -i -t -v .:/workspace vizzu/vizzu-dev-desktop:0.10 bash ``` Run the following commands to build and run the `WASM` version's development @@ -84,7 +84,7 @@ docker run -i -t -v .:/workspace vizzu/vizzu-dev-wasm bash or you can use a specific version of the prebuilt image: ```sh -docker run -i -t -v .:/workspace vizzu/vizzu-dev-wasm:0.9 bash +docker run -i -t -v .:/workspace vizzu/vizzu-dev-wasm:0.10 bash ``` ### Building the project diff --git a/src/chart/main/version.cpp b/src/chart/main/version.cpp index 12c76ec28..df6aac5a5 100644 --- a/src/chart/main/version.cpp +++ b/src/chart/main/version.cpp @@ -1,5 +1,5 @@ #include "version.h" -const App::Version Vizzu::Main::version(0, 9, 3); +const App::Version Vizzu::Main::version(0, 10, 0); -const char *const Vizzu::Main::siteUrl = "https://vizzuhq.com/"; +const char *const Vizzu::Main::siteUrl = "https://vizzu.io/"; diff --git a/tools/ci/gcp/cloudbuild/cloudbuild.yaml b/tools/ci/gcp/cloudbuild/cloudbuild.yaml index 41d0dd1c7..94fb8bd75 100644 --- a/tools/ci/gcp/cloudbuild/cloudbuild.yaml +++ b/tools/ci/gcp/cloudbuild/cloudbuild.yaml @@ -3,15 +3,15 @@ steps: id: pull_wasm waitFor: - '-' - args: ['pull', 'vizzu/vizzu-dev-wasm:0.9'] + args: ['pull', 'vizzu/vizzu-dev-wasm:0.10'] - name: 'gcr.io/cloud-builders/docker' id: pull_desktop waitFor: - '-' - args: ['pull', 'vizzu/vizzu-dev-desktop:0.9'] + args: ['pull', 'vizzu/vizzu-dev-desktop:0.10'] - - name: vizzu/vizzu-dev-wasm:0.9 + - name: vizzu/vizzu-dev-wasm:0.10 id: init waitFor: - pull_wasm @@ -24,7 +24,7 @@ steps: ./tools/ci/run/init-py.sh dir: /workspace - - name: vizzu/vizzu-dev-wasm:0.9 + - name: vizzu/vizzu-dev-wasm:0.10 id: check_src waitFor: - init @@ -41,7 +41,7 @@ steps: npm run lint:src fi dir: /workspace - - name: vizzu/vizzu-dev-wasm:0.9 + - name: vizzu/vizzu-dev-wasm:0.10 id: check_docs waitFor: - init @@ -58,7 +58,7 @@ steps: npm run lint:docs fi dir: /workspace - - name: vizzu/vizzu-dev-wasm:0.9 + - name: vizzu/vizzu-dev-wasm:0.10 id: check_tools waitFor: - init @@ -77,7 +77,7 @@ steps: fi dir: /workspace - - name: vizzu/vizzu-dev-desktop:0.9 + - name: vizzu/vizzu-dev-desktop:0.10 id: build_desktop_clangformat waitFor: - pull_desktop @@ -97,7 +97,7 @@ steps: fi dir: /workspace - - name: vizzu/vizzu-dev-desktop:0.9 + - name: vizzu/vizzu-dev-desktop:0.10 id: build_desktop_clangtidy waitFor: - build_desktop_clangformat @@ -113,7 +113,7 @@ steps: ./tools/ci/run/pkg-build-desktop-clangtidy.sh fi dir: /workspace - - name: vizzu/vizzu-dev-wasm:0.9 + - name: vizzu/vizzu-dev-wasm:0.10 id: build_wasm waitFor: - build_desktop_clangformat @@ -134,7 +134,7 @@ steps: ./tools/ci/run/pkg-build-js.sh dir: /workspace - - name: vizzu/vizzu-dev-wasm:0.9 + - name: vizzu/vizzu-dev-wasm:0.10 id: lib_sha waitFor: - build_wasm @@ -157,7 +157,7 @@ steps: fi dir: /workspace - - name: vizzu/vizzu-dev-wasm:0.9 + - name: vizzu/vizzu-dev-wasm:0.10 id: test waitFor: - lib_sha @@ -172,7 +172,7 @@ steps: fi dir: /workspace - - name: vizzu/vizzu-dev-wasm:0.9 + - name: vizzu/vizzu-dev-wasm:0.10 id: docs waitFor: - test @@ -215,7 +215,7 @@ steps: - VIZZUHQ_GITHUB_USER - VIZZUHQ_GITHUB_EMAIL - - name: vizzu/vizzu-dev-wasm:0.9 + - name: vizzu/vizzu-dev-wasm:0.10 id: publish waitFor: - docs From f9ae0c14ab1d932aab42253b2dd27c048b0f65df Mon Sep 17 00:00:00 2001 From: David Vegh Date: Mon, 11 Mar 2024 13:14:41 +0100 Subject: [PATCH 087/253] fix cloud sdk url --- tools/ci/docker/vizzu-dev-wasm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/docker/vizzu-dev-wasm b/tools/ci/docker/vizzu-dev-wasm index 39be824a1..2dfd4205b 100644 --- a/tools/ci/docker/vizzu-dev-wasm +++ b/tools/ci/docker/vizzu-dev-wasm @@ -28,7 +28,7 @@ RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key && apt-get install -y google-chrome-stable fonts-roboto fonts-noto-cjk --no-install-recommends \ && rm -rf /var/lib/apt/lists/* -RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list \ +RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list \ && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - \ && apt-get update -y && apt-get install -y google-cloud-cli From 370abdae6c1145ab739f3303839e666e22dbb255 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Mon, 11 Mar 2024 13:26:13 +0100 Subject: [PATCH 088/253] fix dev dep --- tools/ci/pdm.lock | 336 ++++++++++++++++++++-------------------- tools/ci/pyproject.toml | 2 +- 2 files changed, 171 insertions(+), 167 deletions(-) diff --git a/tools/ci/pdm.lock b/tools/ci/pdm.lock index 03540c38f..246cc5ea4 100644 --- a/tools/ci/pdm.lock +++ b/tools/ci/pdm.lock @@ -5,19 +5,19 @@ groups = ["default", "codequality", "docs"] strategy = ["cross_platform"] lock_version = "4.4" -content_hash = "sha256:d9b85b7701c9533e23b6da936f433eafb1968fa46ac07790e3e797aafe12e6e2" +content_hash = "sha256:79c36c632aaeef692e288c0eb0be0df8e636d438024f5aff8ad9e57c5338b55c" [[package]] name = "astroid" -version = "3.0.2" +version = "3.1.0" requires_python = ">=3.8.0" summary = "An abstract syntax tree for Python with inference support." dependencies = [ "typing-extensions>=4.0.0; python_version < \"3.11\"", ] files = [ - {file = "astroid-3.0.2-py3-none-any.whl", hash = "sha256:d6e62862355f60e716164082d6b4b041d38e2a8cf1c7cd953ded5108bac8ff5c"}, - {file = "astroid-3.0.2.tar.gz", hash = "sha256:4a61cf0a59097c7bb52689b0fd63717cd2a8a14dc9f1eee97b82d814881c8c91"}, + {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, + {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"}, ] [[package]] @@ -75,7 +75,7 @@ files = [ [[package]] name = "black" -version = "24.1.1" +version = "24.2.0" requires_python = ">=3.8" summary = "The uncompromising code formatter." dependencies = [ @@ -88,20 +88,20 @@ dependencies = [ "typing-extensions>=4.0.1; python_version < \"3.11\"", ] files = [ - {file = "black-24.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c"}, - {file = "black-24.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445"}, - {file = "black-24.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a"}, - {file = "black-24.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4"}, - {file = "black-24.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7"}, - {file = "black-24.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8"}, - {file = "black-24.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161"}, - {file = "black-24.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d"}, - {file = "black-24.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8"}, - {file = "black-24.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e"}, - {file = "black-24.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6"}, - {file = "black-24.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b"}, - {file = "black-24.1.1-py3-none-any.whl", hash = "sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168"}, - {file = "black-24.1.1.tar.gz", hash = "sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b"}, + {file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"}, + {file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"}, + {file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"}, + {file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"}, + {file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"}, + {file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"}, + {file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"}, + {file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"}, + {file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"}, + {file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"}, + {file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"}, + {file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"}, + {file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"}, + {file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"}, ] [[package]] @@ -204,7 +204,7 @@ files = [ [[package]] name = "cssbeautifier" -version = "1.14.11" +version = "1.15.1" summary = "CSS unobfuscator and beautifier." dependencies = [ "editorconfig>=0.12.2", @@ -212,7 +212,7 @@ dependencies = [ "six>=1.13.0", ] files = [ - {file = "cssbeautifier-1.14.11.tar.gz", hash = "sha256:40544c2b62bbcb64caa5e7f37a02df95654e5ce1bcacadac4ca1f3dc89c31513"}, + {file = "cssbeautifier-1.15.1.tar.gz", hash = "sha256:9f7064362aedd559c55eeecf6b6bed65e05f33488dcbe39044f0403c26e1c006"}, ] [[package]] @@ -227,11 +227,10 @@ files = [ [[package]] name = "editorconfig" -version = "0.12.3" +version = "0.12.4" summary = "EditorConfig File Locator and Interpreter for Python" files = [ - {file = "EditorConfig-0.12.3-py3-none-any.whl", hash = "sha256:6b0851425aa875b08b16789ee0eeadbd4ab59666e9ebe728e526314c4a2e52c1"}, - {file = "EditorConfig-0.12.3.tar.gz", hash = "sha256:57f8ce78afcba15c8b18d46b5170848c88d56fd38f05c2ec60dbbfcb8996e89e"}, + {file = "EditorConfig-0.12.4.tar.gz", hash = "sha256:24857fa1793917dd9ccf0c7810a07e05404ce9b823521c7dce22a4fb5d125f80"}, ] [[package]] @@ -258,25 +257,25 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.0.1" +version = "7.0.2" requires_python = ">=3.8" summary = "Read metadata from Python packages" dependencies = [ "zipp>=0.5", ] files = [ - {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, - {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, + {file = "importlib_metadata-7.0.2-py3-none-any.whl", hash = "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100"}, + {file = "importlib_metadata-7.0.2.tar.gz", hash = "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792"}, ] [[package]] name = "importlib-resources" -version = "6.1.1" +version = "6.1.3" requires_python = ">=3.8" summary = "Read resources from Python packages" files = [ - {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, - {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, + {file = "importlib_resources-6.1.3-py3-none-any.whl", hash = "sha256:4c0269e3580fe2634d364b39b38b961540a7738c02cb984e98add8b4221d793d"}, + {file = "importlib_resources-6.1.3.tar.gz", hash = "sha256:56fb4525197b78544a3354ea27793952ab93f935bb4bf746b846bb1015020f2b"}, ] [[package]] @@ -304,27 +303,27 @@ files = [ [[package]] name = "jsbeautifier" -version = "1.14.11" +version = "1.15.1" summary = "JavaScript unobfuscator and beautifier." dependencies = [ "editorconfig>=0.12.2", "six>=1.13.0", ] files = [ - {file = "jsbeautifier-1.14.11.tar.gz", hash = "sha256:6b632581ea60dd1c133cd25a48ad187b4b91f526623c4b0fb5443ef805250505"}, + {file = "jsbeautifier-1.15.1.tar.gz", hash = "sha256:ebd733b560704c602d744eafc839db60a1ee9326e30a2a80c4adb8718adc1b24"}, ] [[package]] name = "linkify-it-py" -version = "2.0.2" +version = "2.0.3" requires_python = ">=3.7" summary = "Links recognition library with FULL unicode support." dependencies = [ "uc-micro-py", ] files = [ - {file = "linkify-it-py-2.0.2.tar.gz", hash = "sha256:19f3060727842c254c808e99d465c80c49d2c7306788140987a1a7a29b0d6ad2"}, - {file = "linkify_it_py-2.0.2-py3-none-any.whl", hash = "sha256:a3a24428f6c96f27370d7fe61d2ac0be09017be5190d68d8658233171f1b6541"}, + {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, + {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, ] [[package]] @@ -418,41 +417,41 @@ files = [ [[package]] name = "markupsafe" -version = "2.1.4" +version = "2.1.5" requires_python = ">=3.7" summary = "Safely add untrusted strings to HTML/XML markup." files = [ - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, - {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] @@ -481,16 +480,16 @@ files = [ [[package]] name = "mdformat-admon" -version = "2.0.1" -requires_python = ">=3.8.0" -summary = "An mdformat plugin for admonitions." +version = "0.0.1" +requires_python = ">=3.7.2" +summary = "An mdformat plugin for..." dependencies = [ - "mdformat>=0.7.16", - "mdit-py-plugins>=0.4.0", + "mdformat<0.8.0,>=0.7.0", + "mdit-py-plugins>=0.3.2", ] files = [ - {file = "mdformat_admon-2.0.1-py3-none-any.whl", hash = "sha256:0d6347b729dff1468b15275d683ce0f4b8592dfe5f2145b95b7983167ee9cc78"}, - {file = "mdformat_admon-2.0.1.tar.gz", hash = "sha256:82bb44d541d266a49501a0c751d9e4367934876093c97c85efa2e1a5d1bb4577"}, + {file = "mdformat_admon-0.0.1-py3-none-any.whl", hash = "sha256:3dc0524a0a99568e294f2f8837f6c3cdf042f562533e0d0d865215895ddaa5e6"}, + {file = "mdformat_admon-0.0.1.tar.gz", hash = "sha256:934677c3b436d645a672ac78f8d697c62097b48c9bd6d81d2af6f03bcfdbbd37"}, ] [[package]] @@ -715,16 +714,17 @@ files = [ [[package]] name = "mkdocs-autorefs" -version = "0.5.0" +version = "1.0.1" requires_python = ">=3.8" summary = "Automatically link across pages in MkDocs." dependencies = [ "Markdown>=3.3", + "markupsafe>=2.0.1", "mkdocs>=1.1", ] files = [ - {file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"}, - {file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"}, + {file = "mkdocs_autorefs-1.0.1-py3-none-any.whl", hash = "sha256:aacdfae1ab197780fb7a2dac92ad8a3d8f7ca8049a9cbe56a4218cd52e8da570"}, + {file = "mkdocs_autorefs-1.0.1.tar.gz", hash = "sha256:f684edf847eced40b570b57846b15f0bf57fb93ac2c510450775dcf16accb971"}, ] [[package]] @@ -780,7 +780,7 @@ files = [ [[package]] name = "mkdocs-material" -version = "9.5.6" +version = "9.5.13" requires_python = ">=3.8" summary = "Documentation that simply works" dependencies = [ @@ -797,8 +797,8 @@ dependencies = [ "requests~=2.26", ] files = [ - {file = "mkdocs_material-9.5.6-py3-none-any.whl", hash = "sha256:e115b90fccf5cd7f5d15b0c2f8e6246b21041628b8f590630e7fca66ed7fcf6c"}, - {file = "mkdocs_material-9.5.6.tar.gz", hash = "sha256:5b24df36d8ac6cecd611241ce6f6423ccde3e1ad89f8360c3f76d5565fc2d82a"}, + {file = "mkdocs_material-9.5.13-py3-none-any.whl", hash = "sha256:5cbe17fee4e3b4980c8420a04cc762d8dc052ef1e10532abd4fce88e5ea9ce6a"}, + {file = "mkdocs_material-9.5.13.tar.gz", hash = "sha256:d8e4caae576312a88fd2609b81cf43d233cdbe36860d67a68702b018b425bd87"}, ] [[package]] @@ -839,7 +839,7 @@ files = [ [[package]] name = "mypy" -version = "1.8.0" +version = "1.9.0" requires_python = ">=3.8" summary = "Optional static typing for Python" dependencies = [ @@ -848,23 +848,23 @@ dependencies = [ "typing-extensions>=4.1.0", ] files = [ - {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, - {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, - {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, - {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, - {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, + {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, + {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, + {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, + {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, + {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, + {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, + {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, + {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, + {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, + {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, + {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, ] [[package]] @@ -879,12 +879,12 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.0" requires_python = ">=3.7" summary = "Core utilities for Python packages" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -982,11 +982,11 @@ files = [ [[package]] name = "pylint" -version = "3.0.3" +version = "3.1.0" requires_python = ">=3.8.0" summary = "python code static checker" dependencies = [ - "astroid<=3.1.0-dev0,>=3.0.1", + "astroid<=3.2.0-dev0,>=3.1.0", "colorama>=0.4.5; sys_platform == \"win32\"", "dill>=0.2; python_version < \"3.11\"", "dill>=0.3.6; python_version >= \"3.11\"", @@ -998,13 +998,13 @@ dependencies = [ "tomlkit>=0.10.1", ] files = [ - {file = "pylint-3.0.3-py3-none-any.whl", hash = "sha256:7a1585285aefc5165db81083c3e06363a27448f6b467b3b0f30dbd0ac1f73810"}, - {file = "pylint-3.0.3.tar.gz", hash = "sha256:58c2398b0301e049609a8429789ec6edf3aabe9b6c5fec916acd18639c16de8b"}, + {file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"}, + {file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"}, ] [[package]] name = "pymdown-extensions" -version = "10.7" +version = "10.7.1" requires_python = ">=3.8" summary = "Extension pack for Python Markdown." dependencies = [ @@ -1012,31 +1012,31 @@ dependencies = [ "pyyaml", ] files = [ - {file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"}, - {file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"}, + {file = "pymdown_extensions-10.7.1-py3-none-any.whl", hash = "sha256:f5cc7000d7ff0d1ce9395d216017fa4df3dde800afb1fb72d1c7d3fd35e710f4"}, + {file = "pymdown_extensions-10.7.1.tar.gz", hash = "sha256:c70e146bdd83c744ffc766b4671999796aba18842b268510a329f7f64700d584"}, ] [[package]] name = "pyparsing" -version = "3.1.1" +version = "3.1.2" requires_python = ">=3.6.8" summary = "pyparsing module - Classes and methods to define and execute parsing grammars" files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, ] [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" summary = "Extensions to the standard Python datetime module" dependencies = [ "six>=1.5", ] files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [[package]] @@ -1157,15 +1157,15 @@ files = [ [[package]] name = "ruamel-yaml" -version = "0.18.5" +version = "0.18.6" requires_python = ">=3.7" summary = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" dependencies = [ "ruamel-yaml-clib>=0.2.7; platform_python_implementation == \"CPython\" and python_version < \"3.13\"", ] files = [ - {file = "ruamel.yaml-0.18.5-py3-none-any.whl", hash = "sha256:a013ac02f99a69cdd6277d9664689eb1acba07069f912823177c5eced21a6ada"}, - {file = "ruamel.yaml-0.18.5.tar.gz", hash = "sha256:61917e3a35a569c1133a8f772e1226961bf5a1198bea7e23f06a0841dea1ab0e"}, + {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, + {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, ] [[package]] @@ -1243,51 +1243,52 @@ files = [ [[package]] name = "tomlkit" -version = "0.12.3" +version = "0.12.4" requires_python = ">=3.7" summary = "Style preserving TOML library" files = [ - {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, - {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, + {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, + {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, ] [[package]] name = "types-colorama" -version = "0.4.15.20240106" +version = "0.4.15.20240311" requires_python = ">=3.8" summary = "Typing stubs for colorama" files = [ - {file = "types-colorama-0.4.15.20240106.tar.gz", hash = "sha256:49096b4c4cbfcaa11699a0470c36e4f5631f193fb980188e013ea64445d35656"}, - {file = "types_colorama-0.4.15.20240106-py3-none-any.whl", hash = "sha256:18294bc18f60dc0b4895de8119964a5d895f5e180c2d1308fdd33009c0fa0f38"}, + {file = "types-colorama-0.4.15.20240311.tar.gz", hash = "sha256:a28e7f98d17d2b14fb9565d32388e419f4108f557a7d939a66319969b2b99c7a"}, + {file = "types_colorama-0.4.15.20240311-py3-none-any.whl", hash = "sha256:6391de60ddc0db3f147e31ecb230006a6823e81e380862ffca1e4695c13a0b8e"}, ] [[package]] name = "types-markdown" -version = "3.5.0.20240129" +version = "3.5.0.20240311" requires_python = ">=3.8" summary = "Typing stubs for Markdown" files = [ - {file = "types-Markdown-3.5.0.20240129.tar.gz", hash = "sha256:9acd36fef264d9ed5a96345c45f7d80f0d967059e92213998b3046fbb64f67fc"}, - {file = "types_Markdown-3.5.0.20240129-py3-none-any.whl", hash = "sha256:d6861d9d68e8268a5346d8a43d14727e6c636ebc6d49f2b8fc034c25996d35dd"}, + {file = "types-Markdown-3.5.0.20240311.tar.gz", hash = "sha256:4c58ef33ee278183c540a58e599cbe97090f655302b6ef65a1084c4b32d748a4"}, + {file = "types_Markdown-3.5.0.20240311-py3-none-any.whl", hash = "sha256:508fedc02601aa8943d0adbb390e2b4f00deab07870a51b411f6653d8e29e390"}, ] [[package]] name = "types-pillow" -version = "10.2.0.20240125" +version = "10.2.0.20240311" requires_python = ">=3.8" summary = "Typing stubs for Pillow" files = [ - {file = "types-Pillow-10.2.0.20240125.tar.gz", hash = "sha256:c449b2c43b9fdbe0494a7b950e6b39a4e50516091213fec24ef3f33c1d017717"}, - {file = "types_Pillow-10.2.0.20240125-py3-none-any.whl", hash = "sha256:322dbae32b4b7918da5e8a47c50ac0f24b0aa72a804a23857620f2722b03c858"}, + {file = "types-Pillow-10.2.0.20240311.tar.gz", hash = "sha256:f611f6baf7c3784fe550ee92b108060f5544a47c37c73acb81a785f1c6312772"}, + {file = "types_Pillow-10.2.0.20240311-py3-none-any.whl", hash = "sha256:34ca2fe768c6b1d05f288374c1a5ef9437f75faa1f91437b43c50970bbb54a94"}, ] [[package]] name = "types-pyyaml" -version = "6.0.12.12" +version = "6.0.12.20240311" +requires_python = ">=3.8" summary = "Typing stubs for PyYAML" files = [ - {file = "types-PyYAML-6.0.12.12.tar.gz", hash = "sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062"}, - {file = "types_PyYAML-6.0.12.12-py3-none-any.whl", hash = "sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24"}, + {file = "types-PyYAML-6.0.12.20240311.tar.gz", hash = "sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342"}, + {file = "types_PyYAML-6.0.12.20240311-py3-none-any.whl", hash = "sha256:b845b06a1c7e54b8e5b4c683043de0d9caf205e7434b3edc678ff2411979b8f6"}, ] [[package]] @@ -1301,32 +1302,32 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" requires_python = ">=3.8" summary = "Backported and Experimental Type Hints for Python 3.8+" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] name = "uc-micro-py" -version = "1.0.2" +version = "1.0.3" requires_python = ">=3.7" summary = "Micro subset of unicode data files for linkify-it-py projects." files = [ - {file = "uc-micro-py-1.0.2.tar.gz", hash = "sha256:30ae2ac9c49f39ac6dce743bd187fcd2b574b16ca095fa74cd9396795c954c54"}, - {file = "uc_micro_py-1.0.2-py3-none-any.whl", hash = "sha256:8c9110c309db9d9e87302e2f4ad2c3152770930d88ab385cd544e7a7e75f3de0"}, + {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, + {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, ] [[package]] name = "urllib3" -version = "2.2.0" +version = "2.2.1" requires_python = ">=3.8" summary = "HTTP library with thread-safe connection pooling, file post, and more." files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [[package]] @@ -1340,43 +1341,46 @@ files = [ [[package]] name = "watchdog" -version = "3.0.0" -requires_python = ">=3.7" +version = "4.0.0" +requires_python = ">=3.8" summary = "Filesystem events monitoring" files = [ - {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, - {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, - {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"}, - {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"}, - {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"}, - {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"}, - {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"}, - {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"}, - {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, - {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, + {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"}, + {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"}, + {file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"}, + {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"}, + {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"}, + {file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"}, + {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"}, + {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"}, + {file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"}, + {file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"}, + {file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"}, + {file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"}, + {file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"}, + {file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"}, + {file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"}, + {file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"}, ] [[package]] name = "wcmatch" -version = "8.5" +version = "8.5.1" requires_python = ">=3.8" summary = "Wildcard/glob file name matcher." dependencies = [ "bracex>=2.1.1", ] files = [ - {file = "wcmatch-8.5-py3-none-any.whl", hash = "sha256:14554e409b142edeefab901dc68ad570b30a72a8ab9a79106c5d5e9a6d241bd5"}, - {file = "wcmatch-8.5.tar.gz", hash = "sha256:86c17572d0f75cbf3bcb1a18f3bf2f9e72b39a9c08c9b4a74e991e1882a8efb3"}, + {file = "wcmatch-8.5.1-py3-none-any.whl", hash = "sha256:24c19cedc92bc9c9e27f39db4e1824d72f95bd2cea32b254a47a45b1a1b227ed"}, + {file = "wcmatch-8.5.1.tar.gz", hash = "sha256:c0088c7f6426cf6bf27e530e2b7b734031905f7e490475fd83c7c5008ab581b3"}, ] [[package]] diff --git a/tools/ci/pyproject.toml b/tools/ci/pyproject.toml index 6617307a3..8e55806ff 100644 --- a/tools/ci/pyproject.toml +++ b/tools/ci/pyproject.toml @@ -22,7 +22,7 @@ docs = [ "mdformat-configurable-black", "mdformat-config", "mdformat-web", - "mdformat-admon", + "mdformat-admon < 0.2.0", "mdformat-gfm", "mdformat-tables", "mdformat-footnote", From fac49d627424d80c0bdfd77d2aa71c86445ee283 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Mon, 11 Mar 2024 13:35:31 +0100 Subject: [PATCH 089/253] fix format --- docs/tutorial/align_range.md | 6 +++--- docs/tutorial/assets/setup/setup_a.md | 8 +++----- docs/tutorial/assets/setup/setup_b.md | 8 +++----- docs/tutorial/assets/setup/setup_c.md | 8 +++----- docs/tutorial/channels_legend.md | 12 ++++++------ docs/tutorial/data.md | 6 +++--- docs/tutorial/filter_add_new_records.md | 8 ++++---- 7 files changed, 25 insertions(+), 31 deletions(-) diff --git a/docs/tutorial/align_range.md b/docs/tutorial/align_range.md index beb3f1652..129703a93 100644 --- a/docs/tutorial/align_range.md +++ b/docs/tutorial/align_range.md @@ -15,9 +15,9 @@ the chart. For example, on a column chart, elements will be vertically centered, whereas on a bar chart, horizontally. !!! info - In the first example, the y-axis labels are hidden because they don't properly - represent the values shown on the column chart anymore, as the chart elements - float off the x-axis. + In the first example, the y-axis labels are hidden because they don't + properly represent the values shown on the column chart anymore, as the + chart elements float off the x-axis.
diff --git a/docs/tutorial/assets/setup/setup_a.md b/docs/tutorial/assets/setup/setup_a.md index 17c43501e..908a429a8 100644 --- a/docs/tutorial/assets/setup/setup_a.md +++ b/docs/tutorial/assets/setup/setup_a.md @@ -1,6 +1,4 @@ -??? info "Info - How to setup Vizzu" {% include-markdown -"tutorial/assets/setup/init.md" %} +??? info "Info - How to setup Vizzu" + {% include-markdown "tutorial/assets/setup/init.md" %} -``` -{% include-markdown "tutorial/assets/setup/config_a.md" %} -``` + {% include-markdown "tutorial/assets/setup/config_a.md" %} diff --git a/docs/tutorial/assets/setup/setup_b.md b/docs/tutorial/assets/setup/setup_b.md index 77767cb80..87e80d7b5 100644 --- a/docs/tutorial/assets/setup/setup_b.md +++ b/docs/tutorial/assets/setup/setup_b.md @@ -1,6 +1,4 @@ -??? info "Info - How to setup Vizzu" {% include-markdown -"tutorial/assets/setup/init.md" %} +??? info "Info - How to setup Vizzu" + {% include-markdown "tutorial/assets/setup/init.md" %} -``` -{% include-markdown "tutorial/assets/setup/config_b.md" %} -``` + {% include-markdown "tutorial/assets/setup/config_b.md" %} diff --git a/docs/tutorial/assets/setup/setup_c.md b/docs/tutorial/assets/setup/setup_c.md index b00c9a5b0..1fcf82a3c 100644 --- a/docs/tutorial/assets/setup/setup_c.md +++ b/docs/tutorial/assets/setup/setup_c.md @@ -1,6 +1,4 @@ -??? info "Info - How to setup Vizzu" {% include-markdown -"tutorial/assets/setup/init.md" %} +??? info "Info - How to setup Vizzu" + {% include-markdown "tutorial/assets/setup/init.md" %} -``` -{% include-markdown "tutorial/assets/setup/config_c.md" %} -``` + {% include-markdown "tutorial/assets/setup/config_c.md" %} diff --git a/docs/tutorial/channels_legend.md b/docs/tutorial/channels_legend.md index 379faa983..46cf603ac 100644 --- a/docs/tutorial/channels_legend.md +++ b/docs/tutorial/channels_legend.md @@ -39,10 +39,10 @@ columns’ height and lightness represent the same values. The legend for the `lightness` channel is turned on using the `legend` property. !!! info - This is an example when we explicitly instruct `Vizzu` to show the legend. By - default `Vizzu` automatically shows/hides the legend when it's necessary. You - can also turn it off with the `legend`: `null` setting or set back to automatic - mode with `legend`: `'auto'`. + This is an example when we explicitly instruct `Vizzu` to show the legend. + By default `Vizzu` automatically shows/hides the legend when it's necessary. + You can also turn it off with the `legend`: `null` setting or set back to + automatic mode with `legend`: `'auto'`.
@@ -64,8 +64,8 @@ is put on it that is on the x-axis resulting in each bar having a different color. If a measure is put on the `color` channel, a color range will be used. !!! info - The value on the `lightness` channel is removed in this step as it doesn’t make - sense to use it together with the `color` channel in this case. + The value on the `lightness` channel is removed in this step as it doesn’t + make sense to use it together with the `color` channel in this case.
diff --git a/docs/tutorial/data.md b/docs/tutorial/data.md index bf1c8d9c2..4d3e1578b 100644 --- a/docs/tutorial/data.md +++ b/docs/tutorial/data.md @@ -131,9 +131,9 @@ let data = { Using data cube form: !!! note - In the example below, the record `Rock,Experimental,36` has been replaced with - `Rock,Smooth,36` in order to illustrate that only data with same dimensions can - be used in the data cube form. + In the example below, the record `Rock,Experimental,36` has been replaced + with `Rock,Smooth,36` in order to illustrate that only data with same + dimensions can be used in the data cube form.
Genres
diff --git a/docs/tutorial/filter_add_new_records.md b/docs/tutorial/filter_add_new_records.md index 5ecebeaa9..2c292911f 100644 --- a/docs/tutorial/filter_add_new_records.md +++ b/docs/tutorial/filter_add_new_records.md @@ -72,9 +72,9 @@ chart.animate({ ``` !!! info - Combining this option with the [store](./shorthands_store.md) function makes it - easy to update previously configured states with fresh data since this function - saves the config and style parameters of the chart into a variable but not the - data. + Combining this option with the [store](./shorthands_store.md) function makes + it easy to update previously configured states with fresh data since this + function saves the config and style parameters of the chart into a variable + but not the data. From afe473adbf1d820957b15b7c233550dd018b369c Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Mon, 11 Mar 2024 15:12:08 +0100 Subject: [PATCH 090/253] Test tuning --- .../web_content/cookbook/data_source/data_from_model.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/test_cases/web_content/cookbook/data_source/data_from_model.mjs b/test/e2e/test_cases/web_content/cookbook/data_source/data_from_model.mjs index 19b41e147..a6893947b 100644 --- a/test/e2e/test_cases/web_content/cookbook/data_source/data_from_model.mjs +++ b/test/e2e/test_cases/web_content/cookbook/data_source/data_from_model.mjs @@ -120,7 +120,7 @@ const testSteps = [ function update(model, chart) { const timeStep = 0.01 model.update(timeStep) - if (model.time < 3) + if (model.time < 1) return chart .animate( { From 84f199b2ff3d94c6a137ded761aec3aa6d670f2f Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Mon, 11 Mar 2024 22:00:38 +0100 Subject: [PATCH 091/253] CRenderer extracted from CCanvas --- src/apps/weblib/ts-api/chart.ts | 12 ++++-- src/apps/weblib/ts-api/index.ts | 1 + src/apps/weblib/ts-api/module/ccanvas.ts | 41 +++--------------- src/apps/weblib/ts-api/module/crenderer.ts | 42 +++++++++++++++++++ src/apps/weblib/ts-api/module/module.ts | 12 +++--- src/apps/weblib/ts-api/plugins.ts | 4 +- .../weblib/ts-api/plugins/canvasrenderer.ts | 10 +---- .../ts-api/plugins/shorthands-augmentation.ts | 6 +-- 8 files changed, 70 insertions(+), 58 deletions(-) create mode 100644 src/apps/weblib/ts-api/module/crenderer.ts diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index 04131e7df..eda3321b4 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -24,10 +24,12 @@ import { CoordSystem } from './plugins/coordsys.js' import { Clock } from './plugins/clock.js' import { Scheduler } from './plugins/scheduler.js' import { RenderControl } from './plugins/rendercontrol.js' +import { CCanvas } from './module/ccanvas.js' export class Chart { private _options: VizzuOptions private _cChart: CChart + private _cCanvas: CCanvas private _module: Module private _cData: CData private _data: Data @@ -40,6 +42,7 @@ export class Chart { this._module = module this._cChart = this._module.createChart() + this._cCanvas = this._module.createCanvas() this._cData = this._module.getData(this._cChart) this._data = new Data(this._cData) @@ -50,7 +53,7 @@ export class Chart { registerBuiltins(): void { this._plugins.register(new Logging(this._module.setLogging.bind(this._module)), false) this._plugins.register(new HtmlCanvas(HtmlCanvas.extractOptions(this._options)), true) - this._plugins.register(this._module.createCanvas(CanvasRenderer), true) + this._plugins.register(new CanvasRenderer(), true) this._plugins.register(new Clock(), true) this._plugins.register(new Scheduler(), true) this._plugins.register(new RenderControl(), true) @@ -81,15 +84,16 @@ export class Chart { this._plugins.hook(Hooks.render, ctx).default((ctx) => { if (ctx.size.x >= 1 && ctx.size.y >= 1 && ctx.timeInMSecs !== null && ctx.renderer) { const renderControl = !ctx.enable ? 2 : force ? 1 : 0 - this._module.registerRenderer(ctx.renderer) + ctx.renderer.canvas = this._cCanvas + this._module.registerRenderer(this._cCanvas, ctx.renderer) this._cChart.update( - ctx.renderer, + this._cCanvas, ctx.size.x, ctx.size.y, ctx.timeInMSecs, renderControl ) - this._module.unregisterRenderer(ctx.renderer) + this._module.unregisterRenderer(this._cCanvas) } }) } diff --git a/src/apps/weblib/ts-api/index.ts b/src/apps/weblib/ts-api/index.ts index 639c32d06..6217e4452 100644 --- a/src/apps/weblib/ts-api/index.ts +++ b/src/apps/weblib/ts-api/index.ts @@ -8,6 +8,7 @@ export * from './module/cdata.js' export * from './module/cenv.js' export * from './module/cerror.js' export * from './module/cproxy.js' +export * from './module/crenderer.js' export * from './module/loader.js' export * from './module/module.js' export * from './module/objregistry.js' diff --git a/src/apps/weblib/ts-api/module/ccanvas.ts b/src/apps/weblib/ts-api/module/ccanvas.ts index d1dd30eb1..7a923dfe4 100644 --- a/src/apps/weblib/ts-api/module/ccanvas.ts +++ b/src/apps/weblib/ts-api/module/ccanvas.ts @@ -1,47 +1,18 @@ -import { CEnv, CObject } from './cenv.js' -import { CPointerClosure } from './objregistry.js' +import { type CEnv, CObject } from './cenv.js' +import { type CPointerClosure } from './objregistry.js' import { CColorGradient } from './ccolorgradient.js' -import { CString, CColorGradientPtr } from '../cvizzu.types' +import type { CString, CColorGradientPtr } from '../cvizzu.types' -export abstract class CCanvas extends CObject { +export class CCanvas extends CObject { constructor(env: CEnv, getId: CPointerClosure) { super(getId, env) } - abstract setFontStyle(font: string): void - abstract drawText(x: number, y: number, sizex: number, sizey: number, text: string): void - abstract setGradient( - x1: number, - y1: number, - x2: number, - y2: number, - gradient: CColorGradient - ): void - - setFont(font: CString): void { - this.setFontStyle(this._getString(font)) - } - - text(x: number, y: number, sizex: number, sizey: number, text: CString): void { - this.drawText(x, y, sizex, sizey, this._getString(text)) - } - - setBrushGradient( - x1: number, - y1: number, - x2: number, - y2: number, - stopCount: number, - stops: CColorGradientPtr - ): void { - this.setGradient(x1, y1, x2, y2, this._getColorGradient(stops, stopCount)) - } - - private _getColorGradient(stops: CColorGradientPtr, stopCount: number): CColorGradient { + getColorGradient(stops: CColorGradientPtr, stopCount: number): CColorGradient { return new CColorGradient(this, stops, stopCount) } - private _getString(text: CString): string { + getString(text: CString): string { return this._wasm.UTF8ToString(text) } } diff --git a/src/apps/weblib/ts-api/module/crenderer.ts b/src/apps/weblib/ts-api/module/crenderer.ts new file mode 100644 index 000000000..bf8e155c0 --- /dev/null +++ b/src/apps/weblib/ts-api/module/crenderer.ts @@ -0,0 +1,42 @@ +import type { CCanvas } from './ccanvas.js' +import type { CColorGradient } from './ccolorgradient.js' +import type { CString, CColorGradientPtr } from '../cvizzu.types' + +export abstract class CRenderer { + canvas?: CCanvas + + abstract setFontStyle(font: string): void + abstract drawText(x: number, y: number, sizex: number, sizey: number, text: string): void + abstract setGradient( + x1: number, + y1: number, + x2: number, + y2: number, + gradient: CColorGradient + ): void + + setFont(font: CString): void { + if (this.canvas) { + this.setFontStyle(this.canvas.getString(font)) + } + } + + text(x: number, y: number, sizex: number, sizey: number, text: CString): void { + if (this.canvas) { + this.drawText(x, y, sizex, sizey, this.canvas.getString(text)) + } + } + + setBrushGradient( + x1: number, + y1: number, + x2: number, + y2: number, + stopCount: number, + stops: CColorGradientPtr + ): void { + if (this.canvas) { + this.setGradient(x1, y1, x2, y2, this.canvas.getColorGradient(stops, stopCount)) + } + } +} diff --git a/src/apps/weblib/ts-api/module/module.ts b/src/apps/weblib/ts-api/module/module.ts index 1802d969d..7637862e0 100644 --- a/src/apps/weblib/ts-api/module/module.ts +++ b/src/apps/weblib/ts-api/module/module.ts @@ -1,6 +1,6 @@ import { CVizzu } from '../cvizzu.types' -import { CPointerClosure, ObjectRegistry } from './objregistry.js' +import { ObjectRegistry } from './objregistry.js' import { CEnv } from './cenv.js' import { CData } from './cdata.js' import { CChart } from './cchart.js' @@ -19,11 +19,11 @@ export class Module extends CEnv { this.setLogging(false) } - registerRenderer(cCanvas: CCanvas & Canvas): void { - this._wasm.canvases[cCanvas.getId()] = cCanvas + registerRenderer(cCanvas: CCanvas, canvas: Canvas): void { + this._wasm.canvases[cCanvas.getId()] = canvas } - unregisterRenderer(cCanvas: CCanvas & Canvas): void { + unregisterRenderer(cCanvas: CCanvas): void { delete this._wasm.canvases[cCanvas.getId()] } @@ -51,7 +51,7 @@ export class Module extends CEnv { return new CChart(this, this._getStatic(this._wasm._vizzu_createChart)) } - createCanvas(ctor: new (env: CEnv, getId: CPointerClosure) => T): T { - return new ctor(this, this._getStatic(this._wasm._vizzu_createCanvas)) + createCanvas(): CCanvas { + return new CCanvas(this, this._getStatic(this._wasm._vizzu_createCanvas)) } } diff --git a/src/apps/weblib/ts-api/plugins.ts b/src/apps/weblib/ts-api/plugins.ts index f6a383010..6286ec84d 100644 --- a/src/apps/weblib/ts-api/plugins.ts +++ b/src/apps/weblib/ts-api/plugins.ts @@ -3,7 +3,7 @@ import * as Anim from './types/anim.js' import { Events, EventType, EventHandler, EventMap } from './events.js' import { AnimCompleting } from './animcompleting.js' import { Point } from './geom.js' -import { CCanvas } from './module/ccanvas.js' +import { CRenderer } from './module/crenderer.js' import { Canvas } from './module/canvas.js' /** Available hooks for plugins in Vizzu. */ @@ -39,7 +39,7 @@ export interface StartContext { } export interface RenderContext { - renderer: (CCanvas & Canvas) | null + renderer: (CRenderer & Canvas) | null timeInMSecs: number | null enable: boolean size: Point diff --git a/src/apps/weblib/ts-api/plugins/canvasrenderer.ts b/src/apps/weblib/ts-api/plugins/canvasrenderer.ts index 7c4898f86..2f3a934a3 100644 --- a/src/apps/weblib/ts-api/plugins/canvasrenderer.ts +++ b/src/apps/weblib/ts-api/plugins/canvasrenderer.ts @@ -1,8 +1,6 @@ import { CColorGradient } from '../module/ccolorgradient.js' -import { CEnv } from '../module/cenv.js' -import { CPointerClosure } from '../module/objregistry.js' import { Canvas } from '../module/canvas.js' -import { CCanvas } from '../module/ccanvas.js' +import { CRenderer } from '../module/crenderer.js' import { Plugin, PluginHooks, RenderContext as Ctx } from '../plugins.js' export class CanvasError extends Error { @@ -20,15 +18,11 @@ export interface HtmlCanvasAlternative { export type RenderContext = Ctx & { htmlCanvas?: HtmlCanvasAlternative } -export class CanvasRenderer extends CCanvas implements Canvas, Plugin { +export class CanvasRenderer extends CRenderer implements Canvas, Plugin { private _htmlCanvas?: HtmlCanvasAlternative private _polygonInProgress: boolean = false private _currentLineWidth: number = 1 - constructor(env: CEnv, getId: CPointerClosure) { - super(env, getId) - } - get hooks(): PluginHooks { const hooks = { render: (ctx: RenderContext, next: () => void): void => { diff --git a/src/apps/weblib/ts-api/plugins/shorthands-augmentation.ts b/src/apps/weblib/ts-api/plugins/shorthands-augmentation.ts index 946c5db76..fb015e7c9 100644 --- a/src/apps/weblib/ts-api/plugins/shorthands-augmentation.ts +++ b/src/apps/weblib/ts-api/plugins/shorthands-augmentation.ts @@ -1,6 +1,6 @@ -import '../vizzu' -import { AnimCompleting } from '../animcompleting' -import { AnyAnimOptions, AnyAnimTarget } from './shorthands' +import '../vizzu.js' +import { AnimCompleting } from '../animcompleting.js' +import { AnyAnimOptions, AnyAnimTarget } from './shorthands.js' declare module '../vizzu' { interface Vizzu { From 325e5ed6bcd5d9f7555d7745becdb3ecc82bd37f Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Tue, 12 Mar 2024 11:10:52 +0100 Subject: [PATCH 092/253] type-fest version updated --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 231568ff3..8a600dadb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "name": "vizzu", "license": "Apache-2.0", "dependencies": { - "type-fest": "^4.6.0" + "type-fest": "^4.12.0" }, "devDependencies": { "@rollup/plugin-terser": "^0.4.3", @@ -8499,9 +8499,9 @@ } }, "node_modules/type-fest": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.3.tgz", - "integrity": "sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.12.0.tgz", + "integrity": "sha512-5Y2/pp2wtJk8o08G0CMkuFPCO354FGwk/vbidxrdhRGZfd0tFnb4Qb8anp9XxXriwBgVPjdWbKpGl4J9lJY2jQ==", "engines": { "node": ">=16" }, diff --git a/package.json b/package.json index 36c7256ee..5ceb6e6ee 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "build:wasm-wocpp-gsutil": "./tools/ci/run/pkg-build-wasm-wocpp-gsutil.sh" }, "dependencies": { - "type-fest": "^4.6.0" + "type-fest": "^4.12.0" }, "devDependencies": { "@rollup/plugin-terser": "^0.4.3", From ce55c2d8e2e0797e0cecb1a13332843c7c673bc0 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Tue, 12 Mar 2024 11:40:38 +0100 Subject: [PATCH 093/253] Set version to 0.10.1 --- CHANGELOG.md | 6 ++++++ src/chart/main/version.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd98607d2..48ccd94bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## [0.10.1] - 2024-03-12 + +### Added + +- Updated `type-fest` dependency version to `4.12.0` + ## [0.10.0] - 2024-03-11 ### Fixed diff --git a/src/chart/main/version.cpp b/src/chart/main/version.cpp index df6aac5a5..e94d405ff 100644 --- a/src/chart/main/version.cpp +++ b/src/chart/main/version.cpp @@ -1,5 +1,5 @@ #include "version.h" -const App::Version Vizzu::Main::version(0, 10, 0); +const App::Version Vizzu::Main::version(0, 10, 1); const char *const Vizzu::Main::siteUrl = "https://vizzu.io/"; From 25ee1134693c23f2e7514b87204569785410273e Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Tue, 12 Mar 2024 15:42:26 +0100 Subject: [PATCH 094/253] Force flag added to Plugin.RenderContext --- src/apps/weblib/ts-api/chart.ts | 7 ++++--- src/apps/weblib/ts-api/plugins.ts | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index eda3321b4..32c54150f 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -11,7 +11,7 @@ import { Events, EventType, EventHandler, EventMap } from './events.js' import { Mirrored } from './tsutils.js' import { VizzuOptions } from './vizzu.js' import { AnimControl } from './animcontrol.js' -import { PluginRegistry, Hooks } from './plugins.js' +import { PluginRegistry, Hooks, RenderContext } from './plugins.js' import { Logging } from './plugins/logging.js' import { Shorthands } from './plugins/shorthands.js' import { PivotData } from './plugins/pivotdata.js' @@ -75,15 +75,16 @@ export class Chart { } updateFrame(force: boolean = false): void { - const ctx = { + const ctx: RenderContext = { renderer: null, timeInMSecs: null, enable: true, + force, size: { x: 0, y: 0 } } this._plugins.hook(Hooks.render, ctx).default((ctx) => { if (ctx.size.x >= 1 && ctx.size.y >= 1 && ctx.timeInMSecs !== null && ctx.renderer) { - const renderControl = !ctx.enable ? 2 : force ? 1 : 0 + const renderControl = !ctx.enable ? 2 : ctx.force ? 1 : 0 ctx.renderer.canvas = this._cCanvas this._module.registerRenderer(this._cCanvas, ctx.renderer) this._cChart.update( diff --git a/src/apps/weblib/ts-api/plugins.ts b/src/apps/weblib/ts-api/plugins.ts index 6286ec84d..0a12ec5e7 100644 --- a/src/apps/weblib/ts-api/plugins.ts +++ b/src/apps/weblib/ts-api/plugins.ts @@ -42,6 +42,7 @@ export interface RenderContext { renderer: (CRenderer & Canvas) | null timeInMSecs: number | null enable: boolean + force: boolean size: Point } From ba4a309bb0a84c0857835ca4eb062cd988292449 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Tue, 12 Mar 2024 16:56:39 +0100 Subject: [PATCH 095/253] valid count on nothing. Throw on duplicate dim/meas. Aggregate on not normalized sizes. --- src/dataframe/impl/data_source.cpp | 88 ++++++++++++++------------ src/dataframe/impl/data_source.h | 5 +- src/dataframe/impl/dataframe.cpp | 37 ++++++----- src/dataframe/interface.h | 4 +- test/unit/chart/events.cpp | 2 +- test/unit/dataframe/interface_test.cpp | 12 +++- 6 files changed, 84 insertions(+), 64 deletions(-) diff --git a/src/dataframe/impl/data_source.cpp b/src/dataframe/impl/data_source.cpp index f74fb5c05..0698f33c0 100644 --- a/src/dataframe/impl/data_source.cpp +++ b/src/dataframe/impl/data_source.cpp @@ -308,12 +308,7 @@ data_source::const_series_data data_source::get_series( void data_source::normalize_sizes() { - std::size_t record_count{}; - for (auto &&dim : dimensions) - record_count = std::max(record_count, dim.values.size()); - for (auto &&mea : measures) - record_count = std::max(record_count, mea.values.size()); - + std::size_t record_count = get_record_count(); for (auto &&dim : dimensions) dim.values.resize(record_count, nav); for (auto &&mea : measures) mea.values.resize(record_count, nan); @@ -422,11 +417,11 @@ data_source::data_source(aggregating_type &&aggregating, measures.reserve(meas.size()); measure_names.reserve(meas.size()); - for (const auto &[name, dim] : dims) { - dimension_t &new_dim = add_new_dimension({}, name); - new_dim.categories = dim.get().categories; - new_dim.info = dim.get().info; - } + for (const auto &[name, dim] : dims) + add_new_dimension({dim.get().categories, + std::initializer_list{}, + dim.get().info}, + name); for (const auto &[name, mea] : meas) { measure_t &new_mea = add_new_measure({}, name); @@ -438,7 +433,6 @@ data_source::data_source(aggregating_type &&aggregating, case measure: new_mea.info = unsafe_get(ser).second.info; break; - case unknown: default: break; } } @@ -451,18 +445,21 @@ data_source::data_source(aggregating_type &&aggregating, if (filtered && (*filtered)[i]) continue; for (std::size_t ix{}; const auto &[name, dim] : dims) - cat_indices[ix++] = dim.get().values[i]; + cat_indices[ix++] = i < dim.get().values.size() + ? dim.get().values[i] + : nav; auto [it, s] = rec_to_index.try_emplace(cat_indices, rec_to_index.size()); auto &[index, aggregators] = it->second; if (s) { - for (auto &m : measures) m.values.emplace_back(NAN); + for (auto &m : measures) m.values.emplace_back(nan); for (std::size_t ix{}; const auto &[name, dim] : dims) dimensions[ix++].values.emplace_back( - dim.get().values[i]); + i < dim.get().values.size() ? dim.get().values[i] + : nav); aggregators.reserve(meas.size()); for (const auto &[ser, agg] : @@ -471,14 +468,18 @@ data_source::data_source(aggregating_type &&aggregating, } for (std::size_t ix{}; const auto &[name, mea] : meas) { - auto &[data, agg] = mea; + const auto &[data, agg] = mea; cell_value val{}; - if (data == series_type::measure) - val = unsafe_get(data) - .second.values[i]; - else - val = unsafe_get(data) - .second.get(i); + switch (data) { + using enum series_type; + case measure: + val = unsafe_get(data).second.get(i); + break; + case dimension: + val = unsafe_get(data).second.get(i); + break; + default: break; + } measures[ix].values[index] = agg.add(aggregators[ix], val); ++ix; @@ -641,6 +642,11 @@ std::uint32_t data_source::dimension_t::get_or_set_cat( return static_cast(it - categories.begin()); } +const double &data_source::measure_t::get(std::size_t index) const +{ + return index < values.size() ? nan : values[index]; +} + std::pair data_source::measure_t::get_min_max() const { auto mini = std::numeric_limits::max(); @@ -654,28 +660,28 @@ std::pair data_source::measure_t::get_min_max() const return {mini, maxi}; } -std::string data_source::aggregating_type::add_aggregated( +std::pair +data_source::aggregating_type::add_aggregated( const_series_data &&data, const custom_aggregator &aggregator) { - return meas - .try_emplace( - std::string{aggregator.get_name()} + '(' - + std::visit( - [](const auto &arg) - { - if constexpr (std::is_same_v>) - return std::string{}; - else - return std::string{arg.first}; - }, - data) - + ')', - data, - aggregator) - .first->first; + auto &&[it, succ] = meas.try_emplace( + std::string{aggregator.get_name()} + '(' + + std::visit( + [](const auto &arg) + { + if constexpr (std::is_same_v>) + return std::string{}; + else + return std::string{arg.first}; + }, + data) + + ')', + data, + aggregator); + return {it->first, succ}; } } \ No newline at end of file diff --git a/src/dataframe/impl/data_source.h b/src/dataframe/impl/data_source.h index 161b7835c..868eba6f1 100644 --- a/src/dataframe/impl/data_source.h +++ b/src/dataframe/impl/data_source.h @@ -79,6 +79,8 @@ class data_source : public std::enable_shared_from_this info(std::begin(info), std::end(info)) {} + const double &get(std::size_t index) const; + [[nodiscard]] std::pair get_min_max() const; }; @@ -132,7 +134,8 @@ class data_source : public std::enable_shared_from_this std::less<>> meas; - std::string add_aggregated(const_series_data &&data, + std::pair add_aggregated( + const_series_data &&data, const custom_aggregator &aggregator); }; diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp index 84027d208..ea0418e58 100644 --- a/src/dataframe/impl/dataframe.cpp +++ b/src/dataframe/impl/dataframe.cpp @@ -46,6 +46,14 @@ dataframe::dataframe(std::shared_ptr other, *cp.other->finalized); } +void valid_unexistent_aggregator( + const dataframe::any_aggregator_type &agg) +{ + if (const auto *aggr = std::get_if(&agg); + !aggr || *aggr != aggregator_type::count) + throw std::runtime_error("Series does not exists."); +} + void valid_dimension_aggregator( const dataframe::any_aggregator_type &agg) { @@ -74,19 +82,8 @@ void valid_measure_aggregator( throw std::runtime_error( "Measure series must be aggregated."); - if (!std::holds_alternative(agg)) { - if (Refl::enum_names.end() - != std::ranges::find(Refl::enum_names, - std::get_if(&agg)->get_name())) { - throw std::runtime_error( - "Custom aggregator name cannot be any of these: " - + std::string{ - Refl::enum_name_holder.data(), - Refl::enum_name_holder.size()}); - } - } - else if (*std::get_if(&agg) - == aggregator_type::distinct) + if (const auto *t = std::get_if(&agg); + t && *t == aggregator_type::distinct) throw std::runtime_error( "Measure series cannot be aggregated by distinct."); } @@ -100,7 +97,7 @@ std::string dataframe::set_aggregate(series_identifier series, auto &&ser = get_data_source().get_series(series); switch (ser) { using enum series_type; - default: throw std::runtime_error("Series does not exists."); + default: valid_unexistent_aggregator(aggregator); break; case dimension: valid_dimension_aggregator(aggregator); break; case measure: valid_measure_aggregator(aggregator); break; } @@ -111,13 +108,21 @@ std::string dataframe::set_aggregate(series_identifier series, const auto *agg = std::get_if(&aggregator); if (ser == series_type::dimension && !agg) { - aggs.dims.emplace(unsafe_get(ser)); + if (!aggs.dims + .emplace(unsafe_get(ser)) + .second) + throw std::runtime_error( + "Duplicated dimension series name."); return {}; } - return aggs.add_aggregated(std::move(ser), + auto &&[name, uniq] = aggs.add_aggregated(std::move(ser), agg ? aggregators[*agg] : std::get(aggregator)); + if (!uniq) + throw std::runtime_error( + "Duplicated aggregated series name."); + return name; } void dataframe::set_filter(std::function &&filt) & diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 113c89a93..81287ef7c 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -53,9 +53,9 @@ class dataframe_interface : { public: using series_identifier = - std::variant; + std::variant; using record_identifier = - std::variant; + std::variant; using any_aggregator_type = std:: variant; diff --git a/test/unit/chart/events.cpp b/test/unit/chart/events.cpp index 4adf773e4..8e19791d8 100644 --- a/test/unit/chart/events.cpp +++ b/test/unit/chart/events.cpp @@ -95,7 +95,7 @@ struct chart_setup {{0, -1, 5, 6, 6, 5, -1, 0, 5, 6, 0, -1, -1, 0, 6, -5}}); auto &channels = chart.getOptions().getChannels(); for (auto &&[ch, name] : series) - channels.addSeries(ch, {name, table}); + channels.addSeries(ch, {name, std::ref(table)}); chart.setBoundRect(Geom::Rect(Geom::Point{}, {{640, 480}})); return chart; } diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index 47350a18a..928086f20 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -247,8 +247,8 @@ const static auto tests = check->*df->get_min_max("test_meas") == std::pair{-1.0, 2.0}; - check->*df->get_data({}, "test_meas") == 2.0; - check->*df->get_data({}, "test_dim") == "test_dim_val"; + check->*df->get_data(std::size_t{}, "test_meas") == 2.0; + check->*df->get_data(std::size_t{}, "test_dim") == "test_dim_val"; check->*df->get_data(std::size_t{1}, "test_meas") == -1.0; check->*df->get_data(std::size_t{1}, "test_dim") @@ -462,6 +462,7 @@ const static auto tests = { df->aggregate_by("d1"); using enum Vizzu::dataframe::aggregator_type; + auto &&pure1c = df->set_aggregate({}, count); auto &&d1c = df->set_aggregate("d1", count); auto &&d1d = df->set_aggregate("d1", distinct); auto &&d1e = df->set_aggregate("d1", exists); @@ -498,7 +499,8 @@ const static auto tests = assert->*df->get_dimensions() == std::array{"d1"}; assert->*df->get_measures() - == std::array{d1c, + == std::array{pure1c, + d1c, m1c, d1d, d1e, @@ -511,6 +513,7 @@ const static auto tests = assert->*df->get_record_count() == std::size_t{4}; + check->*df->get_data(std::size_t{0}, pure1c) == 5.0; check->*df->get_data(std::size_t{0}, d1c) == 5.0; check->*df->get_data(std::size_t{0}, m1c) == 5.0; check->*df->get_data(std::size_t{0}, d1d) == 1.0; @@ -522,6 +525,7 @@ const static auto tests = check->*df->get_data(std::size_t{0}, m1s) == 109.25; check->*df->get_data(std::size_t{0}, m1t) == 3.5; + check->*df->get_data(std::size_t{1}, pure1c) == 4.0; check->*df->get_data(std::size_t{1}, d1c) == 4.0; check->*df->get_data(std::size_t{1}, m1c) == 3.0; check->*df->get_data(std::size_t{1}, d1d) == 1.0; @@ -533,6 +537,7 @@ const static auto tests = check->*df->get_data(std::size_t{1}, m1s) == 15.0; check->*df->get_data(std::size_t{1}, m1t) == 4.25; + check->*df->get_data(std::size_t{2}, pure1c) == 1.0; check->*df->get_data(std::size_t{2}, d1c) == 1.0; check->*df->get_data(std::size_t{2}, m1c) == 0.0; check->*df->get_data(std::size_t{2}, d1d) == 1.0; @@ -554,6 +559,7 @@ const static auto tests = check->*df->get_data(std::size_t{2}, m1t) == std::numeric_limits::max(); + check->*df->get_data(std::size_t{3}, pure1c) == 1.0; check->*df->get_data(std::size_t{3}, d1c) == 0.0; check->*df->get_data(std::size_t{3}, m1c) == 1.0; check->*df->get_data(std::size_t{3}, d1d) == 0.0; From c8d778c35412f0bf85fb31e7f383ea2e73d38334 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Tue, 12 Mar 2024 18:38:24 +0100 Subject: [PATCH 096/253] Plugin hook execution change to allow calling next() multiple times. --- CHANGELOG.md | 1 + src/apps/weblib/ts-api/plugins.ts | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a672aac5f..faefa8ed3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Legend title outerRect was not properly calculated. - Fixed stacked empty min/max aggregated values. - Fixed error when an animation triggered during tooptip activation which removed the corresopnding marker. +- next() can be called multiple times from Plugin hooks ### Added diff --git a/src/apps/weblib/ts-api/plugins.ts b/src/apps/weblib/ts-api/plugins.ts index 0a12ec5e7..be3923cd6 100644 --- a/src/apps/weblib/ts-api/plugins.ts +++ b/src/apps/weblib/ts-api/plugins.ts @@ -293,13 +293,12 @@ export class PluginRegistry { } private _executeHooks(hooks: PluginHook[], ctx: T): void { - let next = (): void => {} - const iterator = hooks - .map((fn) => { - return fn ? (): void => fn(ctx, next) : (): void => next() - }) - [Symbol.iterator]() - next = (): void => iterator.next().value() - next() + const executeHookAtIndex = (index: number): void => { + if (index >= hooks.length) return + const fn = hooks[index] + const next = (): void => executeHookAtIndex(index + 1) + fn ? fn(ctx, next) : next() + } + executeHookAtIndex(0) } } From e0ca865cf236b24872e65cab79010df309c7a7fe Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Wed, 13 Mar 2024 11:27:31 +0100 Subject: [PATCH 097/253] Changelog merge problem fixed --- CHANGELOG.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33823df7d..d48583916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ ## [Unreleased] +### Fixed + +- next() can be called multiple times from Plugin hooks + +### Added + +- New plugins and plugin hooks introduced: + - plugin: scheduler - plugin resposible for scheduling the rendering + - plugin: clock - supplying the current time for the animation + - plugin: canvasRenderer - plugin for rendering the chart on a htmlcanvas compatible canvas + - hook: start - hook for starting the rendering loop + - hook: render - hook for rendering the chart + ## [0.10.1] - 2024-03-12 ### Added @@ -23,7 +36,6 @@ - Legend title outerRect was not properly calculated. - Fixed stacked empty min/max aggregated values. - Fixed error when an animation triggered during tooptip activation which removed the corresopnding marker. -- next() can be called multiple times from Plugin hooks ### Added @@ -38,12 +50,6 @@ - The event handler registration order changed. Now the handlers are called in the opposite order of the registration. - Added the padded rectangle, the bounding rectangle and the align parameter to the draw text event object. - Tooltip works on marker labels too. -- New plugins and plugin hooks introduced: - - plugin: scheduler - plugin resposible for scheduling the rendering - - plugin: clock - supplying the current time for the animation - - plugin: canvasRenderer - plugin for rendering the chart on a htmlcanvas compatible canvas - - hook: start - hook for starting the rendering loop - - hook: render - hook for rendering the chart ## [0.9.3] - 2023-12-20 From 70c82fa7bf0c9849b44032af3e95455f69100f11 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 13 Mar 2024 15:41:21 +0100 Subject: [PATCH 098/253] DataCube is not default constructible. -options contains only sets. dimensionValueAt. OptColIndex as typedef. No addSeries in specific place. Rm unused op[] --- src/chart/generator/marker.h | 2 +- src/chart/generator/plot.cpp | 23 +++++++++++------------ src/chart/generator/plot.h | 9 ++++----- src/chart/options/channel.cpp | 11 +---------- src/chart/options/channel.h | 3 +-- src/chart/options/channels.cpp | 5 ++--- src/chart/options/channels.h | 6 ++---- src/data/datacube/datacube.cpp | 18 ++++++++---------- src/data/datacube/datacube.h | 4 +--- src/data/datacube/datacubecell.h | 3 ++- src/data/datacube/datacubeoptions.h | 18 ++++++++---------- src/data/datacube/datastat.cpp | 9 ++++----- src/data/datacube/datastat.h | 2 +- src/data/datacube/seriesindex.h | 10 +++++----- src/data/table/columninfo.cpp | 6 +++--- src/data/table/columninfo.h | 3 ++- src/data/table/datatable.h | 9 +-------- 17 files changed, 57 insertions(+), 84 deletions(-) diff --git a/src/chart/generator/marker.h b/src/chart/generator/marker.h index 46a625991..6f05f5d15 100644 --- a/src/chart/generator/marker.h +++ b/src/chart/generator/marker.h @@ -41,7 +41,7 @@ class Marker struct Label { std::optional value; - std::optional measureId; + Data::SeriesIndex::OptColIndex measureId; std::string unit; std::string indexStr; Label() = default; diff --git a/src/chart/generator/plot.cpp b/src/chart/generator/plot.cpp index b56fa12af..6e7ce6f3d 100644 --- a/src/chart/generator/plot.cpp +++ b/src/chart/generator/plot.cpp @@ -34,7 +34,7 @@ Plot::MarkersInfo interpolate(const Plot::MarkersInfo &op1, Plot::MarkerInfoContent::MarkerInfoContent() { markerId.reset(); } Plot::MarkerInfoContent::MarkerInfoContent(const Marker &marker, - Data::DataCube *dataCube) + const Data::DataCube *dataCube) { const auto &index = marker.index; if (dataCube && dataCube->getTable() && !index.empty()) { @@ -92,16 +92,17 @@ Plot::Plot(const Data::DataTable &dataTable, dataTable(dataTable), options(std::move(opts)), style(std::move(style)), - dataCube(dataTable, + dataCube(std::in_place, + dataTable, options->getChannels().getDataCubeOptions(), options->dataFilter), - stats(options->getChannels(), dataCube) + stats(options->getChannels(), getDataCube()) { if (setAutoParams) options->setAutoParameters(); anyAxisSet = options->getChannels().anyAxisSet(); - generateMarkers(dataCube, dataTable); + generateMarkers(dataTable); generateMarkersInfo(); SpecLayout specLayout(*this); @@ -136,16 +137,14 @@ bool Plot::isEmpty() const return options->getChannels().isEmpty(); } -void Plot::generateMarkers(const Data::DataCube &dataCube, - const Data::DataTable &table) +void Plot::generateMarkers(const Data::DataTable &table) { - for (auto it = dataCube.getData().begin(); - it != dataCube.getData().end(); - ++it) { + auto &&data = getDataCube().getData(); + for (auto it = data.begin(); it != data.end(); ++it) { auto markerIndex = markers.size(); markers.emplace_back(*options, - dataCube, + getDataCube(), table, stats, it.getIndex(), @@ -169,7 +168,7 @@ void Plot::generateMarkersInfo() for (auto &mi : options->markersInfo) { auto &marker = markers[mi.second]; markersInfo.insert(std::make_pair(mi.first, - MarkerInfo{MarkerInfoContent{marker, &dataCube}})); + MarkerInfo{MarkerInfoContent{marker, &getDataCube()}})); } } @@ -391,7 +390,7 @@ void Plot::calcDimensionAxis(ChannelId type, } } } - axis.setLabels(dataCube, + axis.setLabels(getDataCube(), table, isTypeAxis ? scale.step.getValue(1.0) : 1.0); diff --git a/src/chart/generator/plot.h b/src/chart/generator/plot.h index 2ab857305..beca663b9 100644 --- a/src/chart/generator/plot.h +++ b/src/chart/generator/plot.h @@ -51,7 +51,7 @@ class Plot MarkerInfoContent(); explicit MarkerInfoContent(const Marker &marker, - Data::DataCube *dataCube = nullptr); + const Data::DataCube *dataCube = nullptr); explicit operator bool() const; bool operator==(const MarkerInfoContent &op) const; }; @@ -92,7 +92,7 @@ class Plot } [[nodiscard]] const Data::DataCube &getDataCube() const { - return dataCube; + return *dataCube; } [[nodiscard]] const ChannelsStats &getStats() const { @@ -114,7 +114,7 @@ class Plot const Data::DataTable &dataTable; PlotOptionsPtr options; Styles::Chart style; - Data::DataCube dataCube; + std::optional dataCube; ChannelsStats stats; Markers markers; MarkersInfo markersInfo; @@ -122,8 +122,7 @@ class Plot Buckets mainBuckets; Buckets subBuckets; - void generateMarkers(const Data::DataCube &dataCube, - const Data::DataTable &table); + void generateMarkers(const Data::DataTable &table); void generateMarkersInfo(); void linkMarkers(const Buckets &buckets, bool main); void normalizeXY(); diff --git a/src/chart/options/channel.cpp b/src/chart/options/channel.cpp index 691505b9d..7c19022cc 100644 --- a/src/chart/options/channel.cpp +++ b/src/chart/options/channel.cpp @@ -29,18 +29,9 @@ Channel Channel::makeChannel(Type id) } std::pair Channel::addSeries( - const Data::SeriesIndex &index, - std::optional pos) + const Data::SeriesIndex &index) { if (index.getType().isDimension()) { - if (pos) { - auto actPos = dimensionIds.getIndex(index); - if (static_cast(*pos) == actPos) - return {false, std::nullopt}; - dimensionIds.remove(index); - return {dimensionIds.insertAt(*pos, index), std::nullopt}; - } - return {dimensionIds.pushBack(index), std::nullopt}; } if (!measureId) { diff --git a/src/chart/options/channel.h b/src/chart/options/channel.h index 2c787b9f4..1da2e9a68 100644 --- a/src/chart/options/channel.h +++ b/src/chart/options/channel.h @@ -31,8 +31,7 @@ class Channel static Channel makeChannel(Type id); std::pair addSeries( - const Data::SeriesIndex &index, - std::optional pos = std::nullopt); + const Data::SeriesIndex &index); bool removeSeries(const Data::SeriesIndex &index); [[nodiscard]] bool isSeriesUsed( const Data::SeriesIndex &index) const; diff --git a/src/chart/options/channels.cpp b/src/chart/options/channels.cpp index 8cf57f5be..6a2d2eefe 100644 --- a/src/chart/options/channels.cpp +++ b/src/chart/options/channels.cpp @@ -79,10 +79,9 @@ Data::DataCubeOptions Channels::getDataCubeOptions() const std::pair Channels::addSeries( const ChannelId &id, - const Data::SeriesIndex &index, - std::optional pos) + const Data::SeriesIndex &index) { - return channels[id].addSeries(index, pos); + return channels[id].addSeries(index); } bool Channels::removeSeries(const ChannelId &id, diff --git a/src/chart/options/channels.h b/src/chart/options/channels.h index 9f060714e..449d6cfbe 100644 --- a/src/chart/options/channels.h +++ b/src/chart/options/channels.h @@ -40,10 +40,8 @@ class Channels Channel &at(const ChannelId &id); [[nodiscard]] ChannelId getEmptyAxisId() const; - std::pair addSeries( - const ChannelId &id, - const Data::SeriesIndex &index, - std::optional pos = std::nullopt); + std::pair + addSeries(const ChannelId &id, const Data::SeriesIndex &index); bool removeSeries(const Data::SeriesIndex &index); bool removeSeries(const ChannelId &id, const Data::SeriesIndex &index); diff --git a/src/data/datacube/datacube.cpp b/src/data/datacube/datacube.cpp index 115f60f3e..1c5371577 100644 --- a/src/data/datacube/datacube.cpp +++ b/src/data/datacube/datacube.cpp @@ -38,7 +38,7 @@ DataCube::DataCube(const DataTable &table, auto series = options.getMeasures(); - if (series.empty()) series.emplace_back(SeriesType::Exists); + if (series.empty()) series.emplace(SeriesType::Exists); data = Data(sizes, DataCubeCell(series)); @@ -53,20 +53,19 @@ DataCube::DataCube(const DataTable &table, auto index = getIndex(row, options.getDimensions(), rowIdx); - for (auto idx = 0U; idx < series.size(); ++idx) { - auto value = series[idx].getType().isReal() - ? row[series[idx].getColIndex().value()] - : 0.0; - + for (std::size_t idx{}; auto &seriesIdx : series) { if (filter.match(RowWrapper(table, row))) data.at(index).subCells[idx].add( - static_cast(value)); + seriesIdx.getType().isReal() + ? double{row[seriesIdx.getColIndex().value()]} + : double{}); + ++idx; } } } MultiIndex DataCube::getIndex(const DataTable::Row &row, - const std::vector &indices, + const std::set &indices, size_t rowIndex) { MultiIndex index; @@ -241,8 +240,7 @@ MultiDim::SubSliceIndex DataCube::subSliceIndex( auto colIdx = table->getColumn(pair.first); auto seriesIdx = table->getIndex(colIdx); auto valIdx = - table->getInfo(colIdx).dimensionValueIndexes().at( - pair.second); + table->getInfo(colIdx).dimensionValueAt(pair.second); auto dimIdx = getDimBySeries(SeriesIndex(seriesIdx)); index.push_back( MultiDim::SliceIndex{dimIdx, MultiDim::Index{valIdx}}); diff --git a/src/data/datacube/datacube.h b/src/data/datacube/datacube.h index 1f2275d45..c44b66489 100644 --- a/src/data/datacube/datacube.h +++ b/src/data/datacube/datacube.h @@ -37,8 +37,6 @@ class DataCube public: using Data = MultiDim::Array; - DataCube() = default; - DataCube(const DataTable &table, const DataCubeOptions &options, const Filter &filter = Filter()); @@ -101,7 +99,7 @@ class DataCube std::vector seriesBySubIndex; static MultiDim::MultiIndex getIndex(const DataTable::Row &row, - const std::vector &indices, + const std::set &indices, size_t rowIndex); [[nodiscard]] MultiDim::SubSliceIndex inverseSubSliceIndex( diff --git a/src/data/datacube/datacubecell.h b/src/data/datacube/datacubecell.h index a371af442..2b2f1816a 100644 --- a/src/data/datacube/datacubecell.h +++ b/src/data/datacube/datacubecell.h @@ -1,6 +1,7 @@ #ifndef DATACUBECELL_H #define DATACUBECELL_H +#include #include #include "aggregator.h" @@ -14,7 +15,7 @@ class DataCubeCell public: DataCubeCell() = default; - explicit DataCubeCell(const std::vector &indices) + explicit DataCubeCell(const std::set &indices) { for (const auto &index : indices) subCells.emplace_back(index.getType().aggregatorType()); diff --git a/src/data/datacube/datacubeoptions.h b/src/data/datacube/datacubeoptions.h index 9dd5f0eb7..0262574b9 100644 --- a/src/data/datacube/datacubeoptions.h +++ b/src/data/datacube/datacubeoptions.h @@ -13,27 +13,25 @@ class DataCubeOptions { public: using IndexSet = std::set; - using IndexVector = std::vector; - DataCubeOptions(const IndexSet &dims, const IndexSet &msrs) - { - dimensions.insert(dimensions.end(), dims.begin(), dims.end()); - measures.insert(measures.end(), msrs.begin(), msrs.end()); - } + DataCubeOptions(IndexSet &&dims, IndexSet &&msrs) : + dimensions(std::move(dims)), + measures(std::move(msrs)) + {} - [[nodiscard]] const IndexVector &getDimensions() const + [[nodiscard]] const IndexSet &getDimensions() const { return dimensions; } - [[nodiscard]] const IndexVector &getMeasures() const + [[nodiscard]] const IndexSet &getMeasures() const { return measures; } private: - IndexVector dimensions; - IndexVector measures; + IndexSet dimensions; + IndexSet measures; }; } diff --git a/src/data/datacube/datastat.cpp b/src/data/datacube/datastat.cpp index dd822b994..2e3c1e7b9 100644 --- a/src/data/datacube/datastat.cpp +++ b/src/data/datacube/datastat.cpp @@ -16,8 +16,7 @@ DataStat::DataStat(const DataTable &table, usedColumnIDs.insert( {static_cast(idx.getColIndex().value()), usedValues.size()}); - usedValues.emplace_back(); - usedValues.back().resize(valueCnt); + usedValues.emplace_back().resize(valueCnt); } } @@ -39,13 +38,13 @@ size_t DataStat::usedValueCntOf(const SeriesIndex &index) const } void DataStat::trackIndex(const DataTable::Row &row, - const std::vector &indices) + const std::set &indices) { - for (auto i = 0U; i < indices.size(); ++i) { - const auto &idx = indices[i]; + for (std::size_t i{}; const auto &idx : indices) { if (idx.getType().isReal()) usedValues[i][static_cast( row[idx.getColIndex().value()])] = true; + ++i; } } diff --git a/src/data/datacube/datastat.h b/src/data/datacube/datastat.h index 54d704118..28169032f 100644 --- a/src/data/datacube/datastat.h +++ b/src/data/datacube/datastat.h @@ -20,7 +20,7 @@ class DataStat private: void trackIndex(const DataTable::Row &row, - const std::vector &indices); + const std::set &indices); void countValues(); diff --git a/src/data/datacube/seriesindex.h b/src/data/datacube/seriesindex.h index 7df1c1573..eaa3de061 100644 --- a/src/data/datacube/seriesindex.h +++ b/src/data/datacube/seriesindex.h @@ -14,6 +14,9 @@ namespace Vizzu::Data class SeriesIndex { public: + using OptColIndex = std::remove_reference_t< + decltype(DataTable::DataIndex::value)>; + SeriesIndex() = default; explicit SeriesIndex(const SeriesType &type, const DataTable::DataIndex &dataIndex = @@ -21,10 +24,7 @@ class SeriesIndex explicit SeriesIndex(const DataTable::DataIndex &dataIndex); SeriesIndex(const std::string &str, const DataTable &table); - [[nodiscard]] std::optional getColIndex() const - { - return index; - } + [[nodiscard]] OptColIndex getColIndex() const { return index; } [[nodiscard]] SeriesType getType() const { return type; } bool operator<(const SeriesIndex &other) const @@ -39,7 +39,7 @@ class SeriesIndex [[nodiscard]] std::string toString() const; private: - std::optional index; + OptColIndex index; SeriesType type; void set(const DataTable::DataIndex &dataIndex); }; diff --git a/src/data/table/columninfo.cpp b/src/data/table/columninfo.cpp index 9b05377b2..2447d3f17 100644 --- a/src/data/table/columninfo.cpp +++ b/src/data/table/columninfo.cpp @@ -60,10 +60,10 @@ ColumnInfo::ContiType ColumnInfo::getContiType() const return contiType; } -const ColumnInfo::ValueIndexes & -ColumnInfo::dimensionValueIndexes() const +std::uint64_t ColumnInfo::dimensionValueAt( + const std::string &name) const { - return valueIndexes; + return valueIndexes.at(name); } const ColumnInfo::Values &ColumnInfo::categories() const diff --git a/src/data/table/columninfo.h b/src/data/table/columninfo.h index ade1161b8..53bc84335 100644 --- a/src/data/table/columninfo.h +++ b/src/data/table/columninfo.h @@ -32,7 +32,8 @@ class ColumnInfo void reset(); [[nodiscard]] Type getType() const; [[nodiscard]] ContiType getContiType() const; - [[nodiscard]] const ValueIndexes &dimensionValueIndexes() const; + [[nodiscard]] std::uint64_t dimensionValueAt( + const std::string &str) const; [[nodiscard]] const Values &categories() const; [[nodiscard]] std::string getName() const; diff --git a/src/data/table/datatable.h b/src/data/table/datatable.h index 992d1198d..576269f12 100644 --- a/src/data/table/datatable.h +++ b/src/data/table/datatable.h @@ -89,8 +89,7 @@ class CellWrapperOld double operator[](const std::string &val) const { - return static_cast( - info.dimensionValueIndexes().at(val)); + return static_cast(info.dimensionValueAt(val)); } [[nodiscard]] const char *dimensionValue() const @@ -126,12 +125,6 @@ class RowWrapper return {row[colIndex], table.getInfo(colIndex)}; } - CellWrapper operator[](ColumnIndex colIndex) const - { - const auto &info = table.getInfo(colIndex); - return {row[colIndex], info}; - } - [[nodiscard]] size_t size() const { return row.size(); } private: From 4c7038084163c2de42a43162bd7a1747d26c50e8 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 13 Mar 2024 16:02:13 +0100 Subject: [PATCH 099/253] cppcheck is more of a headache than a helper --- tools/ci/run/pkg-build-desktop-clangtidy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/run/pkg-build-desktop-clangtidy.sh b/tools/ci/run/pkg-build-desktop-clangtidy.sh index 2bbf95b1e..777fe124f 100755 --- a/tools/ci/run/pkg-build-desktop-clangtidy.sh +++ b/tools/ci/run/pkg-build-desktop-clangtidy.sh @@ -11,7 +11,7 @@ if [ -z "$1" ]; then fi mkdir -p build/cmake-desktop pushd build/cmake-desktop -cmake -Dclangtidy:BOOL="ON" -Dcppcheck:BOOL="ON" ../../project/cmake/ +cmake -Dclangtidy:BOOL="ON" ../../project/cmake/ cmake --build . --target vizzutest -- --jobs=$JOBS popd From cc536c8081aeda5763f6e577bc1863300b840e71 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 13 Mar 2024 16:34:53 +0100 Subject: [PATCH 100/253] move filter out of 'for'. Fix clang tidy --- src/data/datacube/datacube.cpp | 16 +++++++--------- src/data/table/columninfo.cpp | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/data/datacube/datacube.cpp b/src/data/datacube/datacube.cpp index 1c5371577..b782be71f 100644 --- a/src/data/datacube/datacube.cpp +++ b/src/data/datacube/datacube.cpp @@ -51,16 +51,14 @@ DataCube::DataCube(const DataTable &table, for (auto rowIdx = 0U; rowIdx < table.getRowCount(); ++rowIdx) { const auto &row = table[rowIdx]; - auto index = getIndex(row, options.getDimensions(), rowIdx); + if (!filter.match(RowWrapper(table, row))) continue; - for (std::size_t idx{}; auto &seriesIdx : series) { - if (filter.match(RowWrapper(table, row))) - data.at(index).subCells[idx].add( - seriesIdx.getType().isReal() - ? double{row[seriesIdx.getColIndex().value()]} - : double{}); - ++idx; - } + auto index = getIndex(row, options.getDimensions(), rowIdx); + for (std::size_t idx{}; const auto &seriesIdx : series) + data.at(index).subCells[idx++].add( + seriesIdx.getType().isReal() + ? double{row[seriesIdx.getColIndex().value()]} + : double{}); } } diff --git a/src/data/table/columninfo.cpp b/src/data/table/columninfo.cpp index 2447d3f17..a98b6b885 100644 --- a/src/data/table/columninfo.cpp +++ b/src/data/table/columninfo.cpp @@ -61,9 +61,9 @@ ColumnInfo::ContiType ColumnInfo::getContiType() const } std::uint64_t ColumnInfo::dimensionValueAt( - const std::string &name) const + const std::string &str) const { - return valueIndexes.at(name); + return valueIndexes.at(str); } const ColumnInfo::Values &ColumnInfo::categories() const From 3fa6d606c84a220e6e9d88e522e3a099c42892bd Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Wed, 13 Mar 2024 23:23:59 +0100 Subject: [PATCH 101/253] Chart onChange and rendering connection moved to TS API --- src/apps/weblib/cinterface.cpp | 10 +++----- src/apps/weblib/cinterface.h | 2 +- src/apps/weblib/interface.cpp | 20 ++++++++++------ src/apps/weblib/interface.h | 4 +--- src/apps/weblib/interface.js | 7 ++++-- src/apps/weblib/interfacejs.h | 3 ++- src/apps/weblib/ts-api/chart.ts | 29 ++++++++++++++++-------- src/apps/weblib/ts-api/cvizzu.types.d.ts | 6 +++-- src/apps/weblib/ts-api/module/cchart.ts | 14 +++++------- src/apps/weblib/ts-api/module/chart.ts | 6 +++++ src/apps/weblib/ts-api/module/module.ts | 10 ++++++++ src/apps/weblib/ts-api/vizzu.ts | 1 + src/base/gui/widget.h | 1 - src/chart/ui/chart.cpp | 2 -- src/chart/ui/chart.h | 6 ----- 15 files changed, 72 insertions(+), 49 deletions(-) create mode 100644 src/apps/weblib/ts-api/module/chart.ts diff --git a/src/apps/weblib/cinterface.cpp b/src/apps/weblib/cinterface.cpp index d5dd8ef47..84dc67e1e 100644 --- a/src/apps/weblib/cinterface.cpp +++ b/src/apps/weblib/cinterface.cpp @@ -185,14 +185,10 @@ void vizzu_update(APIHandles::Chart chart, double width, double height, double timeInMSecs, - int renderControl) + bool render) { - return Interface::getInstance().update(chart, - canvas, - width, - height, - timeInMSecs, - static_cast(renderControl)); + return Interface::getInstance() + .update(chart, canvas, width, height, timeInMSecs, render); } const char *style_getList() { return Interface::getStyleList(); } diff --git a/src/apps/weblib/cinterface.h b/src/apps/weblib/cinterface.h index a76732401..2da382a5c 100644 --- a/src/apps/weblib/cinterface.h +++ b/src/apps/weblib/cinterface.h @@ -67,7 +67,7 @@ extern void vizzu_update(APIHandles::Chart chart, double width, double height, double timeInMSecs, - int renderControl); + bool render); extern const char *vizzu_errorMessage( APIHandles::Exception exceptionPtr, const std::type_info *typeinfo); diff --git a/src/apps/weblib/interface.cpp b/src/apps/weblib/interface.cpp index 57d573d72..29ed943cb 100644 --- a/src/apps/weblib/interface.cpp +++ b/src/apps/weblib/interface.cpp @@ -329,12 +329,19 @@ ObjectRegistry::Handle Interface::createChart() { auto &&widget = std::make_shared(); - widget->openUrl = [&](const std::string &url) + auto handle = objects.reg(std::move(widget)); + + widget->openUrl = [handle](const std::string &url) + { + ::chart_openUrl(handle, url.c_str()); + }; + + widget->doChange = [handle]() { - ::openUrl(url.c_str()); + ::chart_doChange(handle); }; - return objects.reg(std::move(widget)); + return handle; } ObjectRegistry::Handle Interface::createCanvas() @@ -353,7 +360,7 @@ void Interface::update(ObjectRegistry::Handle chart, double width, double height, double timeInMSecs, - RenderControl renderControl) + bool render) { auto &&widget = objects.get(chart); @@ -367,9 +374,8 @@ void Interface::update(ObjectRegistry::Handle chart, widget->getChart().getAnimControl().update(time); - if (const Geom::Size size{width, height}; - renderControl == force - || (renderControl == allow && widget->needsUpdate(size))) { + if (render) { + const Geom::Size size{width, height}; auto ptr = objects.get(canvas); ptr->frameBegin(); widget->onUpdateSize(size); diff --git a/src/apps/weblib/interface.h b/src/apps/weblib/interface.h index 608ff4fce..3c2c08ec8 100644 --- a/src/apps/weblib/interface.h +++ b/src/apps/weblib/interface.h @@ -15,8 +15,6 @@ class Interface public: static Interface &getInstance(); - enum RenderControl { allow = 0, force = 1, inhibit = 2 }; - Interface(); static const char *version(); ObjectRegistry::Handle createChart(); @@ -48,7 +46,7 @@ class Interface double width, double height, double timeInMSecs, - RenderControl renderControl); + bool render); ObjectRegistry::Handle storeAnim(ObjectRegistry::Handle chart); void restoreAnim(ObjectRegistry::Handle chart, diff --git a/src/apps/weblib/interface.js b/src/apps/weblib/interface.js index 26ac0a794..e69d76511 100644 --- a/src/apps/weblib/interface.js +++ b/src/apps/weblib/interface.js @@ -1,6 +1,9 @@ mergeInto(LibraryManager.library, { - openUrl: function (url) { - window.open(UTF8ToString(url), '_blank') + chart_openUrl: function (chart, url) { + Module.charts[chart].openUrl(url) + }, + chart_doChange: function (chart) { + Module.charts[chart].doChange() }, textBoundary: function (font, text, sizeX, sizeY) { const dc = Module.measureCanvas diff --git a/src/apps/weblib/interfacejs.h b/src/apps/weblib/interfacejs.h index 0c037a6c2..1db3a6354 100644 --- a/src/apps/weblib/interfacejs.h +++ b/src/apps/weblib/interfacejs.h @@ -2,7 +2,8 @@ #define INTERFACEJS_H extern "C" { -extern void openUrl(const char *); +extern void chart_openUrl(const void *, const char *); +extern void chart_doChange(const void *); extern void textBoundary(const char *, const char *, double *, double *); } diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index 32c54150f..1bfa5db1d 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -3,6 +3,7 @@ import * as Config from './types/config.js' import * as Styles from './types/styles.js' import * as D from './types/data.js' import { Module } from './module/module.js' +import { Chart as ChartInterface } from './module/chart.js' import { CChart, Snapshot } from './module/cchart.js' import { CAnimControl, CAnimation } from './module/canimctrl.js' import { CData } from './module/cdata.js' @@ -26,7 +27,7 @@ import { Scheduler } from './plugins/scheduler.js' import { RenderControl } from './plugins/rendercontrol.js' import { CCanvas } from './module/ccanvas.js' -export class Chart { +export class Chart implements ChartInterface { private _options: VizzuOptions private _cChart: CChart private _cCanvas: CCanvas @@ -35,6 +36,7 @@ export class Chart { private _data: Data private _events: Events private _plugins: PluginRegistry + private _needsUpdate = true constructor(module: Module, options: VizzuOptions, plugins: PluginRegistry) { this._options = options @@ -42,6 +44,8 @@ export class Chart { this._module = module this._cChart = this._module.createChart() + this._module.registerChart(this._cChart, this) + this._cCanvas = this._module.createCanvas() this._cData = this._module.getData(this._cChart) this._data = new Data(this._cData) @@ -65,6 +69,10 @@ export class Chart { this._plugins.register(new Tooltip(), false) } + detach(): void { + this._module.unregisterChart(this._cChart) + } + start(): void { const ctx = { update: (force: boolean): void => this.updateFrame(force) @@ -84,21 +92,24 @@ export class Chart { } this._plugins.hook(Hooks.render, ctx).default((ctx) => { if (ctx.size.x >= 1 && ctx.size.y >= 1 && ctx.timeInMSecs !== null && ctx.renderer) { - const renderControl = !ctx.enable ? 2 : ctx.force ? 1 : 0 + const render = ctx.force || (ctx.enable && this._needsUpdate) ctx.renderer.canvas = this._cCanvas this._module.registerRenderer(this._cCanvas, ctx.renderer) - this._cChart.update( - this._cCanvas, - ctx.size.x, - ctx.size.y, - ctx.timeInMSecs, - renderControl - ) + this._cChart.update(this._cCanvas, ctx.size.x, ctx.size.y, ctx.timeInMSecs, render) this._module.unregisterRenderer(this._cCanvas) + this._needsUpdate = false } }) } + doChange(): void { + this._needsUpdate = true + } + + openUrl(url: number): void { + window.open(this._cChart.getString(url), '_blank') + } + async prepareAnimation( target: Anim.Keyframes | CAnimation, options?: Anim.ControlOptions & Anim.Options diff --git a/src/apps/weblib/ts-api/cvizzu.types.d.ts b/src/apps/weblib/ts-api/cvizzu.types.d.ts index 72f9d5181..db58ac127 100644 --- a/src/apps/weblib/ts-api/cvizzu.types.d.ts +++ b/src/apps/weblib/ts-api/cvizzu.types.d.ts @@ -1,4 +1,5 @@ -import { Canvas } from './module/canvas' +import { type Canvas } from './module/canvas' +import { type Chart } from './module/chart' export type CPointer = number export type CString = CPointer @@ -46,6 +47,7 @@ export interface ModuleOptions { export interface CVizzu { // decorations canvases: { [key: CPointer]: Canvas } + charts: { [key: CPointer]: Chart } measureCanvas: CanvasRenderingContext2D // members @@ -95,7 +97,7 @@ export interface CVizzu { width: number, height: number, time: number, - renderControl: number + render: boolean ): void _vizzu_errorMessage(exceptionPtr: CException, typeinfo: CTypeInfo): CString _vizzu_version(): CString diff --git a/src/apps/weblib/ts-api/module/cchart.ts b/src/apps/weblib/ts-api/module/cchart.ts index 10940f159..41e4c0507 100644 --- a/src/apps/weblib/ts-api/module/cchart.ts +++ b/src/apps/weblib/ts-api/module/cchart.ts @@ -42,15 +42,9 @@ export class CChart extends CObject { this.animOptions = this._makeAnimOptions() } - update( - cCanvas: CCanvas, - width: number, - height: number, - time: number, - renderControl: number - ): void { + update(cCanvas: CCanvas, width: number, height: number, time: number, render: boolean): void { this._cCanvas = cCanvas - this._call(this._wasm._vizzu_update)(cCanvas.getId(), width, height, time, renderControl) + this._call(this._wasm._vizzu_update)(cCanvas.getId(), width, height, time, render) } animate(callback: (ok: boolean) => void): void { @@ -127,6 +121,10 @@ export class CChart extends CObject { this._call(this._wasm._vizzu_wheel)(this._cCanvas.getId(), delta) } + getString(text: CString): string { + return this._wasm.UTF8ToString(text) + } + private _makeConfig(): CConfig { return new CConfig( this.getId, diff --git a/src/apps/weblib/ts-api/module/chart.ts b/src/apps/weblib/ts-api/module/chart.ts new file mode 100644 index 000000000..82b06d94d --- /dev/null +++ b/src/apps/weblib/ts-api/module/chart.ts @@ -0,0 +1,6 @@ +import * as C from '../cvizzu.types.js' + +export interface Chart { + doChange(): void + openUrl(url: C.CString): void +} diff --git a/src/apps/weblib/ts-api/module/module.ts b/src/apps/weblib/ts-api/module/module.ts index 7637862e0..326930f2e 100644 --- a/src/apps/weblib/ts-api/module/module.ts +++ b/src/apps/weblib/ts-api/module/module.ts @@ -8,6 +8,7 @@ import { CCanvas } from './ccanvas.js' import { CAnimControl } from './canimctrl.js' import { CCoordSystem } from './ccoordsys.js' import { Canvas } from './canvas.js' +import { Chart } from './chart.js' export class Module extends CEnv { constructor(wasm: CVizzu) { @@ -16,9 +17,18 @@ export class Module extends CEnv { if (!context2D) throw new Error('Failed to get 2D context') this._wasm.measureCanvas = context2D this._wasm.canvases = {} + this._wasm.charts = {} this.setLogging(false) } + registerChart(cChart: CChart, chart: Chart): void { + this._wasm.charts[cChart.getId()] = chart + } + + unregisterChart(cChart: CChart): void { + delete this._wasm.charts[cChart.getId()] + } + registerRenderer(cCanvas: CCanvas, canvas: Canvas): void { this._wasm.canvases[cCanvas.getId()] = canvas } diff --git a/src/apps/weblib/ts-api/vizzu.ts b/src/apps/weblib/ts-api/vizzu.ts index feeb6947f..720502539 100644 --- a/src/apps/weblib/ts-api/vizzu.ts +++ b/src/apps/weblib/ts-api/vizzu.ts @@ -286,6 +286,7 @@ export default class Vizzu { detach(): void { try { this._plugins.destruct() + this._chart?.detach() } catch (e) { console.error(`Error during plugin destruct: ${e}`) } diff --git a/src/base/gui/widget.h b/src/base/gui/widget.h index ba74caae9..68b5e8483 100644 --- a/src/base/gui/widget.h +++ b/src/base/gui/widget.h @@ -22,7 +22,6 @@ class Widget virtual void onChanged() = 0; virtual void onDraw(const std::shared_ptr &) = 0; virtual void onUpdateSize(Geom::Size) = 0; - [[nodiscard]] virtual bool needsUpdate(Geom::Size) const = 0; }; } diff --git a/src/chart/ui/chart.cpp b/src/chart/ui/chart.cpp index d03b3f8b0..249971b13 100644 --- a/src/chart/ui/chart.cpp +++ b/src/chart/ui/chart.cpp @@ -37,7 +37,6 @@ ChartWidget::~ChartWidget() void ChartWidget::onChanged() { if (doChange) doChange(); - needUpdate = true; } void ChartWidget::onPointerDown(const GUI::PointerEvent &event) @@ -81,7 +80,6 @@ void ChartWidget::onWheel(double delta) void ChartWidget::onDraw(const std::shared_ptr &canvas) { chart.draw(*canvas); - needUpdate = false; } void ChartWidget::onUpdateSize(Geom::Size size) diff --git a/src/chart/ui/chart.h b/src/chart/ui/chart.h index 39042482e..5837fc86e 100644 --- a/src/chart/ui/chart.h +++ b/src/chart/ui/chart.h @@ -31,11 +31,6 @@ class ChartWidget : public GUI::Widget [[nodiscard]] Chart &getChart() { return chart; } - [[nodiscard]] bool needsUpdate(Geom::Size size) const final - { - return needUpdate || chart.getLayout().boundary.size != size; - } - private: Chart chart; Util::EventDispatcher::event_ptr onClick; @@ -44,7 +39,6 @@ class ChartWidget : public GUI::Widget Util::EventDispatcher::event_ptr onPointerDownEvent; Util::EventDispatcher::event_ptr onPointerUpEvent; Util::EventDispatcher::event_ptr onPointerLeaveEvent; - bool needUpdate{true}; }; } From 6b8acaf7cfdd4b24a229528017dc97174b317628 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 14 Mar 2024 10:01:49 +0100 Subject: [PATCH 102/253] Replace for's indices to iterators. --- src/data/datacube/datacube.cpp | 9 ++++++--- src/data/datacube/datastat.cpp | 11 ++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/data/datacube/datacube.cpp b/src/data/datacube/datacube.cpp index b782be71f..3081a555a 100644 --- a/src/data/datacube/datacube.cpp +++ b/src/data/datacube/datacube.cpp @@ -53,9 +53,12 @@ DataCube::DataCube(const DataTable &table, if (!filter.match(RowWrapper(table, row))) continue; - auto index = getIndex(row, options.getDimensions(), rowIdx); - for (std::size_t idx{}; const auto &seriesIdx : series) - data.at(index).subCells[idx++].add( + for (auto cellIt = data.at(getIndex(row, + options.getDimensions(), + rowIdx)) + .subCells.begin(); + const auto &seriesIdx : series) + cellIt++->add( seriesIdx.getType().isReal() ? double{row[seriesIdx.getColIndex().value()]} : double{}); diff --git a/src/data/datacube/datastat.cpp b/src/data/datacube/datastat.cpp index 2e3c1e7b9..10c619fe9 100644 --- a/src/data/datacube/datastat.cpp +++ b/src/data/datacube/datastat.cpp @@ -24,7 +24,7 @@ DataStat::DataStat(const DataTable &table, const auto &row = table[rowIdx]; if (filter.match(RowWrapper(table, row))) - trackIndex(row, options.getDimensions()); + trackIndex(row, indices); } countValues(); @@ -40,12 +40,9 @@ size_t DataStat::usedValueCntOf(const SeriesIndex &index) const void DataStat::trackIndex(const DataTable::Row &row, const std::set &indices) { - for (std::size_t i{}; const auto &idx : indices) { - if (idx.getType().isReal()) - usedValues[i][static_cast( - row[idx.getColIndex().value()])] = true; - ++i; - } + for (auto it = usedValues.begin(); const auto &idx : indices) + (*it++)[static_cast(row[idx.getColIndex().value()])] = + idx.getType().isReal(); } void DataStat::countValues() From f23e92f1bf8a953917c1d18c4fd1c93ff941a61a Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 14 Mar 2024 10:30:41 +0100 Subject: [PATCH 103/253] Fix cppcheck. --- src/data/datacube/seriesindex.h | 3 +-- src/data/table/datatable.h | 4 +++- tools/ci/run/pkg-build-desktop-clangtidy.sh | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/data/datacube/seriesindex.h b/src/data/datacube/seriesindex.h index eaa3de061..04b4bd858 100644 --- a/src/data/datacube/seriesindex.h +++ b/src/data/datacube/seriesindex.h @@ -14,8 +14,7 @@ namespace Vizzu::Data class SeriesIndex { public: - using OptColIndex = std::remove_reference_t< - decltype(DataTable::DataIndex::value)>; + using OptColIndex = DataTable::DataIndex::OptColIndex; SeriesIndex() = default; explicit SeriesIndex(const SeriesType &type, diff --git a/src/data/table/datatable.h b/src/data/table/datatable.h index 576269f12..fa2343925 100644 --- a/src/data/table/datatable.h +++ b/src/data/table/datatable.h @@ -25,7 +25,9 @@ class DataTableOld : public Table struct DataIndex { - std::optional value; + using OptColIndex = std::optional; + + OptColIndex value; ColumnInfo::Type type; DataIndex(ColumnIndex value, ColumnInfo::Type type) : diff --git a/tools/ci/run/pkg-build-desktop-clangtidy.sh b/tools/ci/run/pkg-build-desktop-clangtidy.sh index 777fe124f..2bbf95b1e 100755 --- a/tools/ci/run/pkg-build-desktop-clangtidy.sh +++ b/tools/ci/run/pkg-build-desktop-clangtidy.sh @@ -11,7 +11,7 @@ if [ -z "$1" ]; then fi mkdir -p build/cmake-desktop pushd build/cmake-desktop -cmake -Dclangtidy:BOOL="ON" ../../project/cmake/ +cmake -Dclangtidy:BOOL="ON" -Dcppcheck:BOOL="ON" ../../project/cmake/ cmake --build . --target vizzutest -- --jobs=$JOBS popd From 7217b781866249ceb8bde98294cb60827ce227a0 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 14 Mar 2024 13:46:11 +0100 Subject: [PATCH 104/253] Remove predefined DataCube class + add missing static_cast --- src/data/datacube/datastat.cpp | 3 ++- src/data/table/datatable.h | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/data/datacube/datastat.cpp b/src/data/datacube/datastat.cpp index 10c619fe9..1f50ea1eb 100644 --- a/src/data/datacube/datastat.cpp +++ b/src/data/datacube/datastat.cpp @@ -32,7 +32,8 @@ DataStat::DataStat(const DataTable &table, size_t DataStat::usedValueCntOf(const SeriesIndex &index) const { - auto it = usedColumnIDs.find(index.getColIndex().value()); + auto it = usedColumnIDs.find( + static_cast(index.getColIndex().value())); if (it != usedColumnIDs.end()) return usedValueCnt[it->second]; return 0; } diff --git a/src/data/table/datatable.h b/src/data/table/datatable.h index fa2343925..bed26f57e 100644 --- a/src/data/table/datatable.h +++ b/src/data/table/datatable.h @@ -14,8 +14,6 @@ namespace Vizzu::Data enum class SortType : uint8_t { AsSeen, Natural }; -class DataCube; - class DataTableOld : public Table { using Infos = std::vector; From 187acc052e2413e3de79fa5bb125e08dc40106f0 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Thu, 14 Mar 2024 15:15:26 +0100 Subject: [PATCH 105/253] update dev dependencies --- .gitignore | 2 + Presentation.pptx | Bin 414121 -> 0 bytes package-lock.json | 511 ++++++++++++++++++++------------------ package.json | 6 +- test/e2e/tests/fixes.json | 2 +- 5 files changed, 277 insertions(+), 244 deletions(-) delete mode 100644 Presentation.pptx diff --git a/.gitignore b/.gitignore index a0345eb60..0f9a6e6bb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ dist .coverage test_report test_report.tgz +*.ppt +*.pptx __pycache__ diff --git a/Presentation.pptx b/Presentation.pptx deleted file mode 100644 index 79910da5b021caa078451953c3e2239aec6359e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 414121 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

P- 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�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

?WKLE>y8zJo859E-r|y4$cZSza{=QL3xHZy; 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

!s4A`hc&54J%u8XnsldWs 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

  • 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 T-t4VLAQecJ!x?A!4m|uH_x@== diff --git a/package-lock.json b/package-lock.json index 8a600dadb..5f60dd23e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,8 @@ "devDependencies": { "@rollup/plugin-terser": "^0.4.3", "@tsconfig/strictest": "^2.0.2", - "@typescript-eslint/eslint-plugin": "^6.7.5", - "@typescript-eslint/parser": "^6.7.5", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.0.0", "@vizzu/eslint-config": "^0.2.0", "@vizzu/prettier-config": "^0.1.0", "aggregate-error": "^5.0.0", @@ -33,7 +33,7 @@ "p-limit": "^4.0.0", "pngjs": "^7.0.0", "prettier": "^3.0.3", - "puppeteer": "^21.3.6", + "puppeteer": "^22.4.1", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-user-preferences": "^2.4.1", "rollup": "^3.29.2", @@ -57,13 +57,13 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -163,9 +163,9 @@ } }, "node_modules/@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", + "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -173,11 +173,11 @@ "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/helpers": "^7.24.0", + "@babel/parser": "^7.24.0", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -307,9 +307,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "dev": true, "engines": { "node": ">=6.9.0" @@ -367,14 +367,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", + "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", "dev": true, "dependencies": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -466,9 +466,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -655,23 +655,23 @@ } }, "node_modules/@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", + "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.23.5", @@ -680,8 +680,8 @@ "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -699,9 +699,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", @@ -810,9 +810,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1266,14 +1266,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -1289,22 +1289,22 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -1314,9 +1314,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1359,16 +1359,17 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz", - "integrity": "sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.1.0.tgz", + "integrity": "sha512-xloWvocjvryHdUjDam/ZuGMh7zn4Sn3ZAaV4Ah2e2EwEt90N3XphZlSsU3n0VDc1F7kggCjMuH0UuxfPQ5mD9w==", "dev": true, "dependencies": { "debug": "4.3.4", "extract-zip": "2.0.1", "progress": "2.0.3", - "proxy-agent": "6.3.1", - "tar-fs": "3.0.4", + "proxy-agent": "6.4.0", + "semver": "7.6.0", + "tar-fs": "3.0.5", "unbzip2-stream": "1.4.3", "yargs": "17.7.2" }, @@ -1376,7 +1377,7 @@ "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.3.0" + "node": ">=18" } }, "node_modules/@rollup/plugin-terser": { @@ -1540,18 +1541,18 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", - "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==", + "version": "20.11.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", + "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/semver": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", - "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/stack-utils": { @@ -1592,16 +1593,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", + "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/type-utils": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1617,8 +1618,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -1627,15 +1628,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4" }, "engines": { @@ -1646,7 +1647,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -1655,13 +1656,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1672,13 +1673,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", + "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/utils": "7.2.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1690,7 +1691,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -1699,9 +1700,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1712,13 +1713,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1740,17 +1741,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", + "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", "semver": "^7.5.4" }, "engines": { @@ -1761,16 +1762,16 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/types": "7.2.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -2289,12 +2290,42 @@ "dev": true }, "node_modules/bare-events": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.0.tgz", - "integrity": "sha512-Yyyqff4PIFfSuthCZqLlPISTWHmnQxoPuAvkmgzsJEmG3CesdIv6Xweayl0JkCZJSB2yYIdJyEz97tpxNhgjbg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.1.tgz", + "integrity": "sha512-9GYPpsPFvrWBkelIhOhTWtkeZxVxZOdb3VnFTCzlOo3OjvmTvzLoZFUT8kNFACx0vJej6QPney1Cf9BvzCNE/A==", "dev": true, "optional": true }, + "node_modules/bare-fs": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.2.2.tgz", + "integrity": "sha512-X9IqgvyB0/VA5OZJyb5ZstoN62AzD7YxVGog13kkfYWYqJYcK0kcqLZ6TrmH5qr4/8//ejVcX4x/a0UvaogXmA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-os": "^2.0.0", + "bare-path": "^2.0.0", + "streamx": "^2.13.0" + } + }, + "node_modules/bare-os": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.2.1.tgz", + "integrity": "sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.0.tgz", + "integrity": "sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2316,22 +2347,22 @@ ] }, "node_modules/basic-ftp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz", - "integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true, "engines": { "node": ">=10.0.0" } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -2339,7 +2370,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -2534,9 +2565,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001589", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz", - "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==", + "version": "1.0.30001597", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz", + "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==", "dev": true, "funding": [ { @@ -2600,9 +2631,9 @@ } }, "node_modules/chromium-bidi": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.8.tgz", - "integrity": "sha512-blqh+1cEQbHBKmok3rVJkBlBxt9beKBgOsxbFgs7UJcoVbbeZ+K7+6liAsjgpc8l1Xd55cQUy14fXZdGSb4zIw==", + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.12.tgz", + "integrity": "sha512-sZMgEBWKbupD0Q7lyFu8AWkrE+rs5ycE12jFkGwIgD/VS8lDPtelPlXM7LYaq4zrkZ/O2L3f4afHUHL0ICdKog==", "dev": true, "dependencies": { "mitt": "3.0.1", @@ -2998,9 +3029,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1232444", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1232444.tgz", - "integrity": "sha512-pM27vqEfxSxRkTMnF+XCmxSEb6duO5R+t8A9DEEJgy4Wz2RVanje2mmj99B6A3zv2r/qGfYlOvYznUhuokizmg==", + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", "dev": true }, "node_modules/diff-sequences": { @@ -3043,9 +3074,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.680", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.680.tgz", - "integrity": "sha512-4nToZ5jlPO14W82NkF32wyjhYqQByVaDmLy4J2/tYcAbJfgO2TKJC780Az1V13gzq4l73CJ0yuyalpXvxXXD9A==", + "version": "1.4.705", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.705.tgz", + "integrity": "sha512-LKqhpwJCLhYId2VVwEzFXWrqQI5n5zBppz1W9ehhTlfYU8CUUW6kClbN8LHF/v7flMgRdETS772nqywJ+ckVAw==", "dev": true }, "node_modules/emittery": { @@ -3103,18 +3134,18 @@ } }, "node_modules/es-abstract": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz", - "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==", + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", + "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.6", + "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.2", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", "get-intrinsic": "^1.2.4", @@ -3122,15 +3153,15 @@ "globalthis": "^1.0.3", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.1", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", "hasown": "^2.0.1", "internal-slot": "^1.0.7", "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", @@ -3143,10 +3174,10 @@ "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.1", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", "unbox-primitive": "^1.0.2", "which-typed-array": "^1.1.14" }, @@ -3275,16 +3306,16 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -3406,9 +3437,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", "dev": true, "peer": true, "dependencies": { @@ -3823,14 +3854,14 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", + "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.5.0", @@ -4314,9 +4345,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", "dev": true, "peer": true, "dependencies": { @@ -4572,9 +4603,9 @@ } }, "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -6296,12 +6327,6 @@ "node": ">=0.10.0" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7158,15 +7183,15 @@ } }, "node_modules/proxy-agent": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", - "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", @@ -7211,38 +7236,38 @@ } }, "node_modules/puppeteer": { - "version": "21.11.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-21.11.0.tgz", - "integrity": "sha512-9jTHuYe22TD3sNxy0nEIzC7ZrlRnDgeX3xPkbS7PnbdwYjl2o/z/YuCrRBwezdKpbTDTJ4VqIggzNyeRcKq3cg==", + "version": "22.4.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.4.1.tgz", + "integrity": "sha512-Mag1wRLanzwS4yEUyrDRBUgsKlH3dpL6oAfVwNHG09oxd0+ySsatMvYj7HwjynWy/S+Hg+XHLgjyC/F6CsL/lg==", "dev": true, "hasInstallScript": true, "dependencies": { - "@puppeteer/browsers": "1.9.1", + "@puppeteer/browsers": "2.1.0", "cosmiconfig": "9.0.0", - "puppeteer-core": "21.11.0" + "puppeteer-core": "22.4.1" }, "bin": { "puppeteer": "lib/esm/puppeteer/node/cli.js" }, "engines": { - "node": ">=16.13.2" + "node": ">=18" } }, "node_modules/puppeteer-core": { - "version": "21.11.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-21.11.0.tgz", - "integrity": "sha512-ArbnyA3U5SGHokEvkfWjW+O8hOxV1RSJxOgriX/3A4xZRqixt9ZFHD0yPgZQF05Qj0oAqi8H/7stDorjoHY90Q==", + "version": "22.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.4.1.tgz", + "integrity": "sha512-l9nf8NcirYOHdID12CIMWyy7dqcJCVtgVS+YAiJuUJHg8+9yjgPiG2PcNhojIEEpCkvw3FxvnyITVfKVmkWpjA==", "dev": true, "dependencies": { - "@puppeteer/browsers": "1.9.1", - "chromium-bidi": "0.5.8", + "@puppeteer/browsers": "2.1.0", + "chromium-bidi": "0.5.12", "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1232444", + "devtools-protocol": "0.0.1249869", "ws": "8.16.0" }, "engines": { - "node": ">=16.13.2" + "node": ">=18" } }, "node_modules/puppeteer-extra": { @@ -7445,9 +7470,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -7658,13 +7683,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", - "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -7821,17 +7846,17 @@ } }, "node_modules/set-function-length": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", - "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "dependencies": { - "define-data-property": "^1.1.2", + "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7937,12 +7962,12 @@ } }, "node_modules/side-channel": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", - "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.7", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", "object-inspect": "^1.13.1" @@ -8284,14 +8309,17 @@ } }, "node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", "dev": true, "dependencies": { - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/tar-stream": { @@ -8306,9 +8334,9 @@ } }, "node_modules/terser": { - "version": "5.27.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.2.tgz", - "integrity": "sha512-sHXmLSkImesJ4p5apTeT63DsV4Obe1s37qT8qvwHRmVxKTBH7Rv9Wr26VcAMmLbmk9UliiwK8z+657NyJHHy/w==", + "version": "5.29.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.2.tgz", + "integrity": "sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -8424,9 +8452,9 @@ "dev": true }, "node_modules/ts-api-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", - "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { "node": ">=16" @@ -8596,9 +8624,9 @@ } }, "node_modules/typedoc": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.8.tgz", - "integrity": "sha512-mh8oLW66nwmeB9uTa0Bdcjfis+48bAjSH3uqdzSuSawfduROQLlXw//WSNZLYDdhmMVB7YcYZicq6e8T0d271A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.12.tgz", + "integrity": "sha512-F+qhkK2VoTweDXd1c42GS/By2DvI2uDF4/EpG424dTexSHdtCH52C6IcAvMA6jR3DzAWZjHpUOW+E02kyPNUNw==", "dev": true, "dependencies": { "lunr": "^2.3.9", @@ -8613,7 +8641,7 @@ "node": ">= 16" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" } }, "node_modules/typedoc-plugin-markdown": { @@ -8638,9 +8666,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8948,16 +8976,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", - "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.6", - "call-bind": "^1.0.5", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.1" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -9045,10 +9073,13 @@ "dev": true }, "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", "dev": true, + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } diff --git a/package.json b/package.json index 5ceb6e6ee..db55b8ac8 100644 --- a/package.json +++ b/package.json @@ -127,8 +127,8 @@ "devDependencies": { "@rollup/plugin-terser": "^0.4.3", "@tsconfig/strictest": "^2.0.2", - "@typescript-eslint/eslint-plugin": "^6.7.5", - "@typescript-eslint/parser": "^6.7.5", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.0.0", "@vizzu/eslint-config": "^0.2.0", "@vizzu/prettier-config": "^0.1.0", "aggregate-error": "^5.0.0", @@ -148,7 +148,7 @@ "p-limit": "^4.0.0", "pngjs": "^7.0.0", "prettier": "^3.0.3", - "puppeteer": "^21.3.6", + "puppeteer": "^22.4.1", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-user-preferences": "^2.4.1", "rollup": "^3.29.2", diff --git a/test/e2e/tests/fixes.json b/test/e2e/tests/fixes.json index f2760851f..5612f8c32 100644 --- a/test/e2e/tests/fixes.json +++ b/test/e2e/tests/fixes.json @@ -38,7 +38,7 @@ "refs": ["b0eeac3"] }, "47977099": { - "refs": ["5f58727"] + "refs": ["680a1d0"] }, "53913538": { "refs": ["66d916e"] From 6100e30da94313188125198b7b36bd4b94ab1665 Mon Sep 17 00:00:00 2001 From: David Vegh Date: Thu, 14 Mar 2024 15:35:19 +0100 Subject: [PATCH 106/253] fix hash --- .gitignore | 1 + test/e2e/tests/fixes.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0f9a6e6bb..e8d5accd0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ test_report test_report.tgz *.ppt *.pptx +*.ipynb __pycache__ diff --git a/test/e2e/tests/fixes.json b/test/e2e/tests/fixes.json index 5612f8c32..f2760851f 100644 --- a/test/e2e/tests/fixes.json +++ b/test/e2e/tests/fixes.json @@ -38,7 +38,7 @@ "refs": ["b0eeac3"] }, "47977099": { - "refs": ["680a1d0"] + "refs": ["5f58727"] }, "53913538": { "refs": ["66d916e"] From d59b24db045f1e032dc885ac566562140a66e0b9 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 14 Mar 2024 16:31:41 +0100 Subject: [PATCH 107/253] put the markers in the middle instead of the top of the positions where bars would be --- src/chart/generator/marker.cpp | 9 +++- test/e2e/test_cases/test_cases.json | 84 ++++++++++++++--------------- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/chart/generator/marker.cpp b/src/chart/generator/marker.cpp index e01655dc3..fe8c20878 100644 --- a/src/chart/generator/marker.cpp +++ b/src/chart/generator/marker.cpp @@ -64,6 +64,9 @@ Marker::Marker(const Options &options, } auto horizontal = options.isHorizontal(); + bool lineOrCircle = options.geometry == ShapeType::line + || options.geometry == ShapeType::circle; + position.x = size.x = getValueForChannel(channels, ChannelId::x, data, @@ -71,7 +74,8 @@ Marker::Marker(const Options &options, options.subAxisOf(ChannelId::x), !horizontal && stackInhibitingShape); - spacing.x = (horizontal && options.getChannels().anyAxisSet() + spacing.x = ((horizontal || lineOrCircle) + && options.getChannels().anyAxisSet() && channels.at(ChannelId::x).isDimension()) ? 1 : 0; @@ -83,7 +87,8 @@ Marker::Marker(const Options &options, options.subAxisOf(ChannelId::y), horizontal && stackInhibitingShape); - spacing.y = (!horizontal && options.getChannels().anyAxisSet() + spacing.y = ((!horizontal || lineOrCircle) + && options.getChannels().anyAxisSet() && channels.at(ChannelId::y).isDimension()) ? 1 : 0; diff --git a/test/e2e/test_cases/test_cases.json b/test/e2e/test_cases/test_cases.json index 8090ca7c1..b80bd4d61 100644 --- a/test/e2e/test_cases/test_cases.json +++ b/test/e2e/test_cases/test_cases.json @@ -551,7 +551,7 @@ "refs": ["17b70ff"] }, "web_content/analytical_operations/distribute/newmeasure_dotplot_1": { - "refs": ["477b699"] + "refs": ["f7f0e10"] }, "web_content/analytical_operations/distribute/newmeasure_dotplot_2": { "refs": ["7d277a4"] @@ -1139,16 +1139,16 @@ "refs": ["d01b016"] }, "ww_animTiming/descartes-polar_orient/05_d-p_o_r-c-r": { - "refs": ["bf1adce"] + "refs": ["b34204e"] }, "ww_animTiming/descartes-polar_orient/06_d-p_o_c-c-c": { - "refs": ["9104d61"] + "refs": ["4886c8a"] }, "ww_animTiming/descartes-polar_orient/07_d-p_o_a-c-a": { - "refs": ["234adf8"] + "refs": ["65dce6d"] }, "ww_animTiming/descartes-polar_orient/08_d-p_o_l-c-l": { - "refs": ["8d4083d"] + "refs": ["f322c16"] }, "ww_animTiming/descartes-polar_orient/09_d-p_o_r-a-r": { "refs": ["490574c"] @@ -1214,16 +1214,16 @@ "refs": ["0463407"] }, "ww_animTiming/descartes_orientation/05_d-d_o_r-c-r": { - "refs": ["e54cc3c"] + "refs": ["f6b3bbf"] }, "ww_animTiming/descartes_orientation/06_d-d_o_c-c-c": { - "refs": ["e61f19f"] + "refs": ["d999663"] }, "ww_animTiming/descartes_orientation/07_d-d_o_a-c-a": { - "refs": ["dfce038"] + "refs": ["af2ce41"] }, "ww_animTiming/descartes_orientation/08_d-d_o_l-c-l": { - "refs": ["34f8fd2"] + "refs": ["fff1d32"] }, "ww_animTiming/descartes_orientation/09_d-d_o_r-a-r": { "refs": ["1b7d7ea"] @@ -1280,13 +1280,13 @@ "refs": ["cb78537"] }, "ww_animTiming/polar_orientation/05_p-p_o_r-c-r": { - "refs": ["1046707"] + "refs": ["234fa7b"] }, "ww_animTiming/polar_orientation/06_p-p_o_c-c-c": { - "refs": ["5c0fa3c"] + "refs": ["5b95921"] }, "ww_animTiming/polar_orientation/07_p-p_o_a-c-a": { - "refs": ["e6265f9"] + "refs": ["9d4ecb8"] }, "ww_animTiming/polar_orientation/08_p-p_o_l-c-l": { "refs": ["8b1c6a0"] @@ -1472,16 +1472,16 @@ "refs": ["69cbcbd"] }, "ww_animTiming_TESTS/descartes-polar_orient/05_d-p_o_r-c-r": { - "refs": ["43f00a0"] + "refs": ["3c2d245"] }, "ww_animTiming_TESTS/descartes-polar_orient/06_d-p_o_c-c-c": { - "refs": ["ce69c8a"] + "refs": ["ef3e63b"] }, "ww_animTiming_TESTS/descartes-polar_orient/07_d-p_o_a-c-a": { - "refs": ["2f088e8"] + "refs": ["7301e31"] }, "ww_animTiming_TESTS/descartes-polar_orient/08_d-p_o_l-c-l": { - "refs": ["fe6cdbd"] + "refs": ["75e8880"] }, "ww_animTiming_TESTS/descartes-polar_orient/09_d-p_o_r-a-r": { "refs": ["8c26a23"] @@ -1547,16 +1547,16 @@ "refs": ["27a3d5e"] }, "ww_animTiming_TESTS/descartes_orientation/05_d-d_o_r-c-r": { - "refs": ["aabbace"] + "refs": ["c76fb8e"] }, "ww_animTiming_TESTS/descartes_orientation/06_d-d_o_c-c-c": { - "refs": ["157472c"] + "refs": ["56a9d06"] }, "ww_animTiming_TESTS/descartes_orientation/07_d-d_o_a-c-a": { - "refs": ["24fc2d2"] + "refs": ["25ad999"] }, "ww_animTiming_TESTS/descartes_orientation/08_d-d_o_l-c-l": { - "refs": ["487ae15"] + "refs": ["c7fa639"] }, "ww_animTiming_TESTS/descartes_orientation/09_d-d_o_r-a-r": { "refs": ["bbfa964"] @@ -1613,13 +1613,13 @@ "refs": ["f1962c6"] }, "ww_animTiming_TESTS/polar_orientation/05_p-p_o_r-c-r": { - "refs": ["1f73c7e"] + "refs": ["e8814e5"] }, "ww_animTiming_TESTS/polar_orientation/06_p-p_o_c-c-c": { - "refs": ["cecccc8"] + "refs": ["1d781f5"] }, "ww_animTiming_TESTS/polar_orientation/07_p-p_o_a-c-a": { - "refs": ["6bbffdd"] + "refs": ["f61ab11"] }, "ww_animTiming_TESTS/polar_orientation/08_p-p_o_l-c-l": { "refs": ["a207e59"] @@ -1763,7 +1763,7 @@ "refs": ["705aa0e"] }, "ww_next_steps/next_steps/21_C_C_dotplot": { - "refs": ["2904e90"] + "refs": ["92eeaaa"] }, "ww_next_steps/next_steps/22_C_C": { "refs": ["87af60f"] @@ -1772,7 +1772,7 @@ "refs": ["4b54e54"] }, "ww_next_steps/next_steps/35_C_A_violin": { - "refs": ["25d77e1"] + "refs": ["2352b28"] }, "ww_next_steps/next_steps/38_C_L_line": { "refs": ["578d265"] @@ -1793,7 +1793,7 @@ "refs": ["80c5969"] }, "ww_next_steps/next_steps_Tests/21_C_C_dotplot": { - "refs": ["54295f5"] + "refs": ["483c9ec"] }, "ww_next_steps/next_steps_Tests/22_C_C": { "refs": ["81fcf00"] @@ -1871,7 +1871,7 @@ "refs": ["d5720b5"] }, "ww_next_steps/next_steps_byOperations/distribute/distribution_06": { - "refs": ["4ef6a21"] + "refs": ["c38881d"] }, "ww_next_steps/next_steps_byOperations/distribute/distribution_07": { "refs": ["f677f00"] @@ -2111,7 +2111,7 @@ "refs": ["8d48a66"] }, "ww_next_steps/next_steps_byOperations/wREGIEKBOL/NoFade_Promobol/8_06b_d-w_cir_1c": { - "refs": ["7174bcb"] + "refs": ["ee2089c"] }, "ww_next_steps/next_steps_byOperations/wREGIEKBOL/NoFade_Promobol/9_06b_d-w_rec_1c": { "refs": ["d486211"] @@ -2558,19 +2558,19 @@ "refs": ["f128aa0"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/04a_lin": { - "refs": ["1a5b410"] + "refs": ["3b15914"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/04b_lin": { "refs": ["0a8514e"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/05a_lin": { - "refs": ["96903d3"] + "refs": ["44e1409"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/05b_lin": { "refs": ["73c8dc9"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/06a_lin": { - "refs": ["c9ceca8"] + "refs": ["a80440a"] }, "ww_noFade/wNoFade_cases/1_des_pol/line/06b_lin": { "refs": ["665ca53"] @@ -2777,19 +2777,19 @@ "refs": ["074f490"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/04a_d-w_lin": { - "refs": ["e58f156"] + "refs": ["169f45a"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/04b_d-w_lin": { "refs": ["561fbb9"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/05a_d-w_lin": { - "refs": ["a6429e4"] + "refs": ["a4f25b5"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/05b_d-w_lin": { "refs": ["9897763"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/06a_d-w_lin": { - "refs": ["9c4e8ab"] + "refs": ["939c274"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line-rectangle/06b_d-w_lin": { "refs": ["16823b3"] @@ -2801,19 +2801,19 @@ "refs": ["1b52593"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/04a_d-w_lin": { - "refs": ["46c8ce8"] + "refs": ["ada5177"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/04b_d-w_lin": { "refs": ["7066fed"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/05a_d-w_lin": { - "refs": ["324aa85"] + "refs": ["bdf906f"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/05b_d-w_lin": { "refs": ["56454e3"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/06a_d-w_lin": { - "refs": ["e196b36"] + "refs": ["c754597"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line/06b_d-w_lin": { "refs": ["672ba90"] @@ -2825,13 +2825,13 @@ "refs": ["1124f4f"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/04a_d-w_lin_V1": { - "refs": ["c3ad3a9"] + "refs": ["4a6b422"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/04b_d-w_lin_V1": { "refs": ["0d0d4eb"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/05a_d-w_lin_V1": { - "refs": ["c8181ab"] + "refs": ["578100d"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/05b_d-w_lin_V1": { "refs": ["df70ce8"] @@ -2840,7 +2840,7 @@ "refs": ["adf156e"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/line_V1/06b_d-w_lin_V1": { - "refs": ["aa1b99e"] + "refs": ["90cefce"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/rectangle/02_d-w_rec": { "refs": ["291bf66"] @@ -3038,7 +3038,7 @@ "refs": ["25c68b8"] }, "ww_samples_for_presets/cartesian_coo_sys/21_C_C_dot_plot_chart": { - "refs": ["1466b36"] + "refs": ["16af4ef"] }, "ww_samples_for_presets/cartesian_coo_sys/22_C_C_scatter_plot": { "refs": ["7a79697"] @@ -3047,7 +3047,7 @@ "refs": ["9d8d76c"] }, "ww_samples_for_presets/cartesian_coo_sys/25_C_C_correlogram": { - "refs": ["95be630"] + "refs": ["24ede3c"] }, "ww_samples_for_presets/cartesian_coo_sys/27_C_A_area_chart": { "refs": ["edba15c"] From 809fdb869a327f5055f01b42e114eb6004f0cdfe Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 14 Mar 2024 16:39:07 +0100 Subject: [PATCH 108/253] Add changelog --- CHANGELOG.md | 4 ++++ src/chart/generator/marker.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cdb596c1..1e2e0fc98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Line and circle chats with only dimensions on x, and y axes the markers were off the axis labels. + ## [0.10.1] - 2024-03-12 ### Added diff --git a/src/chart/generator/marker.cpp b/src/chart/generator/marker.cpp index fe8c20878..e40876030 100644 --- a/src/chart/generator/marker.cpp +++ b/src/chart/generator/marker.cpp @@ -64,7 +64,7 @@ Marker::Marker(const Options &options, } auto horizontal = options.isHorizontal(); - bool lineOrCircle = options.geometry == ShapeType::line + auto lineOrCircle = options.geometry == ShapeType::line || options.geometry == ShapeType::circle; position.x = size.x = getValueForChannel(channels, From 0010a11b08d32186c65fe39e8daa13122ebab48a Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 14 Mar 2024 16:50:16 +0100 Subject: [PATCH 109/253] Add test --- test/e2e/tests/fixes.json | 3 +++ test/e2e/tests/fixes/327.mjs | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 test/e2e/tests/fixes/327.mjs diff --git a/test/e2e/tests/fixes.json b/test/e2e/tests/fixes.json index f2760851f..be46914a6 100644 --- a/test/e2e/tests/fixes.json +++ b/test/e2e/tests/fixes.json @@ -19,6 +19,9 @@ "320": { "refs": ["e4b8a2f"] }, + "327": { + "refs": ["c86fed2"] + }, "333": { "refs": ["22c1f69"] }, diff --git a/test/e2e/tests/fixes/327.mjs b/test/e2e/tests/fixes/327.mjs new file mode 100644 index 000000000..099b2bbe1 --- /dev/null +++ b/test/e2e/tests/fixes/327.mjs @@ -0,0 +1,27 @@ +const testSteps = [ + (chart) => { + const data = { + series: [ + { name: 'Foo', values: ['Alice', 'Bob', 'Ted', 'Alice', 'Bob', 'Ted'] }, + { name: 'Bar', values: ['A', 'A', 'A', 'B', 'B', 'B'] }, + { name: 'Baz', values: [1, 2, 3, 6, 5, 4] } + ] + } + + return chart.animate({ data }) + }, + (chart) => + chart.animate({ + x: 'Foo', + y: 'Bar', + size: 'Baz', + geometry: 'circle' + }), + (chart) => + chart.animate({ + geometry: 'line', + orientation: 'vertical' + }) +] + +export default testSteps From 556757ff6a06e92d3efe87386c5ec58581f0b3b7 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 14 Mar 2024 20:26:35 +0100 Subject: [PATCH 110/253] Fix polar version + add markerConnectionOrientation --- src/chart/animator/planner.cpp | 16 ++++++++++++++ src/chart/generator/marker.cpp | 29 +++++++++++++------------- src/chart/generator/marker.h | 2 +- src/chart/generator/plot.cpp | 18 +++++++++++++--- src/chart/generator/plot.h | 3 ++- test/e2e/tests/fixes.json | 2 +- test/e2e/tests/fixes/327.mjs | 38 ++++++++++++++++++++++++++++++++-- 7 files changed, 85 insertions(+), 23 deletions(-) diff --git a/src/chart/animator/planner.cpp b/src/chart/animator/planner.cpp index e89b1a09a..2fc3c02a5 100644 --- a/src/chart/animator/planner.cpp +++ b/src/chart/animator/planner.cpp @@ -410,6 +410,14 @@ bool Planner::needVertical() const != target->dimensionAxises.at( Gen::ChannelId::size))) || source->anyAxisSet != target->anyAxisSet + || (source->markerConnectionOrientation + != target->markerConnectionOrientation + && ((source->markerConnectionOrientation.value_or( + Gen::Orientation::horizontal) + == Gen::Orientation::vertical) + || (target->markerConnectionOrientation.value_or( + Gen::Orientation::horizontal) + == Gen::Orientation::vertical))) || anyMarker( [&](const auto &source, const auto &target) { @@ -434,6 +442,14 @@ bool Planner::needHorizontal() const != target->guides.at(Gen::ChannelId::x) || source->anyAxisSet != target->anyAxisSet || source->keepAspectRatio != target->keepAspectRatio + || (source->markerConnectionOrientation + != target->markerConnectionOrientation + && ((source->markerConnectionOrientation.value_or( + Gen::Orientation::vertical) + == Gen::Orientation::horizontal) + || (target->markerConnectionOrientation.value_or( + Gen::Orientation::vertical) + == Gen::Orientation::horizontal))) || anyMarker( [&](const auto &source, const auto &target) { diff --git a/src/chart/generator/marker.cpp b/src/chart/generator/marker.cpp index e40876030..7e93eae4b 100644 --- a/src/chart/generator/marker.cpp +++ b/src/chart/generator/marker.cpp @@ -66,6 +66,7 @@ Marker::Marker(const Options &options, auto horizontal = options.isHorizontal(); auto lineOrCircle = options.geometry == ShapeType::line || options.geometry == ShapeType::circle; + auto polar = options.coordSystem.get() == CoordSystem::polar; position.x = size.x = getValueForChannel(channels, ChannelId::x, @@ -74,9 +75,9 @@ Marker::Marker(const Options &options, options.subAxisOf(ChannelId::x), !horizontal && stackInhibitingShape); - spacing.x = ((horizontal || lineOrCircle) - && options.getChannels().anyAxisSet() - && channels.at(ChannelId::x).isDimension()) + spacing.x = (horizontal || (lineOrCircle && !polar)) + && options.getChannels().anyAxisSet() + && channels.at(ChannelId::x).isDimension() ? 1 : 0; @@ -87,9 +88,9 @@ Marker::Marker(const Options &options, options.subAxisOf(ChannelId::y), horizontal && stackInhibitingShape); - spacing.y = ((!horizontal || lineOrCircle) - && options.getChannels().anyAxisSet() - && channels.at(ChannelId::y).isDimension()) + spacing.y = (!horizontal || lineOrCircle) + && options.getChannels().anyAxisSet() + && channels.at(ChannelId::y).isDimension() ? 1 : 0; @@ -115,20 +116,18 @@ Marker::Marker(const Options &options, } void Marker::setNextMarker(uint64_t itemId, - Marker *marker, + Marker &marker, bool horizontal, bool main) { - if (marker) { - (main ? nextMainMarkerIdx : nextSubMarkerIdx) = marker->idx; + (main ? nextMainMarkerIdx : nextSubMarkerIdx) = marker.idx; - if (main) marker->prevMainMarkerIdx = idx; + if (main) marker.prevMainMarkerIdx = idx; - if (itemId != 0) { - double Geom::Point::*const coord = - horizontal ? &Geom::Point::x : &Geom::Point::y; - marker->position.*coord += position.*coord; - } + if (itemId != 0) { + double Geom::Point::*const coord = + horizontal ? &Geom::Point::x : &Geom::Point::y; + marker.position.*coord += position.*coord; } } diff --git a/src/chart/generator/marker.h b/src/chart/generator/marker.h index 6f05f5d15..edcf9b03c 100644 --- a/src/chart/generator/marker.h +++ b/src/chart/generator/marker.h @@ -89,7 +89,7 @@ class Marker ::Anim::Interpolated nextSubMarkerIdx; void setNextMarker(uint64_t itemId, - Marker *marker, + Marker &marker, bool horizontal, bool main); void resetSize(bool horizontal); diff --git a/src/chart/generator/plot.cpp b/src/chart/generator/plot.cpp index 6e7ce6f3d..86219c063 100644 --- a/src/chart/generator/plot.cpp +++ b/src/chart/generator/plot.cpp @@ -159,8 +159,15 @@ void Plot::generateMarkers(const Data::DataTable &table) } clearEmptyBuckets(mainBuckets, true); clearEmptyBuckets(subBuckets, false); - linkMarkers(mainBuckets, true); + auto conn = linkMarkers(mainBuckets, true); linkMarkers(subBuckets, false); + + if (conn && options->geometry.get() == ShapeType::line + && options->getChannels().at(ChannelId::x).isDimension() + && options->getChannels().at(ChannelId::y).isDimension()) { + markerConnectionOrientation.emplace( + *options->orientation.get()); + } } void Plot::generateMarkersInfo() @@ -232,10 +239,11 @@ void Plot::clearEmptyBuckets(const Buckets &buckets, bool main) } } -void Plot::linkMarkers(const Buckets &buckets, bool main) +bool Plot::linkMarkers(const Buckets &buckets, bool main) { auto sorted = sortedBuckets(buckets, main); + bool hasConnection{}; for (const auto &pair : buckets) { const auto &bucket = pair.second; @@ -246,12 +254,16 @@ void Plot::linkMarkers(const Buckets &buckets, bool main) auto iNext = (i + 1) % sorted.size(); auto idNext = sorted[iNext].first; auto indexNext = bucket.at(idNext); + auto &next = markers[indexNext]; act.setNextMarker(iNext, - &markers[indexNext], + next, options->isHorizontal() == main, main); + if (act.enabled && next.enabled && indexAct != indexNext) + hasConnection = true; } } + return hasConnection; } void Plot::normalizeXY() diff --git a/src/chart/generator/plot.h b/src/chart/generator/plot.h index beca663b9..51a0fb547 100644 --- a/src/chart/generator/plot.h +++ b/src/chart/generator/plot.h @@ -67,6 +67,7 @@ class Plot Guides guides; DimensionAxises dimensionAxises; Math::FuzzyBool keepAspectRatio; + std::optional markerConnectionOrientation; Plot(const Plot &other) = default; Plot(PlotOptionsPtr options, const Plot &other); @@ -124,7 +125,7 @@ class Plot void generateMarkers(const Data::DataTable &table); void generateMarkersInfo(); - void linkMarkers(const Buckets &buckets, bool main); + bool linkMarkers(const Buckets &buckets, bool main); void normalizeXY(); void calcMeasureAxises(const Data::DataTable &dataTable); void calcMeasureAxis(ChannelId type, diff --git a/test/e2e/tests/fixes.json b/test/e2e/tests/fixes.json index be46914a6..3b65987a9 100644 --- a/test/e2e/tests/fixes.json +++ b/test/e2e/tests/fixes.json @@ -20,7 +20,7 @@ "refs": ["e4b8a2f"] }, "327": { - "refs": ["c86fed2"] + "refs": ["e22b24c"] }, "333": { "refs": ["22c1f69"] diff --git a/test/e2e/tests/fixes/327.mjs b/test/e2e/tests/fixes/327.mjs index 099b2bbe1..37c3f9433 100644 --- a/test/e2e/tests/fixes/327.mjs +++ b/test/e2e/tests/fixes/327.mjs @@ -15,11 +15,45 @@ const testSteps = [ x: 'Foo', y: 'Bar', size: 'Baz', - geometry: 'circle' + geometry: 'line', + coordSystem: 'polar' + }), + (chart) => + chart.animate({ + orientation: 'vertical' + }), + (chart) => + chart.animate({ + geometry: 'circle', + orientation: 'horizontal' + }), + (chart) => + chart.animate( + { + orientation: 'vertical' + }, + 1 + ), + (chart) => + chart.animate( + { + geometry: 'line', + orientation: 'horizontal', + coordSystem: 'cartesian' + }, + 1 + ), + (chart) => + chart.animate({ + orientation: 'vertical' + }), + (chart) => + chart.animate({ + geometry: 'circle', + orientation: 'horizontal' }), (chart) => chart.animate({ - geometry: 'line', orientation: 'vertical' }) ] From 488af21428c90c7e5bdcfbee325eea6dce26e781 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Mon, 18 Mar 2024 15:07:18 +0100 Subject: [PATCH 111/253] Rendering fixed, update separated to update and render on wasm api --- project/cmake/weblib/emcc.txt | 1 + src/apps/weblib/cinterface.cpp | 17 ++++-- src/apps/weblib/cinterface.h | 7 +-- src/apps/weblib/interface.cpp | 33 ++++++----- src/apps/weblib/interface.h | 7 +-- src/apps/weblib/ts-api/chart.ts | 58 ++++++++++++++----- src/apps/weblib/ts-api/cvizzu.types.d.ts | 11 +--- src/apps/weblib/ts-api/module/cchart.ts | 8 ++- src/apps/weblib/ts-api/plugins.ts | 18 +++++- src/apps/weblib/ts-api/plugins/clock.ts | 4 +- .../weblib/ts-api/plugins/rendercontrol.ts | 19 +++++- 11 files changed, 121 insertions(+), 62 deletions(-) diff --git a/project/cmake/weblib/emcc.txt b/project/cmake/weblib/emcc.txt index 835d2cde8..c7941ffab 100644 --- a/project/cmake/weblib/emcc.txt +++ b/project/cmake/weblib/emcc.txt @@ -20,6 +20,7 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} \ '_vizzu_pointerLeave',\ '_vizzu_wheel',\ '_vizzu_update',\ +'_vizzu_render',\ '_vizzu_setLogging',\ '_vizzu_errorMessage',\ '_vizzu_version',\ diff --git a/src/apps/weblib/cinterface.cpp b/src/apps/weblib/cinterface.cpp index 84dc67e1e..09c02516b 100644 --- a/src/apps/weblib/cinterface.cpp +++ b/src/apps/weblib/cinterface.cpp @@ -180,15 +180,20 @@ void vizzu_wheel(APIHandles::Chart chart, return Interface::getInstance().wheel(chart, canvas, delta); } -void vizzu_update(APIHandles::Chart chart, +void vizzu_update(APIHandles::Chart chart, double timeInMSecs) +{ + return Interface::getInstance().update(chart, timeInMSecs); +} + +void vizzu_render(APIHandles::Chart chart, APIHandles::Canvas canvas, double width, - double height, - double timeInMSecs, - bool render) + double height) { - return Interface::getInstance() - .update(chart, canvas, width, height, timeInMSecs, render); + return Interface::getInstance().render(chart, + canvas, + width, + height); } const char *style_getList() { return Interface::getStyleList(); } diff --git a/src/apps/weblib/cinterface.h b/src/apps/weblib/cinterface.h index 2da382a5c..b754b93f1 100644 --- a/src/apps/weblib/cinterface.h +++ b/src/apps/weblib/cinterface.h @@ -62,12 +62,11 @@ extern void vizzu_wheel(APIHandles::Chart chart, APIHandles::Canvas canvas, double delta); extern void vizzu_setLogging(bool enable); -extern void vizzu_update(APIHandles::Chart chart, +extern void vizzu_update(APIHandles::Chart chart, double timeInMSecs); +extern void vizzu_render(APIHandles::Chart chart, APIHandles::Canvas canvas, double width, - double height, - double timeInMSecs, - bool render); + double height); extern const char *vizzu_errorMessage( APIHandles::Exception exceptionPtr, const std::type_info *typeinfo); diff --git a/src/apps/weblib/interface.cpp b/src/apps/weblib/interface.cpp index 29ed943cb..b9962bf92 100644 --- a/src/apps/weblib/interface.cpp +++ b/src/apps/weblib/interface.cpp @@ -329,7 +329,7 @@ ObjectRegistry::Handle Interface::createChart() { auto &&widget = std::make_shared(); - auto handle = objects.reg(std::move(widget)); + auto handle = objects.reg(widget); widget->openUrl = [handle](const std::string &url) { @@ -356,11 +356,7 @@ void Interface::setLogging(bool enable) } void Interface::update(ObjectRegistry::Handle chart, - ObjectRegistry::Handle canvas, - double width, - double height, - double timeInMSecs, - bool render) + double timeInMSecs) { auto &&widget = objects.get(chart); @@ -373,15 +369,24 @@ void Interface::update(ObjectRegistry::Handle chart, ::Anim::TimePoint time(nanoSecs); widget->getChart().getAnimControl().update(time); +} - if (render) { - const Geom::Size size{width, height}; - auto ptr = objects.get(canvas); - ptr->frameBegin(); - widget->onUpdateSize(size); - widget->onDraw(ptr); - ptr->frameEnd(); - } +void Interface::render(ObjectRegistry::Handle chart, + ObjectRegistry::Handle canvas, + double width, + double height) +{ + auto &&widget = objects.get(chart); + auto ptr = objects.get(canvas); + + ptr->frameBegin(); + + const Geom::Size size{width, height}; + widget->onUpdateSize(size); + + widget->onDraw(ptr); + + ptr->frameEnd(); } void Interface::pointerDown(ObjectRegistry::Handle chart, diff --git a/src/apps/weblib/interface.h b/src/apps/weblib/interface.h index 3c2c08ec8..6d9bf6463 100644 --- a/src/apps/weblib/interface.h +++ b/src/apps/weblib/interface.h @@ -41,12 +41,11 @@ class Interface void wheel(ObjectRegistry::Handle chart, ObjectRegistry::Handle canvas, double delta); - void update(ObjectRegistry::Handle chart, + void update(ObjectRegistry::Handle chart, double timeInMSecs); + void render(ObjectRegistry::Handle chart, ObjectRegistry::Handle canvas, double width, - double height, - double timeInMSecs, - bool render); + double height); ObjectRegistry::Handle storeAnim(ObjectRegistry::Handle chart); void restoreAnim(ObjectRegistry::Handle chart, diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index 1bfa5db1d..d805ef251 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -12,7 +12,13 @@ import { Events, EventType, EventHandler, EventMap } from './events.js' import { Mirrored } from './tsutils.js' import { VizzuOptions } from './vizzu.js' import { AnimControl } from './animcontrol.js' -import { PluginRegistry, Hooks, RenderContext } from './plugins.js' +import { + PluginRegistry, + Hooks, + RenderContext, + UpdateContext, + RenderControlMode +} from './plugins.js' import { Logging } from './plugins/logging.js' import { Shorthands } from './plugins/shorthands.js' import { PivotData } from './plugins/pivotdata.js' @@ -36,7 +42,7 @@ export class Chart implements ChartInterface { private _data: Data private _events: Events private _plugins: PluginRegistry - private _needsUpdate = true + private _changed = true constructor(module: Module, options: VizzuOptions, plugins: PluginRegistry) { this._options = options @@ -75,35 +81,55 @@ export class Chart implements ChartInterface { start(): void { const ctx = { - update: (force: boolean): void => this.updateFrame(force) + update: (force: boolean): void => this.updateAndRender(force) } this._plugins.hook(Hooks.start, ctx).default(() => { - this.updateFrame() + this.updateAndRender() }) } - updateFrame(force: boolean = false): void { + private updateAndRender(force: boolean = false): void { + this._update() + this._render(force) + } + + private _update(): void { + const ctx: UpdateContext = { + timeInMSecs: null + } + this._plugins.hook(Hooks.update, ctx).default((ctx) => { + if (ctx.timeInMSecs) { + this._cChart.update(ctx.timeInMSecs) + } + }) + } + + private _render(force: boolean): void { + const control = force ? RenderControlMode.forced : RenderControlMode.disabled const ctx: RenderContext = { renderer: null, - timeInMSecs: null, - enable: true, - force, + control: control, + changed: this._changed, size: { x: 0, y: 0 } } this._plugins.hook(Hooks.render, ctx).default((ctx) => { - if (ctx.size.x >= 1 && ctx.size.y >= 1 && ctx.timeInMSecs !== null && ctx.renderer) { - const render = ctx.force || (ctx.enable && this._needsUpdate) - ctx.renderer.canvas = this._cCanvas - this._module.registerRenderer(this._cCanvas, ctx.renderer) - this._cChart.update(this._cCanvas, ctx.size.x, ctx.size.y, ctx.timeInMSecs, render) - this._module.unregisterRenderer(this._cCanvas) - this._needsUpdate = false + if (ctx.size.x >= 1 && ctx.size.y >= 1 && ctx.renderer) { + const shouldRender = + ctx.control === RenderControlMode.forced || + (ctx.control === RenderControlMode.allowed && ctx.changed) + if (shouldRender) { + ctx.renderer.canvas = this._cCanvas + this._module.registerRenderer(this._cCanvas, ctx.renderer) + this._cChart.render(this._cCanvas, ctx.size.x, ctx.size.y) + this._module.unregisterRenderer(this._cCanvas) + this._changed = false + } } }) } doChange(): void { - this._needsUpdate = true + this._changed = true } openUrl(url: number): void { diff --git a/src/apps/weblib/ts-api/cvizzu.types.d.ts b/src/apps/weblib/ts-api/cvizzu.types.d.ts index db58ac127..ee75f142e 100644 --- a/src/apps/weblib/ts-api/cvizzu.types.d.ts +++ b/src/apps/weblib/ts-api/cvizzu.types.d.ts @@ -91,14 +91,9 @@ export interface CVizzu { _vizzu_pointerLeave(chart: CChartPtr, canvas: CCanvasPtr, pointerId: number): void _vizzu_wheel(chart: CChartPtr, canvas: CCanvasPtr, delta: number): void _vizzu_setLogging(enable: boolean): void - _vizzu_update( - chart: CChartPtr, - canvas: CCanvasPtr, - width: number, - height: number, - time: number, - render: boolean - ): void + _vizzu_update(chart: CChartPtr, time: number): void + _vizzu_render(chart: CChartPtr, canvas: CCanvasPtr, width: number, height: number): void + _vizzu_errorMessage(exceptionPtr: CException, typeinfo: CTypeInfo): CString _vizzu_version(): CString _data_addDimension( diff --git a/src/apps/weblib/ts-api/module/cchart.ts b/src/apps/weblib/ts-api/module/cchart.ts index 41e4c0507..4ce431290 100644 --- a/src/apps/weblib/ts-api/module/cchart.ts +++ b/src/apps/weblib/ts-api/module/cchart.ts @@ -42,9 +42,13 @@ export class CChart extends CObject { this.animOptions = this._makeAnimOptions() } - update(cCanvas: CCanvas, width: number, height: number, time: number, render: boolean): void { + update(time: number): void { + this._call(this._wasm._vizzu_update)(time) + } + + render(cCanvas: CCanvas, width: number, height: number): void { this._cCanvas = cCanvas - this._call(this._wasm._vizzu_update)(cCanvas.getId(), width, height, time, render) + this._call(this._wasm._vizzu_render)(cCanvas.getId(), width, height) } animate(callback: (ok: boolean) => void): void { diff --git a/src/apps/weblib/ts-api/plugins.ts b/src/apps/weblib/ts-api/plugins.ts index be3923cd6..0d213d4b5 100644 --- a/src/apps/weblib/ts-api/plugins.ts +++ b/src/apps/weblib/ts-api/plugins.ts @@ -10,6 +10,8 @@ import { Canvas } from './module/canvas.js' export enum Hooks { /** Called once on startup for start the rendering loop. */ start = 'start', + /** Called when updateing the chart due to time change. */ + update = 'update', /** Called on rendering. */ render = 'render', /** Called when the animate() parameters gets set in the library to prepare @@ -38,11 +40,20 @@ export interface StartContext { update: (force: boolean) => void } +export interface UpdateContext { + timeInMSecs: number | null +} + +export enum RenderControlMode { + forced = 'forced', + allowed = 'allowed', + disabled = 'disabled' +} + export interface RenderContext { renderer: (CRenderer & Canvas) | null - timeInMSecs: number | null - enable: boolean - force: boolean + control: RenderControlMode + changed: boolean size: Point } @@ -63,6 +74,7 @@ export interface RunAnimationContext { export interface HookContexts { [Hooks.start]: StartContext + [Hooks.update]: UpdateContext [Hooks.render]: RenderContext [Hooks.prepareAnimation]: PrepareAnimationContext [Hooks.registerAnimation]: RegisterAnimationContext diff --git a/src/apps/weblib/ts-api/plugins/clock.ts b/src/apps/weblib/ts-api/plugins/clock.ts index 1fba61f54..e1da8a523 100644 --- a/src/apps/weblib/ts-api/plugins/clock.ts +++ b/src/apps/weblib/ts-api/plugins/clock.ts @@ -1,4 +1,4 @@ -import { Plugin, PluginApi, PluginHooks, RenderContext } from '../plugins.js' +import { Plugin, PluginApi, PluginHooks, UpdateContext } from '../plugins.js' export interface ClockApi extends PluginApi { /** Returns the actual time in miliseconds. */ @@ -16,7 +16,7 @@ export class Clock implements Plugin { get hooks(): PluginHooks { const hooks = { - render: (ctx: RenderContext, next: () => void): void => { + update: (ctx: UpdateContext, next: () => void): void => { if (ctx.timeInMSecs === null) ctx.timeInMSecs = this._now() next() } diff --git a/src/apps/weblib/ts-api/plugins/rendercontrol.ts b/src/apps/weblib/ts-api/plugins/rendercontrol.ts index bd0a2bee1..52b0e96cf 100644 --- a/src/apps/weblib/ts-api/plugins/rendercontrol.ts +++ b/src/apps/weblib/ts-api/plugins/rendercontrol.ts @@ -1,4 +1,12 @@ -import { Plugin, PluginApi, PluginHooks, RenderContext, StartContext } from '../plugins.js' +import { + Plugin, + PluginApi, + PluginHooks, + RenderContext, + UpdateContext, + StartContext, + RenderControlMode +} from '../plugins.js' export interface RenderControlApi extends PluginApi { /** Re-renders the chart. */ @@ -27,12 +35,17 @@ export class RenderControl implements Plugin { this._update = ctx.update next() }, - render: (ctx: RenderContext, next: () => void): void => { + update: (ctx: UpdateContext, next: () => void): void => { if (this._timeInMSecs !== null) { ctx.timeInMSecs = this._timeInMSecs this._timeInMSecs = null } - ctx.enable = this._enabled + next() + }, + render: (ctx: RenderContext, next: () => void): void => { + if (ctx.control === RenderControlMode.disabled && this._enabled) { + ctx.control = RenderControlMode.allowed + } next() } } From 61eed921ec1dbdb375551535b010c8a6d4f8f10c Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Mon, 18 Mar 2024 16:23:51 +0100 Subject: [PATCH 112/253] Review fixes. --- src/apps/weblib/interface.cpp | 5 ++--- src/apps/weblib/ts-api/plugins.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/apps/weblib/interface.cpp b/src/apps/weblib/interface.cpp index b9962bf92..d4577ee59 100644 --- a/src/apps/weblib/interface.cpp +++ b/src/apps/weblib/interface.cpp @@ -377,12 +377,11 @@ void Interface::render(ObjectRegistry::Handle chart, double height) { auto &&widget = objects.get(chart); - auto ptr = objects.get(canvas); + auto &&ptr = objects.get(canvas); ptr->frameBegin(); - const Geom::Size size{width, height}; - widget->onUpdateSize(size); + widget->onUpdateSize({width, height}); widget->onDraw(ptr); diff --git a/src/apps/weblib/ts-api/plugins.ts b/src/apps/weblib/ts-api/plugins.ts index 0d213d4b5..682cf21f9 100644 --- a/src/apps/weblib/ts-api/plugins.ts +++ b/src/apps/weblib/ts-api/plugins.ts @@ -10,7 +10,7 @@ import { Canvas } from './module/canvas.js' export enum Hooks { /** Called once on startup for start the rendering loop. */ start = 'start', - /** Called when updateing the chart due to time change. */ + /** Called when updating the chart due to time change. */ update = 'update', /** Called on rendering. */ render = 'render', From 64e43b22e443818ab441a26084604bb613188b19 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Mon, 18 Mar 2024 17:08:21 +0100 Subject: [PATCH 113/253] Added SVG plugin to test for later use. --- test/e2e/utils/vizzu-svgrender.mjs | 171 +++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 test/e2e/utils/vizzu-svgrender.mjs diff --git a/test/e2e/utils/vizzu-svgrender.mjs b/test/e2e/utils/vizzu-svgrender.mjs new file mode 100644 index 000000000..387552766 --- /dev/null +++ b/test/e2e/utils/vizzu-svgrender.mjs @@ -0,0 +1,171 @@ +import { CRenderer } from '../../../dist/index.js' + +export class SvgRenderer extends CRenderer { + _svg = null + _states = [] + + get api() { + return { + getSvg: () => this._svg.svg() + } + } + + get hooks() { + const hooks = { + render: (ctx, next) => { + ctx.renderer = this + next() + } + } + return hooks + } + + get _defState() { + return { + lineWidth: 1, + brushColor: 'rgba(0, 0, 0, 1)', + lineColor: 'rgba(0, 0, 0, 1)', + font: { size: 10, family: 'sans-serif' }, + path: [], + gradient: null, + transforms: [{ a: 1, b: 0, c: 0, d: 1, e: 0.5, f: 0.5 }], + clip: [] + } + } + + get _state() { + return this._states[this._states.length - 1] + } + + save() { + this._states.push({ + ...this._state, + transforms: [...this._state.transforms], + clip: [...this._state.clip] + }) + } + + restore() { + this._states.pop() + } + + _transform(element) { + this._state.transforms.forEach((t) => element.transform(t)) + } + + _draw(element) { + element + .fill(this._state.gradient ?? this._state.brushColor) + .stroke({ width: this._state.lineWidth, color: this._state.lineColor }) + this._transform(element) + this._state.clip.forEach((c) => { + element.clipWith(c) + }) + } + + _clip(element) { + this._transform(element) + this._state.clip.push(this._svg.clip().add(element)) + } + + frameBegin() { + this._states = [this._defState] + this._svg = window.SVG() + } + + frameEnd() { + this.ready(this._svg.svg()) + } + + setClipRect(x, y, sizex, sizey) { + const rect = this._svg.rect(sizex, sizey).move(x, y) + this._clip(rect) + } + + setClipCircle(x, y, radius) { + const circle = this._svg.circle(radius * 2).move(x - radius, y - radius) + this._clip(circle) + } + + setClipPolygon() { + this._state.path.push(['z']) + const path = this._svg.path(this._state.path) + this._clip(path) + } + + setBrushColor(r, g, b, a) { + this._state.gradient = null + this._state.brushColor = 'rgba(' + r * 255 + ',' + g * 255 + ',' + b * 255 + ',' + a + ')' + } + + setLineColor(r, g, b, a) { + this._state.lineColor = 'rgba(' + r * 255 + ',' + g * 255 + ',' + b * 255 + ',' + a + ')' + } + + setLineWidth(width) { + this._state.lineWidth = width + } + + setFontStyle(font) { + const size = font.match(/([0-9.]+)px/)[1] + const family = font.match(/([0-9.]+)px (.+),/)[2] + this._state.font = { + size, + family + } + } + + beginDropShadow() {} + setDropShadowBlur(radius) {} + setDropShadowColor(r, g, b, a) {} + setDropShadowOffset(x, y) {} + endDropShadow() {} + + beginPolygon() { + this._state.path = [] + } + + addPoint(x, y) { + this._state.path.push([this._state.path.length === 0 ? 'M' : 'L', x, y]) + } + + addBezier(c0x, c0y, c1x, c1y, x, y) { + this._state.path.push(['C', c0x, c0y, c1x, c1y, x, y]) + } + + endPolygon() { + this._state.path.push(['z']) + this._draw(this._svg.path(this._state.path)) + } + + rectangle(x, y, sizex, sizey) { + this._draw(this._svg.rect(sizex, sizey).move(x, y)) + } + + circle(x, y, radius) { + this._draw(this._svg.circle(radius * 2).move(x - radius, y - radius)) + } + + line(x1, y1, x2, y2) { + this._draw(this._svg.line(x1, y1, x2, y2)) + } + + drawText(x, y, sizex, sizey, text) { + const element = this._svg + .text(text) + .font(this._state.font) + .attr('dominant-baseline', 'hanging') + this._draw(element) + } + + setGradient(x1, y1, x2, y2, gradient) { + this._state.gradient = this._svg.gradient('linear', (add) => { + gradient.stops.forEach((g) => add.stop(g.offset, g.color)) + }) + this._gradient.from(x1, y1).to(x2, y2) + } + + transform(a, b, c, d, e, f) { + this._state.transforms.push({ a, b, c, d, e, f }) + } +} From 9a1c45332ef5121b1541715554a67545adb02c51 Mon Sep 17 00:00:00 2001 From: Laszlo Simon Date: Mon, 18 Mar 2024 17:20:03 +0100 Subject: [PATCH 114/253] review fixes. --- src/apps/weblib/ts-api/chart.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index d805ef251..1cb2d0510 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -113,18 +113,19 @@ export class Chart implements ChartInterface { size: { x: 0, y: 0 } } this._plugins.hook(Hooks.render, ctx).default((ctx) => { - if (ctx.size.x >= 1 && ctx.size.y >= 1 && ctx.renderer) { - const shouldRender = - ctx.control === RenderControlMode.forced || - (ctx.control === RenderControlMode.allowed && ctx.changed) - if (shouldRender) { - ctx.renderer.canvas = this._cCanvas - this._module.registerRenderer(this._cCanvas, ctx.renderer) - this._cChart.render(this._cCanvas, ctx.size.x, ctx.size.y) - this._module.unregisterRenderer(this._cCanvas) - this._changed = false - } - } + if (ctx.size.x < 1 || ctx.size.y < 1 || !ctx.renderer) return + + const shouldRender = + ctx.control === RenderControlMode.forced || + (ctx.control === RenderControlMode.allowed && ctx.changed) + + if (!shouldRender) return + + ctx.renderer.canvas = this._cCanvas + this._module.registerRenderer(this._cCanvas, ctx.renderer) + this._cChart.render(this._cCanvas, ctx.size.x, ctx.size.y) + this._module.unregisterRenderer(this._cCanvas) + this._changed = false }) } From 99a1ea16d8045e207639259ff30d29d4e3ba705a Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 20 Mar 2024 16:32:04 +0100 Subject: [PATCH 115/253] Get record dimensions + add default sort --- src/dataframe/interface.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 81287ef7c..21cd0b300 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,21 @@ class dataframe_interface : const dataframe_interface *parent; record_identifier recordId; + + auto operator<=>(const record_type &other) const = default; + + auto get_dimensions() const + { + return std::ranges::transform_view{ + parent->get_dimensions(), + [this](std::string_view dim) + -> std::pair + { + auto &&cell = getValue(dim); + return {dim, + *std::get_if(&cell)}; + }}; + } }; virtual ~dataframe_interface() = default; From 0688eb3c6902e9ef627e76d2f92b8d94bdc57cee Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 20 Mar 2024 16:35:30 +0100 Subject: [PATCH 116/253] Fix treemap negative values --- CHANGELOG.md | 1 + src/chart/speclayout/treemap.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b74774cd1..059726392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - next() can be called multiple times from Plugin hooks - Line and circle chats with only dimensions on x, and y axes the markers were off the axis labels. +- Crash on TreeMap only with negative values ### Added diff --git a/src/chart/speclayout/treemap.cpp b/src/chart/speclayout/treemap.cpp index 060174761..268721372 100644 --- a/src/chart/speclayout/treemap.cpp +++ b/src/chart/speclayout/treemap.cpp @@ -16,11 +16,17 @@ TreeMap::TreeMap(const std::vector &sizes, for (auto j = 0U; j < sizes.size(); ++j) markers.emplace_back(j, sizes[j]); - std::sort(markers.begin(), markers.end(), SpecMarker::sizeOrder); + std::ranges::sort(markers, SpecMarker::sizeOrder); - divide(markers.begin(), markers.end(), p0, p1); + auto it = std::ranges::upper_bound(markers, + SpecMarker{0, 0.0}, + SpecMarker::sizeOrder); - std::sort(markers.begin(), markers.end(), SpecMarker::indexOrder); + divide(markers.begin(), it, p0, p1); + + while (it != markers.end()) it++->emplaceRect({0, 0}, {0, 0}); + + std::ranges::sort(markers, SpecMarker::indexOrder); } void TreeMap::divide(It begin, From 81f7e83dfa87ac907e75fb3db55dda97275dc3c6 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 20 Mar 2024 16:39:28 +0100 Subject: [PATCH 117/253] Add e2e test --- test/e2e/tests/fixes.json | 3 +++ test/e2e/tests/fixes/55278793.mjs | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 test/e2e/tests/fixes/55278793.mjs diff --git a/test/e2e/tests/fixes.json b/test/e2e/tests/fixes.json index 3b65987a9..9862851cc 100644 --- a/test/e2e/tests/fixes.json +++ b/test/e2e/tests/fixes.json @@ -48,6 +48,9 @@ }, "53978116": { "refs": ["d0d0298"] + }, + "55278793": { + "refs": ["6825f33"] } } } diff --git a/test/e2e/tests/fixes/55278793.mjs b/test/e2e/tests/fixes/55278793.mjs new file mode 100644 index 000000000..d63b979d4 --- /dev/null +++ b/test/e2e/tests/fixes/55278793.mjs @@ -0,0 +1,22 @@ +const testSteps = [ + (chart) => + chart.animate({ + data: { + "series": [ + { + "name": "D1", + "values": ["v1", "v2"] + }, + { + "name": "M1", + "values": [-1, -2] + } + ] + }, + config: { + "size": ["D1", "M1"] + } + }) +] + +export default testSteps From 36dcc05721a691da3f905bb723cd77c7df061f5c Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 21 Mar 2024 09:12:22 +0100 Subject: [PATCH 118/253] Previous PR later review --- src/chart/generator/plot.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/chart/generator/plot.cpp b/src/chart/generator/plot.cpp index 86219c063..c331759e1 100644 --- a/src/chart/generator/plot.cpp +++ b/src/chart/generator/plot.cpp @@ -159,10 +159,11 @@ void Plot::generateMarkers(const Data::DataTable &table) } clearEmptyBuckets(mainBuckets, true); clearEmptyBuckets(subBuckets, false); - auto conn = linkMarkers(mainBuckets, true); + auto hasMarkerConnection = linkMarkers(mainBuckets, true); linkMarkers(subBuckets, false); - if (conn && options->geometry.get() == ShapeType::line + if (hasMarkerConnection + && options->geometry.get() == ShapeType::line && options->getChannels().at(ChannelId::x).isDimension() && options->getChannels().at(ChannelId::y).isDimension()) { markerConnectionOrientation.emplace( From 471716b3fefc22f3af13ddcb188061572de42f6d Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 22 Mar 2024 14:05:21 +0100 Subject: [PATCH 119/253] Contains nan/nav + fix aggregation --- src/dataframe/impl/data_source.cpp | 51 +++++++++++++++++++++++------- src/dataframe/impl/data_source.h | 11 +++++-- src/dataframe/impl/dataframe.cpp | 20 +++++++----- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/dataframe/impl/data_source.cpp b/src/dataframe/impl/data_source.cpp index 0698f33c0..257ed9d21 100644 --- a/src/dataframe/impl/data_source.cpp +++ b/src/dataframe/impl/data_source.cpp @@ -308,10 +308,18 @@ data_source::const_series_data data_source::get_series( void data_source::normalize_sizes() { - std::size_t record_count = get_record_count(); + auto record_count = get_record_count(); for (auto &&dim : dimensions) - dim.values.resize(record_count, nav); - for (auto &&mea : measures) mea.values.resize(record_count, nan); + if (dim.values.size() < record_count) { + dim.values.resize(record_count, nav); + dim.contains_nav = true; + } + + for (auto &&mea : measures) + if (mea.values.size() < record_count) { + mea.values.resize(record_count, nan); + mea.contains_nan = true; + } } void data_source::remove_series(std::span dims, @@ -396,8 +404,16 @@ data_source::measure_t &data_source::add_new_measure(measure_t &&mea, void data_source::remove_records(std::span indices) { const index_erase_if indices_remover{indices}; - for (auto &&dim : dimensions) indices_remover(dim.values); - for (auto &&mea : measures) indices_remover(mea.values); + for (auto &&dim : dimensions) { + indices_remover(dim.values); + dim.contains_nav = std::ranges::any_of(dim.values, + std::bind_front(std::equal_to{}, nav)); + } + for (auto &&mea : measures) { + indices_remover(mea.values); + mea.contains_nan = std::ranges::any_of(mea.values, + static_cast(std::isnan)); + } } struct aggregating_helper @@ -485,6 +501,10 @@ data_source::data_source(aggregating_type &&aggregating, ++ix; } } + + for (auto &mea : measures) + mea.contains_nan = std::ranges::any_of(mea.values, + static_cast(std::isnan)); } data_source::data_source( @@ -597,7 +617,8 @@ void data_source::dimension_t::sort_by( void data_source::dimension_t::add_element( std::string_view const &cat) { - values.emplace_back(get_or_set_cat(cat)); + if (values.emplace_back(get_or_set_cat(cat)) == nav) + contains_nav = true; } void data_source::dimension_t::add_more_data( std::span new_categories, @@ -609,28 +630,34 @@ void data_source::dimension_t::add_more_data( } for (const auto val : new_values) values.emplace_back(remap[val]); + + if (!contains_nav) + contains_nav = std::ranges::any_of(new_values, + std::bind_front(std::equal_to{}, nav)); } std::string_view data_source::dimension_t::get( std::size_t index) const { - return values[index] == data_source::nav - ? std::string_view{} - : categories[values[index]]; + return values[index] == nav ? std::string_view{} + : categories[values[index]]; } void data_source::dimension_t::set(std::size_t index, std::string_view value) { values[index] = - value.data() ? get_or_set_cat(value) : data_source::nav; + value.data() != nullptr ? get_or_set_cat(value) : nav; } void data_source::dimension_t::set_nav(std::string_view value) { + if (!contains_nav) return; if (value.data() == nullptr) value = ""; auto ix = get_or_set_cat(value); for (auto &val : values) - if (val == data_source::nav) val = ix; + if (val == nav) val = ix; + + contains_nav = false; } std::uint32_t data_source::dimension_t::get_or_set_cat( std::string_view cat) @@ -644,7 +671,7 @@ std::uint32_t data_source::dimension_t::get_or_set_cat( const double &data_source::measure_t::get(std::size_t index) const { - return index < values.size() ? nan : values[index]; + return index < values.size() ? values[index] : nan; } std::pair data_source::measure_t::get_min_max() const diff --git a/src/dataframe/impl/data_source.h b/src/dataframe/impl/data_source.h index 868eba6f1..acba2fdef 100644 --- a/src/dataframe/impl/data_source.h +++ b/src/dataframe/impl/data_source.h @@ -1,6 +1,7 @@ #ifndef VIZZU_DATAFRAME_DATA_SOURCE_H #define VIZZU_DATAFRAME_DATA_SOURCE_H +#include #include #include #include @@ -34,6 +35,7 @@ class data_source : public std::enable_shared_from_this na_position na_pos{na_position::last}; std::vector values; std::map info; + bool contains_nav; dimension_t() noexcept = default; @@ -43,7 +45,9 @@ class data_source : public std::enable_shared_from_this Range3 &&info) : categories(std::begin(categories), std::end(categories)), values(std::begin(values), std::end(values)), - info(std::begin(info), std::end(info)) + info(std::begin(info), std::end(info)), + contains_nav{std::ranges::any_of(this->values, + std::bind_front(std::equal_to{}, nav))} {} void add_more_data(std::span categories, @@ -70,13 +74,16 @@ class data_source : public std::enable_shared_from_this { std::vector values; std::map info; + bool contains_nan; measure_t() noexcept = default; template measure_t(Range1 &&values, Range2 &&info) : values(std::begin(values), std::end(values)), - info(std::begin(info), std::end(info)) + info(std::begin(info), std::end(info)), + contains_nan(std::ranges::any_of(this->values, + static_cast(std::isnan))) {} const double &get(std::size_t index) const; diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp index ea0418e58..d763c1a1b 100644 --- a/src/dataframe/impl/dataframe.cpp +++ b/src/dataframe/impl/dataframe.cpp @@ -161,9 +161,7 @@ void dataframe::set_sort(series_identifier series, if (const auto &dim = unsafe_get(ser).second; std::ranges::is_sorted( indices.emplace(dim.get_indices(sort))) - && (na_pos == dim.na_pos - || std::ranges::find(dim.values, data_source::nav) - == dim.values.end())) + && (na_pos == dim.na_pos || !dim.contains_nav)) break; if (source == source_type::copying) migrate_data(); @@ -592,8 +590,11 @@ void dataframe::add_record(std::span values) & for (std::size_t measureIx{}, dimensionIx{}; const auto &v : values) { - if (const double *measure = std::get_if(&v)) - s.measures[measureIx++].values.emplace_back(*measure); + if (const double *measure = std::get_if(&v)) { + auto &mea = s.measures[measureIx++]; + mea.values.emplace_back(*measure); + if (std::isnan(*measure)) mea.contains_nan = true; + } else { s.dimensions[dimensionIx++].add_element( *std::get_if(&v)); @@ -733,10 +734,13 @@ void dataframe::fill_na(series_identifier column, cell_value value) & column)) { using enum series_type; default: break; - case measure: - for (auto &v : unsafe_get(ser).second.values) - if (std::isnan(v)) v = *d; + case measure: { + auto &meas = unsafe_get(ser).second; + if (std::exchange(meas.contains_nan, false)) + for (auto &v : meas.values) + if (std::isnan(v)) v = *d; break; + } case dimension: unsafe_get(ser).second.set_nav( *std::get_if(&value)); From 8634552eafae681c5a02f59a53eff3dc04494883 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 22 Mar 2024 17:52:15 +0100 Subject: [PATCH 120/253] custom_aggregator comparator, sort by categories --- src/dataframe/impl/data_source.cpp | 3 ++- src/dataframe/impl/dataframe.cpp | 9 +++++++-- src/dataframe/interface.h | 23 ++++++++++++++++++++++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/dataframe/impl/data_source.cpp b/src/dataframe/impl/data_source.cpp index 257ed9d21..32d9e14cf 100644 --- a/src/dataframe/impl/data_source.cpp +++ b/src/dataframe/impl/data_source.cpp @@ -552,7 +552,8 @@ data_source::final_info::final_info(const data_source &source) : for (std::size_t d{}; d < dimensions; ++d) { auto val = source.dimensions[d].values[r]; record += - source.dimension_names[d] + ':' + source.dimension_names[d] + '[' + + std::to_string(static_cast(val)) + "]:" + (val == nav ? "null" : source.dimensions[d].categories[val]) + ";"; diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp index d763c1a1b..28d5e2161 100644 --- a/src/dataframe/impl/dataframe.cpp +++ b/src/dataframe/impl/dataframe.cpp @@ -159,8 +159,9 @@ void dataframe::set_sort(series_identifier series, case dimension: { std::optional> indices; if (const auto &dim = unsafe_get(ser).second; - std::ranges::is_sorted( - indices.emplace(dim.get_indices(sort))) + ((sort_ptr && *sort_ptr == sort_type::by_categories) + || std::ranges::is_sorted( + indices.emplace(dim.get_indices(sort)))) && (na_pos == dim.na_pos || !dim.contains_nav)) break; @@ -180,12 +181,16 @@ void dataframe::set_sort(series_identifier series, throw std::runtime_error( "Measure series cannot be sorted by categories."); switch (*sort_ptr) { + default: case sort_type::less: case sort_type::greater: break; case sort_type::natural_less: case sort_type::natural_greater: throw std::runtime_error( "Measure series cannot be sorted by natural order."); + case sort_type::by_categories: + throw std::runtime_error( + "Measure series cannot be sorted by categories."); } break; } diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 21cd0b300..99d36b858 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -24,7 +24,13 @@ enum class aggregator_type { exists }; -enum class sort_type { less, greater, natural_less, natural_greater }; +enum class sort_type { + less, + greater, + natural_less, + natural_greater, + by_categories +}; enum class na_position { last, first }; @@ -47,6 +53,21 @@ struct custom_aggregator { return std::visit(std::identity{}, name); } + + auto operator<=>(const custom_aggregator &oth) const + { + return get_name() <=> oth.get_name(); + } + + auto operator!=(const custom_aggregator &oth) const + { + return get_name() != oth.get_name(); + } + + auto operator==(const custom_aggregator &oth) const + { + return get_name() == oth.get_name(); + } }; class dataframe_interface : From 067c56a38d5bbb5324af278c440c8f69ca181789 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 22 Mar 2024 18:07:26 +0100 Subject: [PATCH 121/253] fix record_type. --- src/dataframe/interface.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 99d36b858..9569aea70 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -96,16 +96,14 @@ class dataframe_interface : const dataframe_interface *parent; record_identifier recordId; - auto operator<=>(const record_type &other) const = default; - auto get_dimensions() const { return std::ranges::transform_view{ parent->get_dimensions(), - [this](std::string_view dim) + [rec = *this](std::string_view dim) -> std::pair { - auto &&cell = getValue(dim); + auto &&cell = rec.getValue(dim); return {dim, *std::get_if(&cell)}; }}; From a8122187046320d7b74d79ff8923a88cbbbdb4ee Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 22 Mar 2024 18:15:55 +0100 Subject: [PATCH 122/253] Remove unused functions + return values. Refactor datastat --- src/data/datacube/datacube.cpp | 19 ++------------ src/data/datacube/datacube.h | 2 -- src/data/datacube/datacubecell.h | 3 ++- src/data/datacube/datastat.cpp | 43 ++++++++++--------------------- src/data/datacube/datastat.h | 8 +++--- src/data/datacube/seriesindex.cpp | 7 ----- src/data/datacube/seriesindex.h | 1 - src/data/table/columnindex.h | 10 +++++++ src/data/table/columninfo.cpp | 6 ----- src/data/table/columninfo.h | 2 -- src/data/table/datatable.cpp | 11 +++----- src/data/table/datatable.h | 11 +++----- 12 files changed, 37 insertions(+), 86 deletions(-) diff --git a/src/data/datacube/datacube.cpp b/src/data/datacube/datacube.cpp index 3081a555a..1c1ffe9cf 100644 --- a/src/data/datacube/datacube.cpp +++ b/src/data/datacube/datacube.cpp @@ -72,8 +72,8 @@ MultiIndex DataCube::getIndex(const DataTable::Row &row, MultiIndex index; for (auto idx : indices) { auto indexValue = - idx.getType().isReal() - ? static_cast(row[idx.getColIndex().value()]) + idx.getType().isReal() ? static_cast( + static_cast(row[idx.getColIndex().value()])) : idx.getType() == SeriesType::Index ? rowIndex : throw std::logic_error("internal error: cannot " @@ -233,19 +233,4 @@ CellInfo DataCube::cellInfo(const MultiDim::MultiIndex &index) const return {categories(index), values(index)}; } -MultiDim::SubSliceIndex DataCube::subSliceIndex( - const MarkerIdStrings &stringMarkerId) const -{ - MultiDim::SubSliceIndex index; - for (const auto &pair : stringMarkerId) { - auto colIdx = table->getColumn(pair.first); - auto seriesIdx = table->getIndex(colIdx); - auto valIdx = - table->getInfo(colIdx).dimensionValueAt(pair.second); - auto dimIdx = getDimBySeries(SeriesIndex(seriesIdx)); - index.push_back( - MultiDim::SliceIndex{dimIdx, MultiDim::Index{valIdx}}); - } - return index; -} } \ No newline at end of file diff --git a/src/data/datacube/datacube.h b/src/data/datacube/datacube.h index c44b66489..25c612020 100644 --- a/src/data/datacube/datacube.h +++ b/src/data/datacube/datacube.h @@ -86,8 +86,6 @@ class DataCube const MultiDim::MultiIndex &index) const; [[nodiscard]] CellInfo cellInfo( const MultiDim::MultiIndex &index) const; - [[nodiscard]] MultiDim::SubSliceIndex subSliceIndex( - const MarkerIdStrings &stringMarkerId) const; private: Data data; diff --git a/src/data/datacube/datacubecell.h b/src/data/datacube/datacubecell.h index 2b2f1816a..239245e22 100644 --- a/src/data/datacube/datacubecell.h +++ b/src/data/datacube/datacubecell.h @@ -15,7 +15,8 @@ class DataCubeCell public: DataCubeCell() = default; - explicit DataCubeCell(const std::set &indices) + template + explicit DataCubeCell(const std::set &indices) { for (const auto &index : indices) subCells.emplace_back(index.getType().aggregatorType()); diff --git a/src/data/datacube/datastat.cpp b/src/data/datacube/datastat.cpp index 1f50ea1eb..869a000c2 100644 --- a/src/data/datacube/datastat.cpp +++ b/src/data/datacube/datastat.cpp @@ -1,5 +1,7 @@ #include "datastat.h" +#include + namespace Vizzu::Data { @@ -10,31 +12,23 @@ DataStat::DataStat(const DataTable &table, const auto &indices = options.getDimensions(); for (const auto &idx : indices) { if (idx.getType().isReal()) { - auto valueCnt = table.getInfo(idx.getColIndex().value()) - .categories() - .size(); - usedColumnIDs.insert( - {static_cast(idx.getColIndex().value()), - usedValues.size()}); - usedValues.emplace_back().resize(valueCnt); + usedColumnIDs.try_emplace(idx.getColIndex().value(), + usedValues.size()); + usedValues.emplace_back(); } } - for (auto rowIdx = 0U; rowIdx < table.getRowCount(); ++rowIdx) { - const auto &row = table[rowIdx]; - - if (filter.match(RowWrapper(table, row))) + for (auto rowIdx = 0U; rowIdx < table.getRowCount(); ++rowIdx) + if (const auto &row = table[rowIdx]; + filter.match(RowWrapper(table, row))) trackIndex(row, indices); - } - - countValues(); } size_t DataStat::usedValueCntOf(const SeriesIndex &index) const { - auto it = usedColumnIDs.find( - static_cast(index.getColIndex().value())); - if (it != usedColumnIDs.end()) return usedValueCnt[it->second]; + auto it = usedColumnIDs.find(index.getColIndex().value()); + if (it != usedColumnIDs.end()) + return usedValues[it->second].size(); return 0; } @@ -42,18 +36,9 @@ void DataStat::trackIndex(const DataTable::Row &row, const std::set &indices) { for (auto it = usedValues.begin(); const auto &idx : indices) - (*it++)[static_cast(row[idx.getColIndex().value()])] = - idx.getType().isReal(); -} - -void DataStat::countValues() -{ - for (const auto &values : usedValues) { - auto cnt = 0; - for (auto used : values) - if (used) ++cnt; - usedValueCnt.push_back(cnt); - } + if (idx.getType().isReal()) + it++->emplace( + Conv::toString(row[idx.getColIndex().value()])); } } \ No newline at end of file diff --git a/src/data/datacube/datastat.h b/src/data/datacube/datastat.h index 28169032f..b48326149 100644 --- a/src/data/datacube/datastat.h +++ b/src/data/datacube/datastat.h @@ -22,11 +22,9 @@ class DataStat void trackIndex(const DataTable::Row &row, const std::set &indices); - void countValues(); - - std::unordered_map usedColumnIDs; - std::vector> usedValues; - std::vector usedValueCnt; + std::unordered_map + usedColumnIDs; + std::vector> usedValues; }; } diff --git a/src/data/datacube/seriesindex.cpp b/src/data/datacube/seriesindex.cpp index 63760cfe4..329391ee1 100644 --- a/src/data/datacube/seriesindex.cpp +++ b/src/data/datacube/seriesindex.cpp @@ -70,13 +70,6 @@ std::string SeriesIndex::toString(const DataTable &table) const return type.toString() + "()"; } -std::string SeriesIndex::toString() const -{ - return type.isReal() - ? std::to_string(static_cast(index.value())) - : type.toString(); -} - void SeriesIndex::set(const DataTable::DataIndex &dataIndex) { index = dataIndex.value; diff --git a/src/data/datacube/seriesindex.h b/src/data/datacube/seriesindex.h index 04b4bd858..ef171a2ce 100644 --- a/src/data/datacube/seriesindex.h +++ b/src/data/datacube/seriesindex.h @@ -35,7 +35,6 @@ class SeriesIndex bool operator==(const SeriesIndex &other) const = default; [[nodiscard]] std::string toString(const DataTable &table) const; - [[nodiscard]] std::string toString() const; private: OptColIndex index; diff --git a/src/data/table/columnindex.h b/src/data/table/columnindex.h index 8507ed691..8a4d5f8b1 100644 --- a/src/data/table/columnindex.h +++ b/src/data/table/columnindex.h @@ -3,6 +3,7 @@ #include #include +#include #include "base/type/uniquetype.h" @@ -14,4 +15,13 @@ using ColumnIndex = } +template <> struct std::hash +{ + [[nodiscard]] constexpr auto operator()( + const Vizzu::Data::ColumnIndex &index) const noexcept + { + return static_cast(index); + } +}; + #endif diff --git a/src/data/table/columninfo.cpp b/src/data/table/columninfo.cpp index a98b6b885..4fd03f94b 100644 --- a/src/data/table/columninfo.cpp +++ b/src/data/table/columninfo.cpp @@ -60,12 +60,6 @@ ColumnInfo::ContiType ColumnInfo::getContiType() const return contiType; } -std::uint64_t ColumnInfo::dimensionValueAt( - const std::string &str) const -{ - return valueIndexes.at(str); -} - const ColumnInfo::Values &ColumnInfo::categories() const { return values; diff --git a/src/data/table/columninfo.h b/src/data/table/columninfo.h index 53bc84335..be10f6d48 100644 --- a/src/data/table/columninfo.h +++ b/src/data/table/columninfo.h @@ -32,8 +32,6 @@ class ColumnInfo void reset(); [[nodiscard]] Type getType() const; [[nodiscard]] ContiType getContiType() const; - [[nodiscard]] std::uint64_t dimensionValueAt( - const std::string &str) const; [[nodiscard]] const Values &categories() const; [[nodiscard]] std::string getName() const; diff --git a/src/data/table/datatable.cpp b/src/data/table/datatable.cpp index b1fbde020..b7855abb8 100644 --- a/src/data/table/datatable.cpp +++ b/src/data/table/datatable.cpp @@ -25,8 +25,7 @@ void DataTableOld::pushRow(const TableRow &textRow) } template -DataTableOld::DataIndex DataTableOld::addTypedColumn( - const std::string &name, +void DataTableOld::addTypedColumn(const std::string &name, const std::string &unit, const std::span &values) { @@ -81,20 +80,16 @@ DataTableOld::DataIndex DataTableOld::addTypedColumn( addRow(row); } - - return getIndex(ColumnIndex(colIndex)); } -DataTableOld::DataIndex DataTableOld::addColumn( - const std::string &name, +void DataTableOld::addColumn(const std::string &name, const std::string &unit, const std::span &values) { return addTypedColumn(name, unit, values); } -DataTableOld::DataIndex DataTableOld::addColumn( - const std::string &name, +void DataTableOld::addColumn(const std::string &name, const std::span &categories, const std::span &values) { diff --git a/src/data/table/datatable.h b/src/data/table/datatable.h index bed26f57e..791066786 100644 --- a/src/data/table/datatable.h +++ b/src/data/table/datatable.h @@ -46,10 +46,10 @@ class DataTableOld : public Table const std::string &name) const; [[nodiscard]] DataIndex getIndex(const std::string &name) const; - DataIndex addColumn(const std::string &name, + void addColumn(const std::string &name, const std::string &unit, const std::span &values); - DataIndex addColumn(const std::string &name, + void addColumn(const std::string &name, const std::span &categories, const std::span &values); @@ -65,7 +65,7 @@ class DataTableOld : public Table Infos infos; template - DataIndex addTypedColumn(const std::string &name, + void addTypedColumn(const std::string &name, const std::string &unit, const std::span &values); }; @@ -87,11 +87,6 @@ class CellWrapperOld const double &operator*() const { return value; } - double operator[](const std::string &val) const - { - return static_cast(info.dimensionValueAt(val)); - } - [[nodiscard]] const char *dimensionValue() const { return info.toDimensionString(value); From 9699e05f24db39a69bf780f070b16e7783f5492a Mon Sep 17 00:00:00 2001 From: David Vegh Date: Mon, 25 Mar 2024 10:44:00 +0100 Subject: [PATCH 123/253] set netrc permissions --- tools/ci/run/init-py.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/ci/run/init-py.sh b/tools/ci/run/init-py.sh index a431f04c4..fc6cdac3a 100755 --- a/tools/ci/run/init-py.sh +++ b/tools/ci/run/init-py.sh @@ -7,6 +7,8 @@ if ! python3 -c 'import sys; assert sys.version_info >= (3,10)' > /dev/null; the exit 1 fi +test -f ~/.netrc && chmod u+rw,u-x,go-rwx ~/.netrc + python3.10 -m venv --copies ".venv" || python3 -m venv --copies ".venv" source .venv/bin/activate pip install pdm==2.10.3 From 3a3f0059163a9e4ae2a08f7436b21fd9087b2f43 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 25 Mar 2024 10:56:01 +0100 Subject: [PATCH 124/253] fix new test format --- test/e2e/tests/fixes/55278793.mjs | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/e2e/tests/fixes/55278793.mjs b/test/e2e/tests/fixes/55278793.mjs index d63b979d4..9aaadce70 100644 --- a/test/e2e/tests/fixes/55278793.mjs +++ b/test/e2e/tests/fixes/55278793.mjs @@ -1,22 +1,22 @@ const testSteps = [ - (chart) => - chart.animate({ - data: { - "series": [ - { - "name": "D1", - "values": ["v1", "v2"] - }, - { - "name": "M1", - "values": [-1, -2] - } - ] - }, - config: { - "size": ["D1", "M1"] - } - }) + (chart) => + chart.animate({ + data: { + series: [ + { + name: 'D1', + values: ['v1', 'v2'] + }, + { + name: 'M1', + values: [-1, -2] + } + ] + }, + config: { + size: ['D1', 'M1'] + } + }) ] export default testSteps From 4562ee7f7958a54009716cdeef7da1d182d460fa Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 25 Mar 2024 11:54:16 +0100 Subject: [PATCH 125/253] fix clang-tidy... --- src/data/datacube/datafilter.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/datacube/datafilter.h b/src/data/datacube/datafilter.h index 610a17720..a4a850e44 100644 --- a/src/data/datacube/datafilter.h +++ b/src/data/datacube/datafilter.h @@ -16,9 +16,9 @@ class Filter Filter() = default; template - Filter(Fn &&function, uint64_t hash) : + Filter(Fn &&function, uint64_t hashVal) : function(std::forward(function)), - hash(hash) + hash(hashVal) {} [[nodiscard]] bool match(const RowWrapper &row) const From 73e9c846226e107db2bdfaac350895e956352d85 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 25 Mar 2024 16:40:55 +0100 Subject: [PATCH 126/253] remove unused categories + has na --- src/dataframe/impl/data_source.cpp | 36 +++++++++++++++++++-- src/dataframe/impl/data_source.h | 4 +++ src/dataframe/impl/dataframe.cpp | 45 ++++++++++++++++++++++++++ src/dataframe/impl/dataframe.h | 4 +++ src/dataframe/interface.h | 5 +++ test/unit/dataframe/interface_test.cpp | 5 +++ 6 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/dataframe/impl/data_source.cpp b/src/dataframe/impl/data_source.cpp index 32d9e14cf..0519d1b3e 100644 --- a/src/dataframe/impl/data_source.cpp +++ b/src/dataframe/impl/data_source.cpp @@ -473,9 +473,13 @@ data_source::data_source(aggregating_type &&aggregating, for (auto &m : measures) m.values.emplace_back(nan); for (std::size_t ix{}; const auto &[name, dim] : dims) - dimensions[ix++].values.emplace_back( - i < dim.get().values.size() ? dim.get().values[i] - : nav); + if (auto &newDim = dimensions[ix++]; + newDim.values.emplace_back( + i < dim.get().values.size() + ? dim.get().values[i] + : nav) + == nav) + newDim.contains_nav = true; aggregators.reserve(meas.size()); for (const auto &[ser, agg] : @@ -660,6 +664,32 @@ void data_source::dimension_t::set_nav(std::string_view value) contains_nav = false; } + +std::vector +data_source::dimension_t::get_categories_usage() const +{ + std::vector used(categories.size()); + for (const auto &val : values) + if (val != nav) used[val] = true; + + return used; +} + +void data_source::dimension_t::remove_unused_categories( + std::vector &&used) +{ + std::vector remap(categories.size()); + std::size_t new_size{}; + for (std::size_t i{}; i < categories.size(); ++i) + if (used[i]) + categories[remap[i] = new_size++] = + std::move(categories[i]); + categories.resize(new_size); + + for (auto &val : values) + if (val != nav) val = remap[val]; +} + std::uint32_t data_source::dimension_t::get_or_set_cat( std::string_view cat) { diff --git a/src/dataframe/impl/data_source.h b/src/dataframe/impl/data_source.h index acba2fdef..a60376cc9 100644 --- a/src/dataframe/impl/data_source.h +++ b/src/dataframe/impl/data_source.h @@ -68,6 +68,10 @@ class data_source : public std::enable_shared_from_this void set(std::size_t index, std::string_view value); void set_nav(std::string_view value); + + std::vector get_categories_usage() const; + + void remove_unused_categories(std::vector &&used); }; struct measure_t diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp index 28d5e2161..71e8895e6 100644 --- a/src/dataframe/impl/dataframe.cpp +++ b/src/dataframe/impl/dataframe.cpp @@ -660,6 +660,34 @@ void dataframe::remove_records( remove_ix); } +void dataframe::remove_unused_categories(series_identifier column) & +{ + change_state_to(state_type::modifying, + state_modification_reason::needs_series_type); + + std::vector usage; + switch (auto &&ser = get_data_source().get_series(column)) { + using enum series_type; + default: throw std::runtime_error("Series does not exists."); + case measure: + throw std::runtime_error("Measure series has no categories."); + case dimension: + usage = + unsafe_get(ser).second.get_categories_usage(); + if (std::ranges::all_of(usage, + std::bind_front(std::equal_to{}, true))) + return; + break; + } + + change_state_to(state_type::modifying, + state_modification_reason::needs_own_state); + + unsafe_get( + unsafe_get(source)->get_series(column)) + .second.remove_unused_categories(std::move(usage)); +} + void dataframe::change_data(record_identifier record_id, series_identifier column, cell_value value) & @@ -710,6 +738,23 @@ void dataframe::change_data(record_identifier record_id, } } +bool dataframe::has_na(series_identifier column) const & +{ + switch (auto &&ser = get_data_source().get_series(column)) { + using enum series_type; + default: throw std::runtime_error("Series does not exists."); + case measure: { + const auto &meas = unsafe_get(ser).second; + return meas.contains_nan + || get_record_count() > meas.values.size(); + } + case dimension: + const auto &dim = unsafe_get(ser).second; + return dim.contains_nav + || get_record_count() > dim.values.size(); + } +} + void dataframe::fill_na(series_identifier column, cell_value value) & { const double *d = std::get_if(&value); diff --git a/src/dataframe/impl/dataframe.h b/src/dataframe/impl/dataframe.h index 59a38a8c5..cd7333d94 100644 --- a/src/dataframe/impl/dataframe.h +++ b/src/dataframe/impl/dataframe.h @@ -92,11 +92,15 @@ class dataframe final : public dataframe_interface void remove_records(std::function filter) & final; + void remove_unused_categories(series_identifier column) & final; + void change_data(record_identifier record_id, series_identifier column, cell_value value) & final; + bool has_na(series_identifier column) const & final; + void fill_na(series_identifier column, cell_value value) & final; void finalize() & final; diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 9569aea70..e1baa4d79 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -167,10 +167,15 @@ class dataframe_interface : virtual void remove_records( std::function filter) & = 0; + virtual void remove_unused_categories( + series_identifier column) & = 0; + virtual void change_data(record_identifier record_id, series_identifier column, cell_value value) & = 0; + virtual bool has_na(series_identifier column) const & = 0; + virtual void fill_na(series_identifier column, cell_value value) & = 0; diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index 928086f20..61c561389 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -164,6 +164,11 @@ const static auto tests = assert->*df->get_dimensions() == std::array{"d0", "d1"}; assert->*df->get_measures() == std::array{"m1", "m2"}; + check->*df->has_na("d0") == "d0 has nav"_is_true; + check->*df->has_na("d1") == "d1 has nav"_is_true; + check->*df->has_na("m1") == "m1 has nan"_is_true; + check->*df->has_na("m2") == "m2 has nan"_is_true; + check->*df->get_categories("d0") == std::array{"a"}; assert->*df->get_categories("d1") From f58d24fd8d445cee2d86771eaa661e922fcefe7e Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 27 Mar 2024 09:19:30 +0100 Subject: [PATCH 127/253] aggregators static, sum->no wrapper, fix fill_na, add is_filtered, data get sorted order, rename to snake_case --- src/dataframe/impl/aggregators.cpp | 129 +++++++++++++++++++++++++ src/dataframe/impl/aggregators.h | 125 +----------------------- src/dataframe/impl/data_source.cpp | 49 ++++++---- src/dataframe/impl/data_source.h | 2 +- src/dataframe/impl/dataframe.cpp | 118 +++++++++++++++------- src/dataframe/impl/dataframe.h | 8 +- src/dataframe/interface.h | 20 +++- test/unit/dataframe/interface_test.cpp | 14 +-- 8 files changed, 271 insertions(+), 194 deletions(-) create mode 100644 src/dataframe/impl/aggregators.cpp diff --git a/src/dataframe/impl/aggregators.cpp b/src/dataframe/impl/aggregators.cpp new file mode 100644 index 000000000..1f36a2d6f --- /dev/null +++ b/src/dataframe/impl/aggregators.cpp @@ -0,0 +1,129 @@ + +#include "aggregators.h" + +#include +#include +#include + +namespace Vizzu::dataframe +{ + +inline bool is_valid(cell_value const &value) +{ + const double *d = std::get_if(&value); + return d ? !std::isnan(*d) + : std::get_if(&value)->data() + != nullptr; +} + +// NOLINTNEXTLINE(fuchsia-statically-constructed-objects) +constinit const Refl::EnumArray + aggregators{{{{std::string_view{"sum"}, + []() -> custom_aggregator::id_type + { + return 0.0; + }, + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double + { + auto &ref = *std::any_cast(&id); + const double &value = + *std::get_if(&cell); + if (!std::isnan(value) + && !std::isinf(value)) + ref += value; + + return ref; + }}, + {std::string_view{"min"}, + []() -> custom_aggregator::id_type + { + return std::numeric_limits::quiet_NaN(); + }, + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double + { + auto &ref = *std::any_cast(&id); + const auto &value = *std::get_if(&cell); + if (!std::isnan(value) && !std::isinf(value) + && !(value >= ref)) + ref = value; + + return ref; + }}, + {std::string_view{"max"}, + []() -> custom_aggregator::id_type + { + return std::numeric_limits::quiet_NaN(); + }, + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double + { + auto &ref = *std::any_cast(&id); + const auto &value = *std::get_if(&cell); + if (!std::isnan(value) && !std::isinf(value) + && !(value <= ref)) + ref = value; + + return ref; + }}, + {std::string_view{"mean"}, + []() -> custom_aggregator::id_type + { + return std::pair(0.0, 0); + }, + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double + { + auto &[sum, count] = + *std::any_cast>( + &id); + const auto &value = *std::get_if(&cell); + if (!std::isnan(value) && !std::isinf(value)) { + sum += value; + ++count; + } + return count == 0 ? NAN + : sum / static_cast(count); + }}, + {std::string_view{"count"}, + []() -> custom_aggregator::id_type + { + return std::size_t{}; + }, + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double + { + auto &s = *std::any_cast(&id); + if (is_valid(cell)) s += 1; + return static_cast(s); + }}, + {std::string_view{"distinct"}, + []() -> custom_aggregator::id_type + { + return std::set{}; + }, + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double + { + auto &set = + *std::any_cast>(&id); + if (const char *v = + std::get_if(&cell)->data()) + set.insert(v); + return static_cast(set.size()); + }}, + {std::string_view{"exists"}, + []() -> custom_aggregator::id_type + { + return bool{}; + }, + [](custom_aggregator::id_type &id, + cell_value const &cell) -> double + { + auto &b = *std::any_cast(&id); + if (is_valid(cell)) b = true; + return static_cast(b); + }}}}}; + +} \ No newline at end of file diff --git a/src/dataframe/impl/aggregators.h b/src/dataframe/impl/aggregators.h index 8c39f6fce..d302bef42 100644 --- a/src/dataframe/impl/aggregators.h +++ b/src/dataframe/impl/aggregators.h @@ -1,134 +1,13 @@ #ifndef VIZZU_DATAFRAME_AGGREGATORS_H #define VIZZU_DATAFRAME_AGGREGATORS_H -#include -#include -#include - #include "../interface.h" #include "base/refl/auto_enum.h" namespace Vizzu::dataframe { - -inline bool is_valid(cell_value const &value) -{ - const double *d = std::get_if(&value); - return d ? !std::isnan(*d) - : std::get_if(&value)->data() - != nullptr; -} - -// NOLINTNEXTLINE(fuchsia-statically-constructed-objects) -constinit const static inline Refl::EnumArray - aggregators{{{{std::string_view{"sum"}, - []() -> custom_aggregator::id_type - { - return 0.0; - }, - [](custom_aggregator::id_type &id, - cell_value const &cell) -> double - { - auto &ref = *std::any_cast(&id); - const double &value = - *std::get_if(&cell); - if (!std::isnan(value) - && !std::isinf(value)) - ref += value; - - return ref; - }}, - {std::string_view{"min"}, - []() -> custom_aggregator::id_type - { - return std::numeric_limits::quiet_NaN(); - }, - [](custom_aggregator::id_type &id, - cell_value const &cell) -> double - { - auto &ref = *std::any_cast(&id); - const auto &value = *std::get_if(&cell); - if (!std::isnan(value) && !std::isinf(value) - && !(value >= ref)) - ref = value; - - return ref; - }}, - {std::string_view{"max"}, - []() -> custom_aggregator::id_type - { - return std::numeric_limits::quiet_NaN(); - }, - [](custom_aggregator::id_type &id, - cell_value const &cell) -> double - { - auto &ref = *std::any_cast(&id); - const auto &value = *std::get_if(&cell); - if (!std::isnan(value) && !std::isinf(value) - && !(value <= ref)) - ref = value; - - return ref; - }}, - {std::string_view{"mean"}, - []() -> custom_aggregator::id_type - { - return std::pair(0.0, 0); - }, - [](custom_aggregator::id_type &id, - cell_value const &cell) -> double - { - auto &[sum, count] = - *std::any_cast>( - &id); - const auto &value = *std::get_if(&cell); - if (!std::isnan(value) && !std::isinf(value)) { - sum += value; - ++count; - } - return count == 0 ? NAN - : sum / static_cast(count); - }}, - {std::string_view{"count"}, - []() -> custom_aggregator::id_type - { - return std::size_t{}; - }, - [](custom_aggregator::id_type &id, - cell_value const &cell) -> double - { - auto &s = *std::any_cast(&id); - if (is_valid(cell)) s += 1; - return static_cast(s); - }}, - {std::string_view{"distinct"}, - []() -> custom_aggregator::id_type - { - return std::set{}; - }, - [](custom_aggregator::id_type &id, - cell_value const &cell) -> double - { - auto &set = - *std::any_cast>(&id); - if (const char *v = - std::get_if(&cell)->data()) - set.insert(v); - return static_cast(set.size()); - }}, - {std::string_view{"exists"}, - []() -> custom_aggregator::id_type - { - return bool{}; - }, - [](custom_aggregator::id_type &id, - cell_value const &cell) -> double - { - auto &b = *std::any_cast(&id); - if (is_valid(cell)) b = true; - return static_cast(b); - }}}}}; +extern const Refl::EnumArray + aggregators; } #endif // VIZZU_DATAFRAME_AGGREGATORS_H diff --git a/src/dataframe/impl/data_source.cpp b/src/dataframe/impl/data_source.cpp index 0519d1b3e..77b6e779f 100644 --- a/src/dataframe/impl/data_source.cpp +++ b/src/dataframe/impl/data_source.cpp @@ -9,6 +9,7 @@ #include "base/text/naturalcmp.h" +#include "aggregators.h" #include "dataframe.h" namespace Vizzu::dataframe @@ -653,14 +654,21 @@ void data_source::dimension_t::set(std::size_t index, values[index] = value.data() != nullptr ? get_or_set_cat(value) : nav; } -void data_source::dimension_t::set_nav(std::string_view value) +void data_source::dimension_t::set_nav(std::string_view value, + std::size_t to_size) { - if (!contains_nav) return; if (value.data() == nullptr) value = ""; - auto ix = get_or_set_cat(value); + std::optional cat_ix; + if (values.size() < to_size) + values.resize(to_size, *(cat_ix = get_or_set_cat(value))); + + if (!contains_nav) return; + + if (!cat_ix) cat_ix = get_or_set_cat(value); + for (auto &val : values) - if (val == nav) val = ix; + if (val == nav) val = *cat_ix; contains_nav = false; } @@ -723,21 +731,24 @@ data_source::aggregating_type::add_aggregated( const_series_data &&data, const custom_aggregator &aggregator) { - auto &&[it, succ] = meas.try_emplace( - std::string{aggregator.get_name()} + '(' - + std::visit( - [](const auto &arg) - { - if constexpr (std::is_same_v>) - return std::string{}; - else - return std::string{arg.first}; - }, - data) - + ')', - data, + std::string name = std::visit( + [](const auto &arg) + { + if constexpr (std::is_same_v>) + return std::string{}; + else + return std::string{arg.first}; + }, + data); + + name = &aggregator == &aggregators[aggregator_type::sum] + ? std::move(name) + : std::string{aggregator.get_name()} + '(' + + std::move(name) + ')'; + + auto &&[it, succ] = meas.try_emplace(std::move(name), + std::move(data), aggregator); return {it->first, succ}; } diff --git a/src/dataframe/impl/data_source.h b/src/dataframe/impl/data_source.h index a60376cc9..d5405dec9 100644 --- a/src/dataframe/impl/data_source.h +++ b/src/dataframe/impl/data_source.h @@ -67,7 +67,7 @@ class data_source : public std::enable_shared_from_this void set(std::size_t index, std::string_view value); - void set_nav(std::string_view value); + void set_nav(std::string_view value, std::size_t to_size); std::vector get_categories_usage() const; diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp index 71e8895e6..db0e2de25 100644 --- a/src/dataframe/impl/dataframe.cpp +++ b/src/dataframe/impl/dataframe.cpp @@ -50,8 +50,10 @@ void valid_unexistent_aggregator( const dataframe::any_aggregator_type &agg) { if (const auto *aggr = std::get_if(&agg); - !aggr || *aggr != aggregator_type::count) - throw std::runtime_error("Series does not exists."); + !aggr + || (*aggr != aggregator_type::count + && *aggr != aggregator_type::exists)) + throw std::runtime_error("Series does not exists - aggr."); } void valid_dimension_aggregator( @@ -118,7 +120,7 @@ std::string dataframe::set_aggregate(series_identifier series, auto &&[name, uniq] = aggs.add_aggregated(std::move(ser), agg ? aggregators[*agg] - : std::get(aggregator)); + : *std::get_if(&aggregator)); if (!uniq) throw std::runtime_error( "Duplicated aggregated series name."); @@ -131,12 +133,11 @@ void dataframe::set_filter(std::function &&filt) & bools->assign(get_record_count(), false); if (filt) { visit( - [&filt, it = bools->begin()]( - record_type record) mutable + [&filt, &b = *bools](record_type record) mutable { - *it++ = filt(record); - }, - false); + b[std::get(record.recordId)] = + filt(record); + }); } } else @@ -155,7 +156,8 @@ void dataframe::set_sort(series_identifier series, switch (ser) { using enum series_type; - default: throw std::runtime_error("Series does not exists."); + default: + throw std::runtime_error("Series does not exists - sort."); case dimension: { std::optional> indices; if (const auto &dim = unsafe_get(ser).second; @@ -440,8 +442,7 @@ void dataframe::add_series_by_other(series_identifier curr_series, { add_val(value_transform(record, dim.get(std::get(record.recordId)))); - }, - false); + }); break; } case measure: { @@ -452,10 +453,8 @@ void dataframe::add_series_by_other(series_identifier curr_series, record_type record) { add_val(value_transform(record, - mea.values[std::get( - record.recordId)])); - }, - false); + mea.get(std::get(record.recordId)))); + }); break; } } @@ -494,7 +493,9 @@ void dataframe::remove_series( for (auto &&s = get_data_source(); const auto &name : names) { switch (auto ser = s.get_series(name)) { using enum series_type; - default: throw std::runtime_error("Series does not exists."); + default: + throw std::runtime_error( + "Series does not exists - remove."); case dimension: { auto ix = &unsafe_get(ser).second - s.dimensions.data(); @@ -638,7 +639,8 @@ void dataframe::remove_records( change_state_to(state_type::modifying, state_modification_reason::needs_record_count); - if (get_record_count() == 0) return; + auto count = get_record_count(); + if (count == 0) return; change_state_to(state_type::modifying, state_modification_reason::needs_sorted_records); @@ -650,12 +652,19 @@ void dataframe::remove_records( if (filter(r)) remove_ix.push_back( std::get(r.recordId)); - }, - false); + }); change_state_to(state_type::modifying, state_modification_reason::needs_own_state); + if (auto *p = get_if(&source)) { + if (!p->pre_remove) p->pre_remove.emplace(count); + + for (std::size_t i : remove_ix) (*p->pre_remove)[i] = true; + + return; + } + unsafe_get(source)->remove_records( remove_ix); } @@ -668,7 +677,9 @@ void dataframe::remove_unused_categories(series_identifier column) & std::vector usage; switch (auto &&ser = get_data_source().get_series(column)) { using enum series_type; - default: throw std::runtime_error("Series does not exists."); + default: + throw std::runtime_error( + "Series does not exists - remove cats."); case measure: throw std::runtime_error("Measure series has no categories."); case dimension: @@ -707,7 +718,9 @@ void dataframe::change_data(record_identifier record_id, switch (s.get_series(column)) { using enum series_type; - default: throw std::runtime_error("Series does not exists."); + default: + throw std::runtime_error( + "Series does not exists - change data."); case measure: if (!d) throw std::runtime_error("Measure must be a number."); break; @@ -742,7 +755,8 @@ bool dataframe::has_na(series_identifier column) const & { switch (auto &&ser = get_data_source().get_series(column)) { using enum series_type; - default: throw std::runtime_error("Series does not exists."); + default: + throw std::runtime_error("Series does not exists - has na."); case measure: { const auto &meas = unsafe_get(ser).second; return meas.contains_nan @@ -764,7 +778,8 @@ void dataframe::fill_na(series_identifier column, cell_value value) & switch (get_data_source().get_series(column)) { using enum series_type; - default: throw std::runtime_error("Series does not exists."); + default: + throw std::runtime_error("Series does not exists - fill na."); case measure: if (!d) throw std::runtime_error("Measure must be a number."); if (std::isnan(*d)) @@ -779,6 +794,8 @@ void dataframe::fill_na(series_identifier column, cell_value value) & change_state_to(state_type::modifying, state_modification_reason::needs_own_state); + auto count = get_record_count(); + switch (auto &&ser = unsafe_get(source)->get_series( column)) { @@ -789,11 +806,13 @@ void dataframe::fill_na(series_identifier column, cell_value value) & if (std::exchange(meas.contains_nan, false)) for (auto &v : meas.values) if (std::isnan(v)) v = *d; + if (meas.values.size() < count) meas.values.resize(count, *d); break; } case dimension: unsafe_get(ser).second.set_nav( - *std::get_if(&value)); + *std::get_if(&value), + count); break; } } @@ -818,7 +837,9 @@ std::string dataframe::as_string() const & Conv::JSONObj obj{res}; switch (auto &&ser = s.get_series(name)) { using enum series_type; - default: throw std::runtime_error("Series does not exists."); + default: + throw std::runtime_error( + "Series does not exists - as string."); case dimension: { const auto &[name, dim] = unsafe_get(ser); obj("name", name)("type", "dimension")("unit", @@ -855,7 +876,9 @@ std::span dataframe::get_categories( { switch (auto &&ser = get_data_source().get_series(dimension)) { using enum series_type; - default: throw std::runtime_error("Series does not exists."); + default: + throw std::runtime_error( + "Series does not exists - get cats."); case measure: throw std::runtime_error("Measure series has no categories."); case dimension: @@ -869,7 +892,9 @@ std::pair dataframe::get_min_max( auto &&s = get_data_source(); switch (auto &&meas = s.get_series(measure)) { using enum series_type; - default: throw std::runtime_error("Series does not exists."); + default: + throw std::runtime_error( + "Series does not exists - get min max."); case dimension: throw std::runtime_error("Dimension series has no min/max."); case measure: @@ -884,25 +909,41 @@ cell_value dataframe::get_data(record_identifier record_id, if (std::holds_alternative(record_id)) s.change_record_identifier_type(record_id); + else if (const auto *cp = get_if(&source); + cp && cp->sorted_indices) + std::get(record_id) = { + (*cp->sorted_indices)[std::get(record_id)]}; return s.get_data(*std::get_if(&record_id), column); } -void dataframe::visit(std::function function, - bool filtered) const & +bool dataframe::is_filtered(record_identifier record_id) const & { - if (const auto *fun = std::get_if<0>(&filter); - fun && filtered && *fun) + if (std::holds_alternative(record_id)) + get_data_source().change_record_identifier_type(record_id); + + if (const auto *cp = get_if(&source); + cp && cp->pre_remove + && (*cp->pre_remove)[std::get(record_id)]) + return true; + + const auto *fun = std::get_if<0>(&filter); + if (fun && *fun) throw std::runtime_error( "Filtered dataframe is not finalized."); + return !fun + && (*std::get_if>( + &filter))[*std::get_if(&record_id)]; +} + +void dataframe::visit(std::function function) const +{ const auto *cp = get_if(&source); - const auto *filt = filtered ? std::get_if<1>(&filter) : nullptr; - if (!filt && cp && cp->pre_remove) filt = &*cp->pre_remove; visit(function, cp && cp->sorted_indices ? &*cp->sorted_indices : nullptr, - filt); + cp && cp->pre_remove ? &*cp->pre_remove : nullptr); } void dataframe::visit( @@ -998,7 +1039,7 @@ void dataframe::change_state_to(state_type new_state, switch (new_state) { using enum state_type; case modifying: - migrate_data(); + if (state_data != aggregating) migrate_data(); state_data.emplace(); break; case aggregating: @@ -1049,7 +1090,7 @@ std::string_view dataframe::get_series_name( } std::string_view dataframe::get_record_unique_id( - const record_identifier &id) const & + record_identifier id) const & { if (const auto *ptr = std::get_if(&id)) return *ptr; @@ -1058,6 +1099,11 @@ std::string_view dataframe::get_record_unique_id( if (!state) throw std::runtime_error("Dataframe is not finalized."); + if (const auto *cp = get_if(&source); + cp && cp->sorted_indices) + std::get(id) = { + (*cp->sorted_indices)[std::get(id)]}; + const data_source::final_info &info = state->get(); auto ix = *std::get_if(&id); return ix < info.record_unique_ids.size() diff --git a/src/dataframe/impl/dataframe.h b/src/dataframe/impl/dataframe.h index cd7333d94..18f15e1d1 100644 --- a/src/dataframe/impl/dataframe.h +++ b/src/dataframe/impl/dataframe.h @@ -121,7 +121,7 @@ class dataframe final : public dataframe_interface const series_identifier &id) const & final; [[nodiscard]] std::string_view get_record_unique_id( - const record_identifier &id) const & final; + record_identifier id) const & final; [[nodiscard]] std::string_view get_series_info( const series_identifier &id, @@ -135,8 +135,8 @@ class dataframe final : public dataframe_interface return get_data_source().get_record_count(); } - void visit(std::function function, - bool filtered) const & final; + [[nodiscard]] bool is_filtered( + record_identifier record_id) const & final; private: void migrate_data(); @@ -145,6 +145,8 @@ class dataframe final : public dataframe_interface const data_source &get_data_source() const; + void visit(std::function function) const; + void visit(const std::function &function, const std::vector *sort, const std::vector *filt) const; diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index e1baa4d79..2f792f413 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -88,7 +88,7 @@ class dataframe_interface : struct record_type { - [[nodiscard]] cell_value getValue(series_identifier i) const + [[nodiscard]] cell_value get_value(series_identifier i) const { return parent->get_data(recordId, i); } @@ -103,11 +103,21 @@ class dataframe_interface : [rec = *this](std::string_view dim) -> std::pair { - auto &&cell = rec.getValue(dim); + auto &&cell = rec.get_value(dim); return {dim, *std::get_if(&cell)}; }}; } + + bool has_measure() const + { + return !parent->get_measures().empty(); + } + + bool is_filtered() const + { + return parent->is_filtered(recordId); + } }; virtual ~dataframe_interface() = default; @@ -199,7 +209,7 @@ class dataframe_interface : const series_identifier &id) const & = 0; [[nodiscard]] virtual std::string_view get_record_unique_id( - const record_identifier &id) const & = 0; + record_identifier id) const & = 0; [[nodiscard]] virtual cell_value get_data( record_identifier record_id, @@ -211,8 +221,8 @@ class dataframe_interface : const series_identifier &id, const char *key) const & = 0; - virtual void visit(std::function function, - bool filtered) const & = 0; + [[nodiscard]] virtual bool is_filtered( + record_identifier record_id) const & = 0; }; } diff --git a/test/unit/dataframe/interface_test.cpp b/test/unit/dataframe/interface_test.cpp index 61c561389..e6b43e376 100644 --- a/test/unit/dataframe/interface_test.cpp +++ b/test/unit/dataframe/interface_test.cpp @@ -294,7 +294,7 @@ const static auto tests = auto v = std::get_if(&c); skip->*static_cast(v) == "value is string"_is_true; - auto oth_v = r.getValue("d2"); + auto oth_v = r.get_value("d2"); auto v2 = std::get_if(&oth_v); skip->*static_cast(v2) == "value is string"_is_true; @@ -384,7 +384,7 @@ const static auto tests = df->remove_records( [](record_type r) -> bool { - auto v = r.getValue("m1"); + auto v = r.get_value("m1"); return *std::get_if(&v) < 5.0; }); @@ -510,10 +510,10 @@ const static auto tests = d1d, d1e, m1e, + m1s, m1ma, m1me, m1mi, - m1s, m1t}; assert->*df->get_record_count() == std::size_t{4}; @@ -728,13 +728,13 @@ const static auto tests = [](const record_type &lhs, const record_type &rhs) { auto l = - (std::get(lhs.getValue("d2"))[2] + (std::get(lhs.get_value("d2"))[2] - '0') - + std::get(lhs.getValue("m1")); + + std::get(lhs.get_value("m1")); auto r = - (std::get(rhs.getValue("d2"))[2] + (std::get(rhs.get_value("d2"))[2] - '0') - + std::get(rhs.getValue("m1")); + + std::get(rhs.get_value("m1")); return std::weak_order(l, r); }); From 935c7f68b5e313d0bc57228fa6057fa99d0c082e Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 27 Mar 2024 23:13:31 +0100 Subject: [PATCH 128/253] add get_record_id_by_dims --- src/dataframe/impl/data_source.cpp | 32 ++++++++++++++++++------------ src/dataframe/impl/data_source.h | 4 ++++ src/dataframe/impl/dataframe.cpp | 17 ++++++++++++++++ src/dataframe/impl/dataframe.h | 4 ++++ src/dataframe/interface.h | 4 ++++ 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/dataframe/impl/data_source.cpp b/src/dataframe/impl/data_source.cpp index 77b6e779f..58953e911 100644 --- a/src/dataframe/impl/data_source.cpp +++ b/src/dataframe/impl/data_source.cpp @@ -551,21 +551,27 @@ data_source::final_info::final_info(const data_source &source) : min_max[i] = source.measures[i].get_min_max(); } - for (std::size_t r{}; r < records; ++r) { - auto &record = record_unique_ids[r]; - - for (std::size_t d{}; d < dimensions; ++d) { - auto val = source.dimensions[d].values[r]; - record += - source.dimension_names[d] + '[' - + std::to_string(static_cast(val)) + "]:" - + (val == nav ? "null" - : source.dimensions[d].categories[val]) - + ";"; - } - if (!record_to_index.try_emplace(record, r).second) + for (std::size_t r{}; r < records; ++r) + if (auto &id = record_unique_ids[r] = + get_id(source, r, source.dimension_names); + !record_to_index.try_emplace(id, r).second) throw std::runtime_error("duplicated record"); +} + +std::string data_source::final_info::get_id(const data_source &source, + std::size_t record, + std::span series) const +{ + std::string res; + for (const auto &name : series) { + auto d = series_to_index.at(name); + auto val = source.dimensions[d].get(record); + res += source.dimension_names[d]; + res += ':'; + res += val.data() == nullptr ? "__null__" : val; + res += ';'; } + return res; } std::vector data_source::dimension_t::get_indices( diff --git a/src/dataframe/impl/data_source.h b/src/dataframe/impl/data_source.h index d5405dec9..3cd3b040a 100644 --- a/src/dataframe/impl/data_source.h +++ b/src/dataframe/impl/data_source.h @@ -105,6 +105,10 @@ class data_source : public std::enable_shared_from_this record_to_index; explicit final_info(const data_source &source); + + std::string get_id(const data_source &source, + std::size_t record, + std::span series) const; }; using series_data = Refl::EnumVariant(&record_id)]; } +std::string dataframe::get_record_id_by_dims( + record_identifier my_record, + std::span dimensions) const & +{ + const auto *state = get_if(&state_data); + if (!state) + throw std::runtime_error("Dataframe is not finalized."); + + const auto &s = get_data_source(); + if (std::holds_alternative(my_record)) + s.change_record_identifier_type(my_record); + + return state->get().get_id(s, + std::get(my_record), + dimensions); +} + void dataframe::visit(std::function function) const { const auto *cp = get_if(&source); diff --git a/src/dataframe/impl/dataframe.h b/src/dataframe/impl/dataframe.h index 18f15e1d1..721e8115e 100644 --- a/src/dataframe/impl/dataframe.h +++ b/src/dataframe/impl/dataframe.h @@ -138,6 +138,10 @@ class dataframe final : public dataframe_interface [[nodiscard]] bool is_filtered( record_identifier record_id) const & final; + [[nodiscard]] std::string get_record_id_by_dims( + record_identifier my_record, + std::span dimensions) const & final; + private: void migrate_data(); void change_state_to(state_type new_state, diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 2f792f413..201dcb6c4 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -223,6 +223,10 @@ class dataframe_interface : [[nodiscard]] virtual bool is_filtered( record_identifier record_id) const & = 0; + + [[nodiscard]] virtual std::string get_record_id_by_dims( + record_identifier my_record, + std::span dimensions) const & = 0; }; } From 5df0ebb49a482acfcd8829115ed142ea52632b3f Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 27 Mar 2024 23:28:30 +0100 Subject: [PATCH 129/253] categories -> span, MultiIndex, aggregatorType, cell isEmpty, add testcases --- src/chart/generator/axis.cpp | 2 +- src/chart/generator/marker.cpp | 6 +- src/chart/generator/marker.h | 6 +- src/data/datacube/aggregator.h | 2 + src/data/datacube/datacube.cpp | 10 +- src/data/datacube/datacube.h | 26 ++- src/data/datacube/datacubecell.h | 5 + src/data/table/columninfo.cpp | 2 +- src/data/table/columninfo.h | 3 +- test/unit/chart/events.cpp | 274 +++++++++++++++++++++++++++++-- 10 files changed, 292 insertions(+), 44 deletions(-) diff --git a/src/chart/generator/axis.cpp b/src/chart/generator/axis.cpp index 9d033a3ce..efbbeedec 100644 --- a/src/chart/generator/axis.cpp +++ b/src/chart/generator/axis.cpp @@ -103,7 +103,7 @@ void DimensionAxis::setLabels(const Data::DataCube &data, for (int curr{}; auto &[slice, item] : values) { auto colIndex = data.getSeriesByDim(slice.dimIndex).getColIndex(); - const auto &categories = + auto &&categories = table.getInfo(colIndex.value()).categories(); if (slice.index < categories.size()) diff --git a/src/chart/generator/marker.cpp b/src/chart/generator/marker.cpp index 7e93eae4b..a90f5518c 100644 --- a/src/chart/generator/marker.cpp +++ b/src/chart/generator/marker.cpp @@ -7,7 +7,7 @@ namespace Vizzu::Gen Marker::Id::Id(const Data::DataCube &data, const Channel::DimensionIndices &dimensionIds, - const Data::MultiDim::MultiIndex &index) : + const Data::DataCube::MultiIndex &index) : seriesId(data.subSliceID(dimensionIds, index)), itemSliceIndex(data.subSliceIndex(dimensionIds, index)), itemId(data.getData().unfoldSubSliceIndex(itemSliceIndex)) @@ -17,11 +17,11 @@ Marker::Marker(const Options &options, const Data::DataCube &data, const Data::DataTable &table, ChannelsStats &stats, - const Data::MultiDim::MultiIndex &index, + const Data::DataCube::MultiIndex &index, size_t idx) : index(index), enabled(data.subCellSize() == 0 - || !data.getData().at(index).subCells[0].isEmpty()), + || !data.getData().at(index).isEmpty()), cellInfo(data.cellInfo(index)), idx(idx), table(table) diff --git a/src/chart/generator/marker.h b/src/chart/generator/marker.h index edcf9b03c..162cf4e0a 100644 --- a/src/chart/generator/marker.h +++ b/src/chart/generator/marker.h @@ -26,10 +26,10 @@ class Marker const Data::DataCube &data, const Data::DataTable &table, ChannelsStats &stats, - const Data::MultiDim::MultiIndex &index, + const Data::DataCube::MultiIndex &index, size_t idx); - Data::MultiDim::MultiIndex index; + Data::DataCube::MultiIndex index; ::Anim::Interpolated colorBase; Geom::Point position; Geom::Point size; @@ -75,7 +75,7 @@ class Marker bool operator==(const Id &other) const = default; Id(const Data::DataCube &, const Channel::DimensionIndices &dimensionIds, - const Data::MultiDim::MultiIndex &); + const Data::DataCube::MultiIndex &); }; ::Anim::Interpolated mainId; diff --git a/src/data/datacube/aggregator.h b/src/data/datacube/aggregator.h index 03e0c183d..c102823ad 100644 --- a/src/data/datacube/aggregator.h +++ b/src/data/datacube/aggregator.h @@ -27,6 +27,8 @@ class Aggregator [[nodiscard]] explicit operator double() const; [[nodiscard]] bool isEmpty() const; + [[nodiscard]] Type getType() const { return type; } + private: Type type; double value; diff --git a/src/data/datacube/datacube.cpp b/src/data/datacube/datacube.cpp index 1c1ffe9cf..7c6b368e3 100644 --- a/src/data/datacube/datacube.cpp +++ b/src/data/datacube/datacube.cpp @@ -6,7 +6,6 @@ namespace Vizzu::Data { using MultiDim::DimIndex; -using MultiDim::MultiIndex; using MultiDim::SubSliceIndex; DataCube::DataCube(const DataTable &table, @@ -65,7 +64,7 @@ DataCube::DataCube(const DataTable &table, } } -MultiIndex DataCube::getIndex(const DataTable::Row &row, +DataCube::MultiIndex DataCube::getIndex(const DataTable::Row &row, const std::set &indices, size_t rowIndex) { @@ -198,7 +197,7 @@ size_t DataCube::flatSubSliceIndex(const SeriesList &colIndices, } CellInfo::Categories DataCube::categories( - const MultiDim::MultiIndex &index) const + const MultiIndex &index) const { CellInfo::Categories res; @@ -209,8 +208,7 @@ CellInfo::Categories DataCube::categories( return res; } -CellInfo::Values DataCube::values( - const MultiDim::MultiIndex &index) const +CellInfo::Values DataCube::values(const MultiIndex &index) const { CellInfo::Values res; @@ -226,7 +224,7 @@ CellInfo::Values DataCube::values( return res; } -CellInfo DataCube::cellInfo(const MultiDim::MultiIndex &index) const +CellInfo DataCube::cellInfo(const MultiIndex &index) const { if (!table) return {}; diff --git a/src/data/datacube/datacube.h b/src/data/datacube/datacube.h index 25c612020..3757257ca 100644 --- a/src/data/datacube/datacube.h +++ b/src/data/datacube/datacube.h @@ -36,6 +36,7 @@ class DataCube { public: using Data = MultiDim::Array; + using MultiIndex = MultiDim::MultiIndex; DataCube(const DataTable &table, const DataCubeOptions &options, @@ -51,41 +52,38 @@ class DataCube SubCellIndex index) const; [[nodiscard]] size_t combinedIndexOf(const SeriesList &colIndices, - MultiDim::MultiIndex multiIndex) const; + MultiIndex multiIndex) const; [[nodiscard]] size_t combinedSizeOf( const SeriesList &colIndices) const; - [[nodiscard]] Aggregator aggregateAt( - const MultiDim::MultiIndex &multiIndex, + [[nodiscard]] Aggregator aggregateAt(const MultiIndex &multiIndex, const SeriesList &sumCols, SeriesIndex seriesId) const; - [[nodiscard]] Aggregator valueAt( - const MultiDim::MultiIndex &multiIndex, + [[nodiscard]] Aggregator valueAt(const MultiIndex &multiIndex, const SeriesIndex &seriesId) const; [[nodiscard]] size_t subSliceID(const SeriesList &colIndices, - const MultiDim::MultiIndex &multiIndex) const; + const MultiIndex &multiIndex) const; [[nodiscard]] size_t flatSubSliceIndex( const SeriesList &colIndices, - const MultiDim::MultiIndex &multiIndex) const; + const MultiIndex &multiIndex) const; [[nodiscard]] MultiDim::SubSliceIndex subSliceIndex( const SeriesList &colIndices, - MultiDim::MultiIndex multiIndex) const; + MultiIndex multiIndex) const; [[nodiscard]] size_t subCellSize() const; [[nodiscard]] bool empty() const; [[nodiscard]] CellInfo::Values values( - const MultiDim::MultiIndex &index) const; + const MultiIndex &index) const; [[nodiscard]] CellInfo::Categories categories( - const MultiDim::MultiIndex &index) const; - [[nodiscard]] CellInfo cellInfo( - const MultiDim::MultiIndex &index) const; + const MultiIndex &index) const; + [[nodiscard]] CellInfo cellInfo(const MultiIndex &index) const; private: Data data; @@ -96,13 +94,13 @@ class DataCube std::map subIndexBySeries; std::vector seriesBySubIndex; - static MultiDim::MultiIndex getIndex(const DataTable::Row &row, + static MultiIndex getIndex(const DataTable::Row &row, const std::set &indices, size_t rowIndex); [[nodiscard]] MultiDim::SubSliceIndex inverseSubSliceIndex( const SeriesList &colIndices, - MultiDim::MultiIndex multiIndex) const; + MultiIndex multiIndex) const; }; } diff --git a/src/data/datacube/datacubecell.h b/src/data/datacube/datacubecell.h index 239245e22..37f0dfe11 100644 --- a/src/data/datacube/datacubecell.h +++ b/src/data/datacube/datacubecell.h @@ -22,6 +22,11 @@ class DataCubeCell subCells.emplace_back(index.getType().aggregatorType()); } + [[nodiscard]] bool isEmpty() const + { + return subCells[0].isEmpty(); + } + std::vector subCells; }; diff --git a/src/data/table/columninfo.cpp b/src/data/table/columninfo.cpp index 4fd03f94b..3365cf635 100644 --- a/src/data/table/columninfo.cpp +++ b/src/data/table/columninfo.cpp @@ -60,7 +60,7 @@ ColumnInfo::ContiType ColumnInfo::getContiType() const return contiType; } -const ColumnInfo::Values &ColumnInfo::categories() const +std::span ColumnInfo::categories() const { return values; } diff --git a/src/data/table/columninfo.h b/src/data/table/columninfo.h index be10f6d48..0faca5df4 100644 --- a/src/data/table/columninfo.h +++ b/src/data/table/columninfo.h @@ -2,6 +2,7 @@ #define SERIESINFO_H #include +#include #include #include @@ -32,7 +33,7 @@ class ColumnInfo void reset(); [[nodiscard]] Type getType() const; [[nodiscard]] ContiType getContiType() const; - [[nodiscard]] const Values &categories() const; + [[nodiscard]] std::span categories() const; [[nodiscard]] std::string getName() const; [[nodiscard]] const std::string &getUnit() const; diff --git a/test/unit/chart/events.cpp b/test/unit/chart/events.cpp index 4adf773e4..e652f4748 100644 --- a/test/unit/chart/events.cpp +++ b/test/unit/chart/events.cpp @@ -58,10 +58,197 @@ struct MyCanvas final : Gfx::ICanvas, Vizzu::Draw::Painter ICanvas &getCanvas() final { return *this; } }; +auto testcase_1 = [](Vizzu::Data::DataTable &table) +{ + table.addColumn("Dim5", + {{"A", "B", "C", "D", "E"}}, + {{0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4}}); + table.addColumn("Dim2", + {{"a", "b"}}, + {{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}}); + table.addColumn("Dim3", + {{"a", "b", "c"}}, + {{0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 0, 2, 2, 1, 0}}); + table.addColumn("Meas1", + "", + {{1, 2, 4, 3, 3, 4, 2, 1, 4, 3, 1, 2, 2, 1, 3, 4}}); + table.addColumn("Meas2", + "", + {{0, -1, 5, 6, 6, 5, -1, 0, 5, 6, 0, -1, -1, 0, 6, -5}}); +}; + +auto testcase_2 = [](Vizzu::Data::DataTable &table) +{ + table.addColumn("Channel title for long names", + {{ + "Long name wich has no end", + R"(Raw +break)", + R"(キャラクターセット)", + }}, + {{0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2}}); + + table.addColumn("Childs of long names which have no end", + {{"Very long label of this element", + "", + "It is also long enough", + "Short one", + "Jap", + "キャラクターセット", + R"(Raw +break)", + "h", + "i", + "j"}}, + {{ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + + }}); + + table.addColumn("値3", + "", + {{639, + 354, + 278, + 312, + 1241, + 1512, + 863, + 789, + 765, + 653, + 542, + 497, + 673, + 412, + 308, + 345, + 1329, + 1671, + 962, + 821, + 798, + 681, + 584, + 518, + 706, + 432, + 326, + 358, + 1382, + 1715, + 1073, + 912, + 821, + 721, + 618, + 542, + 721, + 462, + 372, + 367, + 1404, + 1729, + 1142, + 941, + 834, + 778, + 651, + 598}}); +}; + struct chart_setup { std::vector> series; + std::function testcase = + testcase_1; bool is_emscripten{[] { bool is_emscripten{ @@ -78,21 +265,7 @@ struct chart_setup explicit(false) operator Vizzu::Chart &() { auto &table = chart.getTable(); - table.addColumn("Dim5", - {{"A", "B", "C", "D", "E"}}, - {{0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4}}); - table.addColumn("Dim4", - {{"a", "b", "c", "d"}}, - {{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}}); - table.addColumn("Dim3", - {{"a", "b", "c"}}, - {{0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 0, 2, 2, 1, 0}}); - table.addColumn("Meas1", - "", - {{1, 2, 4, 3, 3, 4, 2, 1, 4, 3, 1, 2, 2, 1, 3, 4}}); - table.addColumn("Meas2", - "", - {{0, -1, 5, 6, 6, 5, -1, 0, 5, 6, 0, -1, -1, 0, 6, -5}}); + testcase(table); auto &channels = chart.getOptions().getChannels(); for (auto &&[ch, name] : series) channels.addSeries(ch, {name, table}); @@ -353,6 +526,77 @@ const static auto tests = check->*events.count("plot-axis-draw") == 0u; check->*events.count("plot-marker-draw") == 10u; + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[v, t, d] : values(subrange(beg, end))) + check->*std::get_if(&d) != nullptr; +} + + | "icicle rectangle 2dis 1con" | + [](Vizzu::Chart &chart = chart_setup{{{x, "Dim5"}, + {x, "Meas1"}, + {y, "Dim2"}, + {y, "Meas2"}}}) +{ + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 2u; + check->*events.count("plot-marker-draw") == 5u; + + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[v, t, d] : values(subrange(beg, end))) + check->*std::get_if(&d) != nullptr; +} + + | "53978116" | + [](Vizzu::Chart &chart = chart_setup{{{color, "Dim3"}, + {x, "Dim5"}, + {y, "min(Meas1)"}, + {y, "Dim3"}}}) +{ + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 2u; + check->*events.count("plot-marker-draw") == 10u; + + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[v, t, d] : values(subrange(beg, end))) + check->*std::get_if(&d) != nullptr; +} + + | "aggregators step1" | + [](Vizzu::Chart &chart = chart_setup{{{x, "Dim3"}, + {y, "min(Meas1)"}, + {label, "min(Meas1)"}}}) +{ + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 2u; + check->*events.count("plot-marker-draw") == 3u; + + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); + const auto &[v, t, d] : values(subrange(beg, end))) + check->*std::get_if(&d) != nullptr; +} + + | "column rectangle less disc" | + [](Vizzu::Chart &chart = + chart_setup{ + {{y, "Channel title for long names"}, + {y, "値3"}, + {x, "Childs of long names which have no end"}, + {color, "Channel title for long names"}, + {label, "Channel title for long names"}}, + testcase_2}) +{ + chart.getOptions().getChannels().at(y).range.min = + Vizzu::Base::AutoParam{Vizzu::Gen::ChannelExtrema("110%")}; + chart.getOptions().getChannels().at(y).range.max = + Vizzu::Base::AutoParam{Vizzu::Gen::ChannelExtrema("0%")}; + auto &&events = get_events(chart); + + check->*events.count("plot-axis-draw") == 2u; + check->*events.count("plot-marker-draw") == 26u; + for (auto &&[beg, end] = events.equal_range("plot-marker-draw"); const auto &[v, t, d] : values(subrange(beg, end))) check->*std::get_if(&d) != nullptr; From 750054e124bcdd82cc03602e06e14e5677889bcd Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Thu, 28 Mar 2024 15:22:58 +0100 Subject: [PATCH 130/253] add get_series_orig_index and get_series_type --- src/dataframe/impl/dataframe.cpp | 23 +++++++++++++++++++++++ src/dataframe/impl/dataframe.h | 6 ++++++ 2 files changed, 29 insertions(+) diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp index 81cbd6185..ee9c089fc 100644 --- a/src/dataframe/impl/dataframe.cpp +++ b/src/dataframe/impl/dataframe.cpp @@ -954,6 +954,29 @@ std::string dataframe::get_record_id_by_dims( dimensions); } +std::size_t dataframe::get_series_orig_index( + std::string_view series) const +{ + using enum state_type; + const auto *state = get_if(&state_data); + if (!state || state->empty()) + throw std::runtime_error( + "Dataframe is not created by columns."); + + auto it = std::ranges::find(*state, series); + + if (it == state->end()) + throw std::runtime_error( + "Cannot find series: " + std::string{series}); + + return it - state->begin(); +} + +series_type dataframe::get_series_type(series_identifier series) const +{ + return get_data_source().get_series(series); +} + void dataframe::visit(std::function function) const { const auto *cp = get_if(&source); diff --git a/src/dataframe/impl/dataframe.h b/src/dataframe/impl/dataframe.h index 721e8115e..fef392686 100644 --- a/src/dataframe/impl/dataframe.h +++ b/src/dataframe/impl/dataframe.h @@ -142,6 +142,12 @@ class dataframe final : public dataframe_interface record_identifier my_record, std::span dimensions) const & final; + [[nodiscard]] std::size_t get_series_orig_index( + std::string_view series) const; + + [[nodiscard]] series_type get_series_type( + series_identifier series) const; + private: void migrate_data(); void change_state_to(state_type new_state, From 6eda58215f3f0efd866d46d8b9a4b364501c87fc Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 3 Apr 2024 13:55:12 +0200 Subject: [PATCH 131/253] Move MarkerId to DataCube Id, DataIndex not default constructible, extend dataCube with getValue, remove dataTable object passing, some refactor --- src/chart/generator/axis.cpp | 18 ++------ src/chart/generator/axis.h | 11 ++--- src/chart/generator/channelstats.cpp | 11 ++--- src/chart/generator/channelstats.h | 2 +- src/chart/generator/marker.cpp | 69 ++++++++++++---------------- src/chart/generator/marker.h | 27 +++-------- src/chart/generator/plot.cpp | 46 ++++++++----------- src/chart/generator/plot.h | 2 +- src/data/datacube/datacube.cpp | 34 +++++++++++--- src/data/datacube/datacube.h | 21 ++++++++- src/data/datacube/seriesindex.cpp | 21 +++++---- src/data/datacube/seriesindex.h | 3 +- src/data/table/datatable.h | 7 +-- 13 files changed, 132 insertions(+), 140 deletions(-) diff --git a/src/chart/generator/axis.cpp b/src/chart/generator/axis.cpp index efbbeedec..3590532b0 100644 --- a/src/chart/generator/axis.cpp +++ b/src/chart/generator/axis.cpp @@ -64,9 +64,9 @@ MeasureAxis interpolate(const MeasureAxis &op0, return res; } -bool DimensionAxis::add(const Data::MultiDim::SliceIndex &index, +bool DimensionAxis::add(const Data::DataCube::Id::SliceIndex &index, double value, - Math::Range &range, + const Math::Range &range, double enabled, bool merge) { @@ -93,23 +93,13 @@ bool DimensionAxis::operator==(const DimensionAxis &other) const return enabled == other.enabled && values == other.values; } -void DimensionAxis::setLabels(const Data::DataCube &data, - const Data::DataTable &table, - double step) +void DimensionAxis::setLabels(const Data::DataCube &data, double step) { step = std::max(step, 1.0); double currStep = 0.0; for (int curr{}; auto &[slice, item] : values) { - auto colIndex = - data.getSeriesByDim(slice.dimIndex).getColIndex(); - auto &&categories = - table.getInfo(colIndex.value()).categories(); - - if (slice.index < categories.size()) - item.categoryValue = categories[slice.index]; - else - item.categoryValue = "NA"; + item.categoryValue = data.getValue(slice, "NA"); if (++curr <= currStep) continue; currStep += step; diff --git a/src/chart/generator/axis.h b/src/chart/generator/axis.h index 6ed1f7756..7ad85fd7d 100644 --- a/src/chart/generator/axis.h +++ b/src/chart/generator/axis.h @@ -134,15 +134,16 @@ struct DimensionAxis return index == 0 ? start : index == 1 && end; } }; - using Values = std::multimap; + using Values = + std::multimap; bool enabled{false}; std::string category{}; DimensionAxis() = default; - bool add(const Data::MultiDim::SliceIndex &index, + bool add(const Data::DataCube::Id::SliceIndex &index, double value, - Math::Range &range, + const Math::Range &range, double enabled, bool merge); bool operator==(const DimensionAxis &other) const; @@ -157,9 +158,7 @@ struct DimensionAxis { return values.cend(); } - void setLabels(const Data::DataCube &data, - const Data::DataTable &table, - double step); + void setLabels(const Data::DataCube &data, double step); private: Values values; diff --git a/src/chart/generator/channelstats.cpp b/src/chart/generator/channelstats.cpp index 2ae2d6326..281a8105b 100644 --- a/src/chart/generator/channelstats.cpp +++ b/src/chart/generator/channelstats.cpp @@ -5,13 +5,10 @@ namespace Vizzu::Gen ChannelStats::ChannelStats(const Channel &channel, const Data::DataCube &cube) : - isDimension(channel.isDimension()) -{ - if (isDimension) - usedIndices = - std::vector(cube.combinedSizeOf(channel.dimensionIds), - Data::MultiDim::SubSliceIndex()); -} + isDimension(channel.isDimension()), + usedIndices( + isDimension ? cube.combinedSizeOf(channel.dimensionIds) : 0) +{} void ChannelStats::track(double value) { diff --git a/src/chart/generator/channelstats.h b/src/chart/generator/channelstats.h index 8a5f98b38..04900fd09 100644 --- a/src/chart/generator/channelstats.h +++ b/src/chart/generator/channelstats.h @@ -17,7 +17,7 @@ class ChannelStats public: bool isDimension; Math::Range range; - std::vector usedIndices; + std::vector usedIndices; ChannelStats() : isDimension(true) {} ChannelStats(const Channel &channel, const Data::DataCube &cube); diff --git a/src/chart/generator/marker.cpp b/src/chart/generator/marker.cpp index a90f5518c..6542df4fc 100644 --- a/src/chart/generator/marker.cpp +++ b/src/chart/generator/marker.cpp @@ -5,17 +5,8 @@ namespace Vizzu::Gen { -Marker::Id::Id(const Data::DataCube &data, - const Channel::DimensionIndices &dimensionIds, - const Data::DataCube::MultiIndex &index) : - seriesId(data.subSliceID(dimensionIds, index)), - itemSliceIndex(data.subSliceIndex(dimensionIds, index)), - itemId(data.getData().unfoldSubSliceIndex(itemSliceIndex)) -{} - Marker::Marker(const Options &options, const Data::DataCube &data, - const Data::DataTable &table, ChannelsStats &stats, const Data::DataCube::MultiIndex &index, size_t idx) : @@ -23,8 +14,11 @@ Marker::Marker(const Options &options, enabled(data.subCellSize() == 0 || !data.getData().at(index).isEmpty()), cellInfo(data.cellInfo(index)), + sizeId(data.getId( + options.getChannels().at(ChannelId::size).dimensionIds, + index)), idx(idx), - table(table) + table(*data.getTable()) { const auto &channels = options.getChannels(); auto color = @@ -44,23 +38,21 @@ Marker::Marker(const Options &options, data, stats, options.subAxisOf(ChannelId::size)); - sizeId = - Id(data, channels.at(ChannelId::size).dimensionIds, index); - mainId = Id(data, options.mainAxis().dimensionIds, index); + mainId = data.getId(options.mainAxis().dimensionIds, index); auto stackInhibitingShape = options.geometry == ShapeType::area; if (stackInhibitingShape) { Data::SeriesList subIds(options.subAxis().dimensionIds); subIds.remove(options.mainAxis().dimensionIds); - subId = Id(data, subIds, index); + subId = data.getId(subIds, index); Data::SeriesList stackIds(options.subAxis().dimensionIds); stackIds.section(options.mainAxis().dimensionIds); - stackId = Id(data, stackIds, index); + stackId = data.getId(stackIds, index); } else { stackId = subId = - Id(data, options.subAxis().dimensionIds, index); + data.getId(options.subAxis().dimensionIds, index); } auto horizontal = options.isHorizontal(); @@ -101,17 +93,18 @@ Marker::Marker(const Options &options, ChannelId::label, data, stats); - auto sliceIndex = data.subSliceIndex( + + auto &&labelStr = Label::getIndexString(data, channels.at(ChannelId::label).dimensionIds, index); + if (channels.at(ChannelId::label).isDimension()) - label = Label(sliceIndex, data, table); + label = Label(std::move(labelStr)); else label = Label(value, *channels.at(ChannelId::label).measureId, - sliceIndex, data, - table); + std::move(labelStr)); } } @@ -204,7 +197,7 @@ double Marker::getValueForChannel(const Channels &channels, auto measure = channel.measureId; double value{}; - auto id = Id(data, channel.dimensionIds, index); + auto id = data.getId(channel.dimensionIds, index); auto &stat = stats.channels[type]; @@ -254,23 +247,21 @@ void Marker::setSizeBy(bool horizontal, fromRectangle(rect); } -Marker::Label::Label(const Data::MultiDim::SubSliceIndex &index, - const Data::DataCube &data, - const Data::DataTable &table) : - indexStr{getIndexString(index, data, table)} +Marker::Label::Label(std::string &&indexStr) : + indexStr{std::move(indexStr)} {} Marker::Label::Label(double value, const Data::SeriesIndex &measure, - const Data::MultiDim::SubSliceIndex &index, const Data::DataCube &data, - const Data::DataTable &table) : + std::string &&indexStr) : value(value), - measureId(measure.getColIndex()) -{ - if (measureId) unit = table.getInfo(measureId.value()).getUnit(); - indexStr = getIndexString(index, data, table); -} + measureId(measure.getColIndex()), + unit(measureId + ? data.getTable()->getInfo(measureId.value()).getUnit() + : ""), + indexStr(std::move(indexStr)) +{} bool Marker::Label::operator==(const Marker::Label &other) const { @@ -278,18 +269,16 @@ bool Marker::Label::operator==(const Marker::Label &other) const && unit == other.unit && indexStr == other.indexStr; } -std::string Marker::Label::getIndexString( - const Data::MultiDim::SubSliceIndex &index, - const Data::DataCube &data, - const Data::DataTable &table) +std::string Marker::Label::getIndexString(const Data::DataCube &data, + const Data::SeriesList &series, + const Data::DataCube::MultiIndex &index) { std::string res; - for (const auto &[dimIx, ix] : index) { + for (const auto &sliceIndex : + data.getId(series, index).itemSliceIndex) { if (!res.empty()) res += ", "; - auto colIndex = data.getSeriesByDim(dimIx).getColIndex(); - auto value = table.getInfo(colIndex.value()).categories()[ix]; - res += value; + res += data.getValue(sliceIndex); } return res; } diff --git a/src/chart/generator/marker.h b/src/chart/generator/marker.h index 162cf4e0a..d3b718343 100644 --- a/src/chart/generator/marker.h +++ b/src/chart/generator/marker.h @@ -24,7 +24,6 @@ class Marker public: Marker(const Options &options, const Data::DataCube &data, - const Data::DataTable &table, ChannelsStats &stats, const Data::DataCube::MultiIndex &index, size_t idx); @@ -45,38 +44,24 @@ class Marker std::string unit; std::string indexStr; Label() = default; - Label(const Data::MultiDim::SubSliceIndex &index, - const Data::DataCube &data, - const Data::DataTable &table); + explicit Label(std::string &&indexStr); Label(double value, const Data::SeriesIndex &measure, - const Data::MultiDim::SubSliceIndex &index, const Data::DataCube &data, - const Data::DataTable &table); + std::string &&indexStr); bool operator==(const Label &other) const; [[nodiscard]] bool hasValue() const { return value.has_value(); } - static std::string getIndexString( - const Data::MultiDim::SubSliceIndex &index, - const Data::DataCube &data, - const Data::DataTable &table); + static std::string getIndexString(const Data::DataCube &data, + const Data::SeriesList &series, + const Data::DataCube::MultiIndex &index); }; ::Anim::Interpolated
  • Genres