From ca35f96a3c60132f5e645fcaf2df4a9c173ecf1b Mon Sep 17 00:00:00 2001 From: warioddly Date: Wed, 23 Jul 2025 23:11:46 +0600 Subject: [PATCH 01/13] refactor: update plugin paths and improve Graphify interface structure --- .flutter-plugins-dependencies | 2 +- README.md | 4 +- example/lib/charts/basic_line_chart.dart | 2 +- lib/graphify.dart | 4 +- lib/src/controller/implements/mobile.dart | 24 ++++--- lib/src/controller/implements/web.dart | 2 +- lib/src/controller/interface.dart | 12 ++-- lib/src/{utils => controller}/js_methods.dart | 0 lib/src/resources/dependencies.js.dart | 2 +- lib/src/resources/index.html.dart | 2 +- lib/src/utils/gradient/_gradient.dart | 3 + lib/src/utils/uid.dart | 17 +++++ lib/src/utils/utils.dart | 16 ----- .../view/{interface.dart => _interface.dart} | 13 +++- lib/src/view/console_message.dart | 2 + lib/src/view/implements/facade.dart | 31 +++++---- lib/src/view/implements/mobile.dart | 63 +++++++++---------- lib/src/view/implements/web.dart | 48 +++++--------- 18 files changed, 122 insertions(+), 125 deletions(-) rename lib/src/{utils => controller}/js_methods.dart (100%) create mode 100644 lib/src/utils/gradient/_gradient.dart create mode 100644 lib/src/utils/uid.dart delete mode 100644 lib/src/utils/utils.dart rename lib/src/view/{interface.dart => _interface.dart} (65%) create mode 100644 lib/src/view/console_message.dart diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index 94e7721..d4503bc 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"webview_flutter_wkwebview","path":"/Users/coffeen/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.22.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"webview_flutter_android","path":"/Users/coffeen/.pub-cache/hosted/pub.dev/webview_flutter_android-4.7.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"webview_flutter_wkwebview","path":"/Users/coffeen/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.22.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2025-06-23 22:41:03.527735","version":"3.32.1","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"webview_flutter_wkwebview","path":"C:\\\\Users\\\\user\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\webview_flutter_wkwebview-3.18.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"webview_flutter_android","path":"C:\\\\Users\\\\user\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\webview_flutter_android-4.3.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"webview_flutter_wkwebview","path":"C:\\\\Users\\\\user\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\webview_flutter_wkwebview-3.18.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2025-07-23 22:08:45.282745","version":"3.32.6","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/README.md b/README.md index b3f8df7..4e340ee 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ Pub Package GitHub Repo stars -GitHub closed issues -GitHub open issues +GitHub closed issues +GitHub open issues GitHub contributors Contributing diff --git a/example/lib/charts/basic_line_chart.dart b/example/lib/charts/basic_line_chart.dart index 4738033..19ae012 100644 --- a/example/lib/charts/basic_line_chart.dart +++ b/example/lib/charts/basic_line_chart.dart @@ -35,7 +35,7 @@ class _BasicLineChartState extends State { Widget build(BuildContext context) { return GraphifyView( controller: controller, - onConsoleMessage: (message) { + onConsoleMessage: (message) { print("[ERROR] $message"); }, initialOptions: const { diff --git a/lib/graphify.dart b/lib/graphify.dart index 3600e10..cfee9b6 100644 --- a/lib/graphify.dart +++ b/lib/graphify.dart @@ -7,6 +7,4 @@ export 'src/view/implements/facade.dart' if (dart.library.io) 'src/view/implements/mobile.dart' if (dart.library.html) 'src/view/implements/web.dart'; -export 'src/utils/gradient/graphify_gradient.dart'; -export 'src/utils/gradient/graphify_linear_gradient.dart'; -export 'src/utils/gradient/graphify_radial_gradient.dart'; \ No newline at end of file +export 'src/utils/gradient/_gradient.dart'; diff --git a/lib/src/controller/implements/mobile.dart b/lib/src/controller/implements/mobile.dart index 88111b3..0df061f 100644 --- a/lib/src/controller/implements/mobile.dart +++ b/lib/src/controller/implements/mobile.dart @@ -2,26 +2,34 @@ import 'dart:async'; import 'dart:convert'; import 'package:graphify/src/controller/interface.dart' as controller_interface; -import 'package:graphify/src/utils/js_methods.dart'; +import 'package:graphify/src/controller/js_methods.dart'; import 'package:webview_flutter/webview_flutter.dart'; class GraphifyController extends controller_interface.GraphifyController { late final WebViewController _connector; + set connector(WebViewController connector) => _connector = connector; + + String get _quotedUid => '"$uid"'; + @override Future update(Map? options) async { - await runJavaScript('window.${JsMethods.updateChart}("$uid", ${jsonEncode(options ?? {})})'); + return _eval( + JsMethods.updateChart, + [_quotedUid, jsonEncode(options ?? {})], + ); } - set connector(WebViewController connector) => _connector = connector; + @override + void dispose() async => _eval(JsMethods.disposeChart, [_quotedUid]); - Future runJavaScript(String javaScript) async { - await _connector.runJavaScript(javaScript); + Future _eval(String method, List args) async { + return _connector.runJavaScript(_buildJsMethod(method, args)); } - @override - void dispose() async { - await runJavaScript('window.${JsMethods.disposeChart}("$uid")'); + String _buildJsMethod(String method, List args) { + return 'window.$method(${args.map((String arg) => arg).join(', ')})'; } + } diff --git a/lib/src/controller/implements/web.dart b/lib/src/controller/implements/web.dart index e344503..091510e 100644 --- a/lib/src/controller/implements/web.dart +++ b/lib/src/controller/implements/web.dart @@ -3,7 +3,7 @@ import 'dart:js_interop'; import 'dart:js_interop_unsafe'; import 'package:graphify/src/controller/interface.dart' as controller_interface; -import 'package:graphify/src/utils/js_methods.dart'; +import 'package:graphify/src/controller/js_methods.dart'; import 'package:web/web.dart'; class GraphifyController extends controller_interface.GraphifyController { diff --git a/lib/src/controller/interface.dart b/lib/src/controller/interface.dart index 4f0ada1..7520c5b 100644 --- a/lib/src/controller/interface.dart +++ b/lib/src/controller/interface.dart @@ -1,14 +1,11 @@ -import 'package:graphify/src/utils/utils.dart'; +import 'package:graphify/src/utils/uid.dart'; -mixin Disposable { - void dispose(); -} -abstract class GraphifyController with Disposable { +abstract class GraphifyController { /// Creates a new instance of [GraphifyController] with a unique identifier. - GraphifyController() : uid = Utils.uid(); + GraphifyController() : uid = UID.generate(); /// Unique identifier for the chart instance. final String uid; @@ -16,4 +13,7 @@ abstract class GraphifyController with Disposable { /// Updates the chart with the provided options. void update(Map? options); + /// Disposes of the chart instance, cleaning up resources. + void dispose(); + } \ No newline at end of file diff --git a/lib/src/utils/js_methods.dart b/lib/src/controller/js_methods.dart similarity index 100% rename from lib/src/utils/js_methods.dart rename to lib/src/controller/js_methods.dart diff --git a/lib/src/resources/dependencies.js.dart b/lib/src/resources/dependencies.js.dart index f9bd5e8..6d7f454 100644 --- a/lib/src/resources/dependencies.js.dart +++ b/lib/src/resources/dependencies.js.dart @@ -1,7 +1,7 @@ import 'package:graphify/src/resources/lib/echarts.gl.min.dart'; import 'package:graphify/src/resources/lib/echarts.min.dart'; import 'package:graphify/src/resources/lib/jquery.min.dart'; -import 'package:graphify/src/utils/js_methods.dart'; +import 'package:graphify/src/controller/js_methods.dart'; const dependencies = "$jQuery " "$echartsMin " diff --git a/lib/src/resources/index.html.dart b/lib/src/resources/index.html.dart index 3361ffc..6499b9c 100644 --- a/lib/src/resources/index.html.dart +++ b/lib/src/resources/index.html.dart @@ -1,6 +1,6 @@ // ignore_for_file: leading_newlines_in_multiline_strings -import 'package:graphify/src/utils/js_methods.dart'; +import 'package:graphify/src/controller/js_methods.dart'; String indexHtml({ required String id, String? dependencies}) { return ''' diff --git a/lib/src/utils/gradient/_gradient.dart b/lib/src/utils/gradient/_gradient.dart new file mode 100644 index 0000000..bec2b7f --- /dev/null +++ b/lib/src/utils/gradient/_gradient.dart @@ -0,0 +1,3 @@ +export 'graphify_gradient.dart'; +export 'graphify_linear_gradient.dart'; +export 'graphify_radial_gradient.dart'; \ No newline at end of file diff --git a/lib/src/utils/uid.dart b/lib/src/utils/uid.dart new file mode 100644 index 0000000..0161394 --- /dev/null +++ b/lib/src/utils/uid.dart @@ -0,0 +1,17 @@ +import 'dart:math'; + +class UID { + + static const _chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + static final _secureRandom = Random.secure(); + + static String generate([int length = 10]) { + final buffer = StringBuffer(); + + for (var i = 0; i < length; i++) { + buffer.write(_chars[_secureRandom.nextInt(_chars.length)]); + } + + return buffer.toString(); + } +} diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart deleted file mode 100644 index 13a8f87..0000000 --- a/lib/src/utils/utils.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'dart:math'; - -class Utils { - static String uid() { - var random = Random(); - - var chars = 'abcdefghijklmnopqrstuvwxyz'; - var uid = ''; - - for (var i = 0; i < 10; i++) { - uid += chars[random.nextInt(chars.length)]; - } - - return uid; - } -} diff --git a/lib/src/view/interface.dart b/lib/src/view/_interface.dart similarity index 65% rename from lib/src/view/interface.dart rename to lib/src/view/_interface.dart index f068507..12aea0c 100644 --- a/lib/src/view/interface.dart +++ b/lib/src/view/_interface.dart @@ -1,10 +1,12 @@ import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:graphify/src/controller/interface.dart'; +import 'package:graphify/src/view/console_message.dart'; -typedef OnConsoleMessage = void Function(String message); -abstract class GraphifyView { +abstract class GraphifyView extends StatefulWidget { const GraphifyView({ + super.key, this.controller, this.initialOptions, this.onConsoleMessage, @@ -19,11 +21,15 @@ abstract class GraphifyView { final VoidCallback? onCreated; + @override + State createState(); + } -abstract class GraphifyViewState extends State { +abstract class GraphifyViewState extends State { late Widget view; + @nonVirtual @override void initState() { super.initState(); @@ -31,6 +37,7 @@ abstract class GraphifyViewState extends State { buildView(); } + @nonVirtual @override Widget build(BuildContext context) => view; diff --git a/lib/src/view/console_message.dart b/lib/src/view/console_message.dart new file mode 100644 index 0000000..a16b8bb --- /dev/null +++ b/lib/src/view/console_message.dart @@ -0,0 +1,2 @@ + +typedef OnConsoleMessage = void Function(Object message); \ No newline at end of file diff --git a/lib/src/view/implements/facade.dart b/lib/src/view/implements/facade.dart index bd41723..17a98e8 100644 --- a/lib/src/view/implements/facade.dart +++ b/lib/src/view/implements/facade.dart @@ -1,30 +1,29 @@ import 'package:flutter/cupertino.dart'; -import 'package:graphify/src/controller/interface.dart'; -import 'package:graphify/src/view/interface.dart' as g_view; +import 'package:graphify/src/view/_interface.dart' as g_view; -class GraphifyView extends StatelessWidget implements g_view.GraphifyView { +class GraphifyView extends g_view.GraphifyView { const GraphifyView({ super.key, - this.controller, - this.initialOptions, - this.onConsoleMessage, - this.onCreated, + super.controller, + super.initialOptions, + super.onConsoleMessage, + super.onCreated, }); @override - final GraphifyController? controller; + State createState() => _GraphifyViewState(); - @override - final Map? initialOptions; +} - @override - final g_view.OnConsoleMessage? onConsoleMessage; +class _GraphifyViewState extends g_view.GraphifyViewState { @override - final VoidCallback? onCreated; + void initView() { + throw UnimplementedError("initView() is not implemented"); + } @override - Widget build(BuildContext context) { - throw UnimplementedError(); + Widget buildView() { + throw UnimplementedError("buildView() is not implemented"); } -} +} \ No newline at end of file diff --git a/lib/src/view/implements/mobile.dart b/lib/src/view/implements/mobile.dart index ddb30c0..8746cbb 100644 --- a/lib/src/view/implements/mobile.dart +++ b/lib/src/view/implements/mobile.dart @@ -2,58 +2,48 @@ import 'package:flutter/material.dart'; import 'package:graphify/src/controller/implements/mobile.dart'; import 'package:graphify/src/resources/dependencies.js.dart'; import 'package:graphify/src/resources/index.html.dart'; -import 'package:graphify/src/view/interface.dart' as g_view; +import 'package:graphify/src/view/_interface.dart' as g_view; import 'package:webview_flutter/webview_flutter.dart'; -class GraphifyView extends StatefulWidget implements g_view.GraphifyView { +class GraphifyView extends g_view.GraphifyView { const GraphifyView({ super.key, - this.controller, - this.initialOptions, - this.onConsoleMessage, - this.onCreated, + super.controller, + super.initialOptions, + super.onConsoleMessage, + super.onCreated, }); @override - final GraphifyController? controller; - - @override - final Map? initialOptions; - - @override - final g_view.OnConsoleMessage? onConsoleMessage; - - @override - final VoidCallback? onCreated; - - @override - State createState() => _GraphifyViewMobile(); + State createState() => _GraphifyViewState(); } -class _GraphifyViewMobile extends g_view.GraphifyViewState { - late WebViewController webViewController; - late final _controller = widget.controller ?? GraphifyController(); +class _GraphifyViewState extends g_view.GraphifyViewState { + + late final webViewController = WebViewController(); + late final controller = + (widget.controller ?? GraphifyController()) as GraphifyController; @override void initView() { - _controller.connector = webViewController = WebViewController() + controller.connector = webViewController ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setBackgroundColor(Colors.transparent) + ..setBackgroundColor(const Color(0x00000000)) + ..setOnConsoleMessage(widget.onConsoleMessage ?? (_) {}) ..loadHtmlString( indexHtml( - id: _controller.uid, + id: controller.uid, dependencies: "", ), ) - ..setOnConsoleMessage( - (message) => widget.onConsoleMessage?.call(message.message), - ) - ..setNavigationDelegate(NavigationDelegate( - onPageFinished: (_) async { - widget.onCreated?.call(); - await _controller.update(widget.initialOptions); - }, - )); + ..setNavigationDelegate( + NavigationDelegate( + onPageFinished: (_) { + widget.onCreated?.call(); + controller.update(widget.initialOptions); + }, + ), + ); } @override @@ -64,8 +54,11 @@ class _GraphifyViewMobile extends g_view.GraphifyViewState { @override void dispose() { if (widget.controller == null) { - _controller.dispose(); + controller.dispose(); } + webViewController + ..clearLocalStorage() + ..clearCache(); super.dispose(); } } diff --git a/lib/src/view/implements/web.dart b/lib/src/view/implements/web.dart index d33026e..dbfafc8 100644 --- a/lib/src/view/implements/web.dart +++ b/lib/src/view/implements/web.dart @@ -5,47 +5,35 @@ import 'package:flutter/cupertino.dart'; import 'package:graphify/src/controller/implements/web.dart'; import 'package:graphify/src/resources/dependencies.js.dart'; import 'package:graphify/src/resources/index.html.dart'; -import 'package:graphify/src/view/interface.dart' as g_view; +import 'package:graphify/src/view/_interface.dart' as g_view; import 'package:web/web.dart'; const _chartDependencyId = 'graphify-chart-dependency'; -class GraphifyView extends StatefulWidget implements g_view.GraphifyView { +class GraphifyView extends g_view.GraphifyView { const GraphifyView({ super.key, - this.controller, - this.initialOptions, - this.onConsoleMessage, - this.onCreated, + super.controller, + super.initialOptions, + super.onConsoleMessage, + super.onCreated, }); @override - final GraphifyController? controller; - - @override - final Map? initialOptions; - - @override - final g_view.OnConsoleMessage? onConsoleMessage; - - @override - final VoidCallback? onCreated; - - @override - State createState() => _GraphifyViewWeb(); + State createState() => _GraphifyViewState(); } -class _GraphifyViewWeb extends g_view.GraphifyViewState { +class _GraphifyViewState extends g_view.GraphifyViewState { - late final _controller = widget.controller ?? GraphifyController(); + late final controller = widget.controller ?? GraphifyController(); - String get _uid => _controller.uid; + String get uid => controller.uid; @override void initView() { initChartDependencies(); platformViewRegistry.registerViewFactory( - _uid, + uid, createHTMLIFrameElement, ); } @@ -53,20 +41,18 @@ class _GraphifyViewWeb extends g_view.GraphifyViewState { @override Widget buildView() { widget.onCreated?.call(); - return view = HtmlElementView(viewType: _uid); + return view = HtmlElementView(viewType: uid); } HTMLIFrameElement createHTMLIFrameElement(_) { final iframe = HTMLIFrameElement() - ..id = 'graphify_$_uid' + ..id = 'graphify_$uid' ..style.width = '100%' ..style.height = '100%' ..style.border = 'none' - ..srcdoc = indexHtml(id: _uid).toJS - ..onLoad.listen((_) => _controller.update(widget.initialOptions)) - ..onError.listen((event) { - widget.onConsoleMessage?.call(event.toString()); - }); + ..srcdoc = indexHtml(id: uid).toJS + ..onLoad.listen((_) => controller.update(widget.initialOptions)) + ..onError.listen(widget.onConsoleMessage); return iframe; } @@ -90,7 +76,7 @@ class _GraphifyViewWeb extends g_view.GraphifyViewState { @override void dispose() { if (widget.controller == null) { - _controller.dispose(); + controller.dispose(); } super.dispose(); } From 2f74c7cd26ce75a152a9183cb17e9c83c3623349 Mon Sep 17 00:00:00 2001 From: warioddly Date: Wed, 23 Jul 2025 23:18:37 +0600 Subject: [PATCH 02/13] bump version to 1.3.0 in pubspec.yaml --- pubspec.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9e0db7f..1e9084f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ repository: "https://github.com/warioddly/graphify" documentation: "https://echarts.apache.org/en/option.html#title" issue_tracker: "https://github.com/warioddly/graphify/issues" -version: 1.1.2 +version: 1.3.0 environment: sdk: '>=3.2.3 <4.0.0' @@ -21,10 +21,6 @@ dev_dependencies: sdk: flutter flutter_lints: ^3.0.1 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: # This section identifies this Flutter project as a plugin project. # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) From a0d35a079ee7772c00093a9bc3f9814e83773dbb Mon Sep 17 00:00:00 2001 From: warioddly Date: Wed, 23 Jul 2025 23:19:36 +0600 Subject: [PATCH 03/13] bump version to 1.2.0 in pubspec.yaml and pubspec.lock --- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 9de9b25..0507c68 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -104,7 +104,7 @@ packages: path: ".." relative: true source: path - version: "1.1.2" + version: "1.2.0" integration_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 1e9084f..60ddfbd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ repository: "https://github.com/warioddly/graphify" documentation: "https://echarts.apache.org/en/option.html#title" issue_tracker: "https://github.com/warioddly/graphify/issues" -version: 1.3.0 +version: 1.2.0 environment: sdk: '>=3.2.3 <4.0.0' From 24ff7de898a9afbf5444b2ac6a2bedd5b9b94c33 Mon Sep 17 00:00:00 2001 From: warioddly Date: Wed, 23 Jul 2025 23:23:44 +0600 Subject: [PATCH 04/13] chore: update CHANGELOG.md for version 1.2.0 release --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index acd5595..1148484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.2.0 + +* 31 refactor colorutil by @warioddly in https://github.com/warioddly/graphify/pull/33 +* refactor GraphifyController and update dependencies by @warioddly in https://github.com/warioddly/graphify/pull/36 +* refactor: update plugin paths and improve Graphify interface structure by @warioddly in https://github.com/warioddly/graphify/pull/37 +* bump version to 1.2.0 in pubspec.yaml by @warioddly in https://github.com/warioddly/graphify/pull/38 +**Full Changelog**: https://github.com/warioddly/graphify/compare/v1.1.1...v1.2.0 + ## 1.1.1 * Fix bug in old versions Flutter "Try changing the name to the name of an existing improvement, or identify an improvement, or a field named "r"." From 0e77db567c060d141b8b966c017d0fae72f36070 Mon Sep 17 00:00:00 2001 From: warioddly Date: Tue, 2 Sep 2025 11:43:01 +0600 Subject: [PATCH 05/13] refactor: update conditional imports to use dart.library.js_interop --- example/pubspec.lock | 22 +++++++++++----------- lib/graphify.dart | 5 +++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 0507c68..8d2c793 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -114,26 +114,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -255,10 +255,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" url_launcher: dependency: "direct main" description: @@ -327,10 +327,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -388,5 +388,5 @@ packages: source: hosted version: "3.18.4" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.27.0" diff --git a/lib/graphify.dart b/lib/graphify.dart index cfee9b6..a97fefb 100644 --- a/lib/graphify.dart +++ b/lib/graphify.dart @@ -2,9 +2,10 @@ library graphify; export 'src/controller/implements/facade.dart' if (dart.library.io) 'src/controller/implements/mobile.dart' - if (dart.library.html) 'src/controller/implements/web.dart'; + if (dart.library.js_interop) 'src/controller/implements/web.dart'; + export 'src/view/implements/facade.dart' if (dart.library.io) 'src/view/implements/mobile.dart' - if (dart.library.html) 'src/view/implements/web.dart'; + if (dart.library.js_interop) 'src/view/implements/web.dart'; export 'src/utils/gradient/_gradient.dart'; From b819eeaeb840038bd0aed0cc09f040bff66654f8 Mon Sep 17 00:00:00 2001 From: warioddly Date: Wed, 3 Sep 2025 10:44:17 +0600 Subject: [PATCH 06/13] bump version to 1.2.1 in pubspec.yaml --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 60ddfbd..2bdce7f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ repository: "https://github.com/warioddly/graphify" documentation: "https://echarts.apache.org/en/option.html#title" issue_tracker: "https://github.com/warioddly/graphify/issues" -version: 1.2.0 +version: 1.2.1 environment: sdk: '>=3.2.3 <4.0.0' From 76293144957b77c6846c34a5a118fe4e0ec1c00e Mon Sep 17 00:00:00 2001 From: warioddly Date: Wed, 3 Sep 2025 10:44:30 +0600 Subject: [PATCH 07/13] feat: remove linux platform support This commit removes Linux support from the `pubspec.yaml` file. --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2bdce7f..f33416e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,7 +35,6 @@ flutter: platforms: android: ios: - linux: macos: web: windows: From e34faceeedd3db848c490151b661fa248cf4b772 Mon Sep 17 00:00:00 2001 From: handgull Date: Sat, 25 Oct 2025 18:53:56 +0200 Subject: [PATCH 08/13] new click feature testing setup + gradle bump --- android/build.gradle | 4 +- example/android/build.gradle | 2 +- example/android/settings.gradle | 2 +- example/lib/main.dart | 8 +- example/lib/test_chart_click.dart | 120 ++++++++++++++++++++++++++++++ example/pubspec.lock | 2 +- 6 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 example/lib/test_chart_click.dart diff --git a/android/build.gradle b/android/build.gradle index 62827b9..9cc64c5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,14 +2,14 @@ group 'com.warioddly.graphify' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '2.1.0' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' + classpath 'com.android.tools.build:gradle:8.7.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/example/android/build.gradle b/example/android/build.gradle index 0ae2992..a0c36a3 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.8.10' + ext.kotlin_version = '2.1.0' repositories { google() mavenCentral() diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 7ef64d7..dfd3282 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -23,7 +23,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.1.0" apply false + id "com.android.application" version "8.7.0" apply false } include ":app" diff --git a/example/lib/main.dart b/example/lib/main.dart index 69eff2e..45eb97c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -18,6 +18,7 @@ import 'package:graphify_example/charts/shang_hai_index.dart'; import 'package:graphify_example/charts/stacked_area_chart.dart'; import 'package:graphify_example/charts/tangential_polar_bar_chart.dart'; import 'package:graphify_example/charts/world_population.dart'; +import 'package:graphify_example/test_chart_click.dart'; import 'package:url_launcher/url_launcher_string.dart'; void main() { @@ -28,6 +29,7 @@ class MyApp extends StatelessWidget { const MyApp({super.key}); static const charts = { + "Chart Click Test": TestChartClick(), "Basic Line Chart": BasicLineChart(), 'Basic Area Chart': BasicAreaChart(), 'Stacked Area Chart': StackedAreaChart(), @@ -90,13 +92,15 @@ class MyApp extends StatelessWidget { ListTile( title: const Text("Open Source Code"), onTap: () { - launchUrlString("https://github.com/warioddly/graphify"); + launchUrlString( + "https://github.com/warioddly/graphify"); }, ), ListTile( title: const Text("Pub.dev"), onTap: () { - launchUrlString("https://pub.dev/packages/graphify"); + launchUrlString( + "https://pub.dev/packages/graphify"); }, ), ], diff --git a/example/lib/test_chart_click.dart b/example/lib/test_chart_click.dart new file mode 100644 index 0000000..433c568 --- /dev/null +++ b/example/lib/test_chart_click.dart @@ -0,0 +1,120 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:graphify/graphify.dart'; + +class TestChartClick extends StatefulWidget { + const TestChartClick({super.key}); + + @override + State createState() => _TestChartClickState(); +} + +class _TestChartClickState extends State { + late final GraphifyController _controller; + final List> _clickEvents = []; + String _status = 'Ready to test clicks'; + + @override + void initState() { + super.initState(); + _controller = GraphifyController(); + _controller.chartClickedEvent.listen((data) { + setState(() { + _clickEvents.add(data); + _status = 'Click received: ${data['name'] ?? 'Unknown'}'; + }); + + log('TEST: Chart click received: $data'); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + child: Text( + _status, + style: const TextStyle(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + Expanded( + flex: 1, + child: Container( + padding: const EdgeInsets.all(8), + child: Expanded( + child: ListView.builder( + itemCount: _clickEvents.length, + itemBuilder: (context, index) { + final event = _clickEvents[index]; + return Card( + child: ListTile( + title: Text('Event ${index + 1}'), + subtitle: Text( + 'Name: ${event['name'] ?? 'N/A'}\n' + 'Value: ${event['value'] ?? 'N/A'}\n' + 'Series: ${event['seriesName'] ?? 'N/A'}\n' + 'Index: ${event['dataIndex'] ?? 'N/A'}', + ), + ), + ); + }, + ), + ), + ), + ), + Expanded( + flex: 2, + child: Container( + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + child: GraphifyView( + controller: _controller, + initialOptions: const { + "tooltip": { + "trigger": "axis", + "axisPointer": {"type": "shadow"} + }, + "legend": { + "data": ["Sales", "Marketing"] + }, + "xAxis": { + "type": "category", + "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + }, + "yAxis": {"type": "value"}, + "series": [ + { + "name": "Sales", + "type": "bar", + "data": [120, 200, 150, 80, 70, 110, 130], + "itemStyle": {"color": "#5470c6"} + }, + { + "name": "Marketing", + "type": "bar", + "data": [60, 100, 75, 40, 35, 55, 65], + "itemStyle": {"color": "#91cc75"} + } + ] + }, + ), + ), + ), + ], + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 8d2c793..09f21c2 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -104,7 +104,7 @@ packages: path: ".." relative: true source: path - version: "1.2.0" + version: "1.2.1" integration_test: dependency: "direct dev" description: flutter From 6f4c6bb5ddec6e58ba2401e1ab689f9a1ce1fad5 Mon Sep 17 00:00:00 2001 From: handgull Date: Sat, 25 Oct 2025 20:52:00 +0200 Subject: [PATCH 09/13] chart data sent when tapped --- .flutter-plugins-dependencies | 1 - .gitignore | 4 + example/lib/charts/chart_click.dart | 96 +++++++++++++++++ example/lib/main.dart | 4 +- example/lib/test_chart_click.dart | 120 ---------------------- lib/src/controller/implements/facade.dart | 8 +- lib/src/controller/implements/mobile.dart | 22 +++- lib/src/controller/implements/web.dart | 48 ++++++++- lib/src/controller/interface.dart | 9 +- lib/src/controller/js_methods.dart | 2 + lib/src/resources/dependencies.js.dart | 53 +++++++++- lib/src/resources/index.html.dart | 3 +- lib/src/utils/color_extension.dart | 10 +- lib/src/view/implements/mobile.dart | 8 +- lib/src/view/implements/web.dart | 33 +++++- 15 files changed, 279 insertions(+), 142 deletions(-) delete mode 100644 .flutter-plugins-dependencies create mode 100644 example/lib/charts/chart_click.dart delete mode 100644 example/lib/test_chart_click.dart diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies deleted file mode 100644 index d4503bc..0000000 --- a/.flutter-plugins-dependencies +++ /dev/null @@ -1 +0,0 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"webview_flutter_wkwebview","path":"C:\\\\Users\\\\user\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\webview_flutter_wkwebview-3.18.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"webview_flutter_android","path":"C:\\\\Users\\\\user\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\webview_flutter_android-4.3.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"webview_flutter_wkwebview","path":"C:\\\\Users\\\\user\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\webview_flutter_wkwebview-3.18.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2025-07-23 22:08:45.282745","version":"3.32.6","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/.gitignore b/.gitignore index ac5aa98..8622390 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,7 @@ migrate_working_dir/ **/doc/api/ .dart_tool/ build/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ diff --git a/example/lib/charts/chart_click.dart b/example/lib/charts/chart_click.dart new file mode 100644 index 0000000..7ffa9f1 --- /dev/null +++ b/example/lib/charts/chart_click.dart @@ -0,0 +1,96 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:graphify/graphify.dart'; + +class ChartClick extends StatefulWidget { + const ChartClick({super.key}); + + @override + State createState() => _ChartClickState(); +} + +class _ChartClickState extends State { + late final GraphifyController _controller; + String _status = 'Ready to test clicks'; + + @override + void initState() { + super.initState(); + _controller = GraphifyController(); + _controller.chartClickedEvent.listen((data) { + debugPrint('listener received: $data'); + setState(() { + _status = 'Click received: ${data['name'] ?? 'Unknown'}'; + }); + + log('TEST: Chart click received: $data'); + }); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(16), + child: Text( + _status, + style: const TextStyle(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + ), + Container( + height: 400, + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + child: GraphifyView( + controller: _controller, + onConsoleMessage: (msg) { + debugPrint('WebView console: ${(msg as dynamic).message}'); + }, + initialOptions: const { + "tooltip": { + "trigger": "axis", + "axisPointer": {"type": "shadow"} + }, + "legend": { + "data": ["Sales", "Marketing"] + }, + "xAxis": { + "type": "category", + "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + }, + "yAxis": {"type": "value"}, + "series": [ + { + "name": "Sales", + "type": "bar", + "data": [120, 200, 150, 80, 70, 110, 130], + "itemStyle": {"color": "#5470c6"} + }, + { + "name": "Marketing", + "type": "bar", + "data": [60, 100, 75, 40, 35, 55, 65], + "itemStyle": {"color": "#91cc75"} + } + ] + }, + ), + ), + ], + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 45eb97c..397a359 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,6 +5,7 @@ import 'package:graphify_example/charts/basic_area_chart.dart'; import 'package:graphify_example/charts/basic_bar_chart.dart'; import 'package:graphify_example/charts/basic_line_chart.dart'; import 'package:graphify_example/charts/candle_stick_brush.dart'; +import 'package:graphify_example/charts/chart_click.dart'; import 'package:graphify_example/charts/customized_radar_chart.dart'; import 'package:graphify_example/charts/graph_webkit_dep.dart'; import 'package:graphify_example/charts/heatmap_discrete_mapping_of_color.dart'; @@ -18,7 +19,6 @@ import 'package:graphify_example/charts/shang_hai_index.dart'; import 'package:graphify_example/charts/stacked_area_chart.dart'; import 'package:graphify_example/charts/tangential_polar_bar_chart.dart'; import 'package:graphify_example/charts/world_population.dart'; -import 'package:graphify_example/test_chart_click.dart'; import 'package:url_launcher/url_launcher_string.dart'; void main() { @@ -29,7 +29,7 @@ class MyApp extends StatelessWidget { const MyApp({super.key}); static const charts = { - "Chart Click Test": TestChartClick(), + "Chart Click Test": ChartClick(), "Basic Line Chart": BasicLineChart(), 'Basic Area Chart': BasicAreaChart(), 'Stacked Area Chart': StackedAreaChart(), diff --git a/example/lib/test_chart_click.dart b/example/lib/test_chart_click.dart deleted file mode 100644 index 433c568..0000000 --- a/example/lib/test_chart_click.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'dart:developer'; - -import 'package:flutter/material.dart'; -import 'package:graphify/graphify.dart'; - -class TestChartClick extends StatefulWidget { - const TestChartClick({super.key}); - - @override - State createState() => _TestChartClickState(); -} - -class _TestChartClickState extends State { - late final GraphifyController _controller; - final List> _clickEvents = []; - String _status = 'Ready to test clicks'; - - @override - void initState() { - super.initState(); - _controller = GraphifyController(); - _controller.chartClickedEvent.listen((data) { - setState(() { - _clickEvents.add(data); - _status = 'Click received: ${data['name'] ?? 'Unknown'}'; - }); - - log('TEST: Chart click received: $data'); - }); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - child: Text( - _status, - style: const TextStyle(fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - ), - ), - Expanded( - flex: 1, - child: Container( - padding: const EdgeInsets.all(8), - child: Expanded( - child: ListView.builder( - itemCount: _clickEvents.length, - itemBuilder: (context, index) { - final event = _clickEvents[index]; - return Card( - child: ListTile( - title: Text('Event ${index + 1}'), - subtitle: Text( - 'Name: ${event['name'] ?? 'N/A'}\n' - 'Value: ${event['value'] ?? 'N/A'}\n' - 'Series: ${event['seriesName'] ?? 'N/A'}\n' - 'Index: ${event['dataIndex'] ?? 'N/A'}', - ), - ), - ); - }, - ), - ), - ), - ), - Expanded( - flex: 2, - child: Container( - margin: const EdgeInsets.all(8), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey), - borderRadius: BorderRadius.circular(8), - ), - child: GraphifyView( - controller: _controller, - initialOptions: const { - "tooltip": { - "trigger": "axis", - "axisPointer": {"type": "shadow"} - }, - "legend": { - "data": ["Sales", "Marketing"] - }, - "xAxis": { - "type": "category", - "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] - }, - "yAxis": {"type": "value"}, - "series": [ - { - "name": "Sales", - "type": "bar", - "data": [120, 200, 150, 80, 70, 110, 130], - "itemStyle": {"color": "#5470c6"} - }, - { - "name": "Marketing", - "type": "bar", - "data": [60, 100, 75, 40, 35, 55, 65], - "itemStyle": {"color": "#91cc75"} - } - ] - }, - ), - ), - ), - ], - ); - } -} diff --git a/lib/src/controller/implements/facade.dart b/lib/src/controller/implements/facade.dart index 86d20f9..aa2db1b 100644 --- a/lib/src/controller/implements/facade.dart +++ b/lib/src/controller/implements/facade.dart @@ -1,15 +1,17 @@ import 'package:graphify/src/controller/interface.dart' as controller_interface; class GraphifyController extends controller_interface.GraphifyController { - @override void update(Map? options) { throw UnimplementedError("update() is not implemented"); } + @override + Stream> get chartClickedEvent => + throw UnimplementedError("chartClickedEvent is not implemented"); + @override void dispose() { throw UnimplementedError("dispose() is not implemented"); } - -} \ No newline at end of file +} diff --git a/lib/src/controller/implements/mobile.dart b/lib/src/controller/implements/mobile.dart index 0df061f..a5008da 100644 --- a/lib/src/controller/implements/mobile.dart +++ b/lib/src/controller/implements/mobile.dart @@ -6,6 +6,8 @@ import 'package:graphify/src/controller/js_methods.dart'; import 'package:webview_flutter/webview_flutter.dart'; class GraphifyController extends controller_interface.GraphifyController { + final StreamController> _clicks = + StreamController>.broadcast(); late final WebViewController _connector; @@ -22,7 +24,24 @@ class GraphifyController extends controller_interface.GraphifyController { } @override - void dispose() async => _eval(JsMethods.disposeChart, [_quotedUid]); + void dispose() async { + await _clicks.close(); + try { + await _eval(JsMethods.disposeChart, [_quotedUid]); + } catch (_) {} + } + + @override + Stream> get chartClickedEvent => _clicks.stream; + + void onChartClick(String message) { + try { + final dynamic decoded = jsonDecode(message); + if (decoded is Map) { + _clicks.sink.add(decoded); + } + } catch (_) {} + } Future _eval(String method, List args) async { return _connector.runJavaScript(_buildJsMethod(method, args)); @@ -31,5 +50,4 @@ class GraphifyController extends controller_interface.GraphifyController { String _buildJsMethod(String method, List args) { return 'window.$method(${args.map((String arg) => arg).join(', ')})'; } - } diff --git a/lib/src/controller/implements/web.dart b/lib/src/controller/implements/web.dart index 091510e..55191e7 100644 --- a/lib/src/controller/implements/web.dart +++ b/lib/src/controller/implements/web.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; @@ -7,6 +8,45 @@ import 'package:graphify/src/controller/js_methods.dart'; import 'package:web/web.dart'; class GraphifyController extends controller_interface.GraphifyController { + GraphifyController() { + _messageListener = ((Event event) { + try { + final msgEvent = event as MessageEvent; + final dataAny = msgEvent.data; + if (dataAny is! String) { + return; + } + final decoded = jsonDecode(dataAny as String); + if (decoded is! Map) { + return; + } + if (decoded['source'] != 'graphify') { + return; + } + if (decoded['chartId'] != uid) { + return; + } + + final payloadString = decoded['payload']; + if (payloadString is! String) { + return; + } + final payloadDecoded = jsonDecode(payloadString); + if (payloadDecoded is Map) { + _clicks.add(payloadDecoded); + } + } catch (_) {} + }).toJS as EventListener; + window.addEventListener('message', _messageListener); + } + + final StreamController> _clicks = + StreamController>.broadcast(); + + late final EventListener _messageListener; + + @override + Stream> get chartClickedEvent => _clicks.stream; @override void update(Map? options) { @@ -19,6 +59,12 @@ class GraphifyController extends controller_interface.GraphifyController { @override void dispose() { - window.callMethod(JsMethods.disposeChart.toJS, uid.toJS); + try { + window.removeEventListener('message', _messageListener); + } catch (_) {} + try { + window.callMethod(JsMethods.disposeChart.toJS, uid.toJS); + } catch (_) {} + _clicks.close(); } } diff --git a/lib/src/controller/interface.dart b/lib/src/controller/interface.dart index 7520c5b..60d3931 100644 --- a/lib/src/controller/interface.dart +++ b/lib/src/controller/interface.dart @@ -1,9 +1,8 @@ +import 'dart:async'; import 'package:graphify/src/utils/uid.dart'; - abstract class GraphifyController { - /// Creates a new instance of [GraphifyController] with a unique identifier. GraphifyController() : uid = UID.generate(); @@ -13,7 +12,9 @@ abstract class GraphifyController { /// Updates the chart with the provided options. void update(Map? options); + /// Streams a value when the chart is clicked. + Stream> get chartClickedEvent; + /// Disposes of the chart instance, cleaning up resources. void dispose(); - -} \ No newline at end of file +} diff --git a/lib/src/controller/js_methods.dart b/lib/src/controller/js_methods.dart index 12973a1..27cef73 100644 --- a/lib/src/controller/js_methods.dart +++ b/lib/src/controller/js_methods.dart @@ -6,4 +6,6 @@ final class JsMethods { static const disposeChart = 'disposeChart'; static const normalizeJson = 'normalizeJson'; + + static const initClickListener = 'initClickListener'; } diff --git a/lib/src/resources/dependencies.js.dart b/lib/src/resources/dependencies.js.dart index 6d7f454..d5bf061 100644 --- a/lib/src/resources/dependencies.js.dart +++ b/lib/src/resources/dependencies.js.dart @@ -25,6 +25,58 @@ const String chartScripts = """ chart.setOption(option); graphify_charts[chart_id].option = option; } + + function ${JsMethods.initClickListener}(chart) { + try { + chart.off && chart.off('click'); + } catch (e) {} + + chart.on('click', function (data) { + try { + if (!data) return; + console.log('graphify click'); + + function safeValue(value, depth) { + if (value == null) return null; + if (depth > 3) return undefined; + const t = typeof value; + if (t === 'string' || t === 'number' || t === 'boolean') return value; + if (Array.isArray(value)) return value.map(v => safeValue(v, depth + 1)).filter(v => v !== undefined); + if (t === 'object') { + const out = {}; + const keys = ['type', 'componentType', 'seriesType', 'seriesIndex', 'seriesName', 'name', 'dataIndex', 'data', 'value', 'color', 'marker']; + keys.forEach(k => { + if (k in value) { + const v = safeValue(value[k], depth + 1); + if (v !== undefined) out[k] = v; + } + }); + // fallback: pick plain props if 'data' is missing + if (!('data' in out) && value && typeof value === 'object') { + Object.keys(value).forEach(k => { + if (out[k] !== undefined) return; + const v = value[k]; + const vt = typeof v; + if (vt === 'string' || vt === 'number' || vt === 'boolean') out[k] = v; + }); + } + return out; + } + return undefined; + } + + const sanitized = safeValue(data, 0) || {}; + const payload = JSON.stringify(sanitized); + if (window && window.ClickEventChannel && typeof window.ClickEventChannel.postMessage === 'function') { + window.ClickEventChannel.postMessage(payload); + } else { + console.warn('ClickEventChannel not ready'); + } + } catch (e) { + console.error('Error posting click event:', e); + } + }); + } function ${JsMethods.disposeChart} (chart_id) { const chart = graphify_charts[chart_id]?.chart; @@ -40,4 +92,3 @@ const String chartScripts = """ } """; - diff --git a/lib/src/resources/index.html.dart b/lib/src/resources/index.html.dart index 6499b9c..165c3e3 100644 --- a/lib/src/resources/index.html.dart +++ b/lib/src/resources/index.html.dart @@ -2,7 +2,7 @@ import 'package:graphify/src/controller/js_methods.dart'; -String indexHtml({ required String id, String? dependencies}) { +String indexHtml({required String id, String? dependencies}) { return ''' @@ -30,6 +30,7 @@ String indexHtml({ required String id, String? dependencies}) { const chart = context.echarts.init(dom, 'dark', { renderer: 'canvas', useDirtyRect: false }); context.${JsMethods.initChart}('$id', chart, {}); context.${JsMethods.updateChart}('$id', {}); + context.${JsMethods.initClickListener}(chart); window.addEventListener('resize', chart.resize); diff --git a/lib/src/utils/color_extension.dart b/lib/src/utils/color_extension.dart index 0762dd8..3d783eb 100644 --- a/lib/src/utils/color_extension.dart +++ b/lib/src/utils/color_extension.dart @@ -1,9 +1,11 @@ import 'dart:ui' show Color; extension ColorExtension on Color { - String get toRGBA { - return 'rgba(${(red * 255).toInt()}, ${(green * 255).toInt()}, ${(blue * 255).toInt()}, ${opacity})'; + final r255 = (r * 255.0).round() & 0xff; + final g255 = (g * 255.0).round() & 0xff; + final b255 = (b * 255.0).round() & 0xff; + final alpha = a; // 0..1 + return 'rgba($r255, $g255, $b255, $alpha)'; } - -} \ No newline at end of file +} diff --git a/lib/src/view/implements/mobile.dart b/lib/src/view/implements/mobile.dart index 8746cbb..23b68f1 100644 --- a/lib/src/view/implements/mobile.dart +++ b/lib/src/view/implements/mobile.dart @@ -19,7 +19,6 @@ class GraphifyView extends g_view.GraphifyView { } class _GraphifyViewState extends g_view.GraphifyViewState { - late final webViewController = WebViewController(); late final controller = (widget.controller ?? GraphifyController()) as GraphifyController; @@ -30,6 +29,13 @@ class _GraphifyViewState extends g_view.GraphifyViewState { ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..setOnConsoleMessage(widget.onConsoleMessage ?? (_) {}) + ..addJavaScriptChannel( + 'ClickEventChannel', + onMessageReceived: (JavaScriptMessage m) { + debugPrint('JS->Dart (ClickEventChannel): ${m.message}'); + controller.onChartClick(m.message); + }, + ) ..loadHtmlString( indexHtml( id: controller.uid, diff --git a/lib/src/view/implements/web.dart b/lib/src/view/implements/web.dart index dbfafc8..0c9fb3c 100644 --- a/lib/src/view/implements/web.dart +++ b/lib/src/view/implements/web.dart @@ -24,7 +24,6 @@ class GraphifyView extends g_view.GraphifyView { } class _GraphifyViewState extends g_view.GraphifyViewState { - late final controller = widget.controller ?? GraphifyController(); String get uid => controller.uid; @@ -47,7 +46,7 @@ class _GraphifyViewState extends g_view.GraphifyViewState { HTMLIFrameElement createHTMLIFrameElement(_) { final iframe = HTMLIFrameElement() ..id = 'graphify_$uid' - ..style.width = '100%' + ..style.width = '100%' ..style.height = '100%' ..style.border = 'none' ..srcdoc = indexHtml(id: uid).toJS @@ -71,6 +70,36 @@ class _GraphifyViewState extends g_view.GraphifyViewState { body?.append(scriptElement); } + + final shim = HTMLScriptElement() + ..id = 'graphify-click-channel-shim-$uid' + ..innerHTML = ''' + (function(uid){ + // Definisci il channel solo se non già presente + if (!window.ClickEventChannel) { + window.ClickEventChannel = { + postMessage: function(payload) { + try { + var envelope = JSON.stringify({ + source: 'graphify', + chartId: uid, + payload: String(payload) + }); + // Inoltra come stringa: il controller web si aspetta un JSON string + window.postMessage(envelope, '*'); + } catch (e) { + console.error('ClickEventChannel shim error', e); + } + } + }; + } + })('$uid'); + ''' + .toJS; + + final dom = window.document; + final body = dom.documentElement?.children.item(1); + body?.append(shim); } @override From 5421b50c2d043194ebf95e2aae56a6e1b5bd7f52 Mon Sep 17 00:00:00 2001 From: handgull Date: Fri, 7 Nov 2025 15:34:06 +0100 Subject: [PATCH 10/13] added web implementation --- lib/src/resources/dependencies.js.dart | 16 +++++++++++++--- lib/src/resources/index.html.dart | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/src/resources/dependencies.js.dart b/lib/src/resources/dependencies.js.dart index d5bf061..cbc88eb 100644 --- a/lib/src/resources/dependencies.js.dart +++ b/lib/src/resources/dependencies.js.dart @@ -26,7 +26,7 @@ const String chartScripts = """ graphify_charts[chart_id].option = option; } - function ${JsMethods.initClickListener}(chart) { + function ${JsMethods.initClickListener}(chart, chart_id) { try { chart.off && chart.off('click'); } catch (e) {} @@ -67,10 +67,20 @@ const String chartScripts = """ const sanitized = safeValue(data, 0) || {}; const payload = JSON.stringify(sanitized); + // Mobile (WebView) channel if (window && window.ClickEventChannel && typeof window.ClickEventChannel.postMessage === 'function') { window.ClickEventChannel.postMessage(payload); - } else { - console.warn('ClickEventChannel not ready'); + } + // Web (Flutter web) channel - post directly to the hosting window + try { + const envelope = JSON.stringify({ + source: 'graphify', + chartId: chart_id, + payload: String(payload) + }); + window.postMessage(envelope, '*'); + } catch (e) { + console.error('Error posting window message:', e); } } catch (e) { console.error('Error posting click event:', e); diff --git a/lib/src/resources/index.html.dart b/lib/src/resources/index.html.dart index 165c3e3..1d1438e 100644 --- a/lib/src/resources/index.html.dart +++ b/lib/src/resources/index.html.dart @@ -30,7 +30,7 @@ String indexHtml({required String id, String? dependencies}) { const chart = context.echarts.init(dom, 'dark', { renderer: 'canvas', useDirtyRect: false }); context.${JsMethods.initChart}('$id', chart, {}); context.${JsMethods.updateChart}('$id', {}); - context.${JsMethods.initClickListener}(chart); + context.${JsMethods.initClickListener}(chart, '$id'); window.addEventListener('resize', chart.resize); From cb6264985bfbd44e52dc7f63271b900cd0790252 Mon Sep 17 00:00:00 2001 From: handgull Date: Sat, 8 Nov 2025 16:52:43 +0100 Subject: [PATCH 11/13] some review fixes --- example/lib/charts/basic_line_chart.dart | 9 ++------- lib/src/view/implements/mobile.dart | 7 ++++--- lib/src/view/implements/web.dart | 4 ++-- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/example/lib/charts/basic_line_chart.dart b/example/lib/charts/basic_line_chart.dart index 19ae012..d104a46 100644 --- a/example/lib/charts/basic_line_chart.dart +++ b/example/lib/charts/basic_line_chart.dart @@ -12,7 +12,6 @@ class BasicLineChart extends StatefulWidget { } class _BasicLineChartState extends State { - final controller = GraphifyController(); Timer? timer; @@ -28,14 +27,13 @@ class _BasicLineChartState extends State { ] }); }); - } @override Widget build(BuildContext context) { return GraphifyView( controller: controller, - onConsoleMessage: (message) { + onConsoleMessage: (message) { print("[ERROR] $message"); }, initialOptions: const { @@ -44,9 +42,7 @@ class _BasicLineChartState extends State { "type": "category", "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] }, - "yAxis": { - "type": "value" - }, + "yAxis": {"type": "value"}, "series": [ { "data": [150, 230, 224, 218, 135, 147, 260], @@ -63,5 +59,4 @@ class _BasicLineChartState extends State { controller.dispose(); super.dispose(); } - } diff --git a/lib/src/view/implements/mobile.dart b/lib/src/view/implements/mobile.dart index 23b68f1..671fa2d 100644 --- a/lib/src/view/implements/mobile.dart +++ b/lib/src/view/implements/mobile.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:graphify/src/controller/implements/mobile.dart'; import 'package:graphify/src/resources/dependencies.js.dart'; @@ -62,9 +64,8 @@ class _GraphifyViewState extends g_view.GraphifyViewState { if (widget.controller == null) { controller.dispose(); } - webViewController - ..clearLocalStorage() - ..clearCache(); + unawaited(webViewController.clearLocalStorage()); + unawaited(webViewController.clearCache()); super.dispose(); } } diff --git a/lib/src/view/implements/web.dart b/lib/src/view/implements/web.dart index 0c9fb3c..d211ea2 100644 --- a/lib/src/view/implements/web.dart +++ b/lib/src/view/implements/web.dart @@ -75,7 +75,7 @@ class _GraphifyViewState extends g_view.GraphifyViewState { ..id = 'graphify-click-channel-shim-$uid' ..innerHTML = ''' (function(uid){ - // Definisci il channel solo se non già presente + // Define the channel only if not already present if (!window.ClickEventChannel) { window.ClickEventChannel = { postMessage: function(payload) { @@ -85,7 +85,7 @@ class _GraphifyViewState extends g_view.GraphifyViewState { chartId: uid, payload: String(payload) }); - // Inoltra come stringa: il controller web si aspetta un JSON string + // Forward as a string: the web controller expects a JSON string window.postMessage(envelope, '*'); } catch (e) { console.error('ClickEventChannel shim error', e); From 1b10de3f1fd7bdbf94d930d5e0354133ca470e35 Mon Sep 17 00:00:00 2001 From: handgull Date: Thu, 20 Nov 2025 12:18:00 +0100 Subject: [PATCH 12/13] removed print --- lib/src/view/implements/mobile.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/view/implements/mobile.dart b/lib/src/view/implements/mobile.dart index 671fa2d..c67321c 100644 --- a/lib/src/view/implements/mobile.dart +++ b/lib/src/view/implements/mobile.dart @@ -34,7 +34,6 @@ class _GraphifyViewState extends g_view.GraphifyViewState { ..addJavaScriptChannel( 'ClickEventChannel', onMessageReceived: (JavaScriptMessage m) { - debugPrint('JS->Dart (ClickEventChannel): ${m.message}'); controller.onChartClick(m.message); }, ) From 9b9db1ecabfcfb9e5393f895c49489ae3c32ab99 Mon Sep 17 00:00:00 2001 From: handgull Date: Tue, 25 Nov 2025 13:02:45 +0100 Subject: [PATCH 13/13] remove print --- lib/src/resources/dependencies.js.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/resources/dependencies.js.dart b/lib/src/resources/dependencies.js.dart index cbc88eb..6700313 100644 --- a/lib/src/resources/dependencies.js.dart +++ b/lib/src/resources/dependencies.js.dart @@ -34,7 +34,6 @@ const String chartScripts = """ chart.on('click', function (data) { try { if (!data) return; - console.log('graphify click'); function safeValue(value, depth) { if (value == null) return null;