diff --git a/project/cmake/weblib/emcc.txt b/project/cmake/weblib/emcc.txt index 1d0acdc34..8220c0425 100644 --- a/project/cmake/weblib/emcc.txt +++ b/project/cmake/weblib/emcc.txt @@ -11,6 +11,8 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} \ -s EXPORTED_FUNCTIONS=[\ '_malloc',\ '_free',\ +'_vizzu_createData',\ +'_vizzu_createExternalData',\ '_vizzu_createChart',\ '_vizzu_createCanvas',\ '_vizzu_pointerDown',\ diff --git a/src/apps/weblib/cinterface.cpp b/src/apps/weblib/cinterface.cpp index 6b15ae9f5..220c82f57 100644 --- a/src/apps/weblib/cinterface.cpp +++ b/src/apps/weblib/cinterface.cpp @@ -23,6 +23,7 @@ static_assert(offsetof(Point, y) == 8); static_assert(offsetof(Value, dimension) == 0); static_assert(offsetof(Value, measureValue) == 8); static_assert(offsetof(Value, dimensionValue) == 8); +static_assert(sizeof(const char *) == 4); constexpr std::uint_fast32_t hash(std::string_view s) noexcept { @@ -123,9 +124,34 @@ const char *vizzu_version() { return Interface::version(); } void vizzu_setLogging(bool enable) { Interface::setLogging(enable); } -APIHandles::Chart vizzu_createChart() +APIHandles::DataTable vizzu_createData() { - return Interface::getInstance().createChart(); + return Interface::getInstance().createData(); +} + +APIHandles::DataTable vizzu_createExternalData( + void (*stringDeleter)(const char *), + bool (*seriesMeta)(const char *), + const char *(*seriesInfo)(const char *, const char *), + void (*aggregator)(APIHandles::DataTable, + bool (*)(const Vizzu::Data::RowWrapper *), + bool (*)(const Vizzu::Data::RowWrapper *), + std::uint32_t, + const char *const *, + std::uint32_t, + const char *const *, + const char *const *, + const char **), + void (*deleter)()) +{ + return Interface::getInstance().createExternalData( + {{stringDeleter, seriesMeta, seriesInfo, aggregator}, + deleter}); +} + +APIHandles::Chart vizzu_createChart(APIHandles::DataTable data) +{ + return Interface::getInstance().createChart(data); } APIHandles::Canvas vizzu_createCanvas() @@ -295,7 +321,7 @@ const Value *record_getValue(const Vizzu::Data::RowWrapper *record, void data_addDimension(APIHandles::Chart chart, const char *name, - const char **categories, + const char *const *categories, std::uint32_t categoriesCount, const std::uint32_t *categoryIndices, std::uint32_t categoryIndicesCount, diff --git a/src/apps/weblib/cinterface.h b/src/apps/weblib/cinterface.h index 1f0876d03..734639de0 100644 --- a/src/apps/weblib/cinterface.h +++ b/src/apps/weblib/cinterface.h @@ -14,6 +14,7 @@ namespace APIHandles { using Any = const void *; using Chart = const void *; +using DataTable = const void *; using Snapshot = const void *; using Event = Util::EventDispatcher::Params *; using Animation = const void *; @@ -39,7 +40,22 @@ struct alignas(double) Value }; }; -extern APIHandles::Chart vizzu_createChart(); +extern APIHandles::DataTable vizzu_createData(); +extern APIHandles::DataTable vizzu_createExternalData( + void (*stringDeleter)(const char *), + bool (*seriesMeta)(const char *), + const char *(*seriesInfo)(const char *, const char *), + void (*aggregator)(APIHandles::DataTable, + bool (*)(const Vizzu::Data::RowWrapper *), + bool (*)(const Vizzu::Data::RowWrapper *), + std::uint32_t, + const char *const *, + std::uint32_t, + const char *const *, + const char *const *, + const char **), + void (*deleter)()); +extern APIHandles::Chart vizzu_createChart(APIHandles::DataTable); extern APIHandles::Canvas vizzu_createCanvas(); extern void vizzu_pointerDown(APIHandles::Chart chart, APIHandles::Canvas canvas, @@ -79,7 +95,7 @@ extern const char *vizzu_version(); extern void data_addDimension(APIHandles::Chart chart, const char *name, - const char **categories, + const char *const *categories, std::uint32_t categoriesCount, const std::uint32_t *categoryIndices, std::uint32_t categoryIndicesCount, diff --git a/src/apps/weblib/interface.cpp b/src/apps/weblib/interface.cpp index 899f573bf..a05231bf6 100644 --- a/src/apps/weblib/interface.cpp +++ b/src/apps/weblib/interface.cpp @@ -27,7 +27,7 @@ #include "cinterface.h" #include "interfacejs.h" #include "jscriptcanvas.h" -#include "jsfunctionwrapper.h" +#include "jswrappers.h" #include "objectregistry.h" namespace Vizzu @@ -216,6 +216,12 @@ std::variant Interface::getRecordValue( return record.get_value(column); } +std::shared_ptr Interface::getTable( + ObjectRegistryHandle data) +{ + return objects.get(data); +} + void Interface::addEventListener(ObjectRegistryHandle chart, const char *event, void (*callback)(APIHandles::Event, const char *)) @@ -314,49 +320,217 @@ void Interface::setAnimValue(ObjectRegistryHandle chart, getChart(chart)->getAnimOptions().set(path, value); } -void Interface::addDimension(ObjectRegistryHandle chart, +void Interface::addDimension(ObjectRegistryHandle table, const char *name, - const char **categories, + const char *const *categories, std::uint32_t categoriesCount, const std::uint32_t *categoryIndices, std::uint32_t categoryIndicesCount, bool isContiguous) { - getChart(chart)->getTable().add_dimension( - {categories, categoriesCount}, + getTable(table)->add_dimension({categories, categoriesCount}, {categoryIndices, categoryIndicesCount}, name, - {{{"isContiguous", isContiguous ? "true" : "false"}}}); + {{{"isContiguous", isContiguous ? "true" : "false"}}}, + dataframe::adding_type::create_or_override); } -void Interface::addMeasure(ObjectRegistryHandle chart, +void Interface::addMeasure(ObjectRegistryHandle table, const char *name, const char *unit, const double *values, std::uint32_t count) { - getChart(chart)->getTable().add_measure({values, count}, + getTable(table)->add_measure({values, count}, name, - {{std::pair{"unit", unit}}}); + {{std::pair{"unit", unit}}}, + dataframe::adding_type::create_or_override); } -void Interface::addRecord(ObjectRegistryHandle chart, +void Interface::addRecord(ObjectRegistryHandle table, const char *const *cells, std::uint32_t count) { - getChart(chart)->getTable().add_record({cells, count}); + getTable(table)->add_record({cells, count}); } -const char *Interface::dataMetaInfo(ObjectRegistryHandle chart) +const char *Interface::dataMetaInfo(ObjectRegistryHandle table) { thread_local std::string res; - res = getChart(chart)->getTable().as_string(); + res = getTable(table)->as_string(); return res.c_str(); } -ObjectRegistryHandle Interface::createChart() +struct IntFDataTable final : Data::DataTable +{ + JsCompositionWrapper externalData; + + [[nodiscard]] explicit IntFDataTable( + JsCompositionWrapper + &&external_data) : + externalData(std::move(external_data)) + {} + + [[nodiscard]] dataframe::series_meta_t get_series_meta( + const std::string &id) const & override + { + return {id, + externalData.values.seriesMeta(id.c_str()) + ? dataframe::series_type::dimension + : dataframe::series_type::measure}; + } + + [[nodiscard]] std::string_view get_series_info( + const std::string &id, + const char *key) const & override + { + thread_local std::string res; + res = create_unique_ptr( + externalData.values.seriesInfo(id.c_str(), key), + externalData.values.stringDeleter) + .get(); + return res; + } + + [[noreturn]] static void unsupported() + { + throw std::logic_error("unsupported"); + } + + [[noreturn]] void add_dimension(std::span, + std::span, + std::string_view, + std::span>, + dataframe::adding_type) + & override + { + unsupported(); + } + + [[noreturn]] void add_measure(std::span, + std::string_view, + std::span>, + dataframe::adding_type) + & override + { + unsupported(); + } + + [[noreturn]] void add_record(std::span) + & override + { + unsupported(); + } + + [[nodiscard]] std::string as_string() const & override + { + unsupported(); + } + + [[nodiscard]] std::pair< + std::shared_ptr, + std::map> + aggregate(const Data::Filter &filter, + const std::set &aggregate_by, + const std::set &aggregating) + const & override + { + auto &intf = Interface::getInstance(); + auto &&res = dataframe::dataframe::create_new(); + + std::vector aggregateBy(aggregate_by.size()); + std::ranges::transform(aggregate_by, + aggregateBy.begin(), + [](const Data::SeriesIndex &index) + { + return index.getColIndex().c_str(); + }); + + std::vector aggregatingCols(aggregating.size()); + std::ranges::transform(aggregating, + aggregatingCols.begin(), + [](const Data::SeriesIndex &index) + { + return index.getColIndex().c_str(); + }); + + using ArrType = + Refl::EnumArray; + static ArrType arr = std::apply( + [](auto &&...names) + { + return ArrType{{std::string{names}...}}; + }, + Refl::enum_names); + + std::vector aggregatingFun(aggregating.size()); + std::ranges::transform(aggregating, + aggregatingFun.begin(), + [](const Data::SeriesIndex &index) -> const char * + { + return arr[index.getAggr()].c_str(); + }); + + std::vector< + std::unique_ptr> + aggregatingNames; + aggregatingNames.reserve(aggregating.size()); + + { + std::vector aggregatingRawNames( + aggregating.size()); + + auto &&dataPtr = create_unique_ptr(intf.createData(&res), + &object_free); + + externalData.values.aggregator(dataPtr.get(), + filter.getFun1(), + filter.getFun2(), + aggregateBy.size(), + aggregateBy.data(), + aggregating.size(), + aggregatingCols.data(), + aggregatingFun.data(), + aggregatingRawNames.data()); + + for (auto &&ptr : std::move(aggregatingRawNames)) + aggregatingNames.emplace_back(ptr, + externalData.values.stringDeleter); + } + + std::map resMap; + for (auto it = aggregatingNames.data(); + auto &&agg : aggregating) + resMap[agg] = it++->get(); + + for (const auto &dim : aggregate_by) + res->set_sort(dim.getColIndex(), + dataframe::sort_type::less, + dataframe::na_position::first); + + res->finalize(); + return {res, resMap}; + } +}; + +ObjectRegistryHandle Interface::createData( + std::shared_ptr *df) +{ + return objects.reg(std::make_shared( + df ? *df : dataframe::dataframe::create_new())); +} + +ObjectRegistryHandle Interface::createExternalData( + JsCompositionWrapper &&externalData) { - auto &&widget = std::make_shared(); + return objects.reg( + std::make_shared(std::move(externalData))); +} + +ObjectRegistryHandle Interface::createChart(ObjectRegistryHandle data) +{ + auto &&widget = std::make_shared( + objects.get(data)); auto &openUrl = widget->openUrl; auto &doChange = widget->getChart().onChanged; @@ -380,8 +554,7 @@ ObjectRegistryHandle Interface::createChart() ObjectRegistryHandle Interface::createCanvas() { - return objects.reg( - std::make_shared()); + return objects.reg(std::make_shared()); } void Interface::setLogging(bool enable) diff --git a/src/apps/weblib/interface.h b/src/apps/weblib/interface.h index bda5f0760..d4cddaca7 100644 --- a/src/apps/weblib/interface.h +++ b/src/apps/weblib/interface.h @@ -2,7 +2,7 @@ #define LIB_INTERFACE_H #include "cinterface.h" -#include "jsfunctionwrapper.h" +#include "jswrappers.h" #include "objectregistry.h" namespace Gfx @@ -12,6 +12,14 @@ struct ICanvas; namespace Vizzu { +namespace dataframe +{ +class alignas(alignof(double)) dataframe_interface; +} +namespace Data +{ +struct DataTable; +} namespace UI { class ChartWidget; @@ -22,11 +30,32 @@ class Chart; class Interface { public: + struct ExternalData + { + void (*stringDeleter)(const char *); + bool (*seriesMeta)(const char *); + const char *(*seriesInfo)(const char *, const char *); + void (*aggregator)(ObjectRegistryHandle, + bool (*)(const Data::RowWrapper *), + bool (*)(const Data::RowWrapper *), + std::uint32_t, + const char *const *, + std::uint32_t, + const char *const *, + const char *const *, + const char **); + }; + static Interface &getInstance(); Interface(); static const char *version(); - ObjectRegistryHandle createChart(); + ObjectRegistryHandle createExternalData( + JsCompositionWrapper &&externalData); + ObjectRegistryHandle createData( + std::shared_ptr *df = + nullptr); + ObjectRegistryHandle createChart(ObjectRegistryHandle data); ObjectRegistryHandle createCanvas(); static void setLogging(bool enable); void pointerMove(ObjectRegistryHandle chart, @@ -95,22 +124,23 @@ class Interface double &rx, double &ry); - void addDimension(ObjectRegistryHandle chart, + void addDimension(ObjectRegistryHandle table, const char *name, - const char **categories, + const char *const *categories, std::uint32_t categoriesCount, const std::uint32_t *categoryIndices, std::uint32_t categoryIndicesCount, bool isContiguous); - void addMeasure(ObjectRegistryHandle chart, + void addMeasure(ObjectRegistryHandle table, const char *name, const char *unit, const double *values, std::uint32_t count); - void addRecord(ObjectRegistryHandle chart, + void addRecord(ObjectRegistryHandle table, const char *const *cells, std::uint32_t count); - const char *dataMetaInfo(ObjectRegistryHandle chart); + const char *dataMetaInfo(ObjectRegistryHandle table); + void addEventListener(ObjectRegistryHandle chart, const char *event, void (*callback)(APIHandles::Event, const char *)); @@ -133,13 +163,20 @@ class Interface const Data::RowWrapper &record, const char *column); + std::shared_ptr getTable( + ObjectRegistryHandle data); + private: struct Snapshot; struct Animation; std::shared_ptr getChart(ObjectRegistryHandle chart); - ObjectRegistry + ObjectRegistry objects; }; diff --git a/src/apps/weblib/jsfunctionwrapper.h b/src/apps/weblib/jsfunctionwrapper.h deleted file mode 100644 index 19ba8890a..000000000 --- a/src/apps/weblib/jsfunctionwrapper.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef LIB_JSFUNCTIONWRAPPER_H -#define LIB_JSFUNCTIONWRAPPER_H - -#include - -#include "interfacejs.h" - -namespace Vizzu -{ - -// Unlike std::unique_ptr, the deleter of std::shared_ptr is invoked -// even if the managed pointer is null -> keep unique_ptr -template -using JsFunctionWrapper = - std::unique_ptr *...), - void (*)(R(std::remove_reference_t *...))>; - -} - -#endif diff --git a/src/apps/weblib/jswrappers.h b/src/apps/weblib/jswrappers.h new file mode 100644 index 000000000..558160574 --- /dev/null +++ b/src/apps/weblib/jswrappers.h @@ -0,0 +1,50 @@ +#ifndef LIB_JSWRAPPERS_H +#define LIB_JSWRAPPERS_H + +#include + +namespace Vizzu +{ + +// Unlike std::unique_ptr, the deleter of std::shared_ptr is invoked +// even if the managed pointer is null -> keep unique_ptr +template +using JsFunctionWrapper = + std::unique_ptr *...), + void (*)(R(std::remove_reference_t *...))>; + +template struct JsCompositionWrapper +{ + T values; + void (*deleter)(); + ~JsCompositionWrapper() + { + if (auto &&d = std::exchange(deleter, {})) d(); + } + [[nodiscard]] JsCompositionWrapper(T &&values, + void (*deleter)()) : + values(std::move(values)), + deleter(deleter) + {} + JsCompositionWrapper(const JsCompositionWrapper &) = delete; + JsCompositionWrapper(JsCompositionWrapper &&other) noexcept : + values(std::move(other.values)), + deleter(std::exchange(other.deleter, {})) + {} + JsCompositionWrapper &operator=( + const JsCompositionWrapper &) = delete; + JsCompositionWrapper &operator=( + JsCompositionWrapper &&other) noexcept + { + if (this->deleter != other.deleter) { + if (deleter) deleter(); + values = std::move(other.values); + deleter = std::exchange(other.deleter, {}); + } + return *this; + } +}; + +} + +#endif diff --git a/src/apps/weblib/ts-api/chart.ts b/src/apps/weblib/ts-api/chart.ts index 532195ba9..08d0a52d3 100644 --- a/src/apps/weblib/ts-api/chart.ts +++ b/src/apps/weblib/ts-api/chart.ts @@ -5,8 +5,8 @@ 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' +import { CAnimControl, CAnimation } from './module/canimctrl.js' import { Data } from './data.js' import { Events, EventType, EventHandler, EventMap } from './events.js' import { Mirrored } from './tsutils.js' @@ -38,23 +38,54 @@ export class Chart implements ChartInterface { private _cChart: CChart private _cCanvas: CCanvas private _module: Module - private _cData: CData private _data: Data private _events: Events private _plugins: PluginRegistry private _changed = true + private _createData(): CData { + if (this._options.otherSource) { + const { series, aggregate } = this._options.otherSource + return this._module.createExternalData( + new Map(series.map((info) => [info.name, info.type === 'dimension'] as const)), + new Map(series.map((info) => [info.name, new Map(Object.entries(info))] as const)), + ( + data: CData, + filt1: number, + filt2: number, + grouping: D.SeriesList, + aggregating: D.SeriesList + ): string[] => { + new Data(data).set( + aggregate( + this._data.getFilterByPtr(filt1), + this._data.getFilterByPtr(filt2), + grouping, + aggregating + ) + ) + return data + .getMetaInfo() + .series.filter((series) => series.type === 'measure') + .map((series) => series.name) + } + ) + } + return this._module.createData() + } + constructor(module: Module, options: VizzuOptions, plugins: PluginRegistry) { this._options = options this._plugins = plugins this._module = module - this._cChart = this._module.createChart() + const cDataFromInside = true + const cData = this._createData() + this._cChart = this._module.createChart(cData) this._module.registerChart(this._cChart, this) this._cCanvas = this._module.createCanvas() - this._cData = this._module.getData(this._cChart) - this._data = new Data(this._cData) + this._data = new Data(cData, this._cChart, cDataFromInside) this._events = new Events(this._cChart) this._plugins.init(this._events) @@ -79,6 +110,7 @@ export class Chart implements ChartInterface { this._module.unregisterChart(this._cChart) this._cCanvas.free() this._cChart.free() + this._data.detach() } start(): void { @@ -199,7 +231,7 @@ export class Chart implements ChartInterface { } get data(): Mirrored { - return this._cData.getMetaInfo() + return this._data.getMetaInfo() } get config(): Mirrored { diff --git a/src/apps/weblib/ts-api/cvizzu.types.d.ts b/src/apps/weblib/ts-api/cvizzu.types.d.ts index a7d295b45..50eac0aec 100644 --- a/src/apps/weblib/ts-api/cvizzu.types.d.ts +++ b/src/apps/weblib/ts-api/cvizzu.types.d.ts @@ -7,6 +7,7 @@ export type CException = CPointer export type CTypeInfo = CPointer export type CSnapshotPtr = CPointer export type CAnimationPtr = CPointer +export type CDataPtr = CPointer export type CChartPtr = CPointer export type CCanvasPtr = CPointer export type CEventPtr = CPointer @@ -65,7 +66,15 @@ export interface CVizzu { ExceptionInfo: CExceptionInfoConstructor // exported functions - _vizzu_createChart(): CChartPtr + _vizzu_createData(): CDataPtr + _vizzu_createExternalData( + stringDeleter: CFunction, + seriesMeta: CFunction, + seriesInfo: CFunction, + aggregator: CFunction, + deleter: CFunction + ): CDataPtr + _vizzu_createChart(data: CDataPtr): CChartPtr _vizzu_createCanvas(): CCanvasPtr _vizzu_pointerDown( chart: CChartPtr, diff --git a/src/apps/weblib/ts-api/data.ts b/src/apps/weblib/ts-api/data.ts index 2da3963f8..de1b9ba49 100644 --- a/src/apps/weblib/ts-api/data.ts +++ b/src/apps/weblib/ts-api/data.ts @@ -1,15 +1,37 @@ import * as D from './types/data.js' -import { CRecord, CData } from './module/cdata.js' +import { CData } from './module/cdata.js' +import { CRecord, CChart } from './module/cchart.js' import { DataRecord } from './datarecord.js' import { Mirrored } from './tsutils.js' +import { CPointer } from './cvizzu.types' type DataTypes = D.DimensionValue | D.MeasureValue export class Data { - private _cData: CData + private readonly _cData: CData + private readonly _cChart: CChart | undefined + private readonly _dataOwned: boolean + private readonly _filters: Map - constructor(cData: CData) { + constructor(cData: CData, cChart: CChart | undefined = undefined, dataOwned: boolean = true) { this._cData = cData + this._cChart = cChart + this._dataOwned = dataOwned + this._filters = new Map() + } + + detach(): void { + if (this._dataOwned) { + this._cData.free() + } + } + + getMetaInfo(): Mirrored { + return this._cData.getMetaInfo() + } + + getFilterByPtr(ptr: CPointer): D.OutFilterCallback | null { + return this._filters.get(ptr) ?? null } set(obj?: D.TableBySeries | D.TableByRecords): void { @@ -256,12 +278,26 @@ export class Data { this._cData.addMeasure(name, unit, numbers) } - private _setFilter(filter: D.FilterCallback | null): void { - if (typeof filter === 'function') { - const callback = (cRecord: CRecord): boolean => filter(new DataRecord(cRecord)) - this._cData.setFilter(callback) - } else if (filter === null) { - this._cData.setFilter(null) + private _setFilter(filter: D.FilterCallback | D.OutFilterCallback | null): void { + if (this._cChart === undefined) { + throw new Error('chart is not attached') + } + if (filter === null) { + this._cChart.setFilter(null) + } else if (typeof filter === 'function' && filter.length == 1) { + const callback = (cRecord: CRecord): boolean => + (filter as D.FilterCallback)(new DataRecord(cRecord)) + this._cChart.setFilter(callback) + } else if (typeof filter === 'function' && filter.length == 0) { + this._filters.set( + this._cChart.setFilter( + (): boolean => true, + (ptr: CPointer) => { + this._filters.delete(ptr) + } + ), + filter as D.OutFilterCallback + ) } else { throw new Error('data filter is not a function or null') } diff --git a/src/apps/weblib/ts-api/datarecord.ts b/src/apps/weblib/ts-api/datarecord.ts index b9bf923ce..aeaf614cc 100644 --- a/src/apps/weblib/ts-api/datarecord.ts +++ b/src/apps/weblib/ts-api/datarecord.ts @@ -1,5 +1,5 @@ import * as Data from './types/data.js' -import { CRecord } from './module/cdata.js' +import { CRecord } from './module/cchart.js' export class DataRecord implements Data.Record { [seriesName: Data.SeriesName]: Data.Value diff --git a/src/apps/weblib/ts-api/module/cchart.ts b/src/apps/weblib/ts-api/module/cchart.ts index 927729ab1..4b2ddbcfe 100644 --- a/src/apps/weblib/ts-api/module/cchart.ts +++ b/src/apps/weblib/ts-api/module/cchart.ts @@ -1,4 +1,4 @@ -import { CString, CFunction, CEventPtr } from '../cvizzu.types' +import { CString, CFunction, CEventPtr, CPointer, CRecordPtr } from '../cvizzu.types' import * as Anim from '../types/anim.js' import * as Config from '../types/config.js' @@ -23,6 +23,31 @@ class CConfig extends CProxy {} class CStyle extends CProxy {} class CAnimOptions extends CProxy {} +export class CRecord extends CObject { + constructor(env: CEnv, recordPtr: CRecordPtr) { + super(() => recordPtr, env) + } + + getValue(columnName: string): string | number { + const col = this._toCString(columnName) + let ptr + let value + + try { + ptr = this._call(this._wasm._record_getValue)(col) + + if (this._wasm.getValue(ptr, 'i1')) { + value = this._fromCString(this._wasm.getValue(ptr + 8, 'i8*')) + } else { + value = this._wasm.getValue(ptr + 8, 'double') + } + } finally { + this._wasm._free(col) + } + return value + } +} + export class CChart extends CManagedObject { config: CConfig style: CStyle @@ -161,4 +186,25 @@ export class CChart extends CManagedObject { this._wasm._anim_setValue ) } + + setFilter( + callback: ((record: CRecord) => boolean) | null, + out_deleter: ((ptr: CPointer) => void) | null = null + ): CPointer { + const callbackPtrs: [CPointer, CPointer] = [0, 0] + if (callback !== null) { + const f = (recordPtr: CRecordPtr): boolean => callback(new CRecord(this, recordPtr)) + callbackPtrs[0] = this._wasm.addFunction(f, 'ii') + const deleter = (ptr: CPointer): void => { + if (ptr !== callbackPtrs[0]) console.warn('Wrong pointer passed to destructor') + if (out_deleter) out_deleter(callbackPtrs[0]) + this._wasm.removeFunction(callbackPtrs[0]) + this._wasm.removeFunction(callbackPtrs[1]) + } + callbackPtrs[1] = this._wasm.addFunction(deleter, 'vi') + } + this._call(this._wasm._chart_setFilter)(...callbackPtrs) + + return callbackPtrs[0] + } } diff --git a/src/apps/weblib/ts-api/module/cdata.ts b/src/apps/weblib/ts-api/module/cdata.ts index c4876c293..1896e95f5 100644 --- a/src/apps/weblib/ts-api/module/cdata.ts +++ b/src/apps/weblib/ts-api/module/cdata.ts @@ -1,34 +1,8 @@ -import { CPointer, CRecordPtr } from '../cvizzu.types' -import { CObject, CEnv } from './cenv.js' +import { CManagedObject } from './cenv.js' import { Mirrored } from '../tsutils' import * as Data from '../types/data.js' -export class CRecord extends CObject { - constructor(env: CEnv, recordPtr: CRecordPtr) { - super(() => recordPtr, env) - } - - getValue(columnName: string): string | number { - const col = this._toCString(columnName) - let ptr - let value - - try { - ptr = this._call(this._wasm._record_getValue)(col) - - if (this._wasm.getValue(ptr, 'i1')) { - value = this._fromCString(this._wasm.getValue(ptr + 8, 'i8*')) - } else { - value = this._wasm.getValue(ptr + 8, 'double') - } - } finally { - this._wasm._free(col) - } - return value - } -} - -export class CData extends CObject { +export class CData extends CManagedObject { getMetaInfo(): Mirrored { const cInfo = this._call(this._wasm._data_metaInfo)() const info = this._fromCString(cInfo) @@ -127,19 +101,4 @@ export class CData extends CObject { this._wasm._free(ptrArr) } } - - setFilter(callback: ((record: CRecord) => boolean) | null): void { - const callbackPtrs: [CPointer, CPointer] = [0, 0] - if (callback !== null) { - const f = (recordPtr: CRecordPtr): boolean => callback(new CRecord(this, recordPtr)) - callbackPtrs[0] = this._wasm.addFunction(f, 'ii') - const deleter = (ptr: CPointer): void => { - if (ptr !== callbackPtrs[0]) console.warn('Wrong pointer passed to destructor') - this._wasm.removeFunction(callbackPtrs[0]) - this._wasm.removeFunction(callbackPtrs[1]) - } - callbackPtrs[1] = this._wasm.addFunction(deleter, 'vi') - } - this._call(this._wasm._chart_setFilter)(...callbackPtrs) - } } diff --git a/src/apps/weblib/ts-api/module/cenv.ts b/src/apps/weblib/ts-api/module/cenv.ts index 45de55855..77e8dda7b 100644 --- a/src/apps/weblib/ts-api/module/cenv.ts +++ b/src/apps/weblib/ts-api/module/cenv.ts @@ -68,9 +68,11 @@ export class CObject extends CEnv { } export class CManagedObject extends CObject { - constructor(getId: CPointerClosure, cenv: CEnv) { + constructor(getId: CPointerClosure, cenv: CEnv, manage: boolean = true) { super(getId, cenv) - this._objectRegistry.register(this.getId) + if (manage) { + this._objectRegistry.register(this.getId) + } } free(): void { diff --git a/src/apps/weblib/ts-api/module/module.ts b/src/apps/weblib/ts-api/module/module.ts index 326930f2e..24a985345 100644 --- a/src/apps/weblib/ts-api/module/module.ts +++ b/src/apps/weblib/ts-api/module/module.ts @@ -1,4 +1,5 @@ -import { CVizzu } from '../cvizzu.types' +import { CPointer, CVizzu } from '../cvizzu.types' +import * as Data from '../types/data.js' import { ObjectRegistry } from './objregistry.js' import { CEnv } from './cenv.js' @@ -9,6 +10,7 @@ import { CAnimControl } from './canimctrl.js' import { CCoordSystem } from './ccoordsys.js' import { Canvas } from './canvas.js' import { Chart } from './chart.js' +import { AggregatorType } from '../types/data.js' export class Module extends CEnv { constructor(wasm: CVizzu) { @@ -45,10 +47,6 @@ export class Module extends CEnv { this._callStatic(this._wasm._vizzu_setLogging)(enabled) } - getData(cChart: CChart): CData { - return new CData(cChart.getId, this) - } - getCoordSystem(cChart: CChart): CCoordSystem { return new CCoordSystem(cChart.getId, this) } @@ -57,8 +55,103 @@ export class Module extends CEnv { return new CAnimControl(cChart.getId, this) } - createChart(): CChart { - return new CChart(this, this._getStatic(this._wasm._vizzu_createChart)) + createData(): CData { + return new CData(this._getStatic(this._wasm._vizzu_createData), this) + } + + createExternalData( + isDimension: Map, + toInfo: Map>, + aggregatorFunction: ( + data: CData, + filt1: CPointer, + filt2: CPointer, + grouping: Data.SeriesList, + aggregating: Data.SeriesList + ) => string[] + ): CData { + const callbackPtrs: [CPointer, CPointer, CPointer, CPointer, CPointer] = [0, 0, 0, 0, 0] + const stringDeleter = (ptr: CPointer): void => { + this._wasm._free(ptr) + } + callbackPtrs[0] = this._wasm.addFunction(stringDeleter, 'vi') + + const seriesMeta = (series: CPointer): boolean => { + const res = isDimension.get(this._fromCString(series)) + if (res === undefined) throw new Error('Unknown series') + return res + } + callbackPtrs[1] = this._wasm.addFunction(seriesMeta, 'ii') + + const seriesInfo = (series: CPointer, key: CPointer): CPointer => { + const seriesName = this._fromCString(series) + const keyName = this._fromCString(key) + const info = toInfo.get(seriesName) + if (!info) throw new Error('Unknown series') + return this._toCString(info.get(keyName) || '') + } + callbackPtrs[2] = this._wasm.addFunction(seriesInfo, 'iii') + + const aggregator = ( + dataPtr: CPointer, + filt1: CPointer, + filt2: CPointer, + grouping: number, + groupingList: CPointer, + aggregating: number, + aggregatingNames: CPointer, + aggregatingFunctions: CPointer, + outputNames: CPointer + ): void => { + const data = new CData((): CPointer => dataPtr, this, false) + + const groupingArray: Data.SeriesList = new Array(grouping) + for (let i = 0; i < grouping; i++) { + groupingArray[i] = { + name: this._fromCString(this._wasm.getValue(groupingList + i * 4, 'i8*')) + } + } + const aggregatingArray: Data.SeriesList = new Array(aggregating) + for (let i = 0; i < aggregating; i++) { + aggregatingArray[i] = { + name: this._fromCString(this._wasm.getValue(aggregatingNames + i * 4, 'i8*')), + aggregator: this._fromCString( + this._wasm.getValue(aggregatingFunctions + i * 4, 'i8*') + ) as AggregatorType + } + this._wasm.setValue(outputNames + i * 4, 0, 'i8*') + } + + const aggregated = aggregatorFunction( + data, + filt1, + filt2, + groupingArray, + aggregatingArray + ) + for (let i = 0; i < aggregating; i++) { + const val = aggregated[i] + if (val) { + this._wasm.setValue(outputNames + i * 4, this._toCString(val), 'i8*') + } + } + } + callbackPtrs[3] = this._wasm.addFunction(aggregator, 'viiiiiiiii') + + const deleter = (): void => { + for (const ptr of callbackPtrs) { + this._wasm.removeFunction(ptr) + } + } + callbackPtrs[4] = this._wasm.addFunction(deleter, 'v') + + const cPointer = this._callStatic(this._wasm._vizzu_createExternalData)(...callbackPtrs) + return new CData((): CPointer => cPointer, this) + } + + createChart(data: CData): CChart { + const cPointer = this._callStatic(this._wasm._vizzu_createChart)(data.getId()) + return new CChart(this, (): CPointer => cPointer) } createCanvas(): CCanvas { diff --git a/src/apps/weblib/ts-api/vizzu.ts b/src/apps/weblib/ts-api/vizzu.ts index 720502539..247093ce7 100644 --- a/src/apps/weblib/ts-api/vizzu.ts +++ b/src/apps/weblib/ts-api/vizzu.ts @@ -48,7 +48,21 @@ export interface FeatureOptions { features?: Plugin[] } -export type VizzuOptions = FeatureOptions & LazyCanvasOptions +export interface OtherSource { + series: Data.SeriesMetaInfo[] + aggregate: ( + filter1: Data.OutFilterCallback | null, + filter2: Data.OutFilterCallback | null, + groupBy: Data.SeriesList, + series: Data.SeriesList + ) => Data.Set +} + +export interface OtherSourceOptions { + otherSource?: OtherSource +} + +export type VizzuOptions = FeatureOptions & OtherSourceOptions & LazyCanvasOptions export type FeatureFunction = (feature: Feature | Plugin, enabled?: boolean) => PluginApi export interface Features extends Record, FeatureFunction { diff --git a/src/apps/weblib/typeschema-api/data.yaml b/src/apps/weblib/typeschema-api/data.yaml index 8035a008f..3b8419423 100644 --- a/src/apps/weblib/typeschema-api/data.yaml +++ b/src/apps/weblib/typeschema-api/data.yaml @@ -315,6 +315,13 @@ definitions: required: [record] return: { type: boolean } + OutFilterCallback: + type: function + description: A function returns a specific value for source data + arguments: + required: [] + return: { type: any } + SeriesMetaInfo: $ref: SeriesInfo @@ -335,7 +342,9 @@ definitions: description: | A filter callback is called on each record of the dataset on chart generation. If the callback returns false, the record will not be shown on the chart. - $ref: FilterCallback + oneOf: + - $ref: FilterCallback + - $ref: OutFilterCallback nullable: true TableBySeries: diff --git a/src/chart/main/chart.cpp b/src/chart/main/chart.cpp index c0f80a5a4..1a038fbd1 100644 --- a/src/chart/main/chart.cpp +++ b/src/chart/main/chart.cpp @@ -20,12 +20,13 @@ namespace Vizzu { -Chart::Chart() : +Chart::Chart(const std::shared_ptr &table) : + table{table}, nextOptions(std::make_shared()), stylesheet(Styles::Chart::def(), actStyles), computedStyles(stylesheet.getDefaultParams()), events(eventDispatcher), - animator(table, + animator(*table, *events.animation.begin, *events.animation.complete) { @@ -98,7 +99,7 @@ void Chart::setAnimation(const Anim::AnimationPtr &animation) Gen::Config Chart::getConfig() { - return Gen::Config{getOptions(), table}; + return Gen::Config{getOptions(), *table}; } void Chart::draw(Gfx::ICanvas &canvas) @@ -126,7 +127,7 @@ Gen::PlotPtr Chart::plot(const Gen::PlotOptionsPtr &options) { options->setAutoParameters(); - auto res = Gen::PlotBuilder{table, + auto res = Gen::PlotBuilder{*table, options, stylesheet.getFullParams(options, layout.boundary.size)} .build(); diff --git a/src/chart/main/chart.h b/src/chart/main/chart.h index 7d226b8c6..b901b5953 100644 --- a/src/chart/main/chart.h +++ b/src/chart/main/chart.h @@ -28,12 +28,12 @@ class Chart public: Util::Event<> onChanged; - Chart(); + explicit Chart(const std::shared_ptr &table); Chart(Chart &&) noexcept = delete; void draw(Gfx::ICanvas &canvas); void setBoundRect(const Geom::Rect &rect); - Data::DataTable &getTable() { return table; } + [[nodiscard]] Data::DataTable &getTable() const { return *table; } Styles::Sheet &getStylesheet() { return stylesheet; } Styles::Chart &getStyles() { return actStyles; } [[nodiscard]] const Styles::Chart &getComputedStyles() const @@ -81,7 +81,7 @@ class Chart private: Layout layout; - Data::DataTable table; + std::shared_ptr table; Gen::PlotPtr actPlot; Gen::PlotOptionsPtr nextOptions; Gen::Options prevOptions; diff --git a/src/chart/ui/chart.cpp b/src/chart/ui/chart.cpp index 1b5401c88..b75986088 100644 --- a/src/chart/ui/chart.cpp +++ b/src/chart/ui/chart.cpp @@ -12,7 +12,9 @@ namespace Vizzu::UI { -ChartWidget::ChartWidget() : +ChartWidget::ChartWidget( + const std::shared_ptr &table) : + chart(table), onClick(chart.getEventDispatcher().createEvent("click")), onPointerMoveEvent( chart.getEventDispatcher().createEvent("pointermove")), diff --git a/src/chart/ui/chart.h b/src/chart/ui/chart.h index 104da4f63..dab30b6f6 100644 --- a/src/chart/ui/chart.h +++ b/src/chart/ui/chart.h @@ -14,7 +14,7 @@ class ChartWidget public: Util::Event openUrl; - explicit ChartWidget(); + explicit ChartWidget(const std::shared_ptr &); void onPointerDown(const GUI::PointerEvent &event) const; void onPointerMove(const GUI::PointerEvent &event) const; diff --git a/src/dataframe/impl/data_source.h b/src/dataframe/impl/data_source.h index fb2232637..f7d120ee0 100644 --- a/src/dataframe/impl/data_source.h +++ b/src/dataframe/impl/data_source.h @@ -15,8 +15,6 @@ namespace Vizzu::dataframe { -enum class series_type : std::uint8_t { unknown, dimension, measure }; - enum class error_type : std::uint8_t { series_not_found, duplicated_series, @@ -29,12 +27,6 @@ enum class error_type : std::uint8_t { internal_error }; -struct series_meta_t -{ - std::string name; - series_type type; -}; - [[maybe_unused]] [[noreturn]] void error(error_type err, std::string_view arg = {}); diff --git a/src/dataframe/impl/dataframe.cpp b/src/dataframe/impl/dataframe.cpp index 2be9551fa..e8e863a7f 100644 --- a/src/dataframe/impl/dataframe.cpp +++ b/src/dataframe/impl/dataframe.cpp @@ -723,7 +723,8 @@ std::string dataframe::get_record_id(std::size_t my_record) & return my_record < ids.size() ? ids[my_record] : std::string{}; } -series_meta_t dataframe::get_series_meta(const std::string &id) const +series_meta_t dataframe::get_series_meta( + const std::string &id) const & { switch (auto &&ser = get_data_source().get_series(id)) { using enum series_type; @@ -851,8 +852,7 @@ const data_source &dataframe::get_data_source() const : *unsafe_get(source).other; } -std::string_view dataframe::get_series_info( - const std::string_view &id, +std::string_view dataframe::get_series_info(const std::string &id, const char *key) const & { switch (auto &&ser = get_data_source().get_series(id)) { diff --git a/src/dataframe/impl/dataframe.h b/src/dataframe/impl/dataframe.h index aa60cfdfe..cfe7c7b66 100644 --- a/src/dataframe/impl/dataframe.h +++ b/src/dataframe/impl/dataframe.h @@ -120,11 +120,10 @@ class dataframe const std::string_view &dimension) const &; [[nodiscard]] series_meta_t get_series_meta( - const std::string &id) const; + const std::string &id) const &; - [[nodiscard]] std::string_view get_series_info( - const std::string_view &id, - const char *key) const &; + [[nodiscard]] std::string_view + get_series_info(const std::string &id, const char *key) const &; [[nodiscard]] cell_reference get_data( const record_identifier &record_id, diff --git a/src/dataframe/interface.cpp b/src/dataframe/interface.cpp index 64823e549..2f94b2896 100644 --- a/src/dataframe/interface.cpp +++ b/src/dataframe/interface.cpp @@ -176,8 +176,14 @@ std::size_t dataframe_interface::get_record_count() const & return as_impl(this).get_record_count(); } +series_meta_t dataframe_interface::get_series_meta( + const std::string &id) const & +{ + return as_impl(this).get_series_meta(id); +} + std::string_view dataframe_interface::get_series_info( - const std::string_view &id, + const std::string &id, const char *key) const & { return as_impl(this).get_series_info(id, key); @@ -201,4 +207,9 @@ std::string dataframe_interface::get_record_id( return as_impl(this).get_record_id(my_record); } +[[nodiscard]] std::string dataframe_interface::as_string() const & +{ + return as_impl(this).as_string(); +} + } \ No newline at end of file diff --git a/src/dataframe/interface.h b/src/dataframe/interface.h index 6c2233368..02cc149ee 100644 --- a/src/dataframe/interface.h +++ b/src/dataframe/interface.h @@ -21,6 +21,14 @@ namespace Vizzu::dataframe using cell_value = std::variant; using cell_reference = std::variant; +enum class series_type : std::uint8_t { unknown, dimension, measure }; + +struct series_meta_t +{ + std::string name; + series_type type{}; +}; + enum class aggregator_type : std::uint8_t { sum, min, @@ -154,9 +162,11 @@ class alignas(align_impl) dataframe_interface [[nodiscard]] std::size_t get_record_count() const &; - [[nodiscard]] std::string_view get_series_info( - const std::string_view &id, - const char *key) const &; + [[nodiscard]] series_meta_t get_series_meta( + const std::string &id) const &; + + [[nodiscard]] std::string_view + get_series_info(const std::string &id, const char *key) const &; [[nodiscard]] bool is_removed(std::size_t record_id) const &; @@ -166,6 +176,8 @@ class alignas(align_impl) dataframe_interface [[nodiscard]] std::string get_record_id(std::size_t my_record) &; + [[nodiscard]] std::string as_string() const &; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) alignas(align_impl) std::byte data[max_size_impl]; }; diff --git a/src/dataframe/old/datatable.cpp b/src/dataframe/old/datatable.cpp index 007e66d53..eef5d1fd7 100644 --- a/src/dataframe/old/datatable.cpp +++ b/src/dataframe/old/datatable.cpp @@ -105,38 +105,16 @@ DataCube::DataCube(const DataTable &table, auto &&channels = options.getChannels(); auto &&dimensions = channels.getDimensions(); auto &&measures = channels.getMeasures(); - auto empty = dimensions.empty() && measures.empty(); - df = {empty ? dataframe::dataframe::create_new() - : table.copy(false)}; - - if (empty) { + if (dimensions.empty() && measures.empty()) { + df = dataframe::dataframe::create_new(); df->finalize(); return; } - df->remove_records(options.dataFilter.getFunction()); - - auto removed = df->copy(false); - - for (const auto &dim : dimensions) - df->aggregate_by(dim.getColIndex()); - - for (const auto &meas : measures) { - auto &&sid = meas.getColIndex(); - auto &&aggr = meas.getAggr(); - - measure_names.try_emplace(std::pair{sid, aggr}, - df->set_aggregate(sid, aggr)); - } - - for (const auto &dim : dimensions) { - df->set_sort(dim.getColIndex(), - dataframe::sort_type::less, - dataframe::na_position::first); - } + std::tie(df, measure_names) = + table.aggregate(options.dataFilter, dimensions, measures); - df->finalize(); for (std::size_t ix{}; const auto &dim : dimensions) { auto &&dimName = dim.getColIndex(); auto &&cats = df->get_categories(dimName); @@ -167,22 +145,16 @@ DataCube::DataCube(const DataTable &table, std::swap(sumBy, common); if (sumBy.empty()) continue; - auto &sub_df = - *cacheImpl.try_emplace(channelId, removed->copy(false)) - .first->second; - - auto &&set = sumBy.as_set(); - for (auto first = set.begin(), last = set.end(); - auto &&dim : dimensions) - if (first == last || dim < *first) - sub_df.aggregate_by(dim.getColIndex()); - else - ++first; - - std::ignore = sub_df.set_aggregate(meas->getColIndex(), - meas->getAggr()); + auto dimCp{dimensions}; + std::erase_if(dimCp, + [&sumBy](const auto &dim) + { + return sumBy.contains(dim); + }); - sub_df.finalize(); + cacheImpl.try_emplace(channelId, + table.aggregate(options.dataFilter, dimCp, {*meas}) + .first); } } @@ -209,8 +181,7 @@ bool DataCube::empty() const const std::string &DataCube::getName( const SeriesIndex &seriesId) const { - return measure_names.at( - {seriesId.getColIndex(), seriesId.getAggr()}); + return measure_names.at(seriesId); } MarkerId DataCube::getId(const SeriesList &sl, diff --git a/src/dataframe/old/datatable.h b/src/dataframe/old/datatable.h index e9747f6ea..eb8b6f807 100644 --- a/src/dataframe/old/datatable.h +++ b/src/dataframe/old/datatable.h @@ -25,9 +25,7 @@ class DataCube public: std::shared_ptr df; - std::map, - std::string> - measure_names; + std::map measure_names; struct DimensionInfo { diff --git a/src/dataframe/old/types.h b/src/dataframe/old/types.h index 8ae49cea5..0938f022a 100644 --- a/src/dataframe/old/types.h +++ b/src/dataframe/old/types.h @@ -15,8 +15,7 @@ class dataframe; namespace Vizzu::Data { - -using DataTable = dataframe::dataframe; +struct DataTable; class DataCube; struct RowWrapper @@ -118,6 +117,15 @@ class Filter }; } + [[nodiscard]] Fun *getFun1() const + { + return func1.get() == True ? nullptr : func1.get(); + } + [[nodiscard]] Fun *getFun2() const + { + return func2.get() == True ? nullptr : func2.get(); + } + private: SharedFun func1{std::shared_ptr{}, True}; SharedFun func2{std::shared_ptr{}, True}; @@ -164,6 +172,130 @@ struct MarkerId return itemId <=> id.itemId; } }; + +struct DataTable +{ + [[nodiscard]] virtual std::string_view get_series_info( + const std::string &id, + const char *key) const & = 0; + + [[nodiscard]] virtual dataframe::series_meta_t get_series_meta( + const std::string &id) const & = 0; + + virtual void add_dimension( + std::span dimension_categories, + std::span dimension_values, + std::string_view name, + std::span> info, + dataframe::adding_type adding_strategy) & = 0; + + virtual void add_measure(std::span measure_values, + std::string_view name, + std::span> info, + dataframe::adding_type adding_strategy) & = 0; + + virtual void add_record( + std::span values) & = 0; + + [[nodiscard]] virtual std::string as_string() const & = 0; + + [[nodiscard]] virtual std::pair< + std::shared_ptr, + std::map> + aggregate(const Filter &filter, + const std::set &aggregate_by, + const std::set &aggregating) const & = 0; + + virtual ~DataTable() = default; +}; + +struct DataTableImpl final : DataTable +{ + std::shared_ptr df; + + explicit DataTableImpl( + std::shared_ptr &&df) noexcept + : + df(std::move(df)) + {} + + [[nodiscard]] std::string_view get_series_info( + const std::string &id, + const char *key) const & override + { + return df->get_series_info(id, key); + } + + [[nodiscard]] dataframe::series_meta_t get_series_meta( + const std::string &id) const & override + { + return df->get_series_meta(id); + } + + void add_dimension( + std::span dimension_categories, + std::span dimension_values, + std::string_view name, + std::span> info, + dataframe::adding_type adding_strategy) + & override + { + df->add_dimension(dimension_categories, + dimension_values, + name, + info, + adding_strategy); + } + + void add_measure(std::span measure_values, + std::string_view name, + std::span> info, + dataframe::adding_type adding_strategy) + & override + { + df->add_measure(measure_values, name, info, adding_strategy); + } + + void add_record(std::span values) & override + { + df->add_record(values); + } + + [[nodiscard]] std::string as_string() const & override + { + return df->as_string(); + } + + [[nodiscard]] std::pair< + std::shared_ptr, + std::map> + aggregate(const Filter &filter, + const std::set &aggregate_by, + const std::set &aggregating) const & override + { + auto &&res = std::pair{df->copy(false), + std::map{}}; + + res.first->remove_records(filter.getFunction()); + + for (const auto &dim : aggregate_by) + res.first->aggregate_by(dim.getColIndex()); + + for (const auto &meas : aggregating) + res.second.try_emplace(meas, + res.first->set_aggregate(meas.getColIndex(), + meas.getAggr())); + + for (const auto &dim : aggregate_by) + res.first->set_sort(dim.getColIndex(), + dataframe::sort_type::less, + dataframe::na_position::first); + + res.first->finalize(); + return res; + } +}; + } #endif \ No newline at end of file diff --git a/test/qtest/chart.cpp b/test/qtest/chart.cpp index 151099f3f..3ba1933f9 100644 --- a/test/qtest/chart.cpp +++ b/test/qtest/chart.cpp @@ -9,7 +9,10 @@ #include "chart/ui/events.h" #include "dataframe/old/datatable.h" -TestChart::TestChart() {} +TestChart::TestChart() : + chart(std::make_shared( + Vizzu::dataframe::dataframe::create_new())) +{} void TestChart::prepareData() { @@ -28,11 +31,15 @@ void TestChart::prepareData() auto &table = chart.getChart().getTable(); table.add_dimension(cat1, std::array{0u, 0u, 0u, 1u, 1u, 1u, 2u, 2u, 2u}, - "Cat1"); + "Cat1", + {}, + {}); table.add_dimension(cat2, std::array{0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u}, - "Cat2"); - table.add_measure(val, "Val"); + "Cat2", + {}, + {}); + table.add_measure(val, "Val", {}, {}); chart.getChart() .getEventDispatcher() diff --git a/test/unit/chart/events.cpp b/test/unit/chart/events.cpp index 38fec2389..f66455971 100644 --- a/test/unit/chart/events.cpp +++ b/test/unit/chart/events.cpp @@ -60,28 +60,40 @@ auto testcase_0 = [](Vizzu::Data::DataTable &table) { table.add_dimension(std::initializer_list{}, std::initializer_list{}, - "Index"); - table.add_measure(std::initializer_list{}, "x"); - table.add_measure(std::initializer_list{}, "y"); + "Index", + {}, + {}); + table.add_measure(std::initializer_list{}, "x", {}, {}); + table.add_measure(std::initializer_list{}, "y", {}, {}); }; auto testcase_1 = [](Vizzu::Data::DataTable &table) { table.add_dimension({{"A", "B", "C", "D", "E"}}, {{0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4}}, - "Dim5"); + "Dim5", + {}, + {}); table.add_dimension({{"a", "b"}}, {{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}}, - "Dim2"); + "Dim2", + {}, + {}); table.add_dimension({{"a", "b", "c"}}, {{0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 0, 2, 2, 1, 0}}, - "Dim3"); + "Dim3", + {}, + {}); table.add_measure( {{1, 2, 4, 3, 3, 4, 2, 1, 4, 3, 1, 2, 2, 1, 3, 4}}, - "Meas1"); + "Meas1", + {}, + {}); table.add_measure( {{0, -1, 5, 6, 6, 5, -1, 0, 5, 6, 0, -1, -1, 0, 6, -5}}, - "Meas2"); + "Meas2", + {}, + {}); }; auto testcase_2 = [](Vizzu::Data::DataTable &table) @@ -140,7 +152,9 @@ break)", 2, 2, 2}}, - "Channel title for long names"); + "Channel title for long names", + {}, + {}); table.add_dimension({{"Very long label of this element", "", @@ -196,7 +210,9 @@ break)", 9, }}, - "Childs of long names which have no end"); + "Childs of long names which have no end", + {}, + {}); table.add_measure({{639, 354, @@ -246,7 +262,9 @@ break)", 778, 651, 598}}, - "値3"); + "値3", + {}, + {}); }; struct chart_setup @@ -271,7 +289,8 @@ struct chart_setup skip->*is_emscripten == "Emscripten build"_is_false; return is_emscripten; }()}; - Vizzu::Chart chart{}; + Vizzu::Chart chart{std::make_shared( + Vizzu::dataframe::dataframe::create_new())}; explicit(false) operator Vizzu::Chart &() { diff --git a/tools/ci/type/gen-dts.cjs b/tools/ci/type/gen-dts.cjs index 54ecdec66..db50e4cc9 100644 --- a/tools/ci/type/gen-dts.cjs +++ b/tools/ci/type/gen-dts.cjs @@ -222,6 +222,8 @@ class DTSGenerator { } const returns = definition.return ? this._getType(name, definition.return) : 'void' return `(${args}) => ${returns}` + } else if (definition.type === 'any') { + return 'unknown' } else if (definition.$ref) { this._validateDef(definition, '$ref', '$template') const refType = this._getRef(definition.$ref)