diff --git a/src/ipyvizzu/chart.py b/src/ipyvizzu/chart.py index 1033b024..86057680 100644 --- a/src/ipyvizzu/chart.py +++ b/src/ipyvizzu/chart.py @@ -9,8 +9,13 @@ from ipyvizzu.animation import AbstractAnimation, Snapshot, AnimationMerger from ipyvizzu.animationcontrol import AnimationControl -from ipyvizzu.method import Animate, Feature, Store, EventOn, EventOff, Log -from ipyvizzu.template import ChartProperty, DisplayTarget, DisplayTemplate +from ipyvizzu.method import Animate, Feature, Plugin, Store, EventOn, EventOff, Log +from ipyvizzu.template import ( + ChartProperty, + DisplayTarget, + DisplayTemplate, + VIZZU as VIZZU_URL, +) from ipyvizzu.event import EventHandler from ipyvizzu.__version__ import __version__ @@ -20,8 +25,8 @@ class Chart: # pylint: disable=too-many-instance-attributes - VIZZU: str = "https://cdn.jsdelivr.net/npm/vizzu@0.9/dist/vizzu.min.js" - """A variable for storing the default url of vizzu package.""" + VIZZU: str = VIZZU_URL + """A variable for storing the default url of the `vizzu` package.""" def __init__( self, @@ -225,6 +230,30 @@ def feature(self, name: str, enabled: bool) -> None: ) ) + def plugin( + self, + plugin: str, + options: Optional[dict] = None, + name: str = "default", + enabled: bool = True, + ) -> None: + """ + A method for register/unregister plugins of the chart. + + Args: + plugin: The package name or the url of the plugin. + options: The plugin constructor options. + name: The name of the plugin (default `default`). + enabled: The state of the plugin (default `True`). + """ + + self._display( + DisplayTemplate.PLUGIN.format( + chart_id=self._chart_id, + **Plugin(plugin, options, name, enabled).dump(), + ) + ) + def store(self) -> Snapshot: """ A method for saving and storing the actual state of the chart. diff --git a/src/ipyvizzu/method.py b/src/ipyvizzu/method.py index 8c778ed3..674fdcea 100644 --- a/src/ipyvizzu/method.py +++ b/src/ipyvizzu/method.py @@ -5,11 +5,15 @@ from ipyvizzu.animation import AbstractAnimation, PlainAnimation from ipyvizzu.event import EventHandler -from ipyvizzu.template import ChartProperty +from ipyvizzu.json import RawJavaScriptEncoder +from ipyvizzu.template import ChartProperty, VIZZU_VERSION class Method: - """A class for storing and dumping any kind of data.""" + """ + A class for dumping chart independent parameters to + [DisplayTemplate.STORE][ipyvizzu.template.DisplayTemplate] template. + """ # pylint: disable=too-few-public-methods @@ -28,8 +32,7 @@ def dump(self) -> dict: class Animate(Method): """ - A class for dumping chart independent parameters to - [DisplayTemplate.ANIMATE][ipyvizzu.template.DisplayTemplate] template. + It stores and dumps `chart_target` and `chart_anim_opts` parameters. """ # pylint: disable=too-few-public-methods @@ -42,8 +45,6 @@ def __init__( """ Animate constructor. - It stores and dumps `chart_target` and `chart_anim_opts` parameters. - Args: chart_target: AbstractAnimation inherited object such as @@ -64,8 +65,7 @@ def __init__( class Feature(Method): """ - A class for dumping chart independent parameters to - [DisplayTemplate.FEATURE][ipyvizzu.template.DisplayTemplate] template. + It stores and dumps `name` and `enabled` parameters. """ # pylint: disable=too-few-public-methods @@ -74,8 +74,6 @@ def __init__(self, name: str, enabled: bool): """ Feature constructor. - It stores and dumps `name` and `enabled` parameters. - Args: name: The name of a chart feature. enabled: The new state of a chart feature. @@ -84,10 +82,61 @@ def __init__(self, name: str, enabled: bool): self._data = {"name": name, "enabled": json.dumps(enabled)} +class Plugin(Method): + """ + It stores and dumps `plugin`, `options` and `name` parameters. + """ + + def __init__(self, plugin: str, options: Optional[dict], name: str, enabled: bool): + """ + Plugin constructor. + + Args: + plugin: The package name or the url of the plugin. + options: The plugin constructor options. + name: The name of the plugin (default `default`). + enabled: The state of the plugin (default `True`). + """ + + self._data = { + "plugin": Plugin.resolve_url(plugin), + "options": {} + if options is None + else json.dumps(options, cls=RawJavaScriptEncoder), + "name": name, + "enabled": json.dumps(enabled), + } + + @staticmethod + def resolve_url(plugin: str) -> str: + """ + A static method for resolving the url of the plugin. + + Args: + plugin: The package name or the url of the plugin. + + Returns: + The url of the plugin. + """ + + if Plugin._is_url(plugin): + return plugin + return Plugin._get_url(plugin) + + @staticmethod + def _is_url(plugin: str) -> bool: + return "/" in plugin + + @staticmethod + def _get_url(plugin: str) -> str: + jsdelivr = "https://cdn.jsdelivr.net/npm/@vizzu" + tag = f"vizzu-{VIZZU_VERSION}" + return f"{jsdelivr}/{plugin}@{tag}/dist/mjs/index.min.js" + + class Store(Method): """ - A class for dumping chart independent parameters to - [DisplayTemplate.STORE][ipyvizzu.template.DisplayTemplate] template. + It stores and dumps `snapshot_id` parameter. """ # pylint: disable=too-few-public-methods @@ -96,8 +145,6 @@ def __init__(self, snapshot_id: str): """ Store constructor. - It stores and dumps `snapshot_id` parameter. - Args: snapshot_id: The id of snapshot object. """ @@ -107,8 +154,7 @@ def __init__(self, snapshot_id: str): class EventOn(Method): """ - A class for dumping chart independent parameters to - [DisplayTemplate.SET_EVENT][ipyvizzu.template.DisplayTemplate] template. + It stores and dumps the `id`, the `event` and the `handler` of the event handler object. """ # pylint: disable=too-few-public-methods @@ -117,8 +163,6 @@ def __init__(self, event_handler: EventHandler): """ EventOn constructor. - It stores and dumps the `id`, the `event` and the `handler` of the event handler object. - Args: event_handler: An event handler object. """ @@ -132,8 +176,7 @@ def __init__(self, event_handler: EventHandler): class EventOff(Method): """ - A class for dumping chart independent parameters to - [DisplayTemplate.CLEAR_EVENT][ipyvizzu.template.DisplayTemplate] template. + It stores and dumps the `id` and the `event` of the event handler object. """ # pylint: disable=too-few-public-methods @@ -142,8 +185,6 @@ def __init__(self, event_handler: EventHandler): """ EventOff constructor. - It stores and dumps the `id` and the `event` of the event handler object. - Args: event_handler: An event handler object. """ @@ -153,8 +194,7 @@ def __init__(self, event_handler: EventHandler): class Log(Method): """ - A class for dumping chart independent parameters to - [DisplayTemplate.LOG][ipyvizzu.template.DisplayTemplate] template. + It stores and dumps the value of the chart property object. """ # pylint: disable=too-few-public-methods @@ -163,8 +203,6 @@ def __init__(self, chart_property: ChartProperty): """ Log constructor. - It stores and dumps the value of the chart property object. - Args: chart_property: A chart property such as diff --git a/src/ipyvizzu/template.py b/src/ipyvizzu/template.py index 6279a258..1b2d74ed 100644 --- a/src/ipyvizzu/template.py +++ b/src/ipyvizzu/template.py @@ -2,6 +2,12 @@ from enum import Enum +VIZZU_VERSION: str = "0.9" +"""A variable for storing the default version of the `vizzu` package.""" + +VIZZU: str = f"https://cdn.jsdelivr.net/npm/vizzu@{VIZZU_VERSION}/dist/vizzu.min.js" +"""A variable for storing the default url of the `vizzu` package.""" + class ChartProperty(Enum): """An enum class for storing chart properties.""" @@ -60,6 +66,12 @@ class DisplayTemplate: ) """Call feature JavaScript method.""" + PLUGIN: str = ( + "window.ipyvizzu.plugin(element, " + + "'{chart_id}', '{plugin}', {options}, '{name}', {enabled});" + ) + """Call plugin JavaScript method.""" + STORE: str = "window.ipyvizzu.store(element, '{chart_id}', '{id}');" """Call store JavaScript method.""" diff --git a/src/ipyvizzu/templates/ipyvizzu.js b/src/ipyvizzu/templates/ipyvizzu.js index 1e86ce5a..27cdbf38 100644 --- a/src/ipyvizzu/templates/ipyvizzu.js +++ b/src/ipyvizzu/templates/ipyvizzu.js @@ -37,6 +37,7 @@ if (window.IpyVizzu?.version !== '__version__') { this.events = {} this.loaded = {} this.libs = {} + this.plugins = {} } static clearInhibitScroll(element) { @@ -55,6 +56,30 @@ if (window.IpyVizzu?.version !== '__version__') { this._moveHere(chartId, element) } + plugin(element, chartId, plugin, options, name, enabled) { + this.charts[chartId] = this.charts[chartId].then((chart) => { + if (!this.plugins[plugin]) { + this.plugins[plugin] = import(plugin).catch((error) => { + console.error('Error importing plugin:', plugin, error) + return null + }) + } + + return this.plugins[plugin].then((pluginModule) => { + if (pluginModule) { + const pluginInstance = new pluginModule[name](options) + if (enabled) { + chart.feature(pluginInstance, true) + } else { + chart.feature(pluginInstance.meta.name, false) + } + } + + return chart + }) + }) + } + animate( element, chartId, diff --git a/tests/test_chart.py b/tests/test_chart.py index f604fbe5..94b44d95 100644 --- a/tests/test_chart.py +++ b/tests/test_chart.py @@ -158,7 +158,7 @@ class IPy: ) -class TestChartMethods(TestChart): +class TestChartAnimateMethod(TestChart): def test_animate_chart_target_has_to_be_passed(self) -> None: with self.assertRaises(ValueError): self.chart.animate() @@ -312,6 +312,54 @@ def test_animate_with_not_default_scroll_into_view(self) -> None: + "undefined);", ) + +class TestChartPluginMethod(TestChart): + URL = "https://cdn.jsdelivr.net/npm/@vizzu/marker-dropshadow@vizzu-0.9/dist/mjs/index.min.js" + + def test_plugin_with_package_name(self) -> None: + with unittest.mock.patch(self.mock) as output: + self.chart.plugin("marker-dropshadow") + self.assertEqual( + self.normalizer.normalize_output(output), + f"window.ipyvizzu.plugin(element, id, '{self.URL}', {{}}, 'default', true);", + ) + + def test_plugin_with_package_url(self) -> None: + with unittest.mock.patch(self.mock) as output: + self.chart.plugin(self.URL) + self.assertEqual( + self.normalizer.normalize_output(output), + f"window.ipyvizzu.plugin(element, id, '{self.URL}', {{}}, 'default', true);", + ) + + def test_plugin_with_options(self) -> None: + with unittest.mock.patch(self.mock) as output: + self.chart.plugin("marker-dropshadow", options={"debug": True}) + self.assertEqual( + self.normalizer.normalize_output(output), + "window.ipyvizzu.plugin(element, id, " + + f"'{self.URL}', {{\"debug\": true}}, 'default', true);", + ) + + def test_plugin_with_name(self) -> None: + with unittest.mock.patch(self.mock) as output: + name = "MarkerDropshadow" + self.chart.plugin("marker-dropshadow", name=name) + self.assertEqual( + self.normalizer.normalize_output(output), + f"window.ipyvizzu.plugin(element, id, '{self.URL}', {{}}, '{name}', true);", + ) + + def test_plugin_with_enabled(self) -> None: + with unittest.mock.patch(self.mock) as output: + self.chart.plugin("marker-dropshadow", enabled=False) + self.assertEqual( + self.normalizer.normalize_output(output), + f"window.ipyvizzu.plugin(element, id, '{self.URL}', {{}}, 'default', false);", + ) + + +class TestChartFeatureMethod(TestChart): def test_feature(self) -> None: with unittest.mock.patch(self.mock) as output: self.chart.feature("tooltip", True) @@ -320,6 +368,8 @@ def test_feature(self) -> None: "window.ipyvizzu.feature(element, id, 'tooltip', true);", ) + +class TestChartStoreMethod(TestChart): def test_store(self) -> None: with unittest.mock.patch(self.mock) as output: self.chart.store() @@ -329,7 +379,7 @@ def test_store(self) -> None: ) -class TestChartEvents(TestChart): +class TestChartEventMethods(TestChart): def test_on(self) -> None: with unittest.mock.patch(self.mock) as output: handler_method = """event.renderingContext.fillStyle = @@ -355,7 +405,7 @@ def test_off(self) -> None: ) -class TestChartLogs(TestChart): +class TestChartLogMethod(TestChart): def test_log_config(self) -> None: with unittest.mock.patch(self.mock) as output: self.chart.log(ChartProperty.CONFIG) diff --git a/tools/docs/examples/gen_examples.py b/tools/docs/examples/gen_examples.py index b9e816ea..be6c58da 100644 --- a/tools/docs/examples/gen_examples.py +++ b/tools/docs/examples/gen_examples.py @@ -15,10 +15,8 @@ MKDOCS_PATH = TOOLS_PATH / "docs" GEN_PATH = MKDOCS_PATH / "examples" VIZZU_LIB_PATH = REPO_PATH / "vizzu-lib" -WEB_CONTENT_PATH = ( - VIZZU_LIB_PATH / "test" / "integration" / "test_cases" / "web_content" -) -TEST_DATA_PATH = VIZZU_LIB_PATH / "test" / "integration" / "test_data" +WEB_CONTENT_PATH = VIZZU_LIB_PATH / "test" / "e2e" / "test_cases" / "web_content" +TEST_DATA_PATH = VIZZU_LIB_PATH / "test" / "e2e" / "test_data" STATIC_EXAMPLES_PATH = WEB_CONTENT_PATH / "static" OPERATION_EXAMPLES_PATH = WEB_CONTENT_PATH / "analytical_operations" PRESET_EXAMPLES_PATH = WEB_CONTENT_PATH / "presets"