From 9d1cfc0ffac36a8e8b2f0aac8f2ea63cd88d2d31 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Thu, 9 Feb 2017 13:23:16 +0300 Subject: [PATCH 01/40] Initial directory tree structure created. --- .gitmodules | 6 ++ AUTHORS | 1 + CMakeLists.txt | 21 +++++ COPYING | 165 ++++++++++++++++++++++++++++++++++++++ INSTALL | 84 +++++++++++++++++++ MAINTAINERS | 2 + cmake/backbone | 1 + cpack/CMakeLists.txt | 10 +++ pkg-config/CMakeLists.txt | 13 +++ po/CMakeLists.txt | 1 + po/ru/CMakeLists.txt | 1 + src/CMakeLists.txt | 9 +++ src/GtkChart.vala | 53 ++++++++++++ test/CMakeLists.txt | 7 ++ test/ChartTest.vala | 43 ++++++++++ util/backbone | 1 + valadoc_env | 2 + 17 files changed, 420 insertions(+) create mode 100644 .gitmodules create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 COPYING create mode 100644 INSTALL create mode 100644 MAINTAINERS create mode 160000 cmake/backbone create mode 100644 cpack/CMakeLists.txt create mode 100644 pkg-config/CMakeLists.txt create mode 100644 po/CMakeLists.txt create mode 100644 po/ru/CMakeLists.txt create mode 100644 src/CMakeLists.txt create mode 100644 src/GtkChart.vala create mode 100644 test/CMakeLists.txt create mode 100644 test/ChartTest.vala create mode 160000 util/backbone create mode 100644 valadoc_env diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..50bba8b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "cmake/backbone"] + path = cmake/backbone + url = git@git.backbone.ws:cmake/backbone.git +[submodule "util/backbone-utils"] + path = util/backbone + url = git@git.backbone.ws:cmake/backbone-utils.git diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..609258d --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Kolan Sh diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ba3a742 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +PROJECT (GtkChart C) + +CMAKE_MINIMUM_REQUIRED (VERSION 2.8) + +SET (PROJECT_LOWERCASE_NAME "gtk-chart") +SET (PROJECT_DESCRIPTION "GtkChart for Gtk.DrawingArea.") + +SET (MAJOR 0) +SET (MINOR 0) +SET (PATCH 0) + +LIST (APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/backbone) + +ADD_SUBDIRECTORY (src) +ADD_SUBDIRECTORY (po) +ADD_SUBDIRECTORY (pkg-config) +ADD_SUBDIRECTORY (test) +ADD_SUBDIRECTORY (cpack) + +# enable testing +#ENABLE_TESTING () diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/COPYING @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..3e899a5 --- /dev/null +++ b/INSTALL @@ -0,0 +1,84 @@ + Requirements + ------------ + + Build-Time Dependencies + +Vala: https://wiki.gnome.org/Projects/Vala +Gee: https://wiki.gnome.org/Projects/Libgee +CMake: http://www.cmake.org +NSIS (W32): http://nsis.sourceforge.net + + Run-Time Dependencies + +Gee: https://wiki.gnome.org/Projects/Libgee + + Operation Systems + + * GNU/Linux (Gentoo, Debian, etc.) + * MS Windows (Windows 5.1 aka XP) + * BSD-based (FreeBSD, OpenBSD, NetBSD, Mac OS X) + +If you need support of one more OS, be free in writing of patches and sending +pull-requests to the mainstream. + + + Compilation + ----------- + + Compilation under GNU/Linux + +$ mkdir build-gcc && cd build-gcc +$ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr +$ make -j$((`getconf _NPROCESSORS_ONLN`+1)) + + Compilation under MS Windows + +$ mkdir build-mingw && cd build-mingw +$ cmake -G "MSYS Makefiles" .. -DCMAKE_BUILD_TYPE=Release +$ make -j$((NUMBER_OF_PROCESSORS + 1)) + + Compilation under BSD-based Systems. + +TODO: add description here. + + + Packing/Installation + -------------------- + + Packing/Installation under GNU/Linux + +$ cpack +Install using System Package Manager. + + Packing/Installation under MS Windows + +$ cpack +Install using generated by NSIS executable. + + Packing/Installation under BSD-based + +$ cpack +Install using System Package Manager. + + + Testing + ------- + + Testing under GNU/Linux + +$ ctest -j$((`getconf _NPROCESSORS_ONLN`+1)) + +Automated tests for memory leaks: +$ ctest -j$((NUMBER_OF_PROCESSORS + 1)) -D NightlyMemCheck && grep definitely Testing/Temporary/LastDynamicAnalysis_*.log + + Testing under MS Windows + +$ ctest -j$((NUMBER_OF_PROCESSORS + 1)) +Automated tests for memory leaks are not available as far as Valgrind not +present on this platform. + + Testing under BSD-based + +$ ctest +Automated tests for memory leaks are not available as far as Valgrind not +present on this platform. diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..f2b408a --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,2 @@ +Kolan Sh +email: backbone@backbone.ws diff --git a/cmake/backbone b/cmake/backbone new file mode 160000 index 0000000..784ca10 --- /dev/null +++ b/cmake/backbone @@ -0,0 +1 @@ +Subproject commit 784ca10a530e629885236e9df7fdf5647ddffa7c diff --git a/cpack/CMakeLists.txt b/cpack/CMakeLists.txt new file mode 100644 index 0000000..c895094 --- /dev/null +++ b/cpack/CMakeLists.txt @@ -0,0 +1,10 @@ +SET (CONTACT "backbone@backbone.ws") +SET (DEBIAN_DEPENDENCIES "valac (>= 0.24), libgtk-3-0 (>= 3.20)") +SET (DEBIAN_SECTION "Libraries") +SET (REDHAT_DEPENDENCIES "vala >= 0.24, gtk+ >= 3.20") +SET (REDHAT_SECTION "Development/Libraries") +SET (LICENSE "LGPLv3+") +SET (WIN32_UNINSTALL_NAME "GtkChart") # <= 8 symbols for the name +SET (CPACK_NSIS_MENU_LINKS "https://redmine.backbone.ws/projects/gtkchart" + "Homepage for GtkChart") +INCLUDE (CPackCommonRules) diff --git a/pkg-config/CMakeLists.txt b/pkg-config/CMakeLists.txt new file mode 100644 index 0000000..3c5e0f4 --- /dev/null +++ b/pkg-config/CMakeLists.txt @@ -0,0 +1,13 @@ +INCLUDE (PkgConfigCommonRules) + +SET (PkgConfigLibs "-L\${libdir}") +IF (WIN32) + SET (PkgConfigLibs "${PkgConfigLibs} -l${PROJECT_LOWERCASE_NAME}-${MAJOR}") +ELSE (WIN32) + SET (PkgConfigLibs "${PkgConfigLibs} -l${PROJECT_LOWERCASE_NAME}.so.${MAJOR}") +ENDIF (WIN32) + +CONFIGURE_FILE ( + "${CMAKE_SOURCE_DIR}/cmake/backbone/templates/pkg-config.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOWERCASE_NAME}-${MAJOR}.pc" +) diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt new file mode 100644 index 0000000..c191c1b --- /dev/null +++ b/po/CMakeLists.txt @@ -0,0 +1 @@ +INCLUDE (GettextCommonRules) diff --git a/po/ru/CMakeLists.txt b/po/ru/CMakeLists.txt new file mode 100644 index 0000000..0b6020c --- /dev/null +++ b/po/ru/CMakeLists.txt @@ -0,0 +1 @@ +INCLUDE (GettextLangRules) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..3c61731 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,9 @@ +SET (LibName ${PROJECT_LOWERCASE_NAME}) +FILE (GLOB_RECURSE LibSources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.vala) +SET (LibPackages cairo) +SET (LibPkgModules gtk+-3.0) +SET (LibInstall ON) +SET (LibExtraSources ${CMAKE_CURRENT_BINARY_DIR}/library_constructor.c) +SET (LC_RELATIVE_PREFIX "..") +CONFIGURE_FILE ( "${CMAKE_SOURCE_DIR}/cmake/backbone/templates/library_constructor.c.in" "${LibExtraSources}") +INCLUDE (ValaLibCommonRules) diff --git a/src/GtkChart.vala b/src/GtkChart.vala new file mode 100644 index 0000000..97d226a --- /dev/null +++ b/src/GtkChart.vala @@ -0,0 +1,53 @@ +public class Gtk.Chart { + public Chart () { + } + + public virtual signal bool draw(Cairo.Context context) { + + double width = context.copy_clip_rectangle_list().rectangles[0].width; + double height = context.copy_clip_rectangle_list().rectangles[0].height; + + // Line width + context.set_line_width (1); + + // Axis + context.move_to (30, 30); + context.line_to (30, height - 30); + context.line_to (width - 30, height - 30); + context.stroke (); + + // Arrows (X) + context.move_to (width - 40, height - 35); + context.line_to (width - 30, height - 30); + context.line_to (width - 40, height - 25); + context.stroke (); + + // Arrows (Y) + context.move_to (25, 40); + context.line_to (30, 30); + context.line_to (35, 40); + context.stroke (); + + // Text: + context.set_source_rgb (0.1, 0.1, 0.1); + context.select_font_face ("Adventure", Cairo.FontSlant.NORMAL, Cairo.FontWeight.BOLD); + context.set_font_size (20); + context.move_to (10, 40); + context.show_text ("Y"); + context.move_to (width - 45, height - 7); + context.show_text ("X"); + + // Grid (X) + + // Grid (Y) + + // Marks (X) + + // Marks (Y) + + // Legend + + + return true; + } +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..b2ac63e --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,7 @@ +SET (BinName chart_test) +FILE (GLOB_RECURSE BinSources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ChartTest.vala) +SET (BinPackages gtk+-3.0) +SET (BinCustomVapis ${CMAKE_BINARY_DIR}/src/${PROJECT_LOWERCASE_NAME}-${MAJOR}.vapi) +SET (BinLinkLibs ${PROJECT_LOWERCASE_NAME}) +INCLUDE_DIRECTORIES ("${CMAKE_BINARY_DIR}/src") +INCLUDE (ValaBinCommonRules) diff --git a/test/ChartTest.vala b/test/ChartTest.vala new file mode 100644 index 0000000..958319e --- /dev/null +++ b/test/ChartTest.vala @@ -0,0 +1,43 @@ +using Gtk; + +int main (string[] args) { + init (ref args); + + var window = new Window (); + window.title = "Gtk.Chart Test."; + window.border_width = 10; + window.window_position = WindowPosition.CENTER; + window.set_default_size (640, 480); + window.destroy.connect (main_quit); + + var da = new DrawingArea(); + var chart = new Gtk.Chart(); + var label = new Label ("Gtk.Chart Test!"); + var button = new Button.with_label("Click me"); + button.clicked.connect (() => { + da.draw.connect((context) => { + chart.draw(context); + return true; + }); + + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + }); + + var vbox2 = new Box(Orientation.VERTICAL, 0); + vbox2.pack_end(button, false, false, 0); + + var hbox = new Box(Orientation.HORIZONTAL, 0); + hbox.pack_start(da, true, true, 0); + hbox.pack_end(vbox2, false, false, 0); + + var vbox = new Box(Orientation.VERTICAL, 0); + vbox.pack_start(label, false, false, 0); + vbox.pack_end(hbox, true, true, 0); + + window.add(vbox); + + window.show_all(); + + Gtk.main(); + return 0; +} diff --git a/util/backbone b/util/backbone new file mode 160000 index 0000000..11c998a --- /dev/null +++ b/util/backbone @@ -0,0 +1 @@ +Subproject commit 11c998aca2aa1b787286b336e579e5a4e31f471a diff --git a/valadoc_env b/valadoc_env new file mode 100644 index 0000000..57510e2 --- /dev/null +++ b/valadoc_env @@ -0,0 +1,2 @@ +BASEDIR=src +PKGS=gtk+-3.0 From f2e220b92e42985ed0cce61ccd978ecc0251577b Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Thu, 16 Feb 2017 15:10:53 +0300 Subject: [PATCH 02/40] Main functional added. Need Zoom/Move, Cursors. --- .gitmodules | 4 +- CMakeLists.txt | 6 +- src/CMakeLists.txt | 4 +- src/Common.vala | 115 +++++ src/GtkChart.vala | 1020 +++++++++++++++++++++++++++++++++++++++-- src/Series.vala | 169 +++++++ src/float128type.h | 1 + src/float128type.vapi | 2 + test/CMakeLists.txt | 4 +- test/ChartTest.vala | 402 ++++++++++++++-- 10 files changed, 1652 insertions(+), 75 deletions(-) create mode 100644 src/Common.vala create mode 100644 src/Series.vala create mode 100644 src/float128type.h create mode 100644 src/float128type.vapi diff --git a/.gitmodules b/.gitmodules index 50bba8b..f6fa12d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "cmake/backbone"] path = cmake/backbone - url = git@git.backbone.ws:cmake/backbone.git + url = git@git.backbone.ws:make/cmake-backbone-modules.git [submodule "util/backbone-utils"] path = util/backbone - url = git@git.backbone.ws:cmake/backbone-utils.git + url = git@git.backbone.ws:make/cmake-backbone-utils.git diff --git a/CMakeLists.txt b/CMakeLists.txt index ba3a742..33d21b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ -PROJECT (GtkChart C) +PROJECT (CairoChart C) CMAKE_MINIMUM_REQUIRED (VERSION 2.8) -SET (PROJECT_LOWERCASE_NAME "gtk-chart") -SET (PROJECT_DESCRIPTION "GtkChart for Gtk.DrawingArea.") +SET (PROJECT_LOWERCASE_NAME "cairo-chart") +SET (PROJECT_DESCRIPTION "GtkChart for Gtk.DrawingArea (Cairo).") SET (MAJOR 0) SET (MINOR 0) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3c61731..19f5d33 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,9 +1,11 @@ SET (LibName ${PROJECT_LOWERCASE_NAME}) FILE (GLOB_RECURSE LibSources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.vala) -SET (LibPackages cairo) +SET (LibPackages cairo gtk+-3.0) SET (LibPkgModules gtk+-3.0) SET (LibInstall ON) SET (LibExtraSources ${CMAKE_CURRENT_BINARY_DIR}/library_constructor.c) SET (LC_RELATIVE_PREFIX "..") CONFIGURE_FILE ( "${CMAKE_SOURCE_DIR}/cmake/backbone/templates/library_constructor.c.in" "${LibExtraSources}") +INCLUDE_DIRECTORIES ("${CMAKE_SOURCE_DIR}/src") +SET (LibCustomVapis ${CMAKE_SOURCE_DIR}/src/float128type.vapi) INCLUDE (ValaLibCommonRules) diff --git a/src/Common.vala b/src/Common.vala new file mode 100644 index 0000000..99edbdc --- /dev/null +++ b/src/Common.vala @@ -0,0 +1,115 @@ +using Cairo; + +namespace Gtk.CairoChart { + + public struct Color { + double red; + double green; + double blue; + double alpha; + + public Color (double red = 0.0, double green = 0.0, double blue = 0.0, double alpha = 1.0) { + this.red = red; this.green = green; this.blue = blue; this.alpha = alpha; + } + } + + public enum FontOrient { + HORIZONTAL = 0, + VERTICAL + } + public struct FontStyle { + string family; + FontSlant slant; + FontWeight weight; + + FontOrient orientation; + double size; + + public FontStyle (string family = "Sans", + FontSlant slant = Cairo.FontSlant.NORMAL, + FontWeight weight = Cairo.FontWeight.NORMAL, + double size = 10) { + this.family = family; + this.slant = slant; + this.weight = weight; + this.size = size; + } + } + + public struct LineStyle { + double width; + LineJoin line_join; + LineCap line_cap; + double[]? dashes; + double dash_offset; + Color color; + + public LineStyle (double width = 1, + LineJoin line_join = Cairo.LineJoin.MITER, + LineCap line_cap = Cairo.LineCap.ROUND, + double[]? dashes = null, double dash_offset = 0, + Color color = Color()) { + this.width = width; + this.line_join = line_join; + this.line_cap = line_cap; + this.dashes = dashes; + this.dash_offset = dash_offset; + this.color = color; + } + } + + [Compact] + public class Text { + public string text = ""; + public FontStyle style = new FontStyle (); + public Color color = new Color(); + + TextExtents get_extents (Cairo.Context context) { + context.select_font_face (style.family, + style.slant, + style.weight); + context.set_font_size (style.size); + TextExtents extents; + context.text_extents (text, out extents); + return extents; + } + + public double get_width (Cairo.Context context) { + var extents = get_extents (context); + if (style.orientation == FontOrient.HORIZONTAL) + return extents.width; + else + return extents.height; + } + + public double get_height (Cairo.Context context) { + var extents = get_extents (context); + if (style.orientation == FontOrient.HORIZONTAL) + return extents.height; + else + return extents.width; + } + + public double get_x_bearing (Cairo.Context context) { + var extents = get_extents (context); + if (style.orientation == FontOrient.HORIZONTAL) + return extents.x_bearing; + else + return extents.y_bearing; + } + + public Text (string text = "", + FontStyle style = new FontStyle(), + Color color = new Color()) { + this.text = text; + this.style = style; + this.color = color; + } + + public Text.by_instance (Text text) { + this.text = text.text; + this.style = text.style; + this.color = text.color; + } + } +} diff --git a/src/GtkChart.vala b/src/GtkChart.vala index 97d226a..0640b61 100644 --- a/src/GtkChart.vala +++ b/src/GtkChart.vala @@ -1,53 +1,999 @@ -public class Gtk.Chart { - public Chart () { - } +// даты/время: сетка для малых интервалов (< нескольких секунд) +using Cairo; - public virtual signal bool draw(Cairo.Context context) { +namespace Gtk.CairoChart { - double width = context.copy_clip_rectangle_list().rectangles[0].width; - double height = context.copy_clip_rectangle_list().rectangles[0].height; + public class Chart { - // Line width - context.set_line_width (1); + protected double width = 0; + protected double height = 0; - // Axis - context.move_to (30, 30); - context.line_to (30, height - 30); - context.line_to (width - 30, height - 30); - context.stroke (); + public Cairo.Context context = null; - // Arrows (X) - context.move_to (width - 40, height - 35); - context.line_to (width - 30, height - 30); - context.line_to (width - 40, height - 25); - context.stroke (); + public Color bg_color; + public bool show_legend = true; + public Text title = new Text ("Cairo Chart"); + public Color border_color = new Color(0, 0, 0, 0.3); - // Arrows (Y) - context.move_to (25, 40); - context.line_to (30, 30); - context.line_to (35, 40); - context.stroke (); + public class Legend { + public enum Position { + TOP = 0, // default + LEFT, + RIGHT, + BOTTOM + } + public Position position = Position.TOP; + public FontStyle font_style = new FontStyle(); + public Color bg_color = new Color(1, 1, 1); + public LineStyle border_style = new LineStyle (); + public double indent = 5; - // Text: - context.set_source_rgb (0.1, 0.1, 0.1); - context.select_font_face ("Adventure", Cairo.FontSlant.NORMAL, Cairo.FontWeight.BOLD); - context.set_font_size (20); - context.move_to (10, 40); - context.show_text ("Y"); - context.move_to (width - 45, height - 7); - context.show_text ("X"); + public Legend () { + border_style.color = new Color (0, 0, 0, 0.3); + } + } - // Grid (X) + public Legend legend = new Legend (); - // Grid (Y) + public Series[] series = {}; - // Marks (X) + protected LineStyle selection_style = new LineStyle (); - // Marks (Y) + public Chart () { + bg_color = new Color (1, 1, 1); + } - // Legend + protected Double128 cur_x_min = 0.0; + protected Double128 cur_x_max = 0.0; + protected double cur_y_min = 0.0; + protected double cur_y_max = 0.0; + + public virtual void check_cur_values () { + if (cur_x_min > cur_x_max) + cur_x_max = cur_x_min; + if (cur_y_min > cur_y_max) + cur_y_max = cur_y_min; + } + + public virtual bool draw () { + + update_size (); + + draw_background (); + + cur_x_min = cur_y_min = 0.0; + cur_x_max = width; + cur_y_max = height; + + draw_chart_title (); + check_cur_values (); + + draw_legend (); + check_cur_values (); + + set_vertical_axes_titles (); + + calc_plot_area (); // Calculate plot area + + draw_horizontal_axis (); + check_cur_values (); + + draw_vertical_axis (); + check_cur_values (); + + draw_plot_area_border (); + check_cur_values (); + + draw_series (); + check_cur_values (); + + draw_cursors (); + check_cur_values (); + + return true; + } + + protected virtual void update_size () { + if (context != null) { + width = context.copy_clip_rectangle_list().rectangles[0].width; + height = context.copy_clip_rectangle_list().rectangles[0].height; + } + } + + protected virtual void set_source_rgba (Color color) { + context.set_source_rgba (color.red, color.green, color.blue, color.alpha); + } + + protected virtual void draw_background () { + if (context != null) { + set_source_rgba (bg_color); + context.paint(); + set_source_rgba (new Color (0, 0, 0, 1)); + } + } + + // TODO: + public virtual bool button_release_event (Gdk.EventButton event) { + //stdout.puts ("button_release_event\n"); + return true; + } + + // TODO: + public virtual bool button_press_event (Gdk.EventButton event) { + //stdout.puts ("button_press_event\n"); + return true; + } + + // TODO: + public virtual bool motion_notify_event (Gdk.EventMotion event) { + //stdout.puts ("motion_notify_event\n"); + return true; + } + + protected double title_width = 0.0; + protected double title_height = 0.0; + + public double title_vindent = 4; + + protected virtual void show_text(Text text) { + context.select_font_face(text.style.family, + text.style.slant, + text.style.weight); + context.set_font_size(text.style.size); + if (text.style.orientation == FontOrient.VERTICAL) { + context.rotate(- Math.PI / 2.0); + context.show_text(text.text); + context.rotate(Math.PI / 2.0); + } else { + context.show_text(text.text); + } + } + + protected virtual void draw_chart_title () { + title_width = title.get_width (context); + title_height = title.get_height (context) + (legend.position == Legend.Position.TOP ? title_vindent * 2 : title_vindent); + cur_y_min += title_height; + set_source_rgba(title.color); + context.move_to (width/2 - title_width/2 - title.get_x_bearing(context), title.get_height(context) + title_vindent); + show_text(title); + } + + protected double legend_width = 0; + protected double legend_height = 0; + + protected enum LegendProcessType { + CALC = 0, // default + DRAW + } + + protected virtual void set_line_style (LineStyle style) { + set_source_rgba(style.color); + context.set_line_join(style.line_join); + context.set_line_cap(style.line_cap); + context.set_line_width(style.width); + context.set_dash(style.dashes, style.dash_offset); + } + + protected virtual void draw_legend_rect (out double x0, out double y0) { + x0 = y0 = 0.0; + if (context != null) { + switch (legend.position) { + case Legend.Position.TOP: + x0 = (width - legend_width) / 2; + y0 = title_height; + break; + + case Legend.Position.BOTTOM: + x0 = (width - legend_width) / 2; + y0 = height - legend_height; + break; + + case Legend.Position.LEFT: + x0 = 0; + y0 = (height - legend_height) / 2; + break; + + case Legend.Position.RIGHT: + x0 = width - legend_width; + y0 = (height - legend_height) / 2; + break; + + default: + break; + } + set_source_rgba(legend.bg_color); + context.rectangle (x0, y0, legend_width, legend_height); + context.fill(); + set_line_style(legend.border_style); + context.move_to (x0, y0); + context.rel_line_to (legend_width, 0); + context.rel_line_to (0, legend_height); + context.rel_line_to (-legend_width, 0); + context.rel_line_to (0, -legend_height); + context.stroke (); + } + } + + public double legend_line_length = 30.0; + public double legend_text_hspace = 10.0; + public double legend_text_vspace = 2.0; + public double marker_size = 8.0; + + protected virtual void draw_marker_at_pos (Series.MarkerType marker_type, + double x, double y) { + context.move_to (x, y); + switch (marker_type) { + case Series.MarkerType.SQUARE: + context.rectangle (x - marker_size / 2, y - marker_size / 2, + marker_size, marker_size); + context.fill(); + break; + + case Series.MarkerType.CIRCLE: + context.arc (x, y, marker_size / 2, 0, 2*Math.PI); + context.fill(); + break; + + case Series.MarkerType.TRIANGLE: + context.move_to (x - marker_size / 2, y - marker_size / 2); + context.line_to (x + marker_size / 2, y - marker_size / 2); + context.line_to (x, y + marker_size / 2); + context.line_to (x - marker_size / 2, y - marker_size / 2); + context.fill(); + break; + + case Series.MarkerType.PRICLE_SQUARE: + context.rectangle (x - marker_size / 2, y - marker_size / 2, + marker_size, marker_size); + context.stroke(); + break; + + case Series.MarkerType.PRICLE_CIRCLE: + context.arc (x, y, marker_size / 2, 0, 2*Math.PI); + context.stroke(); + break; + + case Series.MarkerType.PRICLE_TRIANGLE: + context.move_to (x - marker_size / 2, y - marker_size / 2); + context.line_to (x + marker_size / 2, y - marker_size / 2); + context.line_to (x, y + marker_size / 2); + context.line_to (x - marker_size / 2, y - marker_size / 2); + context.stroke(); + break; + + case Series.MarkerType.NONE: + default: + break; + } + } + + double [] max_font_heights; + protected virtual void process_legend (LegendProcessType process_type) { + var legend_x0 = 0.0, legend_y0 = 0.0; + var heights_idx = 0; + var leg_width_sum = 0.0; + var leg_height_sum = 0.0; + double max_font_h = 0.0; + + // prepare + switch (process_type) { + case LegendProcessType.CALC: + legend_width = 0.0; + legend_height = 0.0; + max_font_heights = {}; + heights_idx = 0; + break; + case LegendProcessType.DRAW: + draw_legend_rect(out legend_x0, out legend_y0); + break; + } + + foreach (var s in series) { + + // carry + switch (legend.position) { + case Legend.Position.TOP: + case Legend.Position.BOTTOM: + var ser_title_width = s.title.get_width(context) + legend_line_length; + if (leg_width_sum + (leg_width_sum == 0 ? 0 : legend_text_hspace) + ser_title_width > width) { // carry + leg_height_sum += max_font_h; + switch (process_type) { + case LegendProcessType.CALC: + max_font_heights += max_font_h; + legend_width = double.max(legend_width, leg_width_sum); + break; + case LegendProcessType.DRAW: + heights_idx++; + break; + } + leg_width_sum = 0.0; + max_font_h = 0; + } + break; + } + + switch (process_type) { + case LegendProcessType.DRAW: + var x = legend_x0 + leg_width_sum + (leg_width_sum == 0.0 ? 0.0 : legend_text_hspace); + var y = legend_y0 + leg_height_sum + max_font_heights[heights_idx]; + + // series title + context.move_to (x + legend_line_length - s.title.get_x_bearing(context), y); + set_source_rgba (s.title.color); + show_text(s.title); + + // series line style + context.move_to (x, y - s.title.get_height(context) / 2); + set_line_style(s.line_style); + context.rel_line_to (legend_line_length, 0); + context.stroke(); + draw_marker_at_pos (s.marker_type, x + legend_line_length / 2, y - s.title.get_height(context) / 2); + break; + } + + switch (legend.position) { + case Legend.Position.TOP: + case Legend.Position.BOTTOM: + var ser_title_width = s.title.get_width(context) + legend_line_length; + leg_width_sum += (leg_width_sum == 0 ? 0 : legend_text_hspace) + ser_title_width; + max_font_h = double.max (max_font_h, s.title.get_height(context)) + (leg_height_sum != 0 ? legend_text_vspace : 0); + break; + + case Legend.Position.LEFT: + case Legend.Position.RIGHT: + switch (process_type) { + case LegendProcessType.CALC: + max_font_heights += s.title.get_height(context) + (leg_height_sum != 0 ? legend_text_vspace : 0); + legend_width = double.max (legend_width, s.title.get_width(context) + legend_line_length); + break; + case LegendProcessType.DRAW: + heights_idx++; + break; + } + leg_height_sum += s.title.get_height(context) + (leg_height_sum != 0 ? legend_text_vspace : 0); + break; + } + } + + // TOP, BOTTOM + switch (legend.position) { + case Legend.Position.TOP: + case Legend.Position.BOTTOM: + if (leg_width_sum != 0) { + leg_height_sum += max_font_h; + switch (process_type) { + case LegendProcessType.CALC: + max_font_heights += max_font_h; + legend_width = double.max(legend_width, leg_width_sum); + break; + } + } + break; + } + + switch (process_type) { + case LegendProcessType.CALC: + legend_height = leg_height_sum; + switch (legend.position) { + case Legend.Position.TOP: + cur_y_min += legend_height; + break; + case Legend.Position.BOTTOM: + cur_y_max -= legend_height; + break; + case Legend.Position.LEFT: + cur_x_min += legend_width; + break; + case Legend.Position.RIGHT: + cur_x_max -= legend_width; + break; + } + break; + } + } + + protected virtual void draw_legend () { + process_legend (LegendProcessType.CALC); + process_legend (LegendProcessType.DRAW); + } + + int axis_rec_npoints = 128; + + protected virtual void calc_axis_rec_sizes (Series.Axis axis, out double max_rec_width, out double max_rec_height, bool is_horizontal = true) { + max_rec_width = max_rec_height = 0; + for (var i = 0; i < axis_rec_npoints; ++i) { + Double128 x = axis.min + (axis.max - axis.min) / axis_rec_npoints * i; + switch (axis.type) { + case Series.Axis.Type.NUMBERS: + var text = new Text (axis.format.printf(x) + (is_horizontal ? "_" : "")); + text.style = axis.font_style; + max_rec_width = double.max (max_rec_width, text.get_width(context)); + max_rec_height = double.max (max_rec_height, text.get_height(context)); + break; + case Series.Axis.Type.DATE_TIME: + var dt = new DateTime.from_unix_utc((int64)x); + var text = new Text(""); + var h = 0.0; + if (axis.date_format != "") { + text = new Text (dt.format(axis.date_format) + (is_horizontal ? "_" : "")); + text.style = axis.font_style; + max_rec_width = double.max (max_rec_width, text.get_width(context)); + h = text.get_height(context); + } + if (axis.time_format != "") { + var dsec_str = ("%."+(axis.dsec_signs.to_string())+"f").printf(1.0/3.0).offset(1); + text = new Text (dt.format(axis.time_format) + (is_horizontal ? "_" : "") + dsec_str); + text.style = axis.font_style; + max_rec_width = double.max (max_rec_width, text.get_width(context)); + h += text.get_height(context); + } + max_rec_height = double.max (max_rec_height, h); + break; + default: + break; + } + } + } + + protected virtual Double128 calc_round_step (Double128 aver_step, bool date_time = false) { + Double128 step = 1.0; + + if (aver_step > 1.0) { + if (date_time) while (step < aver_step) step *= 60; + if (date_time) while (step < aver_step) step *= 60; + if (date_time) while (step < aver_step) step *= 24; + while (step < aver_step) step *= 10; + if (step / 5 > aver_step) step /= 5; + while (step / 2 > aver_step) step /= 2; + } else if (aver_step > 0) { + //stdout.printf("aver_step = %Lf\n", aver_step); + while (step / 10 > aver_step) step /= 10; + if (step / 5 > aver_step) step /= 5; + while (step / 2 > aver_step) step /= 2; + } + + return step; + } + + public double plot_area_x_min = 0; + public double plot_area_x_max = 0; + public double plot_area_y_min = 0; + public double plot_area_y_max = 0; + + bool common_x_axes = false; + bool common_y_axes = false; + + bool are_intersect (Double128 a_min, Double128 a_max, Double128 b_min, Double128 b_max) { + if ( a_min < a_max <= b_min < b_max + || b_min < b_max <= a_min < a_max) + return false; + return true; + } + + protected virtual void set_vertical_axes_titles () { + for (var i = 0; i < series.length; ++i) { + var s = series[i]; + s.axis_y.title.style.orientation = FontOrient.VERTICAL; + } + } + + protected virtual void calc_plot_area () { + plot_area_x_min = cur_x_min + legend.indent; + plot_area_x_max = cur_x_max - legend.indent; + plot_area_y_min = cur_y_min + legend.indent; + plot_area_y_max = cur_y_max - legend.indent; + + // Check for common axes + common_x_axes = common_y_axes = true; + for (int si = series.length - 1; si >=0; --si) { + var s = series[si]; + if ( s.axis_x.position != series[0].axis_x.position + || s.axis_x.min != series[0].axis_x.min + || s.axis_x.max != series[0].axis_x.max + || s.place.x_low != series[0].place.x_low + || s.place.x_high != series[0].place.x_high + || s.axis_x.type != series[0].axis_x.type) + common_x_axes = false; + if ( s.axis_y.position != series[0].axis_y.position + || s.axis_y.min != series[0].axis_y.min + || s.axis_y.max != series[0].axis_y.max + || s.place.y_low != series[0].place.y_low + || s.place.y_high != series[0].place.y_high) + common_y_axes = false; + } + if (series.length == 1) common_x_axes = common_y_axes = false; + + // Join and calc X-axes + for (int si = series.length - 1, nskip = 0; si >=0; --si) { + if (nskip != 0) {--nskip; continue;} + var s = series[si]; + double max_rec_width = 0; double max_rec_height = 0; + calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true); + var max_font_indent = s.axis_x.font_indent; + var max_axis_font_height = s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent; + + // join relative x-axes with non-intersect places + for (int sj = si - 1; sj >= 0; --sj) { + var s2 = series[sj]; + bool has_intersection = false; + for (int sk = si; sk > sj; --sk) { + var s3 = series[sk]; + if (are_intersect(s2.place.x_low, s2.place.x_high, s3.place.x_low, s3.place.x_high) + || s2.axis_x.position != s3.axis_x.position + || s2.axis_x.type != s3.axis_x.type) { + has_intersection = true; + break; + } + } + if (!has_intersection) { + double tmp_max_rec_width = 0; double tmp_max_rec_height = 0; + calc_axis_rec_sizes (s2.axis_x, out tmp_max_rec_width, out tmp_max_rec_height, true); + max_rec_width = double.max (max_rec_width, tmp_max_rec_width); + max_rec_height = double.max (max_rec_height, tmp_max_rec_height); + max_font_indent = double.max (max_font_indent, s2.axis_x.font_indent); + max_axis_font_height = double.max (max_axis_font_height, s2.axis_x.title.text == "" ? 0 : + s2.axis_x.title.get_height(context) + s.axis_x.font_indent); + ++nskip; + } else { + break; + } + } + + if (!common_x_axes || si == 0) + switch (s.axis_x.position) { + case Series.Axis.Position.LOW: plot_area_y_max -= max_rec_height + max_font_indent + max_axis_font_height; break; + case Series.Axis.Position.HIGH: plot_area_y_min += max_rec_height + max_font_indent + max_axis_font_height; break; + case Series.Axis.Position.BOTH: break; + default: break; + } + } + + // Join and calc Y-axes + for (int si = series.length - 1, nskip = 0; si >=0; --si) { + if (nskip != 0) {--nskip; continue;} + var s = series[si]; + double max_rec_width = 0; double max_rec_height = 0; + calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false); + var max_font_indent = s.axis_y.font_indent; + var max_axis_font_width = s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent; + + // join relative x-axes with non-intersect places + for (int sj = si - 1; sj >= 0; --sj) { + var s2 = series[sj]; + bool has_intersection = false; + for (int sk = si; sk > sj; --sk) { + var s3 = series[sk]; + if (are_intersect(s2.place.y_low, s2.place.y_high, s3.place.y_low, s3.place.y_high) + || s2.axis_y.position != s3.axis_y.position + || s2.axis_x.type != s3.axis_x.type) { + has_intersection = true; + break; + } + } + if (!has_intersection) { + double tmp_max_rec_width = 0; double tmp_max_rec_height = 0; + calc_axis_rec_sizes (s2.axis_y, out tmp_max_rec_width, out tmp_max_rec_height, false); + max_rec_width = double.max (max_rec_width, tmp_max_rec_width); + max_rec_height = double.max (max_rec_height, tmp_max_rec_height); + max_font_indent = double.max (max_font_indent, s2.axis_y.font_indent); + max_axis_font_width = double.max (max_axis_font_width, s2.axis_y.title.text == "" ? 0 + : s2.axis_y.title.get_width(context) + s.axis_y.font_indent); + ++nskip; + } else { + break; + } + } + + if (!common_y_axes || si == 0) + switch (s.axis_y.position) { + case Series.Axis.Position.LOW: plot_area_x_min += max_rec_width + max_font_indent + max_axis_font_width; break; + case Series.Axis.Position.HIGH: plot_area_x_max -= max_rec_width + max_font_indent + max_axis_font_width; break; + case Series.Axis.Position.BOTH: break; + default: break; + } + } + } + + bool point_belong (Double128 p, Double128 a, Double128 b) { + if (a > b) { Double128 tmp = a; a = b; b = tmp; } + if (a <= p <= b) return true; + return false; + } + + protected virtual void draw_horizontal_axis () { + for (int si = series.length - 1, nskip = 0; si >=0; --si) { + if (common_x_axes && si != 0) continue; + var s = series[si]; + // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. + double max_rec_width, max_rec_height; + calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true); + + // 2. Calculate maximal available number of records, take into account the space width. + long max_nrecs = (long) ((plot_area_x_max - plot_area_x_min) * (s.place.x_high - s.place.x_low) / max_rec_width); + + // 3. Calculate grid step. + Double128 step = calc_round_step ((s.axis_x.max - s.axis_x.min) / max_nrecs, s.axis_x.type == Series.Axis.Type.DATE_TIME); + if (step > s.axis_x.max - s.axis_x.min) + step = s.axis_x.max - s.axis_x.min; + + // 4. Calculate x_min (s.axis_x.min / step, round, multiply on step, add step if < s.axis_x.min). + Double128 x_min = 0.0; + if (step >= 1) { + int64 x_min_nsteps = (int64) (s.axis_x.min / step); + x_min = x_min_nsteps * step; + } else { + int64 round_axis_x_min = (int64)s.axis_x.min; + int64 x_min_nsteps = (int64) ((s.axis_x.min - round_axis_x_min) / step); + x_min = round_axis_x_min + x_min_nsteps * step; + } + if (x_min < s.axis_x.min) x_min += step; + + // 4.5. Draw Axis title + if (s.axis_x.title.text != "") + switch (s.axis_x.position) { + case Series.Axis.Position.LOW: + var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + s.place.x_high) / 2.0; + var scr_y = cur_y_max - s.axis_x.font_indent; + context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y); + set_source_rgba(s.axis_x.color); + if (common_x_axes) set_source_rgba(Color(0,0,0,1)); + show_text(s.axis_x.title); + break; + case Series.Axis.Position.HIGH: + var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + s.place.x_high) / 2.0; + var scr_y = cur_y_min + s.axis_x.font_indent + s.axis_x.title.get_height(context); + context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y); + set_source_rgba(s.axis_x.color); + if (common_x_axes) set_source_rgba(Color(0,0,0,1)); + show_text(s.axis_x.title); + break; + case Series.Axis.Position.BOTH: + break; + } + + // 5. Draw records, update cur_{x,y}_{min,max}. + for (Double128 x = x_min, x_max = s.axis_x.max; point_belong (x, x_min, x_max); x += step) { + if (common_x_axes) set_source_rgba(Color(0,0,0,1)); + else set_source_rgba(s.axis_x.color); + string text = "", time_text = ""; + switch (s.axis_x.type) { + case Series.Axis.Type.NUMBERS: + text = s.axis_x.format.printf(x); + break; + case Series.Axis.Type.DATE_TIME: + var dt = new DateTime.from_unix_utc((int64)x); + text = dt.format(s.axis_x.date_format); + var dsec_str = ("%."+(s.axis_x.dsec_signs.to_string())+"Lf").printf((x - (int64)x)).offset(1); + time_text = dt.format(s.axis_x.time_format) + dsec_str; + break; + default: + break; + } + var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) + * (s.place.x_low + (s.place.x_high - s.place.x_low) / (s.axis_x.max - s.axis_x.min) * (x - s.axis_x.min)); + var text_t = new Text(text, s.axis_x.font_style, s.axis_x.color); + switch (s.axis_x.position) { + case Series.Axis.Position.LOW: + var print_y = cur_y_max - s.axis_x.font_indent - (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); + switch (s.axis_x.type) { + case Series.Axis.Type.NUMBERS: + var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) + - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + context.move_to (print_x, print_y); + show_text(text_t); + break; + case Series.Axis.Type.DATE_TIME: + var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) + - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + context.move_to (print_x, print_y); + if (s.axis_x.date_format != "") show_text(text_t); + var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color); + print_x = scr_x - time_text_t.get_width(context) / 2.0 - time_text_t.get_x_bearing(context) + - time_text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent)); + if (s.axis_x.time_format != "") show_text(time_text_t); + break; + default: + break; + } + // 6. Draw grid lines to the s.place.y_high. + var line_style = s.grid.line_style; + if (common_x_axes) line_style.color = Color(0, 0, 0, 0.5); + set_line_style(line_style); + double y = cur_y_max - max_rec_height - s.axis_x.font_indent - (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); + context.move_to (scr_x, y); + if (common_x_axes) + context.line_to (scr_x, plot_area_y_min); + else + context.line_to (scr_x, double.min (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.y_high)); + break; + case Series.Axis.Position.HIGH: + var print_y = cur_y_min + max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); + switch (s.axis_x.type) { + case Series.Axis.Type.NUMBERS: + var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) + - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + context.move_to (print_x, print_y); + show_text(text_t); + break; + case Series.Axis.Type.DATE_TIME: + var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) + - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + context.move_to (print_x, print_y); + if (s.axis_x.date_format != "") show_text(text_t); + var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color); + print_x = scr_x - time_text_t.get_width(context) / 2.0 - time_text_t.get_x_bearing(context) + - time_text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent)); + if (s.axis_x.time_format != "") show_text(time_text_t); + break; + default: + break; + } + // 6. Draw grid lines to the s.place.y_high. + var line_style = s.grid.line_style; + if (common_x_axes) line_style.color = Color(0, 0, 0, 0.5); + set_line_style(line_style); + double y = cur_y_min + max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); + context.move_to (scr_x, y); + if (common_x_axes) + context.line_to (scr_x, plot_area_y_max); + else + context.line_to (scr_x, double.max (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.y_low)); + break; + case Series.Axis.Position.BOTH: + break; + default: + break; + } + context.stroke (); + } + + // join relative x-axes with non-intersect places + for (int sj = si - 1; sj >= 0; --sj) { + var s2 = series[sj]; + bool has_intersection = false; + for (int sk = si; sk > sj; --sk) { + var s3 = series[sk]; + if (are_intersect(s2.place.x_low, s2.place.x_high, s3.place.x_low, s3.place.x_high) + || s2.axis_x.position != s3.axis_x.position + || s2.axis_x.type != s3.axis_x.type) { + has_intersection = true; + break; + } + } + if (!has_intersection) { + ++nskip; + } else { + break; + } + } + + if (nskip != 0) {--nskip; continue;} + + switch (s.axis_x.position) { + case Series.Axis.Position.LOW: + cur_y_max -= max_rec_height + s.axis_x.font_indent + + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); + break; + case Series.Axis.Position.HIGH: + cur_y_min += max_rec_height + s.axis_x.font_indent + + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); + break; + case Series.Axis.Position.BOTH: + break; + default: break; + } + } + } + + protected virtual void draw_vertical_axis () { + for (int si = series.length - 1, nskip = 0; si >=0; --si) { + if (common_y_axes && si != 0) continue; + var s = series[si]; + // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. + double max_rec_width, max_rec_height; + calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false); + + // 2. Calculate maximal available number of records, take into account the space width. + long max_nrecs = (long) ((plot_area_y_max - plot_area_y_min) * (s.place.y_high - s.place.y_low) / max_rec_height); + + // 3. Calculate grid step. + Double128 step = calc_round_step ((s.axis_y.max - s.axis_y.min) / max_nrecs); + if (step > s.axis_y.max - s.axis_y.min) + step = s.axis_y.max - s.axis_y.min; + + // 4. Calculate y_min (s.axis_y.min / step, round, multiply on step, add step if < s.axis_y.min). + Double128 y_min = 0.0; + if (step >= 1) { + int64 y_min_nsteps = (int64) (s.axis_y.min / step); + y_min = y_min_nsteps * step; + } else { + int64 round_axis_y_min = (int64)s.axis_y.min; + int64 y_min_nsteps = (int64) ((s.axis_y.min - round_axis_y_min) / step); + y_min = round_axis_y_min + y_min_nsteps * step; + } + if (y_min < s.axis_y.min) y_min += step; + + // 4.5. Draw Axis title + if (s.axis_y.title.text != "") + switch (s.axis_y.position) { + case Series.Axis.Position.LOW: + var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + s.place.y_high) / 2.0; + var scr_x = cur_x_min + s.axis_y.font_indent + s.axis_y.title.get_width(context); + context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0); + set_source_rgba(s.axis_y.color); + if (common_y_axes) set_source_rgba(Color(0,0,0,1)); + show_text(s.axis_y.title); + break; + case Series.Axis.Position.HIGH: + var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + s.place.y_high) / 2.0; + var scr_x = cur_x_max - s.axis_y.font_indent; + context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0); + set_source_rgba(s.axis_y.color); + if (common_y_axes) set_source_rgba(Color(0,0,0,1)); + show_text(s.axis_y.title); + break; + case Series.Axis.Position.BOTH: + break; + } + + // 5. Draw records, update cur_{x,y}_{min,max}. + for (Double128 y = y_min, y_max = s.axis_y.max; point_belong (y, y_min, y_max); y += step) { + if (common_y_axes) set_source_rgba(Color(0,0,0,1)); + else set_source_rgba(s.axis_y.color); + var text = s.axis_y.format.printf(y); + var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) + * (s.place.y_low + (s.place.y_high - s.place.y_low) / (s.axis_y.max - s.axis_y.min) * (y - s.axis_y.min)); + var text_t = new Text(text, s.axis_y.font_style, s.axis_y.color); + switch (s.axis_y.position) { + case Series.Axis.Position.LOW: + context.move_to (cur_x_min + max_rec_width - (new Text(text)).get_width(context) + s.axis_y.font_indent - text_t.get_x_bearing(context) + + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent), + scr_y + (new Text(text)).get_height(context) / 2.0 + + text_t.get_height(context) * (y - (s.axis_y.min + s.axis_y.max) / 2.0) / (s.axis_y.max - s.axis_y.min)); + show_text(text_t); + // 6. Draw grid lines to the s.place.y_high. + var line_style = s.grid.line_style; + if (common_y_axes) line_style.color = Color(0, 0, 0, 0.5); + set_line_style(line_style); + double x = cur_x_min + max_rec_width + s.axis_y.font_indent + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent); + context.move_to (x, scr_y); + if (common_y_axes) + context.line_to (plot_area_x_max, scr_y); + else + context.line_to (double.max (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.x_high), scr_y); + break; + case Series.Axis.Position.HIGH: + context.move_to (cur_x_max - (new Text(text)).get_width(context) - s.axis_y.font_indent - text_t.get_x_bearing(context) + - (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent), + scr_y + (new Text(text)).get_height(context) / 2.0 + + text_t.get_height(context) * (y - (s.axis_y.min + s.axis_y.max) / 2.0) / (s.axis_y.max - s.axis_y.min)); + show_text(text_t); + // 6. Draw grid lines to the s.place.y_high. + var line_style = s.grid.line_style; + if (common_y_axes) line_style.color = Color(0, 0, 0, 0.5); + set_line_style(line_style); + double x = cur_x_max - max_rec_width - s.axis_y.font_indent - (s.axis_y.title.text == "" ? 0 :s.axis_y.title.get_width(context) + s.axis_y.font_indent); + context.move_to (x, scr_y); + if (common_y_axes) + context.line_to (plot_area_x_min, scr_y); + else + context.line_to (double.min (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.x_low), scr_y); + break; + case Series.Axis.Position.BOTH: + break; + default: + break; + } + context.stroke (); + } + + // join relative x-axes with non-intersect places + for (int sj = si - 1; sj >= 0; --sj) { + var s2 = series[sj]; + bool has_intersection = false; + for (int sk = si; sk > sj; --sk) { + var s3 = series[sk]; + if (are_intersect(s2.place.y_low, s2.place.y_high, s3.place.y_low, s3.place.y_high) + || s2.axis_y.position != s3.axis_y.position) { + has_intersection = true; + break; + } + } + if (!has_intersection) { + ++nskip; + } else { + break; + } + } + + if (nskip != 0) {--nskip; continue;} - return true; + switch (s.axis_y.position) { + case Series.Axis.Position.LOW: + cur_x_min += max_rec_width + s.axis_y.font_indent + + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent); break; + case Series.Axis.Position.HIGH: + cur_x_max -= max_rec_width + s.axis_y.font_indent + + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent); break; + case Series.Axis.Position.BOTH: + break; + default: break; + } + } + } + + protected virtual void draw_plot_area_border () { + set_source_rgba (border_color); + context.set_dash(null, 0); + context.move_to (plot_area_x_min, plot_area_y_min); + context.line_to (plot_area_x_min, plot_area_y_max); + context.line_to (plot_area_x_max, plot_area_y_max); + context.line_to (plot_area_x_max, plot_area_y_min); + context.line_to (plot_area_x_min, plot_area_y_min); + context.stroke (); + } + + protected virtual double get_scr_x (Series s, Double128 x) { + return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + (x - s.axis_x.min) + / (s.axis_x.max - s.axis_x.min) * (s.place.x_high - s.place.x_low)); + } + + protected virtual double get_scr_y (Series s, Double128 y) { + return plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + (y - s.axis_y.min) + / (s.axis_y.max - s.axis_y.min) * (s.place.y_high - s.place.y_low)); + } + + delegate int PointComparator(Series.Point a, Series.Point b); + void sort_points(Series.Point[] points, PointComparator compare) { + for(var i = 0; i < points.length; ++i) { + for(var j = i + 1; j < points.length; ++j) { + if(compare(points[i], points[j]) > 0) { + var tmp = points[i]; + points[i] = points[j]; + points[j] = tmp; + } + } + } + } + + protected virtual void draw_series () { + for (int si = 0; si < series.length; ++si) { + var s = series[si]; + if (s.points.length == 0) continue; + var points = s.points.copy(); + switch(s.sort) { + case Series.Sort.BY_X: + sort_points(points, (a, b) => { + if (a.x < b.x) return -1; + if (a.x > b.x) return 1; + return 0; + }); + break; + case Series.Sort.BY_Y: + sort_points(points, (a, b) => { + if (a.y < b.y) return -1; + if (a.y > b.y) return 1; + return 0; + }); + break; + } + set_line_style(s.line_style); + // move to s.points[0] + context.move_to (get_scr_x(s, points[0].x), get_scr_y(s, points[0].y)); + // draw series line + for (int i = 1; i < points.length; ++i) + context.line_to (get_scr_x(s, points[i].x), get_scr_y(s, points[i].y)); + context.stroke(); + for (int i = 0; i < points.length; ++i) + draw_marker_at_pos(s.marker_type, get_scr_x(s, points[i].x), get_scr_y(s, points[i].y)); + } + } + + // TODO: + protected virtual void draw_cursors () { + } } } diff --git a/src/Series.vala b/src/Series.vala new file mode 100644 index 0000000..c1299b2 --- /dev/null +++ b/src/Series.vala @@ -0,0 +1,169 @@ +using Cairo; + +namespace Gtk.CairoChart { + + public class Series { + + public struct Point { + Double128 x; + Double128 y; + + public Point (Double128 x, Double128 y) { + this.x = x; this.y = y; + } + } + + public Point[] points = {}; + public enum Sort { + BY_X = 0, + BY_Y = 1, + NO_SORT + } + public Sort sort = Sort.BY_X; + + // If one of axis:title or axis:min/max are different + // then draw separate axis for each/all series + // or specify series name near the axis + public class Axis { + public Double128 min = 0; + public Double128 max = 1; + public Text title = new Text (""); + public enum Type { + NUMBERS = 0, + DATE_TIME + } + public enum ScaleType { + LINEAR = 0, // default + // LOGARITHMIC, // TODO + // etc + } + public Type type; + public ScaleType scale_type; + public enum Position { + LOW = 0, + HIGH = 1, + BOTH = 2 + } + public Position position = Position.LOW; + + string _format = "%.2Lf"; + string _date_format = "%Y.%m.%d"; + string _time_format = "%H:%M:%S"; + int _dsec_signs = 2; // 2 signs = centiseconds + public string format { + get { return _format; } + set { + // TODO: check format + _format = value; + } + default = "%.2Lf"; + } + public string date_format { + get { return _date_format; } + set { + // TODO: check format + _date_format = value; + } + default = "%Y.%m.%d"; + } + public string time_format { + get { return _time_format; } + set { + // TODO: check format + _time_format = value; + } + default = "%H:%M:%S"; + } + public int dsec_signs { + get { return _dsec_signs; } + set { + // TODO: check format + _dsec_signs = value; + } + default = 2; + } + public FontStyle font_style = new FontStyle (); + public Color color = new Color (); + public LineStyle line_style = new LineStyle (); + public double font_indent = 5; + + public Axis () {} + } + + public Axis axis_x = new Axis(); + public Axis axis_y = new Axis(); + + public struct Place { + double x_low; + double x_high; + double y_low; + double y_high; + + public Place (double x_low = 0, double x_high = 0, double y_low = 0, double y_high = 0) { + this.x_low = x_low; + this.x_high = x_high; + this.y_low = y_low; + this.y_high = y_high; + } + } + + public enum MarkerType { + NONE = 0, // default + SQUARE, + CIRCLE, + TRIANGLE, + PRICLE_SQUARE, + PRICLE_CIRCLE, + PRICLE_TRIANGLE + } + + public Place place = new Place(); + public Text title = new Text (); + public MarkerType marker_type = MarkerType.SQUARE; + + public class Grid { + /*public enum GridType { + PRICK_LINE = 0, // default + LINE + }*/ + public Color color = Color (0, 0, 0, 0.1); + + public LineStyle line_style = new LineStyle (); + + public Grid () { + line_style.dashes = {2, 3}; + } + } + + public Grid grid = new Grid (); + + public GLib.List cursors = new List (); + public LineStyle line_style = new LineStyle (); + + protected Color _color = Color (0.0, 0.0, 0.0, 1.0); + public Color color { + get { return _color; } + set { + _color = value; + line_style.color = _color; + axis_x.color = _color; + axis_y.color = _color; + grid.color = _color; + grid.color.alpha = 0.5; + grid.line_style.color = _color; + grid.line_style.color.alpha = 0.5; + } + default = new Color (0.0, 0.0, 0.0, 1.0); + } + + public Series () { + } + + public class LabelStyle { + FontStyle font_style = new FontStyle(); + LineStyle frame_line_style = new LineStyle(); + Color bg_color = new Color(); + Color frame_color = new Color(); + } + } +} diff --git a/src/float128type.h b/src/float128type.h new file mode 100644 index 0000000..b230774 --- /dev/null +++ b/src/float128type.h @@ -0,0 +1 @@ +typedef long double double128; diff --git a/src/float128type.vapi b/src/float128type.vapi new file mode 100644 index 0000000..8b19687 --- /dev/null +++ b/src/float128type.vapi @@ -0,0 +1,2 @@ +[CCode (cname = "double128", has_type_id = false, cheader_filename = "float128type.h")] +public struct Double128 : double {} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b2ac63e..2067fb2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,7 +1,7 @@ SET (BinName chart_test) FILE (GLOB_RECURSE BinSources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ChartTest.vala) SET (BinPackages gtk+-3.0) -SET (BinCustomVapis ${CMAKE_BINARY_DIR}/src/${PROJECT_LOWERCASE_NAME}-${MAJOR}.vapi) +SET (BinCustomVapis ${CMAKE_BINARY_DIR}/src/${PROJECT_LOWERCASE_NAME}-${MAJOR}.vapi ${CMAKE_SOURCE_DIR}/src/float128type.vapi) SET (BinLinkLibs ${PROJECT_LOWERCASE_NAME}) -INCLUDE_DIRECTORIES ("${CMAKE_BINARY_DIR}/src") +INCLUDE_DIRECTORIES ("${CMAKE_BINARY_DIR}/src;${CMAKE_SOURCE_DIR}/src") INCLUDE (ValaBinCommonRules) diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 958319e..4bd3d70 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -1,43 +1,385 @@ -using Gtk; +using Gtk, CairoChart; + +void plot_chart1 (Chart chart) { + var s1 = new Series (); + var s2 = new Series (); + var s3 = new Series (); + + s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); + s1.points = {new Series.Point(0, 0), new Series.Point(2, 1), new Series.Point(1, 3)}; + s1.axis_x.position = Series.Axis.Position.HIGH; + s1.axis_x.format = "%.3Lf"; + s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); + s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)}; + s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); + s3.points = {new Series.Point(9, 17), new Series.Point(2, 10), new Series.Point(122, 31)}; + s3.axis_y.position = Series.Axis.Position.HIGH; + + s1.axis_x.min = 0; s1.axis_x.max = 2; + s1.axis_y.min = 0; s1.axis_y.max = 3; + s1.place.x_low = 0.25; s1.place.x_high = 0.75; + s1.place.y_low = 0.3; s1.place.y_high = 0.9; + + s2.axis_x.min = -15; s2.axis_x.max = 30; + s2.axis_y.min = -20; s2.axis_y.max = 200; + s2.place.x_low = 0.5; s2.place.x_high = 1; + s2.place.y_low = 0.0; s2.place.y_high = 0.5; + + s3.axis_x.min = 0; s3.axis_x.max = 130; + s3.axis_y.min = 15; s3.axis_y.max = 35; + s3.place.x_low = 0; s3.place.x_high = 0.5; + s3.place.y_low = 0.5; s3.place.y_high = 1.0; + + s2.marker_type = Series.MarkerType.CIRCLE; + s3.marker_type = Series.MarkerType.PRICLE_TRIANGLE; + + s1.axis_x.title = new Text("Series 1: Axis X."); + s1.axis_y.title = new Text("Series 1: Axis Y."); + s2.axis_x.title = new Text("Series 2: Axis X."); + s2.axis_y.title = new Text("Series 2: Axis Y."); + s3.axis_x.title = new Text("Series 3: Axis X."); + s3.axis_y.title = new Text("Series 3: Axis Y."); + + chart.series = { s1, s2, s3 }; +} + +void plot_chart2 (Chart chart) { + var s1 = new Series (); + var s2 = new Series (); + var s3 = new Series (); + + s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); + s1.points = {new Series.Point(-12, 0), new Series.Point(2, 1), new Series.Point(20, 3)}; + s2.axis_y.position = Series.Axis.Position.HIGH; + s1.axis_x.format = "%.3Lf"; + s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); + s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)}; + s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); + s3.points = {new Series.Point(9, 17), new Series.Point(2, 10), new Series.Point(-15, 31)}; + s3.axis_y.position = Series.Axis.Position.HIGH; + + s1.axis_x.min = -15; s1.axis_x.max = 30; + s1.axis_y.min = 0; s1.axis_y.max = 3; + s1.place.x_low = 0.0; s1.place.x_high = 1.0; + s1.place.y_low = 0.3; s1.place.y_high = 0.9; + + s2.axis_x.min = -15; s2.axis_x.max = 30; + s2.axis_y.min = -20; s2.axis_y.max = 200; + s2.place.x_low = 0.0; s2.place.x_high = 1.0; + s2.place.y_low = 0.0; s2.place.y_high = 0.5; + + s3.axis_x.min = -15; s3.axis_x.max = 30; + s3.axis_y.min = 15; s3.axis_y.max = 35; + s3.place.x_low = 0.0; s3.place.x_high = 1.0; + s3.place.y_low = 0.5; s3.place.y_high = 1.0; + + s1.marker_type = Series.MarkerType.PRICLE_CIRCLE; + s2.marker_type = Series.MarkerType.PRICLE_SQUARE; + + s1.axis_x.title = new Text("All Series: Axis X."); + s1.axis_y.title = new Text("Series 1: Axis Y."); + s2.axis_x.title = new Text("All Series: Axis X."); + s2.axis_y.title = new Text("Series 2: Axis Y."); + s3.axis_x.title = new Text("All Series: Axis X."); + s3.axis_y.title = new Text("Series 3: Axis Y."); + + chart.series = { s1, s2, s3 }; +} + +void plot_chart3 (Chart chart) { + var s1 = new Series (); + var s2 = new Series (); + var s3 = new Series (); + + s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); + s1.points = {new Series.Point(0, 70), new Series.Point(2, 155), new Series.Point(1, -3)}; + s1.axis_x.position = Series.Axis.Position.HIGH; + s1.axis_y.position = Series.Axis.Position.HIGH; + s1.axis_x.format = "%.3Lf"; + s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); + s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)}; + s2.axis_y.position = Series.Axis.Position.HIGH; + s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); + s3.points = {new Series.Point(9, -17), new Series.Point(2, 10), new Series.Point(122, 31)}; + s3.axis_y.position = Series.Axis.Position.HIGH; + + s1.axis_x.min = 0; s1.axis_x.max = 2; + s1.axis_y.min = -20; s1.axis_y.max = 200; + s1.place.x_low = 0.25; s1.place.x_high = 0.75; + s1.place.y_low = 0.0; s1.place.y_high = 1.0; + + s2.axis_x.min = -15; s2.axis_x.max = 30; + s2.axis_y.min = -20; s2.axis_y.max = 200; + s2.place.x_low = 0.5; s2.place.x_high = 1; + s2.place.y_low = 0.0; s2.place.y_high = 1.0; + + s3.axis_x.min = 0; s3.axis_x.max = 130; + s3.axis_y.min = -20; s3.axis_y.max = 200; + s3.place.x_low = 0; s3.place.x_high = 0.5; + s3.place.y_low = 0.0; s3.place.y_high = 1.0; + + s2.marker_type = Series.MarkerType.PRICLE_CIRCLE; + s3.marker_type = Series.MarkerType.TRIANGLE; + + s1.axis_x.title = new Text("Series 1: Axis X."); + s1.axis_y.title = new Text("Series 1: Axis Y."); + s2.axis_x.title = new Text("Series 2: Axis X."); + s2.axis_y.title = new Text("Series 2: Axis Y."); + s3.axis_x.title = new Text("Series 3: Axis X."); + s3.axis_y.title = new Text("Series 3: Axis Y."); + + chart.series = { s1, s2, s3 }; +} + +void plot_chart4 (Chart chart) { + var s1 = new Series (); + var s2 = new Series (); + var s3 = new Series (); + var s4 = new Series (); + + s1.axis_x.type = Series.Axis.Type.DATE_TIME; + s3.axis_x.type = Series.Axis.Type.DATE_TIME; + s4.axis_x.type = Series.Axis.Type.DATE_TIME; + s4.axis_x.dsec_signs = 5; + + var now = new DateTime.now_local().to_unix(); + var high = (uint64) (253000000000L); + + s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); + s1.points = {new Series.Point(now, 70), new Series.Point(now - 100000, 155), new Series.Point(now + 100000, 30)}; + s1.axis_x.position = Series.Axis.Position.HIGH; + s1.axis_y.position = Series.Axis.Position.HIGH; + s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); + s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)}; + s2.axis_y.position = Series.Axis.Position.HIGH; + s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); + s3.points = {new Series.Point(high - 2 + 0.73, -17), new Series.Point(high - 1 + 0.234, 10), new Series.Point(high + 1 + 0.411, 31)}; + s3.axis_y.position = Series.Axis.Position.HIGH; + s4.title = new Text("Series 4"); s4.color = new Color (0.5, 0.3, 0.9); + s4.points = {new Series.Point(high + 0.005, -19.05), new Series.Point(high + 0.0051, 28), new Series.Point(high + 0.0052, 55), new Series.Point(high + 0.0053, 44)}; + s4.axis_y.position = Series.Axis.Position.HIGH; + + s1.axis_x.min = now - 100000; s1.axis_x.max = now + 100000; + s1.axis_y.min = -20; s1.axis_y.max = 200; + s1.place.x_low = 0.25; s1.place.x_high = 0.75; + s1.place.y_low = 0.0; s1.place.y_high = 1.0; + + s2.axis_x.min = -15; s2.axis_x.max = 30; + s2.axis_y.min = -20; s2.axis_y.max = 200; + s2.place.x_low = 0.2; s2.place.x_high = 1; + s2.place.y_low = 0.0; s2.place.y_high = 1.0; + + s3.axis_x.min = high - 2; s3.axis_x.max = high + 1; + s3.axis_y.min = -20; s3.axis_y.max = 200; + s3.place.x_low = 0; s3.place.x_high = 0.8; + s3.place.y_low = 0.0; s3.place.y_high = 1.0; + + s4.axis_x.min = high + 0.0049; s4.axis_x.max = high + 0.0054; + s4.axis_y.min = -20; s4.axis_y.max = 200; + s4.place.x_low = 0.2; s4.place.x_high = 1.0; + s4.place.y_low = 0.0; s4.place.y_high = 1.0; + + s2.marker_type = Series.MarkerType.PRICLE_CIRCLE; + s3.marker_type = Series.MarkerType.TRIANGLE; + s4.marker_type = Series.MarkerType.PRICLE_SQUARE; + + s1.axis_x.title = new Text("Series 1: Axis X."); + s1.axis_y.title = new Text("Series 1: Axis Y."); + s2.axis_x.title = new Text("Series 2: Axis X."); + s2.axis_y.title = new Text("Series 2: Axis Y."); + s3.axis_x.title = new Text("Series 3: Axis X."); + s3.axis_y.title = new Text("Series 3: Axis Y."); + s4.axis_x.title = new Text("Series 4: Axis X."); + s4.axis_y.title = new Text("Series 4: Axis Y."); + + chart.series = { s1, s2, s3, s4 }; +} int main (string[] args) { - init (ref args); + init (ref args); - var window = new Window (); - window.title = "Gtk.Chart Test."; - window.border_width = 10; - window.window_position = WindowPosition.CENTER; - window.set_default_size (640, 480); - window.destroy.connect (main_quit); + var window = new Window (); + window.title = "Chart Test."; + window.border_width = 5; + window.window_position = WindowPosition.CENTER; + window.set_default_size (640, 480); + window.destroy.connect (main_quit); - var da = new DrawingArea(); - var chart = new Gtk.Chart(); - var label = new Label ("Gtk.Chart Test!"); - var button = new Button.with_label("Click me"); - button.clicked.connect (() => { - da.draw.connect((context) => { - chart.draw(context); - return true; - }); + var chart1 = new Chart(); + var chart2 = new Chart(); + var chart3 = new Chart(); + var chart4 = new Chart(); + var label = new Label ("Chart Test!"); + var button1 = new Button.with_label("Separate axes"); + var button2 = new Button.with_label("Common X axes"); + var button3 = new Button.with_label("Common Y axes"); + var button4 = new Button.with_label("Dates/Times"); + var button5 = new Button.with_label("rm Axis Titles"); + var button6 = new Button.with_label("rm Dates"); + var button7 = new Button.with_label("rm Times"); - da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + plot_chart1 (chart1); + plot_chart2 (chart2); + plot_chart3 (chart3); + plot_chart4 (chart4); + + var da = new DrawingArea(); + da.set_events ( Gdk.EventMask.BUTTON_PRESS_MASK + |Gdk.EventMask.BUTTON_RELEASE_MASK + |Gdk.EventMask.POINTER_MOTION_MASK + ); + + var chart = chart1; + + var radio_button1 = new RadioButton.with_label (null, "Top Legend"); + var radio_button2 = new RadioButton.with_label (radio_button1.get_group(), "Right Legend"); + var radio_button3 = new RadioButton.with_label_from_widget (radio_button1, "Left Legend"); + var radio_button4 = new RadioButton.with_label_from_widget (radio_button1, "Bottom Legend"); + + button1.clicked.connect (() => { + chart = chart1; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + switch (chart.legend.position) { + case Chart.Legend.Position.TOP: radio_button1.set_active(true); break; + case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break; + case Chart.Legend.Position.LEFT: radio_button3.set_active(true); break; + case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break; + default: break; + } + }); + button2.clicked.connect (() => { + chart = chart2; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + switch (chart.legend.position) { + case Chart.Legend.Position.TOP: radio_button1.set_active(true); break; + case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break; + case Chart.Legend.Position.LEFT: radio_button3.set_active(true); break; + case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break; + default: break; + } + }); + button3.clicked.connect (() => { + chart = chart3; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + switch (chart.legend.position) { + case Chart.Legend.Position.TOP: radio_button1.set_active(true); break; + case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break; + case Chart.Legend.Position.LEFT: radio_button3.set_active(true); break; + case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break; + default: break; + } + }); + button4.clicked.connect (() => { + chart = chart4; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + switch (chart.legend.position) { + case Chart.Legend.Position.TOP: radio_button1.set_active(true); break; + case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break; + case Chart.Legend.Position.LEFT: radio_button4.set_active(true); break; + case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break; + default: break; + } + }); + button5.clicked.connect (() => { + for (var i = 0; i < chart.series.length; ++i) { + var s = chart.series[i]; + s.axis_x.title.text = ""; + s.axis_y.title.text = ""; + } + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + }); + button6.clicked.connect (() => { + for (var i = 0; i < chart.series.length; ++i) { + var s = chart.series[i]; + s.axis_x.date_format = ""; + } + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); }); - var vbox2 = new Box(Orientation.VERTICAL, 0); - vbox2.pack_end(button, false, false, 0); + button7.clicked.connect (() => { + for (var i = 0; i < chart.series.length; ++i) { + var s = chart.series[i]; + s.axis_x.time_format = ""; + } + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + }); - var hbox = new Box(Orientation.HORIZONTAL, 0); - hbox.pack_start(da, true, true, 0); - hbox.pack_end(vbox2, false, false, 0); - var vbox = new Box(Orientation.VERTICAL, 0); - vbox.pack_start(label, false, false, 0); - vbox.pack_end(hbox, true, true, 0); + radio_button1.toggled.connect ((button) => { + if (button.get_active()) { + chart.legend.position = Chart.Legend.Position.TOP; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + }); + radio_button2.toggled.connect ((button) => { + if (button.get_active()) { + chart.legend.position = Chart.Legend.Position.RIGHT; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + }); + radio_button3.toggled.connect ((button) => { + if (button.get_active()) { + chart.legend.position = Chart.Legend.Position.LEFT; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + }); + radio_button4.toggled.connect ((button) => { + if (button.get_active()) { + chart.legend.position = Chart.Legend.Position.BOTTOM; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + }); - window.add(vbox); + da.draw.connect((context) => { + // user's pre draw operations here... + chart.context = context; + var ret = chart.draw(); + // user's post draw operations here... + return ret; + }); + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); - window.show_all(); + da.button_release_event.connect((event) => { + // user's pre button_release_event operations here... + var ret = chart.button_release_event(event); + // user's post button_release_event operations here... + return ret; + }); + da.button_press_event.connect((event) => { + // user's pre button_press_event operations here... + var ret = chart.button_press_event(event); + // user's post button_press_event operations here... + return ret; + }); + da.motion_notify_event.connect((event) => { + // user's pre motion_notify_event operations here... + var ret = chart.motion_notify_event(event); + // user's post motion_notify_event operations here... + return ret; + }); - Gtk.main(); - return 0; + var vbox2 = new Box(Orientation.VERTICAL, 0); + vbox2.pack_start(button1, false, false, 0); + vbox2.pack_start(button2, false, false, 0); + vbox2.pack_start(button3, false, false, 0); + vbox2.pack_start(button4, false, false, 0); + vbox2.pack_start(button5, false, false, 0); + vbox2.pack_start(button6, false, false, 0); + vbox2.pack_start(button7, false, false, 0); + vbox2.pack_start(radio_button1, false, false, 0); + vbox2.pack_start(radio_button2, false, false, 0); + vbox2.pack_start(radio_button3, false, false, 0); + vbox2.pack_start(radio_button4, false, false, 0); + + var hbox = new Box(Orientation.HORIZONTAL, 0); + hbox.pack_start(da, true, true, 0); + hbox.pack_end(vbox2, false, false, 0); + + var vbox = new Box(Orientation.VERTICAL, 0); + vbox.pack_start(label, false, false, 0); + vbox.pack_end(hbox, true, true, 0); + + window.add(vbox); + + window.show_all(); + +Double128 d = 5.5; + Gtk.main(); + return 0; } From 8832d6e6c8dcd7771ce79f7ab9d0f6511a4b88ec Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Sat, 19 Aug 2017 00:07:04 +0300 Subject: [PATCH 03/40] Avoiding warnings by creating structs without new operator. --- src/Common.vala | 8 ++++---- src/GtkChart.vala | 12 ++++++------ src/Series.vala | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Common.vala b/src/Common.vala index 99edbdc..c882426 100644 --- a/src/Common.vala +++ b/src/Common.vala @@ -61,8 +61,8 @@ namespace Gtk.CairoChart { [Compact] public class Text { public string text = ""; - public FontStyle style = new FontStyle (); - public Color color = new Color(); + public FontStyle style = FontStyle (); + public Color color = Color(); TextExtents get_extents (Cairo.Context context) { context.select_font_face (style.family, @@ -99,8 +99,8 @@ namespace Gtk.CairoChart { } public Text (string text = "", - FontStyle style = new FontStyle(), - Color color = new Color()) { + FontStyle style = FontStyle(), + Color color = Color()) { this.text = text; this.style = style; this.color = color; diff --git a/src/GtkChart.vala b/src/GtkChart.vala index 0640b61..c7a77b9 100644 --- a/src/GtkChart.vala +++ b/src/GtkChart.vala @@ -13,7 +13,7 @@ namespace Gtk.CairoChart { public Color bg_color; public bool show_legend = true; public Text title = new Text ("Cairo Chart"); - public Color border_color = new Color(0, 0, 0, 0.3); + public Color border_color = Color(0, 0, 0, 0.3); public class Legend { public enum Position { @@ -23,13 +23,13 @@ namespace Gtk.CairoChart { BOTTOM } public Position position = Position.TOP; - public FontStyle font_style = new FontStyle(); - public Color bg_color = new Color(1, 1, 1); + public FontStyle font_style = FontStyle(); + public Color bg_color = Color(1, 1, 1); public LineStyle border_style = new LineStyle (); public double indent = 5; public Legend () { - border_style.color = new Color (0, 0, 0, 0.3); + border_style.color = Color (0, 0, 0, 0.3); } } @@ -40,7 +40,7 @@ namespace Gtk.CairoChart { protected LineStyle selection_style = new LineStyle (); public Chart () { - bg_color = new Color (1, 1, 1); + bg_color = Color (1, 1, 1); } protected Double128 cur_x_min = 0.0; @@ -108,7 +108,7 @@ namespace Gtk.CairoChart { if (context != null) { set_source_rgba (bg_color); context.paint(); - set_source_rgba (new Color (0, 0, 0, 1)); + set_source_rgba (Color (0, 0, 0, 1)); } } diff --git a/src/Series.vala b/src/Series.vala index c1299b2..db74ffd 100644 --- a/src/Series.vala +++ b/src/Series.vala @@ -82,8 +82,8 @@ namespace Gtk.CairoChart { } default = 2; } - public FontStyle font_style = new FontStyle (); - public Color color = new Color (); + public FontStyle font_style = FontStyle (); + public Color color = Color (); public LineStyle line_style = new LineStyle (); public double font_indent = 5; @@ -153,17 +153,17 @@ namespace Gtk.CairoChart { grid.line_style.color = _color; grid.line_style.color.alpha = 0.5; } - default = new Color (0.0, 0.0, 0.0, 1.0); + default = Color (0.0, 0.0, 0.0, 1.0); } public Series () { } public class LabelStyle { - FontStyle font_style = new FontStyle(); + FontStyle font_style = FontStyle(); LineStyle frame_line_style = new LineStyle(); - Color bg_color = new Color(); - Color frame_color = new Color(); + Color bg_color = Color(); + Color frame_color = Color(); } } } From 734f49eb2944b708315cf83858754e91eba9c33e Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Sat, 19 Aug 2017 00:06:26 +0300 Subject: [PATCH 04/40] Fixes #134: float128type{vapi,h} file installation. --- src/CMakeLists.txt | 9 ++++++- src/GtkChart.vala | 41 ++++++++++++++++--------------- src/Series.vala | 12 ++++----- src/cairo-chart-float128type.h | 2 ++ src/cairo-chart-float128type.vapi | 6 +++++ src/cairo-chart.deps.in | 1 + src/float128type.h | 1 - src/float128type.vapi | 2 -- test/CMakeLists.txt | 2 +- test/ChartTest.vala | 1 - 10 files changed, 45 insertions(+), 32 deletions(-) create mode 100644 src/cairo-chart-float128type.h create mode 100644 src/cairo-chart-float128type.vapi create mode 100644 src/cairo-chart.deps.in delete mode 100644 src/float128type.h delete mode 100644 src/float128type.vapi diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 19f5d33..18cf02f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,5 +7,12 @@ SET (LibExtraSources ${CMAKE_CURRENT_BINARY_DIR}/library_constructor.c) SET (LC_RELATIVE_PREFIX "..") CONFIGURE_FILE ( "${CMAKE_SOURCE_DIR}/cmake/backbone/templates/library_constructor.c.in" "${LibExtraSources}") INCLUDE_DIRECTORIES ("${CMAKE_SOURCE_DIR}/src") -SET (LibCustomVapis ${CMAKE_SOURCE_DIR}/src/float128type.vapi) +SET (LibCustomVapis ${CMAKE_SOURCE_DIR}/src/cairo-chart-float128type.vapi) INCLUDE (ValaLibCommonRules) +INSTALL (FILES ${CMAKE_CURRENT_SOURCE_DIR}/cairo-chart-float128type.h DESTINATION include + RENAME cairo-chart-float128type-${MAJOR}.h) +INSTALL (FILES ${CMAKE_CURRENT_SOURCE_DIR}/cairo-chart-float128type.vapi DESTINATION share/vala/vapi + RENAME cairo-chart-float128type-${MAJOR}.vapi) +CONFIGURE_FILE (${CMAKE_CURRENT_SOURCE_DIR}/cairo-chart.deps.in + ${CMAKE_CURRENT_BINARY_DIR}/cairo-chart-${MAJOR}.deps) +INSTALL (FILES ${CMAKE_CURRENT_BINARY_DIR}/cairo-chart-${MAJOR}.deps DESTINATION share/vala/vapi) diff --git a/src/GtkChart.vala b/src/GtkChart.vala index c7a77b9..8b00d7b 100644 --- a/src/GtkChart.vala +++ b/src/GtkChart.vala @@ -43,8 +43,8 @@ namespace Gtk.CairoChart { bg_color = Color (1, 1, 1); } - protected Double128 cur_x_min = 0.0; - protected Double128 cur_x_max = 0.0; + protected Float128 cur_x_min = 0.0; + protected Float128 cur_x_max = 0.0; protected double cur_y_min = 0.0; protected double cur_y_max = 0.0; @@ -402,10 +402,10 @@ namespace Gtk.CairoChart { protected virtual void calc_axis_rec_sizes (Series.Axis axis, out double max_rec_width, out double max_rec_height, bool is_horizontal = true) { max_rec_width = max_rec_height = 0; for (var i = 0; i < axis_rec_npoints; ++i) { - Double128 x = axis.min + (axis.max - axis.min) / axis_rec_npoints * i; + Float128 x = axis.min + (axis.max - axis.min) / axis_rec_npoints * i; switch (axis.type) { case Series.Axis.Type.NUMBERS: - var text = new Text (axis.format.printf(x) + (is_horizontal ? "_" : "")); + var text = new Text (axis.format.printf((LongDouble)x) + (is_horizontal ? "_" : "")); text.style = axis.font_style; max_rec_width = double.max (max_rec_width, text.get_width(context)); max_rec_height = double.max (max_rec_height, text.get_height(context)); @@ -435,8 +435,8 @@ namespace Gtk.CairoChart { } } - protected virtual Double128 calc_round_step (Double128 aver_step, bool date_time = false) { - Double128 step = 1.0; + protected virtual Float128 calc_round_step (Float128 aver_step, bool date_time = false) { + Float128 step = 1.0; if (aver_step > 1.0) { if (date_time) while (step < aver_step) step *= 60; @@ -463,7 +463,7 @@ namespace Gtk.CairoChart { bool common_x_axes = false; bool common_y_axes = false; - bool are_intersect (Double128 a_min, Double128 a_max, Double128 b_min, Double128 b_max) { + bool are_intersect (Float128 a_min, Float128 a_max, Float128 b_min, Float128 b_max) { if ( a_min < a_max <= b_min < b_max || b_min < b_max <= a_min < a_max) return false; @@ -594,8 +594,8 @@ namespace Gtk.CairoChart { } } - bool point_belong (Double128 p, Double128 a, Double128 b) { - if (a > b) { Double128 tmp = a; a = b; b = tmp; } + bool point_belong (Float128 p, Float128 a, Float128 b) { + if (a > b) { Float128 tmp = a; a = b; b = tmp; } if (a <= p <= b) return true; return false; } @@ -612,12 +612,12 @@ namespace Gtk.CairoChart { long max_nrecs = (long) ((plot_area_x_max - plot_area_x_min) * (s.place.x_high - s.place.x_low) / max_rec_width); // 3. Calculate grid step. - Double128 step = calc_round_step ((s.axis_x.max - s.axis_x.min) / max_nrecs, s.axis_x.type == Series.Axis.Type.DATE_TIME); + Float128 step = calc_round_step ((s.axis_x.max - s.axis_x.min) / max_nrecs, s.axis_x.type == Series.Axis.Type.DATE_TIME); if (step > s.axis_x.max - s.axis_x.min) step = s.axis_x.max - s.axis_x.min; // 4. Calculate x_min (s.axis_x.min / step, round, multiply on step, add step if < s.axis_x.min). - Double128 x_min = 0.0; + Float128 x_min = 0.0; if (step >= 1) { int64 x_min_nsteps = (int64) (s.axis_x.min / step); x_min = x_min_nsteps * step; @@ -652,18 +652,19 @@ namespace Gtk.CairoChart { } // 5. Draw records, update cur_{x,y}_{min,max}. - for (Double128 x = x_min, x_max = s.axis_x.max; point_belong (x, x_min, x_max); x += step) { + for (Float128 x = x_min, x_max = s.axis_x.max; point_belong (x, x_min, x_max); x += step) { if (common_x_axes) set_source_rgba(Color(0,0,0,1)); else set_source_rgba(s.axis_x.color); string text = "", time_text = ""; switch (s.axis_x.type) { case Series.Axis.Type.NUMBERS: - text = s.axis_x.format.printf(x); + text = s.axis_x.format.printf((LongDouble)x); break; case Series.Axis.Type.DATE_TIME: var dt = new DateTime.from_unix_utc((int64)x); text = dt.format(s.axis_x.date_format); - var dsec_str = ("%."+(s.axis_x.dsec_signs.to_string())+"Lf").printf((x - (int64)x)).offset(1); + var dsec_str = + ("%."+(s.axis_x.dsec_signs.to_string())+"Lf").printf((LongDouble)(x - (int64)x)).offset(1); time_text = dt.format(s.axis_x.time_format) + dsec_str; break; default: @@ -799,12 +800,12 @@ namespace Gtk.CairoChart { long max_nrecs = (long) ((plot_area_y_max - plot_area_y_min) * (s.place.y_high - s.place.y_low) / max_rec_height); // 3. Calculate grid step. - Double128 step = calc_round_step ((s.axis_y.max - s.axis_y.min) / max_nrecs); + Float128 step = calc_round_step ((s.axis_y.max - s.axis_y.min) / max_nrecs); if (step > s.axis_y.max - s.axis_y.min) step = s.axis_y.max - s.axis_y.min; // 4. Calculate y_min (s.axis_y.min / step, round, multiply on step, add step if < s.axis_y.min). - Double128 y_min = 0.0; + Float128 y_min = 0.0; if (step >= 1) { int64 y_min_nsteps = (int64) (s.axis_y.min / step); y_min = y_min_nsteps * step; @@ -839,10 +840,10 @@ namespace Gtk.CairoChart { } // 5. Draw records, update cur_{x,y}_{min,max}. - for (Double128 y = y_min, y_max = s.axis_y.max; point_belong (y, y_min, y_max); y += step) { + for (Float128 y = y_min, y_max = s.axis_y.max; point_belong (y, y_min, y_max); y += step) { if (common_y_axes) set_source_rgba(Color(0,0,0,1)); else set_source_rgba(s.axis_y.color); - var text = s.axis_y.format.printf(y); + var text = s.axis_y.format.printf((LongDouble)y); var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + (s.place.y_high - s.place.y_low) / (s.axis_y.max - s.axis_y.min) * (y - s.axis_y.min)); var text_t = new Text(text, s.axis_y.font_style, s.axis_y.color); @@ -936,12 +937,12 @@ namespace Gtk.CairoChart { context.stroke (); } - protected virtual double get_scr_x (Series s, Double128 x) { + protected virtual double get_scr_x (Series s, Float128 x) { return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + (x - s.axis_x.min) / (s.axis_x.max - s.axis_x.min) * (s.place.x_high - s.place.x_low)); } - protected virtual double get_scr_y (Series s, Double128 y) { + protected virtual double get_scr_y (Series s, Float128 y) { return plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + (y - s.axis_y.min) / (s.axis_y.max - s.axis_y.min) * (s.place.y_high - s.place.y_low)); } diff --git a/src/Series.vala b/src/Series.vala index db74ffd..47d5b69 100644 --- a/src/Series.vala +++ b/src/Series.vala @@ -5,10 +5,10 @@ namespace Gtk.CairoChart { public class Series { public struct Point { - Double128 x; - Double128 y; + Float128 x; + Float128 y; - public Point (Double128 x, Double128 y) { + public Point (Float128 x, Float128 y) { this.x = x; this.y = y; } } @@ -25,8 +25,8 @@ namespace Gtk.CairoChart { // then draw separate axis for each/all series // or specify series name near the axis public class Axis { - public Double128 min = 0; - public Double128 max = 1; + public Float128 min = 0; + public Float128 max = 1; public Text title = new Text (""); public enum Type { NUMBERS = 0, @@ -137,7 +137,7 @@ namespace Gtk.CairoChart { public Grid grid = new Grid (); - public GLib.List cursors = new List (); + public GLib.List cursors = new List (); public LineStyle line_style = new LineStyle (); protected Color _color = Color (0.0, 0.0, 0.0, 1.0); diff --git a/src/cairo-chart-float128type.h b/src/cairo-chart-float128type.h new file mode 100644 index 0000000..9e68163 --- /dev/null +++ b/src/cairo-chart-float128type.h @@ -0,0 +1,2 @@ +typedef __float128 cairo_chart_float128; +typedef long double cairo_chart_long_double; diff --git a/src/cairo-chart-float128type.vapi b/src/cairo-chart-float128type.vapi new file mode 100644 index 0000000..04aaa8c --- /dev/null +++ b/src/cairo-chart-float128type.vapi @@ -0,0 +1,6 @@ +namespace Gtk.CairoChart { + [CCode (cname = "cairo_chart_float128", has_type_id = false, cheader_filename = "cairo-chart-float128type.h")] + public struct Float128 : double {} + [CCode (cname = "cairo_chart_long_double", has_type_id = false, cheader_filename = "cairo-chart-float128type.h")] + public struct LongDouble : double {} +} diff --git a/src/cairo-chart.deps.in b/src/cairo-chart.deps.in new file mode 100644 index 0000000..c6ec5d0 --- /dev/null +++ b/src/cairo-chart.deps.in @@ -0,0 +1 @@ +cairo-chart-float128type-@MAJOR@ diff --git a/src/float128type.h b/src/float128type.h deleted file mode 100644 index b230774..0000000 --- a/src/float128type.h +++ /dev/null @@ -1 +0,0 @@ -typedef long double double128; diff --git a/src/float128type.vapi b/src/float128type.vapi deleted file mode 100644 index 8b19687..0000000 --- a/src/float128type.vapi +++ /dev/null @@ -1,2 +0,0 @@ -[CCode (cname = "double128", has_type_id = false, cheader_filename = "float128type.h")] -public struct Double128 : double {} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2067fb2..417fe08 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,7 +1,7 @@ SET (BinName chart_test) FILE (GLOB_RECURSE BinSources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ChartTest.vala) SET (BinPackages gtk+-3.0) -SET (BinCustomVapis ${CMAKE_BINARY_DIR}/src/${PROJECT_LOWERCASE_NAME}-${MAJOR}.vapi ${CMAKE_SOURCE_DIR}/src/float128type.vapi) +SET (BinCustomVapis ${CMAKE_BINARY_DIR}/src/${PROJECT_LOWERCASE_NAME}-${MAJOR}.vapi ${CMAKE_SOURCE_DIR}/src/cairo-chart-float128type.vapi) SET (BinLinkLibs ${PROJECT_LOWERCASE_NAME}) INCLUDE_DIRECTORIES ("${CMAKE_BINARY_DIR}/src;${CMAKE_SOURCE_DIR}/src") INCLUDE (ValaBinCommonRules) diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 4bd3d70..7ed7019 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -379,7 +379,6 @@ int main (string[] args) { window.show_all(); -Double128 d = 5.5; Gtk.main(); return 0; } From 5597e2e752aa4e5971d1e9ec887d3aba526b66f8 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Wed, 30 Aug 2017 16:07:46 +0300 Subject: [PATCH 05/40] Float types fxd. --- src/GtkChart.vala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GtkChart.vala b/src/GtkChart.vala index 8b00d7b..cb3fc4e 100644 --- a/src/GtkChart.vala +++ b/src/GtkChart.vala @@ -43,8 +43,8 @@ namespace Gtk.CairoChart { bg_color = Color (1, 1, 1); } - protected Float128 cur_x_min = 0.0; - protected Float128 cur_x_max = 0.0; + protected double cur_x_min = 0.0; + protected double cur_x_max = 0.0; protected double cur_y_min = 0.0; protected double cur_y_max = 0.0; @@ -463,7 +463,7 @@ namespace Gtk.CairoChart { bool common_x_axes = false; bool common_y_axes = false; - bool are_intersect (Float128 a_min, Float128 a_max, Float128 b_min, Float128 b_max) { + bool are_intersect (double a_min, double a_max, double b_min, double b_max) { if ( a_min < a_max <= b_min < b_max || b_min < b_max <= a_min < a_max) return false; From cf9ec1dc04c2c591e2e63b58b49fbf72c73be375 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Tue, 22 Aug 2017 12:07:32 +0300 Subject: [PATCH 06/40] In progress: scrolling added. --- src/GtkChart.vala | 6 ++++++ test/ChartTest.vala | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/GtkChart.vala b/src/GtkChart.vala index cb3fc4e..4d70531 100644 --- a/src/GtkChart.vala +++ b/src/GtkChart.vala @@ -130,6 +130,12 @@ namespace Gtk.CairoChart { return true; } + // TODO: + public virtual bool scroll_notify_event (Gdk.EventScroll event) { + //stdout.puts ("scroll_notify_event\n"); + return true; + } + protected double title_width = 0.0; protected double title_height = 0.0; diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 7ed7019..f8981d7 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -353,6 +353,18 @@ int main (string[] args) { // user's post motion_notify_event operations here... return ret; }); + da.add_events(Gdk.EventMask.SCROLL_MASK); + da.scroll_event.connect((event) => { + // user's pre scroll_notify_event operations here... + stdout.puts("pre_scroll\n"); + + var ret = chart.scroll_notify_event(event); + + // user's post scroll_notify_event operations here... + stdout.puts("post_scroll\n"); + + return ret; + }); var vbox2 = new Box(Orientation.VERTICAL, 0); vbox2.pack_start(button1, false, false, 0); From accddc2b790f7f0c975e7d82d14483694fd4a81f Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Mon, 28 Aug 2017 14:47:31 +0300 Subject: [PATCH 07/40] Split source files on classes. --- src/Axis.vala | 70 ++++++++++++++++++ src/{GtkChart.vala => Chart.vala} | 117 +++++++++--------------------- src/Color.vala | 12 +++ src/Common.vala | 115 ----------------------------- src/FontStyle.vala | 25 +++++++ src/Grid.vala | 15 ++++ src/LabelStyle.vala | 8 ++ src/Legend.vala | 19 +++++ src/LineStyle.vala | 23 ++++++ src/Place.vala | 15 ++++ src/Point.vala | 10 +++ src/Series.vala | 112 ---------------------------- src/Text.vala | 56 ++++++++++++++ test/ChartTest.vala | 98 ++++++++++++------------- 14 files changed, 338 insertions(+), 357 deletions(-) create mode 100644 src/Axis.vala rename src/{GtkChart.vala => Chart.vala} (91%) create mode 100644 src/Color.vala delete mode 100644 src/Common.vala create mode 100644 src/FontStyle.vala create mode 100644 src/Grid.vala create mode 100644 src/LabelStyle.vala create mode 100644 src/Legend.vala create mode 100644 src/LineStyle.vala create mode 100644 src/Place.vala create mode 100644 src/Point.vala create mode 100644 src/Text.vala diff --git a/src/Axis.vala b/src/Axis.vala new file mode 100644 index 0000000..c533f97 --- /dev/null +++ b/src/Axis.vala @@ -0,0 +1,70 @@ +namespace Gtk.CairoChart { + // If one of axis:title or axis:min/max are different + // then draw separate axis for each/all series + // or specify series name near the axis + public class Axis { + public Float128 min = 0; + public Float128 max = 1; + public Text title = new Text (""); + public enum Type { + NUMBERS = 0, + DATE_TIME + } + public enum ScaleType { + LINEAR = 0, // default + // LOGARITHMIC, // TODO + // etc + } + public Type type; + public ScaleType scale_type; + public enum Position { + LOW = 0, + HIGH = 1, + BOTH = 2 + } + public Position position = Position.LOW; + + string _format = "%.2Lf"; + string _date_format = "%Y.%m.%d"; + string _time_format = "%H:%M:%S"; + int _dsec_signs = 2; // 2 signs = centiseconds + public string format { + get { return _format; } + set { + // TODO: check format + _format = value; + } + default = "%.2Lf"; + } + public string date_format { + get { return _date_format; } + set { + // TODO: check format + _date_format = value; + } + default = "%Y.%m.%d"; + } + public string time_format { + get { return _time_format; } + set { + // TODO: check format + _time_format = value; + } + default = "%H:%M:%S"; + } + public int dsec_signs { + get { return _dsec_signs; } + set { + // TODO: check format + _dsec_signs = value; + } + default = 2; + } + public FontStyle font_style = FontStyle (); + public Color color = Color (); + public LineStyle line_style = new LineStyle (); + public double font_indent = 5; + + public Axis () {} + } +} diff --git a/src/GtkChart.vala b/src/Chart.vala similarity index 91% rename from src/GtkChart.vala rename to src/Chart.vala index 4d70531..af9dab2 100644 --- a/src/GtkChart.vala +++ b/src/Chart.vala @@ -1,8 +1,4 @@ -// даты/время: сетка для малых интервалов (< нескольких секунд) -using Cairo; - namespace Gtk.CairoChart { - public class Chart { protected double width = 0; @@ -15,23 +11,6 @@ namespace Gtk.CairoChart { public Text title = new Text ("Cairo Chart"); public Color border_color = Color(0, 0, 0, 0.3); - public class Legend { - public enum Position { - TOP = 0, // default - LEFT, - RIGHT, - BOTTOM - } - public Position position = Position.TOP; - public FontStyle font_style = FontStyle(); - public Color bg_color = Color(1, 1, 1); - public LineStyle border_style = new LineStyle (); - public double indent = 5; - - public Legend () { - border_style.color = Color (0, 0, 0, 0.3); - } - } public Legend legend = new Legend (); @@ -112,30 +91,6 @@ namespace Gtk.CairoChart { } } - // TODO: - public virtual bool button_release_event (Gdk.EventButton event) { - //stdout.puts ("button_release_event\n"); - return true; - } - - // TODO: - public virtual bool button_press_event (Gdk.EventButton event) { - //stdout.puts ("button_press_event\n"); - return true; - } - - // TODO: - public virtual bool motion_notify_event (Gdk.EventMotion event) { - //stdout.puts ("motion_notify_event\n"); - return true; - } - - // TODO: - public virtual bool scroll_notify_event (Gdk.EventScroll event) { - //stdout.puts ("scroll_notify_event\n"); - return true; - } - protected double title_width = 0.0; protected double title_height = 0.0; @@ -405,18 +360,18 @@ namespace Gtk.CairoChart { int axis_rec_npoints = 128; - protected virtual void calc_axis_rec_sizes (Series.Axis axis, out double max_rec_width, out double max_rec_height, bool is_horizontal = true) { + protected virtual void calc_axis_rec_sizes (Axis axis, out double max_rec_width, out double max_rec_height, bool is_horizontal = true) { max_rec_width = max_rec_height = 0; for (var i = 0; i < axis_rec_npoints; ++i) { Float128 x = axis.min + (axis.max - axis.min) / axis_rec_npoints * i; switch (axis.type) { - case Series.Axis.Type.NUMBERS: + case Axis.Type.NUMBERS: var text = new Text (axis.format.printf((LongDouble)x) + (is_horizontal ? "_" : "")); text.style = axis.font_style; max_rec_width = double.max (max_rec_width, text.get_width(context)); max_rec_height = double.max (max_rec_height, text.get_height(context)); break; - case Series.Axis.Type.DATE_TIME: + case Axis.Type.DATE_TIME: var dt = new DateTime.from_unix_utc((int64)x); var text = new Text(""); var h = 0.0; @@ -547,9 +502,9 @@ namespace Gtk.CairoChart { if (!common_x_axes || si == 0) switch (s.axis_x.position) { - case Series.Axis.Position.LOW: plot_area_y_max -= max_rec_height + max_font_indent + max_axis_font_height; break; - case Series.Axis.Position.HIGH: plot_area_y_min += max_rec_height + max_font_indent + max_axis_font_height; break; - case Series.Axis.Position.BOTH: break; + case Axis.Position.LOW: plot_area_y_max -= max_rec_height + max_font_indent + max_axis_font_height; break; + case Axis.Position.HIGH: plot_area_y_min += max_rec_height + max_font_indent + max_axis_font_height; break; + case Axis.Position.BOTH: break; default: break; } } @@ -592,9 +547,9 @@ namespace Gtk.CairoChart { if (!common_y_axes || si == 0) switch (s.axis_y.position) { - case Series.Axis.Position.LOW: plot_area_x_min += max_rec_width + max_font_indent + max_axis_font_width; break; - case Series.Axis.Position.HIGH: plot_area_x_max -= max_rec_width + max_font_indent + max_axis_font_width; break; - case Series.Axis.Position.BOTH: break; + case Axis.Position.LOW: plot_area_x_min += max_rec_width + max_font_indent + max_axis_font_width; break; + case Axis.Position.HIGH: plot_area_x_max -= max_rec_width + max_font_indent + max_axis_font_width; break; + case Axis.Position.BOTH: break; default: break; } } @@ -618,7 +573,7 @@ namespace Gtk.CairoChart { long max_nrecs = (long) ((plot_area_x_max - plot_area_x_min) * (s.place.x_high - s.place.x_low) / max_rec_width); // 3. Calculate grid step. - Float128 step = calc_round_step ((s.axis_x.max - s.axis_x.min) / max_nrecs, s.axis_x.type == Series.Axis.Type.DATE_TIME); + Float128 step = calc_round_step ((s.axis_x.max - s.axis_x.min) / max_nrecs, s.axis_x.type == Axis.Type.DATE_TIME); if (step > s.axis_x.max - s.axis_x.min) step = s.axis_x.max - s.axis_x.min; @@ -637,7 +592,7 @@ namespace Gtk.CairoChart { // 4.5. Draw Axis title if (s.axis_x.title.text != "") switch (s.axis_x.position) { - case Series.Axis.Position.LOW: + case Axis.Position.LOW: var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + s.place.x_high) / 2.0; var scr_y = cur_y_max - s.axis_x.font_indent; context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y); @@ -645,7 +600,7 @@ namespace Gtk.CairoChart { if (common_x_axes) set_source_rgba(Color(0,0,0,1)); show_text(s.axis_x.title); break; - case Series.Axis.Position.HIGH: + case Axis.Position.HIGH: var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + s.place.x_high) / 2.0; var scr_y = cur_y_min + s.axis_x.font_indent + s.axis_x.title.get_height(context); context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y); @@ -653,7 +608,7 @@ namespace Gtk.CairoChart { if (common_x_axes) set_source_rgba(Color(0,0,0,1)); show_text(s.axis_x.title); break; - case Series.Axis.Position.BOTH: + case Axis.Position.BOTH: break; } @@ -663,10 +618,10 @@ namespace Gtk.CairoChart { else set_source_rgba(s.axis_x.color); string text = "", time_text = ""; switch (s.axis_x.type) { - case Series.Axis.Type.NUMBERS: + case Axis.Type.NUMBERS: text = s.axis_x.format.printf((LongDouble)x); break; - case Series.Axis.Type.DATE_TIME: + case Axis.Type.DATE_TIME: var dt = new DateTime.from_unix_utc((int64)x); text = dt.format(s.axis_x.date_format); var dsec_str = @@ -680,16 +635,16 @@ namespace Gtk.CairoChart { * (s.place.x_low + (s.place.x_high - s.place.x_low) / (s.axis_x.max - s.axis_x.min) * (x - s.axis_x.min)); var text_t = new Text(text, s.axis_x.font_style, s.axis_x.color); switch (s.axis_x.position) { - case Series.Axis.Position.LOW: + case Axis.Position.LOW: var print_y = cur_y_max - s.axis_x.font_indent - (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); switch (s.axis_x.type) { - case Series.Axis.Type.NUMBERS: + case Axis.Type.NUMBERS: var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); context.move_to (print_x, print_y); show_text(text_t); break; - case Series.Axis.Type.DATE_TIME: + case Axis.Type.DATE_TIME: var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); context.move_to (print_x, print_y); @@ -714,16 +669,16 @@ namespace Gtk.CairoChart { else context.line_to (scr_x, double.min (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.y_high)); break; - case Series.Axis.Position.HIGH: + case Axis.Position.HIGH: var print_y = cur_y_min + max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); switch (s.axis_x.type) { - case Series.Axis.Type.NUMBERS: + case Axis.Type.NUMBERS: var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); context.move_to (print_x, print_y); show_text(text_t); break; - case Series.Axis.Type.DATE_TIME: + case Axis.Type.DATE_TIME: var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); context.move_to (print_x, print_y); @@ -748,7 +703,7 @@ namespace Gtk.CairoChart { else context.line_to (scr_x, double.max (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.y_low)); break; - case Series.Axis.Position.BOTH: + case Axis.Position.BOTH: break; default: break; @@ -779,15 +734,15 @@ namespace Gtk.CairoChart { if (nskip != 0) {--nskip; continue;} switch (s.axis_x.position) { - case Series.Axis.Position.LOW: + case Axis.Position.LOW: cur_y_max -= max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); break; - case Series.Axis.Position.HIGH: + case Axis.Position.HIGH: cur_y_min += max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); break; - case Series.Axis.Position.BOTH: + case Axis.Position.BOTH: break; default: break; } @@ -825,7 +780,7 @@ namespace Gtk.CairoChart { // 4.5. Draw Axis title if (s.axis_y.title.text != "") switch (s.axis_y.position) { - case Series.Axis.Position.LOW: + case Axis.Position.LOW: var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + s.place.y_high) / 2.0; var scr_x = cur_x_min + s.axis_y.font_indent + s.axis_y.title.get_width(context); context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0); @@ -833,7 +788,7 @@ namespace Gtk.CairoChart { if (common_y_axes) set_source_rgba(Color(0,0,0,1)); show_text(s.axis_y.title); break; - case Series.Axis.Position.HIGH: + case Axis.Position.HIGH: var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + s.place.y_high) / 2.0; var scr_x = cur_x_max - s.axis_y.font_indent; context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0); @@ -841,7 +796,7 @@ namespace Gtk.CairoChart { if (common_y_axes) set_source_rgba(Color(0,0,0,1)); show_text(s.axis_y.title); break; - case Series.Axis.Position.BOTH: + case Axis.Position.BOTH: break; } @@ -854,7 +809,7 @@ namespace Gtk.CairoChart { * (s.place.y_low + (s.place.y_high - s.place.y_low) / (s.axis_y.max - s.axis_y.min) * (y - s.axis_y.min)); var text_t = new Text(text, s.axis_y.font_style, s.axis_y.color); switch (s.axis_y.position) { - case Series.Axis.Position.LOW: + case Axis.Position.LOW: context.move_to (cur_x_min + max_rec_width - (new Text(text)).get_width(context) + s.axis_y.font_indent - text_t.get_x_bearing(context) + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent), scr_y + (new Text(text)).get_height(context) / 2.0 @@ -871,7 +826,7 @@ namespace Gtk.CairoChart { else context.line_to (double.max (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.x_high), scr_y); break; - case Series.Axis.Position.HIGH: + case Axis.Position.HIGH: context.move_to (cur_x_max - (new Text(text)).get_width(context) - s.axis_y.font_indent - text_t.get_x_bearing(context) - (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent), scr_y + (new Text(text)).get_height(context) / 2.0 @@ -888,7 +843,7 @@ namespace Gtk.CairoChart { else context.line_to (double.min (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.x_low), scr_y); break; - case Series.Axis.Position.BOTH: + case Axis.Position.BOTH: break; default: break; @@ -919,13 +874,13 @@ namespace Gtk.CairoChart { switch (s.axis_y.position) { - case Series.Axis.Position.LOW: + case Axis.Position.LOW: cur_x_min += max_rec_width + s.axis_y.font_indent + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent); break; - case Series.Axis.Position.HIGH: + case Axis.Position.HIGH: cur_x_max -= max_rec_width + s.axis_y.font_indent + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent); break; - case Series.Axis.Position.BOTH: + case Axis.Position.BOTH: break; default: break; } @@ -953,8 +908,8 @@ namespace Gtk.CairoChart { / (s.axis_y.max - s.axis_y.min) * (s.place.y_high - s.place.y_low)); } - delegate int PointComparator(Series.Point a, Series.Point b); - void sort_points(Series.Point[] points, PointComparator compare) { + delegate int PointComparator(Point a, Point b); + void sort_points(Point[] points, PointComparator compare) { for(var i = 0; i < points.length; ++i) { for(var j = i + 1; j < points.length; ++j) { if(compare(points[i], points[j]) > 0) { diff --git a/src/Color.vala b/src/Color.vala new file mode 100644 index 0000000..6cbea9a --- /dev/null +++ b/src/Color.vala @@ -0,0 +1,12 @@ +namespace Gtk.CairoChart { + public struct Color { + double red; + double green; + double blue; + double alpha; + + public Color (double red = 0.0, double green = 0.0, double blue = 0.0, double alpha = 1.0) { + this.red = red; this.green = green; this.blue = blue; this.alpha = alpha; + } + } +} diff --git a/src/Common.vala b/src/Common.vala deleted file mode 100644 index c882426..0000000 --- a/src/Common.vala +++ /dev/null @@ -1,115 +0,0 @@ -using Cairo; - -namespace Gtk.CairoChart { - - public struct Color { - double red; - double green; - double blue; - double alpha; - - public Color (double red = 0.0, double green = 0.0, double blue = 0.0, double alpha = 1.0) { - this.red = red; this.green = green; this.blue = blue; this.alpha = alpha; - } - } - - public enum FontOrient { - HORIZONTAL = 0, - VERTICAL - } - public struct FontStyle { - string family; - FontSlant slant; - FontWeight weight; - - FontOrient orientation; - double size; - - public FontStyle (string family = "Sans", - FontSlant slant = Cairo.FontSlant.NORMAL, - FontWeight weight = Cairo.FontWeight.NORMAL, - double size = 10) { - this.family = family; - this.slant = slant; - this.weight = weight; - this.size = size; - } - } - - public struct LineStyle { - double width; - LineJoin line_join; - LineCap line_cap; - double[]? dashes; - double dash_offset; - Color color; - - public LineStyle (double width = 1, - LineJoin line_join = Cairo.LineJoin.MITER, - LineCap line_cap = Cairo.LineCap.ROUND, - double[]? dashes = null, double dash_offset = 0, - Color color = Color()) { - this.width = width; - this.line_join = line_join; - this.line_cap = line_cap; - this.dashes = dashes; - this.dash_offset = dash_offset; - this.color = color; - } - } - - [Compact] - public class Text { - public string text = ""; - public FontStyle style = FontStyle (); - public Color color = Color(); - - TextExtents get_extents (Cairo.Context context) { - context.select_font_face (style.family, - style.slant, - style.weight); - context.set_font_size (style.size); - TextExtents extents; - context.text_extents (text, out extents); - return extents; - } - - public double get_width (Cairo.Context context) { - var extents = get_extents (context); - if (style.orientation == FontOrient.HORIZONTAL) - return extents.width; - else - return extents.height; - } - - public double get_height (Cairo.Context context) { - var extents = get_extents (context); - if (style.orientation == FontOrient.HORIZONTAL) - return extents.height; - else - return extents.width; - } - - public double get_x_bearing (Cairo.Context context) { - var extents = get_extents (context); - if (style.orientation == FontOrient.HORIZONTAL) - return extents.x_bearing; - else - return extents.y_bearing; - } - - public Text (string text = "", - FontStyle style = FontStyle(), - Color color = Color()) { - this.text = text; - this.style = style; - this.color = color; - } - - public Text.by_instance (Text text) { - this.text = text.text; - this.style = text.style; - this.color = text.color; - } - } -} diff --git a/src/FontStyle.vala b/src/FontStyle.vala new file mode 100644 index 0000000..df4b8de --- /dev/null +++ b/src/FontStyle.vala @@ -0,0 +1,25 @@ +namespace Gtk.CairoChart { + public enum FontOrient { + HORIZONTAL = 0, + VERTICAL + } + + public struct FontStyle { + string family; + Cairo.FontSlant slant; + Cairo.FontWeight weight; + + FontOrient orientation; + double size; + + public FontStyle (string family = "Sans", + Cairo.FontSlant slant = Cairo.FontSlant.NORMAL, + Cairo.FontWeight weight = Cairo.FontWeight.NORMAL, + double size = 10) { + this.family = family; + this.slant = slant; + this.weight = weight; + this.size = size; + } + } +} diff --git a/src/Grid.vala b/src/Grid.vala new file mode 100644 index 0000000..c753fcc --- /dev/null +++ b/src/Grid.vala @@ -0,0 +1,15 @@ +namespace Gtk.CairoChart { + public class Grid { + /*public enum GridType { + PRICK_LINE = 0, // default + LINE + }*/ + public Color color = Color (0, 0, 0, 0.1); + + public LineStyle line_style = new LineStyle (); + + public Grid () { + line_style.dashes = {2, 3}; + } + } +} diff --git a/src/LabelStyle.vala b/src/LabelStyle.vala new file mode 100644 index 0000000..0f6dddf --- /dev/null +++ b/src/LabelStyle.vala @@ -0,0 +1,8 @@ +namespace Gtk.CairoChart { + public class LabelStyle { + FontStyle font_style = FontStyle(); + LineStyle frame_line_style = new LineStyle(); + Color bg_color = Color(); + Color frame_color = Color(); + } +} diff --git a/src/Legend.vala b/src/Legend.vala new file mode 100644 index 0000000..c60f0bd --- /dev/null +++ b/src/Legend.vala @@ -0,0 +1,19 @@ +namespace Gtk.CairoChart { + public class Legend { + public enum Position { + TOP = 0, // default + LEFT, + RIGHT, + BOTTOM + } + public Position position = Position.TOP; + public FontStyle font_style = FontStyle(); + public Color bg_color = Color(1, 1, 1); + public LineStyle border_style = new LineStyle (); + public double indent = 5; + + public Legend () { + border_style.color = Color (0, 0, 0, 0.3); + } + } +} diff --git a/src/LineStyle.vala b/src/LineStyle.vala new file mode 100644 index 0000000..0efad62 --- /dev/null +++ b/src/LineStyle.vala @@ -0,0 +1,23 @@ +namespace Gtk.CairoChart { + public struct LineStyle { + double width; + Cairo.LineJoin line_join; + Cairo.LineCap line_cap; + double[]? dashes; + double dash_offset; + Color color; + + public LineStyle (double width = 1, + Cairo.LineJoin line_join = Cairo.LineJoin.MITER, + Cairo.LineCap line_cap = Cairo.LineCap.ROUND, + double[]? dashes = null, double dash_offset = 0, + Color color = Color()) { + this.width = width; + this.line_join = line_join; + this.line_cap = line_cap; + this.dashes = dashes; + this.dash_offset = dash_offset; + this.color = color; + } + } +} diff --git a/src/Place.vala b/src/Place.vala new file mode 100644 index 0000000..13a9eee --- /dev/null +++ b/src/Place.vala @@ -0,0 +1,15 @@ +namespace Gtk.CairoChart { + public struct Place { + double x_low; + double x_high; + double y_low; + double y_high; + + public Place (double x_low = 0, double x_high = 0, double y_low = 0, double y_high = 0) { + this.x_low = x_low; + this.x_high = x_high; + this.y_low = y_low; + this.y_high = y_high; + } + } +} diff --git a/src/Point.vala b/src/Point.vala new file mode 100644 index 0000000..8710a75 --- /dev/null +++ b/src/Point.vala @@ -0,0 +1,10 @@ +namespace Gtk.CairoChart { + public struct Point { + Float128 x; + Float128 y; + + public Point (Float128 x, Float128 y) { + this.x = x; this.y = y; + } + } +} diff --git a/src/Series.vala b/src/Series.vala index 47d5b69..033e484 100644 --- a/src/Series.vala +++ b/src/Series.vala @@ -4,15 +4,6 @@ namespace Gtk.CairoChart { public class Series { - public struct Point { - Float128 x; - Float128 y; - - public Point (Float128 x, Float128 y) { - this.x = x; this.y = y; - } - } - public Point[] points = {}; public enum Sort { BY_X = 0, @@ -21,92 +12,9 @@ namespace Gtk.CairoChart { } public Sort sort = Sort.BY_X; - // If one of axis:title or axis:min/max are different - // then draw separate axis for each/all series - // or specify series name near the axis - public class Axis { - public Float128 min = 0; - public Float128 max = 1; - public Text title = new Text (""); - public enum Type { - NUMBERS = 0, - DATE_TIME - } - public enum ScaleType { - LINEAR = 0, // default - // LOGARITHMIC, // TODO - // etc - } - public Type type; - public ScaleType scale_type; - public enum Position { - LOW = 0, - HIGH = 1, - BOTH = 2 - } - public Position position = Position.LOW; - - string _format = "%.2Lf"; - string _date_format = "%Y.%m.%d"; - string _time_format = "%H:%M:%S"; - int _dsec_signs = 2; // 2 signs = centiseconds - public string format { - get { return _format; } - set { - // TODO: check format - _format = value; - } - default = "%.2Lf"; - } - public string date_format { - get { return _date_format; } - set { - // TODO: check format - _date_format = value; - } - default = "%Y.%m.%d"; - } - public string time_format { - get { return _time_format; } - set { - // TODO: check format - _time_format = value; - } - default = "%H:%M:%S"; - } - public int dsec_signs { - get { return _dsec_signs; } - set { - // TODO: check format - _dsec_signs = value; - } - default = 2; - } - public FontStyle font_style = FontStyle (); - public Color color = Color (); - public LineStyle line_style = new LineStyle (); - public double font_indent = 5; - - public Axis () {} - } - public Axis axis_x = new Axis(); public Axis axis_y = new Axis(); - public struct Place { - double x_low; - double x_high; - double y_low; - double y_high; - - public Place (double x_low = 0, double x_high = 0, double y_low = 0, double y_high = 0) { - this.x_low = x_low; - this.x_high = x_high; - this.y_low = y_low; - this.y_high = y_high; - } - } - public enum MarkerType { NONE = 0, // default SQUARE, @@ -121,20 +29,6 @@ namespace Gtk.CairoChart { public Text title = new Text (); public MarkerType marker_type = MarkerType.SQUARE; - public class Grid { - /*public enum GridType { - PRICK_LINE = 0, // default - LINE - }*/ - public Color color = Color (0, 0, 0, 0.1); - - public LineStyle line_style = new LineStyle (); - - public Grid () { - line_style.dashes = {2, 3}; - } - } - public Grid grid = new Grid (); public GLib.List cursors = new List (); @@ -159,11 +53,5 @@ namespace Gtk.CairoChart { public Series () { } - public class LabelStyle { - FontStyle font_style = FontStyle(); - LineStyle frame_line_style = new LineStyle(); - Color bg_color = Color(); - Color frame_color = Color(); - } } } diff --git a/src/Text.vala b/src/Text.vala new file mode 100644 index 0000000..a7f6a58 --- /dev/null +++ b/src/Text.vala @@ -0,0 +1,56 @@ +namespace Gtk.CairoChart { + [Compact] + public class Text { + public string text = ""; + public FontStyle style = FontStyle (); + public Color color = Color(); + + Cairo.TextExtents get_extents (Cairo.Context context) { + context.select_font_face (style.family, + style.slant, + style.weight); + context.set_font_size (style.size); + Cairo.TextExtents extents; + context.text_extents (text, out extents); + return extents; + } + + public double get_width (Cairo.Context context) { + var extents = get_extents (context); + if (style.orientation == FontOrient.HORIZONTAL) + return extents.width; + else + return extents.height; + } + + public double get_height (Cairo.Context context) { + var extents = get_extents (context); + if (style.orientation == FontOrient.HORIZONTAL) + return extents.height; + else + return extents.width; + } + + public double get_x_bearing (Cairo.Context context) { + var extents = get_extents (context); + if (style.orientation == FontOrient.HORIZONTAL) + return extents.x_bearing; + else + return extents.y_bearing; + } + + public Text (string text = "", + FontStyle style = FontStyle(), + Color color = Color()) { + this.text = text; + this.style = style; + this.color = color; + } + + public Text.by_instance (Text text) { + this.text = text.text; + this.style = text.style; + this.color = text.color; + } + } +} diff --git a/test/ChartTest.vala b/test/ChartTest.vala index f8981d7..c40e56f 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -6,14 +6,14 @@ void plot_chart1 (Chart chart) { var s3 = new Series (); s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); - s1.points = {new Series.Point(0, 0), new Series.Point(2, 1), new Series.Point(1, 3)}; - s1.axis_x.position = Series.Axis.Position.HIGH; + s1.points = {new Point(0, 0), new Point(2, 1), new Point(1, 3)}; + s1.axis_x.position = Axis.Position.HIGH; s1.axis_x.format = "%.3Lf"; s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); - s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)}; + s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)}; s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); - s3.points = {new Series.Point(9, 17), new Series.Point(2, 10), new Series.Point(122, 31)}; - s3.axis_y.position = Series.Axis.Position.HIGH; + s3.points = {new Point(9, 17), new Point(2, 10), new Point(122, 31)}; + s3.axis_y.position = Axis.Position.HIGH; s1.axis_x.min = 0; s1.axis_x.max = 2; s1.axis_y.min = 0; s1.axis_y.max = 3; @@ -49,14 +49,14 @@ void plot_chart2 (Chart chart) { var s3 = new Series (); s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); - s1.points = {new Series.Point(-12, 0), new Series.Point(2, 1), new Series.Point(20, 3)}; - s2.axis_y.position = Series.Axis.Position.HIGH; + s1.points = {new Point(-12, 0), new Point(2, 1), new Point(20, 3)}; + s2.axis_y.position = Axis.Position.HIGH; s1.axis_x.format = "%.3Lf"; s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); - s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)}; + s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)}; s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); - s3.points = {new Series.Point(9, 17), new Series.Point(2, 10), new Series.Point(-15, 31)}; - s3.axis_y.position = Series.Axis.Position.HIGH; + s3.points = {new Point(9, 17), new Point(2, 10), new Point(-15, 31)}; + s3.axis_y.position = Axis.Position.HIGH; s1.axis_x.min = -15; s1.axis_x.max = 30; s1.axis_y.min = 0; s1.axis_y.max = 3; @@ -92,16 +92,16 @@ void plot_chart3 (Chart chart) { var s3 = new Series (); s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); - s1.points = {new Series.Point(0, 70), new Series.Point(2, 155), new Series.Point(1, -3)}; - s1.axis_x.position = Series.Axis.Position.HIGH; - s1.axis_y.position = Series.Axis.Position.HIGH; + s1.points = {new Point(0, 70), new Point(2, 155), new Point(1, -3)}; + s1.axis_x.position = Axis.Position.HIGH; + s1.axis_y.position = Axis.Position.HIGH; s1.axis_x.format = "%.3Lf"; s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); - s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)}; - s2.axis_y.position = Series.Axis.Position.HIGH; + s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)}; + s2.axis_y.position = Axis.Position.HIGH; s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); - s3.points = {new Series.Point(9, -17), new Series.Point(2, 10), new Series.Point(122, 31)}; - s3.axis_y.position = Series.Axis.Position.HIGH; + s3.points = {new Point(9, -17), new Point(2, 10), new Point(122, 31)}; + s3.axis_y.position = Axis.Position.HIGH; s1.axis_x.min = 0; s1.axis_x.max = 2; s1.axis_y.min = -20; s1.axis_y.max = 200; @@ -137,27 +137,27 @@ void plot_chart4 (Chart chart) { var s3 = new Series (); var s4 = new Series (); - s1.axis_x.type = Series.Axis.Type.DATE_TIME; - s3.axis_x.type = Series.Axis.Type.DATE_TIME; - s4.axis_x.type = Series.Axis.Type.DATE_TIME; + s1.axis_x.type = Axis.Type.DATE_TIME; + s3.axis_x.type = Axis.Type.DATE_TIME; + s4.axis_x.type = Axis.Type.DATE_TIME; s4.axis_x.dsec_signs = 5; var now = new DateTime.now_local().to_unix(); var high = (uint64) (253000000000L); s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); - s1.points = {new Series.Point(now, 70), new Series.Point(now - 100000, 155), new Series.Point(now + 100000, 30)}; - s1.axis_x.position = Series.Axis.Position.HIGH; - s1.axis_y.position = Series.Axis.Position.HIGH; + s1.points = {new Point(now, 70), new Point(now - 100000, 155), new Point(now + 100000, 30)}; + s1.axis_x.position = Axis.Position.HIGH; + s1.axis_y.position = Axis.Position.HIGH; s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); - s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)}; - s2.axis_y.position = Series.Axis.Position.HIGH; + s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)}; + s2.axis_y.position = Axis.Position.HIGH; s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); - s3.points = {new Series.Point(high - 2 + 0.73, -17), new Series.Point(high - 1 + 0.234, 10), new Series.Point(high + 1 + 0.411, 31)}; - s3.axis_y.position = Series.Axis.Position.HIGH; + s3.points = {new Point(high - 2 + 0.73, -17), new Point(high - 1 + 0.234, 10), new Point(high + 1 + 0.411, 31)}; + s3.axis_y.position = Axis.Position.HIGH; s4.title = new Text("Series 4"); s4.color = new Color (0.5, 0.3, 0.9); - s4.points = {new Series.Point(high + 0.005, -19.05), new Series.Point(high + 0.0051, 28), new Series.Point(high + 0.0052, 55), new Series.Point(high + 0.0053, 44)}; - s4.axis_y.position = Series.Axis.Position.HIGH; + s4.points = {new Point(high + 0.005, -19.05), new Point(high + 0.0051, 28), new Point(high + 0.0052, 55), new Point(high + 0.0053, 44)}; + s4.axis_y.position = Axis.Position.HIGH; s1.axis_x.min = now - 100000; s1.axis_x.max = now + 100000; s1.axis_y.min = -20; s1.axis_y.max = 200; @@ -239,40 +239,40 @@ int main (string[] args) { button1.clicked.connect (() => { chart = chart1; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); switch (chart.legend.position) { - case Chart.Legend.Position.TOP: radio_button1.set_active(true); break; - case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break; - case Chart.Legend.Position.LEFT: radio_button3.set_active(true); break; - case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break; + case Legend.Position.TOP: radio_button1.set_active(true); break; + case Legend.Position.RIGHT: radio_button2.set_active(true); break; + case Legend.Position.LEFT: radio_button3.set_active(true); break; + case Legend.Position.BOTTOM: radio_button4.set_active(true); break; default: break; } }); button2.clicked.connect (() => { chart = chart2; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); switch (chart.legend.position) { - case Chart.Legend.Position.TOP: radio_button1.set_active(true); break; - case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break; - case Chart.Legend.Position.LEFT: radio_button3.set_active(true); break; - case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break; + case Legend.Position.TOP: radio_button1.set_active(true); break; + case Legend.Position.RIGHT: radio_button2.set_active(true); break; + case Legend.Position.LEFT: radio_button3.set_active(true); break; + case Legend.Position.BOTTOM: radio_button4.set_active(true); break; default: break; } }); button3.clicked.connect (() => { chart = chart3; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); switch (chart.legend.position) { - case Chart.Legend.Position.TOP: radio_button1.set_active(true); break; - case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break; - case Chart.Legend.Position.LEFT: radio_button3.set_active(true); break; - case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break; + case Legend.Position.TOP: radio_button1.set_active(true); break; + case Legend.Position.RIGHT: radio_button2.set_active(true); break; + case Legend.Position.LEFT: radio_button3.set_active(true); break; + case Legend.Position.BOTTOM: radio_button4.set_active(true); break; default: break; } }); button4.clicked.connect (() => { chart = chart4; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); switch (chart.legend.position) { - case Chart.Legend.Position.TOP: radio_button1.set_active(true); break; - case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break; - case Chart.Legend.Position.LEFT: radio_button4.set_active(true); break; - case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break; + case Legend.Position.TOP: radio_button1.set_active(true); break; + case Legend.Position.RIGHT: radio_button2.set_active(true); break; + case Legend.Position.LEFT: radio_button4.set_active(true); break; + case Legend.Position.BOTTOM: radio_button4.set_active(true); break; default: break; } }); @@ -303,25 +303,25 @@ int main (string[] args) { radio_button1.toggled.connect ((button) => { if (button.get_active()) { - chart.legend.position = Chart.Legend.Position.TOP; + chart.legend.position = Legend.Position.TOP; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } }); radio_button2.toggled.connect ((button) => { if (button.get_active()) { - chart.legend.position = Chart.Legend.Position.RIGHT; + chart.legend.position = Legend.Position.RIGHT; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } }); radio_button3.toggled.connect ((button) => { if (button.get_active()) { - chart.legend.position = Chart.Legend.Position.LEFT; + chart.legend.position = Legend.Position.LEFT; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } }); radio_button4.toggled.connect ((button) => { if (button.get_active()) { - chart.legend.position = Chart.Legend.Position.BOTTOM; + chart.legend.position = Legend.Position.BOTTOM; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } }); From 82aa857a358d9acf690c763cd3294391bd679789 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Mon, 28 Aug 2017 18:16:48 +0300 Subject: [PATCH 08/40] Add copy() method to classes. --- src/Axis.vala | 21 ++++++++++++++++++++- src/Chart.vala | 43 ++++++++++++++++++++++++++++++++++++++++++- src/Grid.vala | 9 ++++++++- src/LabelStyle.vala | 10 +++++----- src/Legend.vala | 11 ++++++++++- src/Series.vala | 21 ++++++++++++++++++--- src/Text.vala | 10 ++++++---- 7 files changed, 109 insertions(+), 16 deletions(-) diff --git a/src/Axis.vala b/src/Axis.vala index c533f97..3356c19 100644 --- a/src/Axis.vala +++ b/src/Axis.vala @@ -62,9 +62,28 @@ namespace Gtk.CairoChart { } public FontStyle font_style = FontStyle (); public Color color = Color (); - public LineStyle line_style = new LineStyle (); + public LineStyle line_style = LineStyle (); public double font_indent = 5; + public Axis copy () { + var axis = new Axis (); + axis._date_format = this._date_format; + axis._dsec_signs = this._dsec_signs; + axis._format = this._format; + axis._time_format = this._time_format; + axis.color = this.color; + axis.font_indent = this.font_indent; + axis.font_style = this.font_style; + axis.line_style = this.line_style; + axis.max = this.max; + axis.min = this.min; + axis.position = this.position; + axis.scale_type = this.scale_type; + axis.title = this.title.copy(); + axis.type = this.type; + return axis; + } + public Axis () {} } } diff --git a/src/Chart.vala b/src/Chart.vala index af9dab2..2a81736 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -16,7 +16,7 @@ namespace Gtk.CairoChart { public Series[] series = {}; - protected LineStyle selection_style = new LineStyle (); + protected LineStyle selection_style = LineStyle (); public Chart () { bg_color = Color (1, 1, 1); @@ -957,5 +957,46 @@ namespace Gtk.CairoChart { // TODO: protected virtual void draw_cursors () { } + + public Chart copy () { + var chart = new Chart (); + chart.axis_rec_npoints = this.axis_rec_npoints; + chart.bg_color = this.bg_color; + chart.border_color = this.border_color; + chart.common_x_axes = this.common_x_axes; + chart.common_y_axes = this.common_y_axes; + chart.context = this.context; + chart.cur_x_max = this.cur_x_max; + chart.cur_x_min = this.cur_x_min; + chart.cur_y_max = this.cur_y_max; + chart.cur_y_min = this.cur_y_min; + chart.height = this.height; + chart.legend = this.legend.copy(); + chart.legend_height = this.legend_height; + chart.legend_line_length = this.legend_line_length; + chart.legend_text_hspace = this.legend_text_hspace; + chart.legend_text_vspace = this.legend_text_vspace; + chart.legend_width = this.legend_width; + chart.marker_size = this.marker_size; + chart.max_font_heights = this.max_font_heights.copy(); + chart.plot_area_x_max = this.plot_area_x_max; + chart.plot_area_x_min = this.plot_area_x_min; + chart.plot_area_y_max = this.plot_area_y_max; + chart.plot_area_y_min = this.plot_area_y_min; + chart.selection_style = this.selection_style; + chart.series = this.series.copy(); + chart.show_legend = this.show_legend; + chart.title = this.title.copy().copy(); + chart.title_height = this.title_height; + chart.title_vindent = this.title_vindent; + chart.title_width = this.title_width; + chart.width = this.width; + chart.zoom = this.zoom; + chart.zoom_x0 = this.zoom_x0; + chart.zoom_x1 = this.zoom_x1; + chart.zoom_y0 = this.zoom_y0; + chart.zoom_y1 = this.zoom_y1; + return chart; + } } } diff --git a/src/Grid.vala b/src/Grid.vala index c753fcc..d57691e 100644 --- a/src/Grid.vala +++ b/src/Grid.vala @@ -6,7 +6,14 @@ namespace Gtk.CairoChart { }*/ public Color color = Color (0, 0, 0, 0.1); - public LineStyle line_style = new LineStyle (); + public LineStyle line_style = LineStyle (); + + public Grid copy () { + var grid = new Grid (); + grid.color = this.color; + grid.line_style = this.line_style; + return grid; + } public Grid () { line_style.dashes = {2, 3}; diff --git a/src/LabelStyle.vala b/src/LabelStyle.vala index 0f6dddf..fc610ee 100644 --- a/src/LabelStyle.vala +++ b/src/LabelStyle.vala @@ -1,8 +1,8 @@ namespace Gtk.CairoChart { - public class LabelStyle { - FontStyle font_style = FontStyle(); - LineStyle frame_line_style = new LineStyle(); - Color bg_color = Color(); - Color frame_color = Color(); + public struct LabelStyle { + FontStyle font_style; + LineStyle frame_line_style; + Color bg_color; + Color frame_color; } } diff --git a/src/Legend.vala b/src/Legend.vala index c60f0bd..93e63b9 100644 --- a/src/Legend.vala +++ b/src/Legend.vala @@ -9,9 +9,18 @@ namespace Gtk.CairoChart { public Position position = Position.TOP; public FontStyle font_style = FontStyle(); public Color bg_color = Color(1, 1, 1); - public LineStyle border_style = new LineStyle (); + public LineStyle border_style = LineStyle (); public double indent = 5; + public Legend copy () { + var legend = new Legend (); + legend.position = this.position; + legend.font_style = this.font_style; + legend.bg_color = this.bg_color; + legend.indent = this.indent; + return legend; + } + public Legend () { border_style.color = Color (0, 0, 0, 0.3); } diff --git a/src/Series.vala b/src/Series.vala index 033e484..965de1a 100644 --- a/src/Series.vala +++ b/src/Series.vala @@ -25,14 +25,14 @@ namespace Gtk.CairoChart { PRICLE_TRIANGLE } - public Place place = new Place(); + public Place place = Place(); public Text title = new Text (); public MarkerType marker_type = MarkerType.SQUARE; public Grid grid = new Grid (); public GLib.List cursors = new List (); - public LineStyle line_style = new LineStyle (); + public LineStyle line_style = LineStyle (); protected Color _color = Color (0.0, 0.0, 0.0, 1.0); public Color color { @@ -50,8 +50,23 @@ namespace Gtk.CairoChart { default = Color (0.0, 0.0, 0.0, 1.0); } - public Series () { + public Series copy () { + var series = new Series (); + series._color = this._color; + series.axis_x = this.axis_x.copy (); + series.axis_y = this.axis_y.copy (); + series.cursors = this.cursors.copy (); + series.grid = this.grid.copy (); + series.line_style = this.line_style; + series.marker_type = this.marker_type; + series.place = this.place; + series.points = this.points.copy(); + series.sort = this.sort; + series.title = this.title.copy(); + return series; } + public Series () { + } } } diff --git a/src/Text.vala b/src/Text.vala index a7f6a58..b52f677 100644 --- a/src/Text.vala +++ b/src/Text.vala @@ -47,10 +47,12 @@ namespace Gtk.CairoChart { this.color = color; } - public Text.by_instance (Text text) { - this.text = text.text; - this.style = text.style; - this.color = text.color; + public Text copy () { + var text = new Text (); + text.text = this.text; + text.style = this.style; + text.color = this.color; + return text; } } } From 002e995b1457575cfe5cdff05b6c765de04bb396 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Wed, 6 Sep 2017 10:40:09 +0300 Subject: [PATCH 09/40] Fixes #139: Cut lines when they are outside of the chart grid. --- src/Chart.vala | 91 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 2a81736..391f2ac 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -908,6 +908,39 @@ namespace Gtk.CairoChart { / (s.axis_y.max - s.axis_y.min) * (s.place.y_high - s.place.y_low)); } + protected virtual bool point_in_rect (Point p, double x0, double x1, double y0, double y1) { + if ( (x0 <= p.x <= x1 || x1 <= p.x <= x0) + && (y0 <= p.y <= y1 || y1 <= p.y <= y0)) + return true; + return false; + } + + protected virtual bool point_in_plot_area (Point p) { + if (point_in_rect (p, plot_area_x_min, plot_area_x_max, plot_area_y_min, plot_area_y_max)) + return true; + return false; + } + + protected virtual bool hcross (Point a1, Point a2, Float128 h_x1, Float128 h_x2, Float128 h_y, out Float128 x) { + x = 0; + if (a1.y == a2.y) return false; + if (a1.y >= h_y && a2.y >= h_y || a1.y <= h_y && a2.y <= h_y) return false; + x = a1.x + (a2.x - a1.x) * (h_y - a1.y) / (a2.y - a1.y); + if (h_x1 <= x <= h_x2 || h_x2 <= x <= h_x1) + return true; + return false; + } + + protected virtual bool vcross (Point a1, Point a2, Float128 v_x, Float128 v_y1, Float128 v_y2, out Float128 y) { + y = 0; + if (a1.x == a2.x) return false; + if (a1.x >= v_x && a2.x >= v_x || a1.x <= v_x && a2.x <= v_x) return false; + y = a1.y + (a2.y - a1.y) * (v_x - a1.x) / (a2.x - a1.x); + if (v_y1 <= y <= v_y2 || v_y2 <= y <= v_y1) + return true; + return false; + } + delegate int PointComparator(Point a, Point b); void sort_points(Point[] points, PointComparator compare) { for(var i = 0; i < points.length; ++i) { @@ -921,6 +954,43 @@ namespace Gtk.CairoChart { } } + protected virtual bool cut_line (Point a, Point b, out Point c, out Point d) { + int ncross = 0; + Float128 x = 0, y = 0; + Point pc[4]; + if (hcross(a, b, plot_area_x_min, plot_area_x_max, plot_area_y_min, out x)) + pc[ncross++] = Point(x, plot_area_y_min); + if (hcross(a, b, plot_area_x_min, plot_area_x_max, plot_area_y_max, out x)) + pc[ncross++] = Point(x, plot_area_y_max); + if (vcross(a, b, plot_area_x_min, plot_area_y_min, plot_area_y_max, out y)) + pc[ncross++] = Point(plot_area_x_min, y); + if (vcross(a, b, plot_area_x_max, plot_area_y_min, plot_area_y_max, out y)) + pc[ncross++] = Point(plot_area_x_max, y); + c = a; + d = b; + if (ncross == 0) { + if (point_in_plot_area (a) && point_in_plot_area (b)) + return true; + return false; + } + if (ncross >= 2) { + c = pc[0]; d = pc[1]; + return true; + } + if (ncross == 1) { + if (point_in_plot_area (a)) { + c = a; + d = pc[0]; + return true; + } else if (point_in_plot_area (b)) { + c = b; + d = pc[0]; + return true; + } + } + return false; + } + protected virtual void draw_series () { for (int si = 0; si < series.length; ++si) { var s = series[si]; @@ -943,14 +1013,23 @@ namespace Gtk.CairoChart { break; } set_line_style(s.line_style); - // move to s.points[0] - context.move_to (get_scr_x(s, points[0].x), get_scr_y(s, points[0].y)); // draw series line - for (int i = 1; i < points.length; ++i) - context.line_to (get_scr_x(s, points[i].x), get_scr_y(s, points[i].y)); + for (int i = 1; i < points.length; ++i) { + Point c, d; + if (cut_line (Point(get_scr_x(s, points[i - 1].x), get_scr_y(s, points[i - 1].y)), + Point(get_scr_x(s, points[i].x), get_scr_y(s, points[i].y)), + out c, out d)) { + context.move_to (c.x, c.y); + context.line_to (d.x, d.y); + } + } context.stroke(); - for (int i = 0; i < points.length; ++i) - draw_marker_at_pos(s.marker_type, get_scr_x(s, points[i].x), get_scr_y(s, points[i].y)); + for (int i = 0; i < points.length; ++i) { + var x = get_scr_x(s, points[i].x); + var y = get_scr_y(s, points[i].y); + if (point_in_plot_area (Point (x, y))) + draw_marker_at_pos(s.marker_type, x, y); + } } } From 8c2d84c1292a00bf58ce90e3bb2f9e1ff8a68ea8 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Thu, 14 Sep 2017 08:42:49 +0300 Subject: [PATCH 10/40] fix build --- src/Chart.vala | 5 ----- test/ChartTest.vala | 8 ++++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 391f2ac..1a51f0b 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -1070,11 +1070,6 @@ namespace Gtk.CairoChart { chart.title_vindent = this.title_vindent; chart.title_width = this.title_width; chart.width = this.width; - chart.zoom = this.zoom; - chart.zoom_x0 = this.zoom_x0; - chart.zoom_x1 = this.zoom_x1; - chart.zoom_y0 = this.zoom_y0; - chart.zoom_y1 = this.zoom_y1; return chart; } } diff --git a/test/ChartTest.vala b/test/ChartTest.vala index c40e56f..8f00346 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -337,19 +337,19 @@ int main (string[] args) { da.button_release_event.connect((event) => { // user's pre button_release_event operations here... - var ret = chart.button_release_event(event); + var ret = true;//chart.button_release_event(event); // user's post button_release_event operations here... return ret; }); da.button_press_event.connect((event) => { // user's pre button_press_event operations here... - var ret = chart.button_press_event(event); + var ret = true;//chart.button_press_event(event); // user's post button_press_event operations here... return ret; }); da.motion_notify_event.connect((event) => { // user's pre motion_notify_event operations here... - var ret = chart.motion_notify_event(event); + var ret = true;//chart.motion_notify_event(event); // user's post motion_notify_event operations here... return ret; }); @@ -358,7 +358,7 @@ int main (string[] args) { // user's pre scroll_notify_event operations here... stdout.puts("pre_scroll\n"); - var ret = chart.scroll_notify_event(event); + var ret = true;//chart.scroll_notify_event(event); // user's post scroll_notify_event operations here... stdout.puts("post_scroll\n"); From 0e1a746639d72ad61882f1374d8a1f36606ec0f9 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Tue, 22 Aug 2017 11:54:18 +0300 Subject: [PATCH 11/40] Closes #130: Zoom added. --- src/Axis.vala | 16 ++- src/Chart.vala | 273 +++++++++++++++++++++++++++++--------------- src/Place.vala | 49 +++++++- src/Series.vala | 4 +- test/ChartTest.vala | 143 ++++++++++++++++------- 5 files changed, 341 insertions(+), 144 deletions(-) diff --git a/src/Axis.vala b/src/Axis.vala index 3356c19..6c14f7f 100644 --- a/src/Axis.vala +++ b/src/Axis.vala @@ -3,8 +3,20 @@ namespace Gtk.CairoChart { // then draw separate axis for each/all series // or specify series name near the axis public class Axis { - public Float128 min = 0; - public Float128 max = 1; + Float128 _min = 0; + Float128 _max = 0; + public Float128 min { + get { return _min; } + set { _min = zoom_min = value; } + default = 0; + } + public Float128 max { + get { return _max; } + set { _max = zoom_max = value; } + default = 1; + } + public Float128 zoom_min = 0; + public Float128 zoom_max = 1; public Text title = new Text (""); public enum Type { NUMBERS = 0, diff --git a/src/Chart.vala b/src/Chart.vala index 1a51f0b..1328fe7 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -14,7 +14,19 @@ namespace Gtk.CairoChart { public Legend legend = new Legend (); - public Series[] series = {}; + Series[] _series = {}; + public Series[] series { + get { return _series; } + set { + _series = value.copy(); + zoom_series = value.copy(); + for (var i = 0; i < value.length; ++i) { + _series[i] = value[i].copy(); + zoom_series[i] = value[i].copy(); + } + } + } + public Series[] zoom_series = {}; protected LineStyle selection_style = LineStyle (); @@ -91,6 +103,74 @@ namespace Gtk.CairoChart { } } + Series[] rm_series_by_idx (Series[] series, int idx) { + Series[] new_series = series.copy(); + for (var i = idx + 1; i < series.length; ++i) + new_series[i - 1] = series[i]; + new_series.length--; + return new_series; + } + + public virtual void zoom_in (double x0, double y0, double x1, double y1) { + for (var i = 0, max_i = zoom_series.length; i < max_i; ++i) { + var s = zoom_series[i]; + var real_x0 = get_real_x (s, x0); + var real_x1 = get_real_x (s, x1); + var real_y0 = get_real_y (s, y0); + var real_y1 = get_real_y (s, y1); + // if selected square does not intersect with the zoom_series's square + if ( real_x1 <= s.axis_x.zoom_min || real_x0 >= s.axis_x.zoom_max + || real_y0 <= s.axis_y.zoom_min || real_y1 >= s.axis_y.zoom_max) { + zoom_series = rm_series_by_idx (zoom_series, i); + --i; + --max_i; + continue; + } + if (real_x0 >= s.axis_x.zoom_min) { + s.axis_x.zoom_min = real_x0; + s.place.zoom_x_low = 0.0; + } else { + s.place.zoom_x_low = (s.axis_x.zoom_min - real_x0) / (real_x1 - real_x0); + } + if (real_x1 <= s.axis_x.zoom_max) { + s.axis_x.zoom_max = real_x1; + s.place.zoom_x_high = 1.0; + } else { + s.place.zoom_x_high = (s.axis_x.zoom_max - real_x0) / (real_x1 - real_x0); + } + if (real_y1 >= s.axis_y.zoom_min) { + s.axis_y.zoom_min = real_y1; + s.place.zoom_y_low = 0.0; + } else { + s.place.zoom_y_low = (s.axis_y.zoom_min - real_y1) / (real_y0 - real_y1); + } + if (real_y0 <= s.axis_y.zoom_max) { + s.axis_y.zoom_max = real_y0; + s.place.zoom_y_high = 1.0; + } else { + s.place.zoom_y_high = (s.axis_y.zoom_max - real_y1) / (real_y0 - real_y1); + } + } + } + + public virtual void zoom_out () { + zoom_series = _series.copy(); + for (var i = 0; i < _series.length; ++i) { + _series[i] = _series[i].copy(); + zoom_series[i] = _series[i].copy(); + } + foreach (var s in zoom_series) { + s.axis_x.zoom_min = s.axis_x.min; + s.axis_x.zoom_max = s.axis_x.max; + s.axis_y.zoom_min = s.axis_y.min; + s.axis_y.zoom_max = s.axis_y.max; + s.place.zoom_x_low = s.place.x_low; + s.place.zoom_x_high = s.place.x_high; + s.place.zoom_y_low = s.place.y_low; + s.place.zoom_y_high = s.place.y_high; + } + } + protected double title_width = 0.0; protected double title_height = 0.0; @@ -249,7 +329,7 @@ namespace Gtk.CairoChart { break; } - foreach (var s in series) { + foreach (var s in zoom_series) { // carry switch (legend.position) { @@ -278,12 +358,12 @@ namespace Gtk.CairoChart { var x = legend_x0 + leg_width_sum + (leg_width_sum == 0.0 ? 0.0 : legend_text_hspace); var y = legend_y0 + leg_height_sum + max_font_heights[heights_idx]; - // series title + // zoom_series title context.move_to (x + legend_line_length - s.title.get_x_bearing(context), y); set_source_rgba (s.title.color); show_text(s.title); - // series line style + // zoom_series line style context.move_to (x, y - s.title.get_height(context) / 2); set_line_style(s.line_style); context.rel_line_to (legend_line_length, 0); @@ -363,7 +443,7 @@ namespace Gtk.CairoChart { protected virtual void calc_axis_rec_sizes (Axis axis, out double max_rec_width, out double max_rec_height, bool is_horizontal = true) { max_rec_width = max_rec_height = 0; for (var i = 0; i < axis_rec_npoints; ++i) { - Float128 x = axis.min + (axis.max - axis.min) / axis_rec_npoints * i; + Float128 x = axis.zoom_min + (axis.zoom_max - axis.zoom_min) / axis_rec_npoints * i; switch (axis.type) { case Axis.Type.NUMBERS: var text = new Text (axis.format.printf((LongDouble)x) + (is_horizontal ? "_" : "")); @@ -407,7 +487,6 @@ namespace Gtk.CairoChart { if (step / 5 > aver_step) step /= 5; while (step / 2 > aver_step) step /= 2; } else if (aver_step > 0) { - //stdout.printf("aver_step = %Lf\n", aver_step); while (step / 10 > aver_step) step /= 10; if (step / 5 > aver_step) step /= 5; while (step / 2 > aver_step) step /= 2; @@ -432,8 +511,8 @@ namespace Gtk.CairoChart { } protected virtual void set_vertical_axes_titles () { - for (var i = 0; i < series.length; ++i) { - var s = series[i]; + for (var i = 0; i < zoom_series.length; ++i) { + var s = zoom_series[i]; s.axis_y.title.style.orientation = FontOrient.VERTICAL; } } @@ -446,28 +525,28 @@ namespace Gtk.CairoChart { // Check for common axes common_x_axes = common_y_axes = true; - for (int si = series.length - 1; si >=0; --si) { - var s = series[si]; - if ( s.axis_x.position != series[0].axis_x.position - || s.axis_x.min != series[0].axis_x.min - || s.axis_x.max != series[0].axis_x.max - || s.place.x_low != series[0].place.x_low - || s.place.x_high != series[0].place.x_high - || s.axis_x.type != series[0].axis_x.type) + for (int si = zoom_series.length - 1; si >=0; --si) { + var s = zoom_series[si]; + if ( s.axis_x.position != zoom_series[0].axis_x.position + || s.axis_x.zoom_min != zoom_series[0].axis_x.zoom_min + || s.axis_x.zoom_max != zoom_series[0].axis_x.zoom_max + || s.place.zoom_x_low != zoom_series[0].place.zoom_x_low + || s.place.zoom_x_high != zoom_series[0].place.zoom_x_high + || s.axis_x.type != zoom_series[0].axis_x.type) common_x_axes = false; - if ( s.axis_y.position != series[0].axis_y.position - || s.axis_y.min != series[0].axis_y.min - || s.axis_y.max != series[0].axis_y.max - || s.place.y_low != series[0].place.y_low - || s.place.y_high != series[0].place.y_high) + if ( s.axis_y.position != zoom_series[0].axis_y.position + || s.axis_y.zoom_min != zoom_series[0].axis_y.zoom_min + || s.axis_y.zoom_max != zoom_series[0].axis_y.zoom_max + || s.place.zoom_y_low != zoom_series[0].place.zoom_y_low + || s.place.zoom_y_high != zoom_series[0].place.zoom_y_high) common_y_axes = false; } - if (series.length == 1) common_x_axes = common_y_axes = false; + if (zoom_series.length == 1) common_x_axes = common_y_axes = false; // Join and calc X-axes - for (int si = series.length - 1, nskip = 0; si >=0; --si) { + for (int si = zoom_series.length - 1, nskip = 0; si >=0; --si) { if (nskip != 0) {--nskip; continue;} - var s = series[si]; + var s = zoom_series[si]; double max_rec_width = 0; double max_rec_height = 0; calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true); var max_font_indent = s.axis_x.font_indent; @@ -475,11 +554,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { - var s2 = series[sj]; + var s2 = zoom_series[sj]; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { - var s3 = series[sk]; - if (are_intersect(s2.place.x_low, s2.place.x_high, s3.place.x_low, s3.place.x_high) + var s3 = zoom_series[sk]; + if (are_intersect(s2.place.zoom_x_low, s2.place.zoom_x_high, s3.place.zoom_x_low, s3.place.zoom_x_high) || s2.axis_x.position != s3.axis_x.position || s2.axis_x.type != s3.axis_x.type) { has_intersection = true; @@ -510,9 +589,9 @@ namespace Gtk.CairoChart { } // Join and calc Y-axes - for (int si = series.length - 1, nskip = 0; si >=0; --si) { + for (int si = zoom_series.length - 1, nskip = 0; si >=0; --si) { if (nskip != 0) {--nskip; continue;} - var s = series[si]; + var s = zoom_series[si]; double max_rec_width = 0; double max_rec_height = 0; calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false); var max_font_indent = s.axis_y.font_indent; @@ -520,11 +599,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { - var s2 = series[sj]; + var s2 = zoom_series[sj]; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { - var s3 = series[sk]; - if (are_intersect(s2.place.y_low, s2.place.y_high, s3.place.y_low, s3.place.y_high) + var s3 = zoom_series[sk]; + if (are_intersect(s2.place.zoom_y_low, s2.place.zoom_y_high, s3.place.zoom_y_low, s3.place.zoom_y_high) || s2.axis_y.position != s3.axis_y.position || s2.axis_x.type != s3.axis_x.type) { has_intersection = true; @@ -562,38 +641,38 @@ namespace Gtk.CairoChart { } protected virtual void draw_horizontal_axis () { - for (int si = series.length - 1, nskip = 0; si >=0; --si) { + for (int si = zoom_series.length - 1, nskip = 0; si >=0; --si) { if (common_x_axes && si != 0) continue; - var s = series[si]; + var s = zoom_series[si]; // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. double max_rec_width, max_rec_height; calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true); // 2. Calculate maximal available number of records, take into account the space width. - long max_nrecs = (long) ((plot_area_x_max - plot_area_x_min) * (s.place.x_high - s.place.x_low) / max_rec_width); + long max_nrecs = (long) ((plot_area_x_max - plot_area_x_min) * (s.place.zoom_x_high - s.place.zoom_x_low) / max_rec_width); // 3. Calculate grid step. - Float128 step = calc_round_step ((s.axis_x.max - s.axis_x.min) / max_nrecs, s.axis_x.type == Axis.Type.DATE_TIME); - if (step > s.axis_x.max - s.axis_x.min) - step = s.axis_x.max - s.axis_x.min; + Float128 step = calc_round_step ((s.axis_x.zoom_max - s.axis_x.zoom_min) / max_nrecs, s.axis_x.type == Axis.Type.DATE_TIME); + if (step > s.axis_x.zoom_max - s.axis_x.zoom_min) + step = s.axis_x.zoom_max - s.axis_x.zoom_min; - // 4. Calculate x_min (s.axis_x.min / step, round, multiply on step, add step if < s.axis_x.min). + // 4. Calculate x_min (s.axis_x.zoom_min / step, round, multiply on step, add step if < s.axis_x.zoom_min). Float128 x_min = 0.0; if (step >= 1) { - int64 x_min_nsteps = (int64) (s.axis_x.min / step); + int64 x_min_nsteps = (int64) (s.axis_x.zoom_min / step); x_min = x_min_nsteps * step; } else { - int64 round_axis_x_min = (int64)s.axis_x.min; - int64 x_min_nsteps = (int64) ((s.axis_x.min - round_axis_x_min) / step); + int64 round_axis_x_min = (int64)s.axis_x.zoom_min; + int64 x_min_nsteps = (int64) ((s.axis_x.zoom_min - round_axis_x_min) / step); x_min = round_axis_x_min + x_min_nsteps * step; } - if (x_min < s.axis_x.min) x_min += step; + if (x_min < s.axis_x.zoom_min) x_min += step; // 4.5. Draw Axis title if (s.axis_x.title.text != "") switch (s.axis_x.position) { case Axis.Position.LOW: - var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + s.place.x_high) / 2.0; + var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.zoom_x_low + s.place.zoom_x_high) / 2.0; var scr_y = cur_y_max - s.axis_x.font_indent; context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y); set_source_rgba(s.axis_x.color); @@ -601,7 +680,7 @@ namespace Gtk.CairoChart { show_text(s.axis_x.title); break; case Axis.Position.HIGH: - var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + s.place.x_high) / 2.0; + var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.zoom_x_low + s.place.zoom_x_high) / 2.0; var scr_y = cur_y_min + s.axis_x.font_indent + s.axis_x.title.get_height(context); context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y); set_source_rgba(s.axis_x.color); @@ -613,7 +692,7 @@ namespace Gtk.CairoChart { } // 5. Draw records, update cur_{x,y}_{min,max}. - for (Float128 x = x_min, x_max = s.axis_x.max; point_belong (x, x_min, x_max); x += step) { + for (Float128 x = x_min, x_max = s.axis_x.zoom_max; point_belong (x, x_min, x_max); x += step) { if (common_x_axes) set_source_rgba(Color(0,0,0,1)); else set_source_rgba(s.axis_x.color); string text = "", time_text = ""; @@ -632,7 +711,7 @@ namespace Gtk.CairoChart { break; } var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) - * (s.place.x_low + (s.place.x_high - s.place.x_low) / (s.axis_x.max - s.axis_x.min) * (x - s.axis_x.min)); + * (s.place.zoom_x_low + (s.place.zoom_x_high - s.place.zoom_x_low) / (s.axis_x.zoom_max - s.axis_x.zoom_min) * (x - s.axis_x.zoom_min)); var text_t = new Text(text, s.axis_x.font_style, s.axis_x.color); switch (s.axis_x.position) { case Axis.Position.LOW: @@ -640,25 +719,25 @@ namespace Gtk.CairoChart { switch (s.axis_x.type) { case Axis.Type.NUMBERS: var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + - text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); context.move_to (print_x, print_y); show_text(text_t); break; case Axis.Type.DATE_TIME: var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + - text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); context.move_to (print_x, print_y); if (s.axis_x.date_format != "") show_text(text_t); var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color); print_x = scr_x - time_text_t.get_width(context) / 2.0 - time_text_t.get_x_bearing(context) - - time_text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + - time_text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent)); if (s.axis_x.time_format != "") show_text(time_text_t); break; default: break; } - // 6. Draw grid lines to the s.place.y_high. + // 6. Draw grid lines to the s.place.zoom_y_high. var line_style = s.grid.line_style; if (common_x_axes) line_style.color = Color(0, 0, 0, 0.5); set_line_style(line_style); @@ -667,32 +746,32 @@ namespace Gtk.CairoChart { if (common_x_axes) context.line_to (scr_x, plot_area_y_min); else - context.line_to (scr_x, double.min (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.y_high)); + context.line_to (scr_x, double.min (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.zoom_y_high)); break; case Axis.Position.HIGH: var print_y = cur_y_min + max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); switch (s.axis_x.type) { case Axis.Type.NUMBERS: var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + - text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); context.move_to (print_x, print_y); show_text(text_t); break; case Axis.Type.DATE_TIME: var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + - text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); context.move_to (print_x, print_y); if (s.axis_x.date_format != "") show_text(text_t); var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color); print_x = scr_x - time_text_t.get_width(context) / 2.0 - time_text_t.get_x_bearing(context) - - time_text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min); + - time_text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent)); if (s.axis_x.time_format != "") show_text(time_text_t); break; default: break; } - // 6. Draw grid lines to the s.place.y_high. + // 6. Draw grid lines to the s.place.zoom_y_high. var line_style = s.grid.line_style; if (common_x_axes) line_style.color = Color(0, 0, 0, 0.5); set_line_style(line_style); @@ -701,7 +780,7 @@ namespace Gtk.CairoChart { if (common_x_axes) context.line_to (scr_x, plot_area_y_max); else - context.line_to (scr_x, double.max (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.y_low)); + context.line_to (scr_x, double.max (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.zoom_y_low)); break; case Axis.Position.BOTH: break; @@ -713,11 +792,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { - var s2 = series[sj]; + var s2 = zoom_series[sj]; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { - var s3 = series[sk]; - if (are_intersect(s2.place.x_low, s2.place.x_high, s3.place.x_low, s3.place.x_high) + var s3 = zoom_series[sk]; + if (are_intersect(s2.place.zoom_x_low, s2.place.zoom_x_high, s3.place.zoom_x_low, s3.place.zoom_x_high) || s2.axis_x.position != s3.axis_x.position || s2.axis_x.type != s3.axis_x.type) { has_intersection = true; @@ -750,38 +829,38 @@ namespace Gtk.CairoChart { } protected virtual void draw_vertical_axis () { - for (int si = series.length - 1, nskip = 0; si >=0; --si) { + for (int si = zoom_series.length - 1, nskip = 0; si >=0; --si) { if (common_y_axes && si != 0) continue; - var s = series[si]; + var s = zoom_series[si]; // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. double max_rec_width, max_rec_height; calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false); // 2. Calculate maximal available number of records, take into account the space width. - long max_nrecs = (long) ((plot_area_y_max - plot_area_y_min) * (s.place.y_high - s.place.y_low) / max_rec_height); + long max_nrecs = (long) ((plot_area_y_max - plot_area_y_min) * (s.place.zoom_y_high - s.place.zoom_y_low) / max_rec_height); // 3. Calculate grid step. - Float128 step = calc_round_step ((s.axis_y.max - s.axis_y.min) / max_nrecs); - if (step > s.axis_y.max - s.axis_y.min) - step = s.axis_y.max - s.axis_y.min; + Float128 step = calc_round_step ((s.axis_y.zoom_max - s.axis_y.zoom_min) / max_nrecs); + if (step > s.axis_y.zoom_max - s.axis_y.zoom_min) + step = s.axis_y.zoom_max - s.axis_y.zoom_min; - // 4. Calculate y_min (s.axis_y.min / step, round, multiply on step, add step if < s.axis_y.min). + // 4. Calculate y_min (s.axis_y.zoom_min / step, round, multiply on step, add step if < s.axis_y.zoom_min). Float128 y_min = 0.0; if (step >= 1) { - int64 y_min_nsteps = (int64) (s.axis_y.min / step); + int64 y_min_nsteps = (int64) (s.axis_y.zoom_min / step); y_min = y_min_nsteps * step; } else { - int64 round_axis_y_min = (int64)s.axis_y.min; - int64 y_min_nsteps = (int64) ((s.axis_y.min - round_axis_y_min) / step); + int64 round_axis_y_min = (int64)s.axis_y.zoom_min; + int64 y_min_nsteps = (int64) ((s.axis_y.zoom_min - round_axis_y_min) / step); y_min = round_axis_y_min + y_min_nsteps * step; } - if (y_min < s.axis_y.min) y_min += step; + if (y_min < s.axis_y.zoom_min) y_min += step; // 4.5. Draw Axis title if (s.axis_y.title.text != "") switch (s.axis_y.position) { case Axis.Position.LOW: - var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + s.place.y_high) / 2.0; + var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.zoom_y_low + s.place.zoom_y_high) / 2.0; var scr_x = cur_x_min + s.axis_y.font_indent + s.axis_y.title.get_width(context); context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0); set_source_rgba(s.axis_y.color); @@ -789,7 +868,7 @@ namespace Gtk.CairoChart { show_text(s.axis_y.title); break; case Axis.Position.HIGH: - var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + s.place.y_high) / 2.0; + var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.zoom_y_low + s.place.zoom_y_high) / 2.0; var scr_x = cur_x_max - s.axis_y.font_indent; context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0); set_source_rgba(s.axis_y.color); @@ -801,21 +880,21 @@ namespace Gtk.CairoChart { } // 5. Draw records, update cur_{x,y}_{min,max}. - for (Float128 y = y_min, y_max = s.axis_y.max; point_belong (y, y_min, y_max); y += step) { + for (Float128 y = y_min, y_max = s.axis_y.zoom_max; point_belong (y, y_min, y_max); y += step) { if (common_y_axes) set_source_rgba(Color(0,0,0,1)); else set_source_rgba(s.axis_y.color); var text = s.axis_y.format.printf((LongDouble)y); var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) - * (s.place.y_low + (s.place.y_high - s.place.y_low) / (s.axis_y.max - s.axis_y.min) * (y - s.axis_y.min)); + * (s.place.zoom_y_low + (s.place.zoom_y_high - s.place.zoom_y_low) / (s.axis_y.zoom_max - s.axis_y.zoom_min) * (y - s.axis_y.zoom_min)); var text_t = new Text(text, s.axis_y.font_style, s.axis_y.color); switch (s.axis_y.position) { case Axis.Position.LOW: context.move_to (cur_x_min + max_rec_width - (new Text(text)).get_width(context) + s.axis_y.font_indent - text_t.get_x_bearing(context) + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent), scr_y + (new Text(text)).get_height(context) / 2.0 - + text_t.get_height(context) * (y - (s.axis_y.min + s.axis_y.max) / 2.0) / (s.axis_y.max - s.axis_y.min)); + + text_t.get_height(context) * (y - (s.axis_y.zoom_min + s.axis_y.zoom_max) / 2.0) / (s.axis_y.zoom_max - s.axis_y.zoom_min)); show_text(text_t); - // 6. Draw grid lines to the s.place.y_high. + // 6. Draw grid lines to the s.place.zoom_y_high. var line_style = s.grid.line_style; if (common_y_axes) line_style.color = Color(0, 0, 0, 0.5); set_line_style(line_style); @@ -824,15 +903,15 @@ namespace Gtk.CairoChart { if (common_y_axes) context.line_to (plot_area_x_max, scr_y); else - context.line_to (double.max (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.x_high), scr_y); + context.line_to (double.max (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.zoom_x_high), scr_y); break; case Axis.Position.HIGH: context.move_to (cur_x_max - (new Text(text)).get_width(context) - s.axis_y.font_indent - text_t.get_x_bearing(context) - (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent), scr_y + (new Text(text)).get_height(context) / 2.0 - + text_t.get_height(context) * (y - (s.axis_y.min + s.axis_y.max) / 2.0) / (s.axis_y.max - s.axis_y.min)); + + text_t.get_height(context) * (y - (s.axis_y.zoom_min + s.axis_y.zoom_max) / 2.0) / (s.axis_y.zoom_max - s.axis_y.zoom_min)); show_text(text_t); - // 6. Draw grid lines to the s.place.y_high. + // 6. Draw grid lines to the s.place.zoom_y_high. var line_style = s.grid.line_style; if (common_y_axes) line_style.color = Color(0, 0, 0, 0.5); set_line_style(line_style); @@ -841,7 +920,7 @@ namespace Gtk.CairoChart { if (common_y_axes) context.line_to (plot_area_x_min, scr_y); else - context.line_to (double.min (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.x_low), scr_y); + context.line_to (double.min (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.zoom_x_low), scr_y); break; case Axis.Position.BOTH: break; @@ -853,11 +932,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { - var s2 = series[sj]; + var s2 = zoom_series[sj]; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { - var s3 = series[sk]; - if (are_intersect(s2.place.y_low, s2.place.y_high, s3.place.y_low, s3.place.y_high) + var s3 = zoom_series[sk]; + if (are_intersect(s2.place.zoom_y_low, s2.place.zoom_y_high, s3.place.zoom_y_low, s3.place.zoom_y_high) || s2.axis_y.position != s3.axis_y.position) { has_intersection = true; break; @@ -899,13 +978,23 @@ namespace Gtk.CairoChart { } protected virtual double get_scr_x (Series s, Float128 x) { - return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + (x - s.axis_x.min) - / (s.axis_x.max - s.axis_x.min) * (s.place.x_high - s.place.x_low)); + return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.zoom_x_low + (x - s.axis_x.zoom_min) + / (s.axis_x.zoom_max - s.axis_x.zoom_min) * (s.place.zoom_x_high - s.place.zoom_x_low)); } protected virtual double get_scr_y (Series s, Float128 y) { - return plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + (y - s.axis_y.min) - / (s.axis_y.max - s.axis_y.min) * (s.place.y_high - s.place.y_low)); + return plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.zoom_y_low + (y - s.axis_y.zoom_min) + / (s.axis_y.zoom_max - s.axis_y.zoom_min) * (s.place.zoom_y_high - s.place.zoom_y_low)); + } + + protected virtual Float128 get_real_x (Series s, double scr_x) { + return s.axis_x.zoom_min + ((scr_x - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) - s.place.zoom_x_low) + * (s.axis_x.zoom_max - s.axis_x.zoom_min) / (s.place.zoom_x_high - s.place.zoom_x_low); + } + + protected virtual Float128 get_real_y (Series s, double scr_y) { + return s.axis_y.zoom_min + ((plot_area_y_max - scr_y) / (plot_area_y_max - plot_area_y_min) - s.place.zoom_y_low) + * (s.axis_y.zoom_max - s.axis_y.zoom_min) / (s.place.zoom_y_high - s.place.zoom_y_low); } protected virtual bool point_in_rect (Point p, double x0, double x1, double y0, double y1) { @@ -992,8 +1081,8 @@ namespace Gtk.CairoChart { } protected virtual void draw_series () { - for (int si = 0; si < series.length; ++si) { - var s = series[si]; + for (int si = 0; si < zoom_series.length; ++si) { + var s = zoom_series[si]; if (s.points.length == 0) continue; var points = s.points.copy(); switch(s.sort) { @@ -1013,7 +1102,7 @@ namespace Gtk.CairoChart { break; } set_line_style(s.line_style); - // draw series line + // draw zoom_series line for (int i = 1; i < points.length; ++i) { Point c, d; if (cut_line (Point(get_scr_x(s, points[i - 1].x), get_scr_y(s, points[i - 1].y)), @@ -1063,7 +1152,7 @@ namespace Gtk.CairoChart { chart.plot_area_y_max = this.plot_area_y_max; chart.plot_area_y_min = this.plot_area_y_min; chart.selection_style = this.selection_style; - chart.series = this.series.copy(); + chart.zoom_series = this.zoom_series.copy(); chart.show_legend = this.show_legend; chart.title = this.title.copy().copy(); chart.title_height = this.title_height; diff --git a/src/Place.vala b/src/Place.vala index 13a9eee..6ac611d 100644 --- a/src/Place.vala +++ b/src/Place.vala @@ -1,15 +1,52 @@ namespace Gtk.CairoChart { - public struct Place { - double x_low; - double x_high; - double y_low; - double y_high; + public class Place { + double _x_low = 0; + double _x_high = 0; + double _y_low = 0; + double _y_high = 0; + public double x_low { + get { return _x_low; } + set { _x_low = zoom_x_low = value; } + default = 0; + } + public double x_high { + get { return _x_high; } + set { _x_high = zoom_x_high = value; } + default = 0; + } + public double y_low { + get { return _y_low; } + set { _y_low = zoom_y_low = value; } + default = 0; + } + public double y_high { + get { return _y_high; } + set { _y_high = zoom_y_high = value; } + default = 0; + } + public double zoom_x_low = 0; + public double zoom_x_high = 1; + public double zoom_y_low = 0; + public double zoom_y_high = 1; - public Place (double x_low = 0, double x_high = 0, double y_low = 0, double y_high = 0) { + public Place copy () { + var place = new Place (); + place.x_low = this.x_low; + place.x_high = this.x_high; + place.y_low = this.y_low; + place.y_high = this.y_high; + return place; + } + + public Place (double x_low = 0, double x_high = 1, double y_low = 0, double y_high = 1) { this.x_low = x_low; this.x_high = x_high; this.y_low = y_low; this.y_high = y_high; + zoom_x_low = x_low; + zoom_x_high = x_high; + zoom_y_low = y_low; + zoom_y_high = y_high; } } } diff --git a/src/Series.vala b/src/Series.vala index 965de1a..7a568b7 100644 --- a/src/Series.vala +++ b/src/Series.vala @@ -25,7 +25,7 @@ namespace Gtk.CairoChart { PRICLE_TRIANGLE } - public Place place = Place(); + public Place place = new Place(); public Text title = new Text (); public MarkerType marker_type = MarkerType.SQUARE; @@ -59,7 +59,7 @@ namespace Gtk.CairoChart { series.grid = this.grid.copy (); series.line_style = this.line_style; series.marker_type = this.marker_type; - series.place = this.place; + series.place = this.place.copy(); series.points = this.points.copy(); series.sort = this.sort; series.title = this.title.copy(); diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 8f00346..1cfb4a9 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -5,14 +5,14 @@ void plot_chart1 (Chart chart) { var s2 = new Series (); var s3 = new Series (); - s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); - s1.points = {new Point(0, 0), new Point(2, 1), new Point(1, 3)}; + s1.title = new Text("Series 1"); s1.color = Color (1, 0, 0); + s1.points = {Point(0, 0), Point(2, 1), Point(1, 3)}; s1.axis_x.position = Axis.Position.HIGH; s1.axis_x.format = "%.3Lf"; - s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); - s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)}; - s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); - s3.points = {new Point(9, 17), new Point(2, 10), new Point(122, 31)}; + s2.title = new Text("Series 2"); s2.color = Color (0, 1, 0); + s2.points = {Point(5, -3), Point(25, -18), Point(-11, 173)}; + s3.title = new Text("Series 3"); s3.color = Color (0, 0, 1); + s3.points = {Point(9, 17), Point(2, 10), Point(122, 31)}; s3.axis_y.position = Axis.Position.HIGH; s1.axis_x.min = 0; s1.axis_x.max = 2; @@ -48,14 +48,14 @@ void plot_chart2 (Chart chart) { var s2 = new Series (); var s3 = new Series (); - s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); - s1.points = {new Point(-12, 0), new Point(2, 1), new Point(20, 3)}; + s1.title = new Text("Series 1"); s1.color = Color (1, 0, 0); + s1.points = {Point(-12, 0), Point(2, 1), Point(20, 3)}; s2.axis_y.position = Axis.Position.HIGH; s1.axis_x.format = "%.3Lf"; - s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); - s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)}; - s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); - s3.points = {new Point(9, 17), new Point(2, 10), new Point(-15, 31)}; + s2.title = new Text("Series 2"); s2.color = Color (0, 1, 0); + s2.points = {Point(5, -3), Point(25, -18), Point(-11, 173)}; + s3.title = new Text("Series 3"); s3.color = Color (0, 0, 1); + s3.points = {Point(9, 17), Point(2, 10), Point(-15, 31)}; s3.axis_y.position = Axis.Position.HIGH; s1.axis_x.min = -15; s1.axis_x.max = 30; @@ -91,16 +91,16 @@ void plot_chart3 (Chart chart) { var s2 = new Series (); var s3 = new Series (); - s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); - s1.points = {new Point(0, 70), new Point(2, 155), new Point(1, -3)}; + s1.title = new Text("Series 1"); s1.color = Color (1, 0, 0); + s1.points = {Point(0, 70), Point(2, 155), Point(1, -3)}; s1.axis_x.position = Axis.Position.HIGH; s1.axis_y.position = Axis.Position.HIGH; s1.axis_x.format = "%.3Lf"; - s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); - s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)}; + s2.title = new Text("Series 2"); s2.color = Color (0, 1, 0); + s2.points = {Point(5, -3), Point(25, -18), Point(-11, 173)}; s2.axis_y.position = Axis.Position.HIGH; - s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); - s3.points = {new Point(9, -17), new Point(2, 10), new Point(122, 31)}; + s3.title = new Text("Series 3"); s3.color = Color (0, 0, 1); + s3.points = {Point(9, -17), Point(2, 10), Point(122, 31)}; s3.axis_y.position = Axis.Position.HIGH; s1.axis_x.min = 0; s1.axis_x.max = 2; @@ -145,18 +145,18 @@ void plot_chart4 (Chart chart) { var now = new DateTime.now_local().to_unix(); var high = (uint64) (253000000000L); - s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0); - s1.points = {new Point(now, 70), new Point(now - 100000, 155), new Point(now + 100000, 30)}; + s1.title = new Text("Series 1"); s1.color = Color (1, 0, 0); + s1.points = {Point(now, 70), Point(now - 100000, 155), Point(now + 100000, 30)}; s1.axis_x.position = Axis.Position.HIGH; s1.axis_y.position = Axis.Position.HIGH; - s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0); - s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)}; + s2.title = new Text("Series 2"); s2.color = Color (0, 1, 0); + s2.points = {Point(5, -3), Point(25, -18), Point(-11, 173)}; s2.axis_y.position = Axis.Position.HIGH; - s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1); - s3.points = {new Point(high - 2 + 0.73, -17), new Point(high - 1 + 0.234, 10), new Point(high + 1 + 0.411, 31)}; + s3.title = new Text("Series 3"); s3.color = Color (0, 0, 1); + s3.points = {Point(high - 2 + 0.73, -17), Point(high - 1 + 0.234, 10), Point(high + 1 + 0.411, 31)}; s3.axis_y.position = Axis.Position.HIGH; - s4.title = new Text("Series 4"); s4.color = new Color (0.5, 0.3, 0.9); - s4.points = {new Point(high + 0.005, -19.05), new Point(high + 0.0051, 28), new Point(high + 0.0052, 55), new Point(high + 0.0053, 44)}; + s4.title = new Text("Series 4"); s4.color = Color (0.5, 0.3, 0.9); + s4.points = {Point(high + 0.005, -19.05), Point(high + 0.0051, 28), Point(high + 0.0052, 55), Point(high + 0.0053, 44)}; s4.axis_y.position = Axis.Position.HIGH; s1.axis_x.min = now - 100000; s1.axis_x.max = now + 100000; @@ -195,6 +195,14 @@ void plot_chart4 (Chart chart) { chart.series = { s1, s2, s3, s4 }; } +bool point_in_chart (Chart chart, double x, double y) { + if (x < chart.plot_area_x_min) return false; + if (x > chart.plot_area_x_max) return false; + if (y < chart.plot_area_y_min) return false; + if (y > chart.plot_area_y_max) return false; + return true; +} + int main (string[] args) { init (ref args); @@ -326,44 +334,95 @@ int main (string[] args) { } }); + bool draw_selection = false; + double sel_x0 = 0, sel_x1 = 0, sel_y0 = 0, sel_y1 = 0; + da.draw.connect((context) => { // user's pre draw operations here... + chart.context = context; - var ret = chart.draw(); + /*var ret = */chart.draw(); + // user's post draw operations here... - return ret; + if (draw_selection) { + context.set_source_rgba (0.5, 0.5, 0.5, 0.7); + context.set_line_join(Cairo.LineJoin.MITER); + context.set_line_cap(Cairo.LineCap.ROUND); + context.set_line_width(1); + //context.set_dash(null, 0); + context.rectangle (sel_x0, sel_y0, sel_x1 - sel_x0, sel_y1 - sel_y0); + //context.fill(); + context.stroke(); + } + + return true;//ret; }); da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); - da.button_release_event.connect((event) => { - // user's pre button_release_event operations here... - var ret = true;//chart.button_release_event(event); - // user's post button_release_event operations here... - return ret; - }); da.button_press_event.connect((event) => { // user's pre button_press_event operations here... - var ret = true;//chart.button_press_event(event); + //stdout.puts("pre_press\n"); + + if (event.button == 2 && point_in_chart(chart, event.x, event.y)) { + draw_selection = true; + sel_x0 = sel_x1 = event.x; + sel_y0 = sel_y1 = event.y; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + // user's post button_press_event operations here... - return ret; + //stdout.puts("post_press\n"); + + return true; // return ret; + }); + da.button_release_event.connect((event) => { + // user's pre button_release_event operations here... + //stdout.puts("pre_release\n"); + + //var ret = chart.button_release_event(event); + if (event.button == 2) { + draw_selection = false; + sel_x1 = event.x; + sel_y1 = event.y; + if (sel_x1 > sel_x0 && sel_y1 > sel_y0) + chart.zoom_in (sel_x0, sel_y0, sel_x1, sel_y1); + else + chart.zoom_out (); + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + + // user's post button_release_event operations here... + //stdout.puts("post_release\n"); + + return true; // return ret; }); da.motion_notify_event.connect((event) => { // user's pre motion_notify_event operations here... - var ret = true;//chart.motion_notify_event(event); + //stdout.puts("pre_motion\n"); + + //var ret = chart.motion_notify_event(event); + // user's post motion_notify_event operations here... - return ret; + //stdout.puts("post_motion\n"); + if (draw_selection && point_in_chart(chart, event.x, event.y)) { + sel_x1 = event.x; + sel_y1 = event.y; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + + return true; // return ret; }); da.add_events(Gdk.EventMask.SCROLL_MASK); da.scroll_event.connect((event) => { // user's pre scroll_notify_event operations here... - stdout.puts("pre_scroll\n"); + //stdout.puts("pre_scroll\n"); - var ret = true;//chart.scroll_notify_event(event); + //var ret = chart.scroll_notify_event(event); // user's post scroll_notify_event operations here... - stdout.puts("post_scroll\n"); + //stdout.puts("post_scroll\n"); - return ret; + return true; // return ret; }); var vbox2 = new Box(Orientation.VERTICAL, 0); From 42b846fa75fee632bc85ab33482af1fda294dae9 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Fri, 6 Oct 2017 22:46:28 +0300 Subject: [PATCH 12/40] Fix width/height when context is out of the Chart. --- src/Chart.vala | 17 ++++------------- test/ChartTest.vala | 2 ++ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 1328fe7..281f4a8 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -1,8 +1,8 @@ namespace Gtk.CairoChart { public class Chart { - protected double width = 0; - protected double height = 0; + public double width = 0; + public double height = 0; public Cairo.Context context = null; @@ -35,9 +35,9 @@ namespace Gtk.CairoChart { } protected double cur_x_min = 0.0; - protected double cur_x_max = 0.0; + protected double cur_x_max = 1.0; protected double cur_y_min = 0.0; - protected double cur_y_max = 0.0; + protected double cur_y_max = 1.0; public virtual void check_cur_values () { if (cur_x_min > cur_x_max) @@ -48,8 +48,6 @@ namespace Gtk.CairoChart { public virtual bool draw () { - update_size (); - draw_background (); cur_x_min = cur_y_min = 0.0; @@ -84,13 +82,6 @@ namespace Gtk.CairoChart { return true; } - protected virtual void update_size () { - if (context != null) { - width = context.copy_clip_rectangle_list().rectangles[0].width; - height = context.copy_clip_rectangle_list().rectangles[0].height; - } - } - protected virtual void set_source_rgba (Color color) { context.set_source_rgba (color.red, color.green, color.blue, color.alpha); } diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 1cfb4a9..f3db13a 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -341,6 +341,8 @@ int main (string[] args) { // user's pre draw operations here... chart.context = context; + chart.width = da.get_allocated_width(); + chart.height = da.get_allocated_height(); /*var ret = */chart.draw(); // user's post draw operations here... From d4d092c376c93dc5fdf6b2f3bdc10a4f4a00aee5 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Fri, 6 Oct 2017 22:46:51 +0300 Subject: [PATCH 13/40] Closes #140: Move feature. --- src/Chart.vala | 46 +++++++++++++++++++++++++++++++++++++++++++++ test/ChartTest.vala | 37 +++++++++++++++++++----------------- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 281f4a8..905a3c3 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -102,6 +102,11 @@ namespace Gtk.CairoChart { return new_series; } + double rel_zoom_x_min = 0.0; + double rel_zoom_x_max = 1.0; + double rel_zoom_y_min = 0.0; + double rel_zoom_y_max = 1.0; + public virtual void zoom_in (double x0, double y0, double x1, double y1) { for (var i = 0, max_i = zoom_series.length; i < max_i; ++i) { var s = zoom_series[i]; @@ -142,6 +147,15 @@ namespace Gtk.CairoChart { s.place.zoom_y_high = (s.axis_y.zoom_max - real_y1) / (real_y0 - real_y1); } } + + var new_rel_zoom_x_min = rel_zoom_x_min + (x0 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (rel_zoom_x_max - rel_zoom_x_min); + var new_rel_zoom_x_max = rel_zoom_x_min + (x1 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (rel_zoom_x_max - rel_zoom_x_min); + var new_rel_zoom_y_min = rel_zoom_y_min + (y0 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (rel_zoom_y_max - rel_zoom_y_min); + var new_rel_zoom_y_max = rel_zoom_y_min + (y1 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (rel_zoom_y_max - rel_zoom_y_min); + rel_zoom_x_min = new_rel_zoom_x_min; + rel_zoom_x_max = new_rel_zoom_x_max; + rel_zoom_y_min = new_rel_zoom_y_min; + rel_zoom_y_max = new_rel_zoom_y_max; } public virtual void zoom_out () { @@ -160,6 +174,34 @@ namespace Gtk.CairoChart { s.place.zoom_y_low = s.place.y_low; s.place.zoom_y_high = s.place.y_high; } + rel_zoom_x_min = 0; + rel_zoom_x_max = 1; + rel_zoom_y_min = 0; + rel_zoom_y_max = 1; + } + + public virtual void move (double delta_x, double delta_y) { + delta_x /= plot_area_x_max - plot_area_x_min; delta_x *= - 1.0; + delta_y /= plot_area_y_max - plot_area_y_min; delta_y *= - 1.0; + var rzxmin = rel_zoom_x_min, rzxmax = rel_zoom_x_max, rzymin = rel_zoom_y_min, rzymax = rel_zoom_y_max; + zoom_out(); + draw(); // TODO: optimize here + delta_x *= plot_area_x_max - plot_area_x_min; + delta_y *= plot_area_y_max - plot_area_y_min; + var xmin = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * rzxmin; + var xmax = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * rzxmax; + var ymin = plot_area_y_min + (plot_area_y_max - plot_area_y_min) * rzymin; + var ymax = plot_area_y_min + (plot_area_y_max - plot_area_y_min) * rzymax; + + delta_x *= rzxmax - rzxmin; delta_y *= rzymax - rzymin; + + if (xmin + delta_x < plot_area_x_min) delta_x = plot_area_x_min - xmin; + if (xmax + delta_x > plot_area_x_max) delta_x = plot_area_x_max - xmax; + if (ymin + delta_y < plot_area_y_min) delta_y = plot_area_y_min - ymin; + if (ymax + delta_y > plot_area_y_max) delta_y = plot_area_y_max - ymax; + + zoom_in (xmin + delta_x, ymin + delta_y, xmax + delta_x, ymax + delta_y); + draw(); // TODO: optimize here } protected double title_width = 0.0; @@ -1142,6 +1184,10 @@ namespace Gtk.CairoChart { chart.plot_area_x_min = this.plot_area_x_min; chart.plot_area_y_max = this.plot_area_y_max; chart.plot_area_y_min = this.plot_area_y_min; + chart.rel_zoom_x_min = this.rel_zoom_x_min; + chart.rel_zoom_x_max = this.rel_zoom_x_max; + chart.rel_zoom_y_min = this.rel_zoom_y_min; + chart.rel_zoom_y_max = this.rel_zoom_y_max; chart.selection_style = this.selection_style; chart.zoom_series = this.zoom_series.copy(); chart.show_legend = this.show_legend; diff --git a/test/ChartTest.vala b/test/ChartTest.vala index f3db13a..68a6378 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -336,6 +336,8 @@ int main (string[] args) { bool draw_selection = false; double sel_x0 = 0, sel_x1 = 0, sel_y0 = 0, sel_y1 = 0; + bool moving_chart = false; + double mov_x0 = 0, mov_y0 = 0; da.draw.connect((context) => { // user's pre draw operations here... @@ -362,8 +364,6 @@ int main (string[] args) { da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); da.button_press_event.connect((event) => { - // user's pre button_press_event operations here... - //stdout.puts("pre_press\n"); if (event.button == 2 && point_in_chart(chart, event.x, event.y)) { draw_selection = true; @@ -372,14 +372,16 @@ int main (string[] args) { da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } - // user's post button_press_event operations here... - //stdout.puts("post_press\n"); + if (event.button == 3 && point_in_chart(chart, event.x, event.y)) { + moving_chart = true; + mov_x0 = event.x; + mov_y0 = event.y; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } return true; // return ret; }); da.button_release_event.connect((event) => { - // user's pre button_release_event operations here... - //stdout.puts("pre_release\n"); //var ret = chart.button_release_event(event); if (event.button == 2) { @@ -393,37 +395,38 @@ int main (string[] args) { da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } - // user's post button_release_event operations here... - //stdout.puts("post_release\n"); + if (event.button == 3 && point_in_chart(chart, event.x, event.y)) { + moving_chart = false; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } return true; // return ret; }); da.motion_notify_event.connect((event) => { - // user's pre motion_notify_event operations here... - //stdout.puts("pre_motion\n"); //var ret = chart.motion_notify_event(event); - // user's post motion_notify_event operations here... - //stdout.puts("post_motion\n"); if (draw_selection && point_in_chart(chart, event.x, event.y)) { sel_x1 = event.x; sel_y1 = event.y; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } + if (moving_chart && point_in_chart(chart, event.x, event.y)) { + var delta_x = event.x - mov_x0, delta_y = event.y - mov_y0; + chart.move (delta_x, delta_y); + mov_x0 = event.x; + mov_y0 = event.y; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + return true; // return ret; }); da.add_events(Gdk.EventMask.SCROLL_MASK); da.scroll_event.connect((event) => { - // user's pre scroll_notify_event operations here... - //stdout.puts("pre_scroll\n"); //var ret = chart.scroll_notify_event(event); - // user's post scroll_notify_event operations here... - //stdout.puts("post_scroll\n"); - return true; // return ret; }); From 4b147c15ef3a09bb8c7c8556178b22b7c728a651 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Fri, 13 Oct 2017 16:39:07 +0300 Subject: [PATCH 14/40] Closes #138: Separate clear() method for pre_draw operations. --- src/Chart.vala | 6 ++++-- test/ChartTest.vala | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 905a3c3..dcae56a 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -46,9 +46,11 @@ namespace Gtk.CairoChart { cur_y_max = cur_y_min; } - public virtual bool draw () { - + public virtual void clear () { draw_background (); + } + + public virtual bool draw () { cur_x_min = cur_y_min = 0.0; cur_x_max = width; diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 68a6378..deb3872 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -340,11 +340,14 @@ int main (string[] args) { double mov_x0 = 0, mov_y0 = 0; da.draw.connect((context) => { - // user's pre draw operations here... - chart.context = context; chart.width = da.get_allocated_width(); chart.height = da.get_allocated_height(); + chart.clear(); + + // user's pre draw operations here... + // ... + /*var ret = */chart.draw(); // user's post draw operations here... From ede419392df36c30530a6add48d547a9dce5554c Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Sat, 21 Oct 2017 20:56:08 +0300 Subject: [PATCH 15/40] Closes #137: Custom Chart position. --- src/Chart.vala | 13 ++++++++----- test/CMakeLists.txt | 1 + test/ChartTest.vala | 14 +++++++++----- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index dcae56a..bc2086b 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -1,8 +1,10 @@ namespace Gtk.CairoChart { public class Chart { - public double width = 0; - public double height = 0; + public double x_min = 0.0; + public double y_min = 0.0; + public double width = 0.0; + public double height = 0.0; public Cairo.Context context = null; @@ -52,9 +54,10 @@ namespace Gtk.CairoChart { public virtual bool draw () { - cur_x_min = cur_y_min = 0.0; - cur_x_max = width; - cur_y_max = height; + cur_x_min = x_min; + cur_y_min = y_min; + cur_x_max = x_min + width; + cur_y_max = y_min + height; draw_chart_title (); check_cur_values (); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 417fe08..fc3fae5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,7 @@ SET (BinName chart_test) FILE (GLOB_RECURSE BinSources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ChartTest.vala) SET (BinPackages gtk+-3.0) +SET (BinPkgModules gtk+-3.0) SET (BinCustomVapis ${CMAKE_BINARY_DIR}/src/${PROJECT_LOWERCASE_NAME}-${MAJOR}.vapi ${CMAKE_SOURCE_DIR}/src/cairo-chart-float128type.vapi) SET (BinLinkLibs ${PROJECT_LOWERCASE_NAME}) INCLUDE_DIRECTORIES ("${CMAKE_BINARY_DIR}/src;${CMAKE_SOURCE_DIR}/src") diff --git a/test/ChartTest.vala b/test/ChartTest.vala index deb3872..099bb43 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -367,15 +367,16 @@ int main (string[] args) { da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); da.button_press_event.connect((event) => { + if (!point_in_chart(chart, event.x, event.y)) return true; - if (event.button == 2 && point_in_chart(chart, event.x, event.y)) { + if (event.button == 2) { draw_selection = true; sel_x0 = sel_x1 = event.x; sel_y0 = sel_y1 = event.y; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } - if (event.button == 3 && point_in_chart(chart, event.x, event.y)) { + if (event.button == 3) { moving_chart = true; mov_x0 = event.x; mov_y0 = event.y; @@ -386,6 +387,8 @@ int main (string[] args) { }); da.button_release_event.connect((event) => { + if (!point_in_chart(chart, event.x, event.y)) return true; + //var ret = chart.button_release_event(event); if (event.button == 2) { draw_selection = false; @@ -398,7 +401,7 @@ int main (string[] args) { da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } - if (event.button == 3 && point_in_chart(chart, event.x, event.y)) { + if (event.button == 3) { moving_chart = false; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } @@ -406,16 +409,17 @@ int main (string[] args) { return true; // return ret; }); da.motion_notify_event.connect((event) => { + if (!point_in_chart(chart, event.x, event.y)) return true; //var ret = chart.motion_notify_event(event); - if (draw_selection && point_in_chart(chart, event.x, event.y)) { + if (draw_selection) { sel_x1 = event.x; sel_y1 = event.y; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); } - if (moving_chart && point_in_chart(chart, event.x, event.y)) { + if (moving_chart) { var delta_x = event.x - mov_x0, delta_y = event.y - mov_y0; chart.move (delta_x, delta_y); mov_x0 = event.x; From 37e72a0d75845dc22b7fe628fd824edff8f83c92 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Wed, 25 Oct 2017 19:31:30 +0300 Subject: [PATCH 16/40] Closes #141: Make zoom_ fields atomic. --- src/Chart.vala | 155 ++++++++++++++++++++++---------------------- src/Series.vala | 5 +- test/ChartTest.vala | 8 ++- 3 files changed, 87 insertions(+), 81 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index bc2086b..71ad60f 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -16,19 +16,7 @@ namespace Gtk.CairoChart { public Legend legend = new Legend (); - Series[] _series = {}; - public Series[] series { - get { return _series; } - set { - _series = value.copy(); - zoom_series = value.copy(); - for (var i = 0; i < value.length; ++i) { - _series[i] = value[i].copy(); - zoom_series[i] = value[i].copy(); - } - } - } - public Series[] zoom_series = {}; + public Series[] series = {}; protected LineStyle selection_style = LineStyle (); @@ -99,32 +87,25 @@ namespace Gtk.CairoChart { } } - Series[] rm_series_by_idx (Series[] series, int idx) { - Series[] new_series = series.copy(); - for (var i = idx + 1; i < series.length; ++i) - new_series[i - 1] = series[i]; - new_series.length--; - return new_series; - } - double rel_zoom_x_min = 0.0; double rel_zoom_x_max = 1.0; double rel_zoom_y_min = 0.0; double rel_zoom_y_max = 1.0; + int zoom_first_show = 0; + public virtual void zoom_in (double x0, double y0, double x1, double y1) { - for (var i = 0, max_i = zoom_series.length; i < max_i; ++i) { - var s = zoom_series[i]; + for (var si = 0, max_i = series.length; si < max_i; ++si) { + var s = series[si]; + if (!s.zoom_show) continue; var real_x0 = get_real_x (s, x0); var real_x1 = get_real_x (s, x1); var real_y0 = get_real_y (s, y0); var real_y1 = get_real_y (s, y1); - // if selected square does not intersect with the zoom_series's square + // if selected square does not intersect with the series's square if ( real_x1 <= s.axis_x.zoom_min || real_x0 >= s.axis_x.zoom_max || real_y0 <= s.axis_y.zoom_min || real_y1 >= s.axis_y.zoom_max) { - zoom_series = rm_series_by_idx (zoom_series, i); - --i; - --max_i; + s.zoom_show = false; continue; } if (real_x0 >= s.axis_x.zoom_min) { @@ -153,6 +134,13 @@ namespace Gtk.CairoChart { } } + zoom_first_show = 0; + for (var si = 0, max_i = series.length; si < max_i; ++si) + if (series[si].zoom_show) { + zoom_first_show = si; + break; + } + var new_rel_zoom_x_min = rel_zoom_x_min + (x0 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (rel_zoom_x_max - rel_zoom_x_min); var new_rel_zoom_x_max = rel_zoom_x_min + (x1 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (rel_zoom_x_max - rel_zoom_x_min); var new_rel_zoom_y_min = rel_zoom_y_min + (y0 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (rel_zoom_y_max - rel_zoom_y_min); @@ -164,12 +152,8 @@ namespace Gtk.CairoChart { } public virtual void zoom_out () { - zoom_series = _series.copy(); - for (var i = 0; i < _series.length; ++i) { - _series[i] = _series[i].copy(); - zoom_series[i] = _series[i].copy(); - } - foreach (var s in zoom_series) { + foreach (var s in series) { + s.zoom_show = true; s.axis_x.zoom_min = s.axis_x.min; s.axis_x.zoom_max = s.axis_x.max; s.axis_y.zoom_min = s.axis_y.min; @@ -183,6 +167,8 @@ namespace Gtk.CairoChart { rel_zoom_x_max = 1; rel_zoom_y_min = 0; rel_zoom_y_max = 1; + + zoom_first_show = 0; } public virtual void move (double delta_x, double delta_y) { @@ -367,7 +353,9 @@ namespace Gtk.CairoChart { break; } - foreach (var s in zoom_series) { + foreach (var s in series) { + + if (!s.zoom_show) continue; // carry switch (legend.position) { @@ -396,12 +384,12 @@ namespace Gtk.CairoChart { var x = legend_x0 + leg_width_sum + (leg_width_sum == 0.0 ? 0.0 : legend_text_hspace); var y = legend_y0 + leg_height_sum + max_font_heights[heights_idx]; - // zoom_series title + // series title context.move_to (x + legend_line_length - s.title.get_x_bearing(context), y); set_source_rgba (s.title.color); show_text(s.title); - // zoom_series line style + // series line style context.move_to (x, y - s.title.get_height(context) / 2); set_line_style(s.line_style); context.rel_line_to (legend_line_length, 0); @@ -549,8 +537,8 @@ namespace Gtk.CairoChart { } protected virtual void set_vertical_axes_titles () { - for (var i = 0; i < zoom_series.length; ++i) { - var s = zoom_series[i]; + for (var si = 0; si < series.length; ++si) { + var s = series[si]; s.axis_y.title.style.orientation = FontOrient.VERTICAL; } } @@ -563,28 +551,32 @@ namespace Gtk.CairoChart { // Check for common axes common_x_axes = common_y_axes = true; - for (int si = zoom_series.length - 1; si >=0; --si) { - var s = zoom_series[si]; - if ( s.axis_x.position != zoom_series[0].axis_x.position - || s.axis_x.zoom_min != zoom_series[0].axis_x.zoom_min - || s.axis_x.zoom_max != zoom_series[0].axis_x.zoom_max - || s.place.zoom_x_low != zoom_series[0].place.zoom_x_low - || s.place.zoom_x_high != zoom_series[0].place.zoom_x_high - || s.axis_x.type != zoom_series[0].axis_x.type) + int nzoom_series_show = 0; + for (var si = series.length - 1; si >=0; --si) { + var s = series[si]; + if (!s.zoom_show) continue; + ++nzoom_series_show; + if ( s.axis_x.position != series[0].axis_x.position + || s.axis_x.zoom_min != series[0].axis_x.zoom_min + || s.axis_x.zoom_max != series[0].axis_x.zoom_max + || s.place.zoom_x_low != series[0].place.zoom_x_low + || s.place.zoom_x_high != series[0].place.zoom_x_high + || s.axis_x.type != series[0].axis_x.type) common_x_axes = false; - if ( s.axis_y.position != zoom_series[0].axis_y.position - || s.axis_y.zoom_min != zoom_series[0].axis_y.zoom_min - || s.axis_y.zoom_max != zoom_series[0].axis_y.zoom_max - || s.place.zoom_y_low != zoom_series[0].place.zoom_y_low - || s.place.zoom_y_high != zoom_series[0].place.zoom_y_high) + if ( s.axis_y.position != series[0].axis_y.position + || s.axis_y.zoom_min != series[0].axis_y.zoom_min + || s.axis_y.zoom_max != series[0].axis_y.zoom_max + || s.place.zoom_y_low != series[0].place.zoom_y_low + || s.place.zoom_y_high != series[0].place.zoom_y_high) common_y_axes = false; } - if (zoom_series.length == 1) common_x_axes = common_y_axes = false; + if (nzoom_series_show == 1) common_x_axes = common_y_axes = false; // Join and calc X-axes - for (int si = zoom_series.length - 1, nskip = 0; si >=0; --si) { + for (var si = series.length - 1, nskip = 0; si >=0; --si) { + var s = series[si]; + if (!s.zoom_show) continue; if (nskip != 0) {--nskip; continue;} - var s = zoom_series[si]; double max_rec_width = 0; double max_rec_height = 0; calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true); var max_font_indent = s.axis_x.font_indent; @@ -592,10 +584,12 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { - var s2 = zoom_series[sj]; + var s2 = series[sj]; + if (!s2.zoom_show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { - var s3 = zoom_series[sk]; + var s3 = series[sk]; + if (!s3.zoom_show) continue; if (are_intersect(s2.place.zoom_x_low, s2.place.zoom_x_high, s3.place.zoom_x_low, s3.place.zoom_x_high) || s2.axis_x.position != s3.axis_x.position || s2.axis_x.type != s3.axis_x.type) { @@ -617,7 +611,7 @@ namespace Gtk.CairoChart { } } - if (!common_x_axes || si == 0) + if (!common_x_axes || si == zoom_first_show) switch (s.axis_x.position) { case Axis.Position.LOW: plot_area_y_max -= max_rec_height + max_font_indent + max_axis_font_height; break; case Axis.Position.HIGH: plot_area_y_min += max_rec_height + max_font_indent + max_axis_font_height; break; @@ -627,9 +621,10 @@ namespace Gtk.CairoChart { } // Join and calc Y-axes - for (int si = zoom_series.length - 1, nskip = 0; si >=0; --si) { + for (var si = series.length - 1, nskip = 0; si >=0; --si) { + var s = series[si]; + if (!s.zoom_show) continue; if (nskip != 0) {--nskip; continue;} - var s = zoom_series[si]; double max_rec_width = 0; double max_rec_height = 0; calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false); var max_font_indent = s.axis_y.font_indent; @@ -637,10 +632,12 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { - var s2 = zoom_series[sj]; + var s2 = series[sj]; + if (!s2.zoom_show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { - var s3 = zoom_series[sk]; + var s3 = series[sk]; + if (!s3.zoom_show) continue; if (are_intersect(s2.place.zoom_y_low, s2.place.zoom_y_high, s3.place.zoom_y_low, s3.place.zoom_y_high) || s2.axis_y.position != s3.axis_y.position || s2.axis_x.type != s3.axis_x.type) { @@ -662,7 +659,7 @@ namespace Gtk.CairoChart { } } - if (!common_y_axes || si == 0) + if (!common_y_axes || si == zoom_first_show) switch (s.axis_y.position) { case Axis.Position.LOW: plot_area_x_min += max_rec_width + max_font_indent + max_axis_font_width; break; case Axis.Position.HIGH: plot_area_x_max -= max_rec_width + max_font_indent + max_axis_font_width; break; @@ -679,9 +676,10 @@ namespace Gtk.CairoChart { } protected virtual void draw_horizontal_axis () { - for (int si = zoom_series.length - 1, nskip = 0; si >=0; --si) { - if (common_x_axes && si != 0) continue; - var s = zoom_series[si]; + for (var si = series.length - 1, nskip = 0; si >=0; --si) { + var s = series[si]; + if (!s.zoom_show) continue; + if (common_x_axes && si != zoom_first_show) continue; // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. double max_rec_width, max_rec_height; calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true); @@ -830,10 +828,12 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { - var s2 = zoom_series[sj]; + var s2 = series[sj]; + if (!s2.zoom_show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { - var s3 = zoom_series[sk]; + var s3 = series[sk]; + if (!s3.zoom_show) continue; if (are_intersect(s2.place.zoom_x_low, s2.place.zoom_x_high, s3.place.zoom_x_low, s3.place.zoom_x_high) || s2.axis_x.position != s3.axis_x.position || s2.axis_x.type != s3.axis_x.type) { @@ -867,9 +867,10 @@ namespace Gtk.CairoChart { } protected virtual void draw_vertical_axis () { - for (int si = zoom_series.length - 1, nskip = 0; si >=0; --si) { - if (common_y_axes && si != 0) continue; - var s = zoom_series[si]; + for (var si = series.length - 1, nskip = 0; si >=0; --si) { + var s = series[si]; + if (!s.zoom_show) continue; + if (common_y_axes && si != zoom_first_show) continue; // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. double max_rec_width, max_rec_height; calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false); @@ -970,10 +971,12 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { - var s2 = zoom_series[sj]; + var s2 = series[sj]; + if (!s2.zoom_show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { - var s3 = zoom_series[sk]; + var s3 = series[sk]; + if (!s3.zoom_show) continue; if (are_intersect(s2.place.zoom_y_low, s2.place.zoom_y_high, s3.place.zoom_y_low, s3.place.zoom_y_high) || s2.axis_y.position != s3.axis_y.position) { has_intersection = true; @@ -1119,8 +1122,9 @@ namespace Gtk.CairoChart { } protected virtual void draw_series () { - for (int si = 0; si < zoom_series.length; ++si) { - var s = zoom_series[si]; + for (var si = 0; si < series.length; ++si) { + var s = series[si]; + if (!s.zoom_show) continue; if (s.points.length == 0) continue; var points = s.points.copy(); switch(s.sort) { @@ -1140,7 +1144,7 @@ namespace Gtk.CairoChart { break; } set_line_style(s.line_style); - // draw zoom_series line + // draw series line for (int i = 1; i < points.length; ++i) { Point c, d; if (cut_line (Point(get_scr_x(s, points[i - 1].x), get_scr_y(s, points[i - 1].y)), @@ -1194,7 +1198,6 @@ namespace Gtk.CairoChart { chart.rel_zoom_y_min = this.rel_zoom_y_min; chart.rel_zoom_y_max = this.rel_zoom_y_max; chart.selection_style = this.selection_style; - chart.zoom_series = this.zoom_series.copy(); chart.show_legend = this.show_legend; chart.title = this.title.copy().copy(); chart.title_height = this.title_height; diff --git a/src/Series.vala b/src/Series.vala index 7a568b7..2d4bc89 100644 --- a/src/Series.vala +++ b/src/Series.vala @@ -31,7 +31,6 @@ namespace Gtk.CairoChart { public Grid grid = new Grid (); - public GLib.List cursors = new List (); public LineStyle line_style = LineStyle (); protected Color _color = Color (0.0, 0.0, 0.0, 1.0); @@ -50,12 +49,13 @@ namespace Gtk.CairoChart { default = Color (0.0, 0.0, 0.0, 1.0); } + public bool zoom_show = true; + public Series copy () { var series = new Series (); series._color = this._color; series.axis_x = this.axis_x.copy (); series.axis_y = this.axis_y.copy (); - series.cursors = this.cursors.copy (); series.grid = this.grid.copy (); series.line_style = this.line_style; series.marker_type = this.marker_type; @@ -63,6 +63,7 @@ namespace Gtk.CairoChart { series.points = this.points.copy(); series.sort = this.sort; series.title = this.title.copy(); + series.zoom_show = this.zoom_show; return series; } diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 099bb43..9a3e203 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -223,8 +223,8 @@ int main (string[] args) { var button3 = new Button.with_label("Common Y axes"); var button4 = new Button.with_label("Dates/Times"); var button5 = new Button.with_label("rm Axis Titles"); - var button6 = new Button.with_label("rm Dates"); - var button7 = new Button.with_label("rm Times"); + var button6 = new Button.with_label("Dates only"); + var button7 = new Button.with_label("Times only"); plot_chart1 (chart1); plot_chart2 (chart2); @@ -295,7 +295,8 @@ int main (string[] args) { button6.clicked.connect (() => { for (var i = 0; i < chart.series.length; ++i) { var s = chart.series[i]; - s.axis_x.date_format = ""; + s.axis_x.date_format = "%Y.%m.%d"; + s.axis_x.time_format = ""; } da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); }); @@ -304,6 +305,7 @@ int main (string[] args) { for (var i = 0; i < chart.series.length; ++i) { var s = chart.series[i]; s.axis_x.time_format = ""; + s.axis_x.date_format = "%H:%M:%S"; } da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); }); From 52e7e4f029b0db51fa26c9b8f1e63d22d4084530 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Fri, 27 Oct 2017 18:51:45 +0300 Subject: [PATCH 17/40] Date+Time button added. --- test/ChartTest.vala | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 9a3e203..11c88a7 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -225,6 +225,7 @@ int main (string[] args) { var button5 = new Button.with_label("rm Axis Titles"); var button6 = new Button.with_label("Dates only"); var button7 = new Button.with_label("Times only"); + var button8 = new Button.with_label("Date+Time"); plot_chart1 (chart1); plot_chart2 (chart2); @@ -304,8 +305,17 @@ int main (string[] args) { button7.clicked.connect (() => { for (var i = 0; i < chart.series.length; ++i) { var s = chart.series[i]; - s.axis_x.time_format = ""; - s.axis_x.date_format = "%H:%M:%S"; + s.axis_x.date_format = ""; + s.axis_x.time_format = "%H:%M:%S"; + } + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + }); + + button8.clicked.connect (() => { + for (var i = 0; i < chart.series.length; ++i) { + var s = chart.series[i]; + s.axis_x.date_format = "%Y.%m.%d"; + s.axis_x.time_format = "%H:%M:%S"; } da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); }); @@ -447,6 +457,7 @@ int main (string[] args) { vbox2.pack_start(button5, false, false, 0); vbox2.pack_start(button6, false, false, 0); vbox2.pack_start(button7, false, false, 0); + vbox2.pack_start(button8, false, false, 0); vbox2.pack_start(radio_button1, false, false, 0); vbox2.pack_start(radio_button2, false, false, 0); vbox2.pack_start(radio_button3, false, false, 0); From 8a7ce14f5d397f0cfd6d775572f04e20d6de9656 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Wed, 13 Dec 2017 15:37:42 +0300 Subject: [PATCH 18/40] Move() optimizations: spare draw() calls removed. --- src/Chart.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 71ad60f..909846f 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -176,7 +176,7 @@ namespace Gtk.CairoChart { delta_y /= plot_area_y_max - plot_area_y_min; delta_y *= - 1.0; var rzxmin = rel_zoom_x_min, rzxmax = rel_zoom_x_max, rzymin = rel_zoom_y_min, rzymax = rel_zoom_y_max; zoom_out(); - draw(); // TODO: optimize here + //draw(); // TODO: optimize here delta_x *= plot_area_x_max - plot_area_x_min; delta_y *= plot_area_y_max - plot_area_y_min; var xmin = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * rzxmin; @@ -192,7 +192,7 @@ namespace Gtk.CairoChart { if (ymax + delta_y > plot_area_y_max) delta_y = plot_area_y_max - ymax; zoom_in (xmin + delta_x, ymin + delta_y, xmax + delta_x, ymax + delta_y); - draw(); // TODO: optimize here + //draw(); // TODO: optimize here } protected double title_width = 0.0; From b465034bc34a94a92bd0287abd98209a9e8621c1 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Tue, 28 Nov 2017 21:41:25 +0300 Subject: [PATCH 19/40] Several cursors methods added. Make rel_zoom_{x,y}_m{in,ax} readonly properties. --- src/Chart.vala | 317 +++++++++++++++++++++++++++++++++++--------- src/Point.vala | 2 +- src/Series.vala | 4 +- test/ChartTest.vala | 110 ++++++++++++--- 4 files changed, 352 insertions(+), 81 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 909846f..51d62c3 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -87,17 +87,21 @@ namespace Gtk.CairoChart { } } - double rel_zoom_x_min = 0.0; - double rel_zoom_x_max = 1.0; - double rel_zoom_y_min = 0.0; - double rel_zoom_y_max = 1.0; + double _rel_zoom_x_min = 0.0; + double _rel_zoom_x_max = 1.0; + double _rel_zoom_y_min = 0.0; + double _rel_zoom_y_max = 1.0; + public double rel_zoom_x_min { get { return _rel_zoom_x_min; } default = 0.0; } + public double rel_zoom_x_max { get { return _rel_zoom_x_max; } default = 1.0; } + public double rel_zoom_y_min { get { return _rel_zoom_y_min; } default = 0.0; } + public double rel_zoom_y_max { get { return _rel_zoom_y_max; } default = 1.0; } int zoom_first_show = 0; public virtual void zoom_in (double x0, double y0, double x1, double y1) { for (var si = 0, max_i = series.length; si < max_i; ++si) { var s = series[si]; - if (!s.zoom_show) continue; + if (!s.show) continue; var real_x0 = get_real_x (s, x0); var real_x1 = get_real_x (s, x1); var real_y0 = get_real_y (s, y0); @@ -105,7 +109,7 @@ namespace Gtk.CairoChart { // if selected square does not intersect with the series's square if ( real_x1 <= s.axis_x.zoom_min || real_x0 >= s.axis_x.zoom_max || real_y0 <= s.axis_y.zoom_min || real_y1 >= s.axis_y.zoom_max) { - s.zoom_show = false; + s.show = false; continue; } if (real_x0 >= s.axis_x.zoom_min) { @@ -136,24 +140,24 @@ namespace Gtk.CairoChart { zoom_first_show = 0; for (var si = 0, max_i = series.length; si < max_i; ++si) - if (series[si].zoom_show) { + if (series[si].show) { zoom_first_show = si; break; } - var new_rel_zoom_x_min = rel_zoom_x_min + (x0 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (rel_zoom_x_max - rel_zoom_x_min); - var new_rel_zoom_x_max = rel_zoom_x_min + (x1 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (rel_zoom_x_max - rel_zoom_x_min); - var new_rel_zoom_y_min = rel_zoom_y_min + (y0 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (rel_zoom_y_max - rel_zoom_y_min); - var new_rel_zoom_y_max = rel_zoom_y_min + (y1 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (rel_zoom_y_max - rel_zoom_y_min); - rel_zoom_x_min = new_rel_zoom_x_min; - rel_zoom_x_max = new_rel_zoom_x_max; - rel_zoom_y_min = new_rel_zoom_y_min; - rel_zoom_y_max = new_rel_zoom_y_max; + var new_rel_zoom_x_min = _rel_zoom_x_min + (x0 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (_rel_zoom_x_max - _rel_zoom_x_min); + var new_rel_zoom_x_max = _rel_zoom_x_min + (x1 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (_rel_zoom_x_max - _rel_zoom_x_min); + var new_rel_zoom_y_min = _rel_zoom_y_min + (y0 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (_rel_zoom_y_max - _rel_zoom_y_min); + var new_rel_zoom_y_max = _rel_zoom_y_min + (y1 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (_rel_zoom_y_max - _rel_zoom_y_min); + _rel_zoom_x_min = new_rel_zoom_x_min; + _rel_zoom_x_max = new_rel_zoom_x_max; + _rel_zoom_y_min = new_rel_zoom_y_min; + _rel_zoom_y_max = new_rel_zoom_y_max; } public virtual void zoom_out () { foreach (var s in series) { - s.zoom_show = true; + s.show = true; s.axis_x.zoom_min = s.axis_x.min; s.axis_x.zoom_max = s.axis_x.max; s.axis_y.zoom_min = s.axis_y.min; @@ -163,10 +167,10 @@ namespace Gtk.CairoChart { s.place.zoom_y_low = s.place.y_low; s.place.zoom_y_high = s.place.y_high; } - rel_zoom_x_min = 0; - rel_zoom_x_max = 1; - rel_zoom_y_min = 0; - rel_zoom_y_max = 1; + _rel_zoom_x_min = 0; + _rel_zoom_x_max = 1; + _rel_zoom_y_min = 0; + _rel_zoom_y_max = 1; zoom_first_show = 0; } @@ -174,7 +178,7 @@ namespace Gtk.CairoChart { public virtual void move (double delta_x, double delta_y) { delta_x /= plot_area_x_max - plot_area_x_min; delta_x *= - 1.0; delta_y /= plot_area_y_max - plot_area_y_min; delta_y *= - 1.0; - var rzxmin = rel_zoom_x_min, rzxmax = rel_zoom_x_max, rzymin = rel_zoom_y_min, rzymax = rel_zoom_y_max; + var rzxmin = _rel_zoom_x_min, rzxmax = _rel_zoom_x_max, rzymin = _rel_zoom_y_min, rzymax = _rel_zoom_y_max; zoom_out(); //draw(); // TODO: optimize here delta_x *= plot_area_x_max - plot_area_x_min; @@ -355,7 +359,7 @@ namespace Gtk.CairoChart { foreach (var s in series) { - if (!s.zoom_show) continue; + if (!s.show) continue; // carry switch (legend.position) { @@ -554,7 +558,7 @@ namespace Gtk.CairoChart { int nzoom_series_show = 0; for (var si = series.length - 1; si >=0; --si) { var s = series[si]; - if (!s.zoom_show) continue; + if (!s.show) continue; ++nzoom_series_show; if ( s.axis_x.position != series[0].axis_x.position || s.axis_x.zoom_min != series[0].axis_x.zoom_min @@ -575,7 +579,7 @@ namespace Gtk.CairoChart { // Join and calc X-axes for (var si = series.length - 1, nskip = 0; si >=0; --si) { var s = series[si]; - if (!s.zoom_show) continue; + if (!s.show) continue; if (nskip != 0) {--nskip; continue;} double max_rec_width = 0; double max_rec_height = 0; calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true); @@ -585,11 +589,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { var s2 = series[sj]; - if (!s2.zoom_show) continue; + if (!s2.show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { var s3 = series[sk]; - if (!s3.zoom_show) continue; + if (!s3.show) continue; if (are_intersect(s2.place.zoom_x_low, s2.place.zoom_x_high, s3.place.zoom_x_low, s3.place.zoom_x_high) || s2.axis_x.position != s3.axis_x.position || s2.axis_x.type != s3.axis_x.type) { @@ -623,7 +627,7 @@ namespace Gtk.CairoChart { // Join and calc Y-axes for (var si = series.length - 1, nskip = 0; si >=0; --si) { var s = series[si]; - if (!s.zoom_show) continue; + if (!s.show) continue; if (nskip != 0) {--nskip; continue;} double max_rec_width = 0; double max_rec_height = 0; calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false); @@ -633,11 +637,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { var s2 = series[sj]; - if (!s2.zoom_show) continue; + if (!s2.show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { var s3 = series[sk]; - if (!s3.zoom_show) continue; + if (!s3.show) continue; if (are_intersect(s2.place.zoom_y_low, s2.place.zoom_y_high, s3.place.zoom_y_low, s3.place.zoom_y_high) || s2.axis_y.position != s3.axis_y.position || s2.axis_x.type != s3.axis_x.type) { @@ -678,7 +682,7 @@ namespace Gtk.CairoChart { protected virtual void draw_horizontal_axis () { for (var si = series.length - 1, nskip = 0; si >=0; --si) { var s = series[si]; - if (!s.zoom_show) continue; + if (!s.show) continue; if (common_x_axes && si != zoom_first_show) continue; // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. double max_rec_width, max_rec_height; @@ -829,11 +833,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { var s2 = series[sj]; - if (!s2.zoom_show) continue; + if (!s2.show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { var s3 = series[sk]; - if (!s3.zoom_show) continue; + if (!s3.show) continue; if (are_intersect(s2.place.zoom_x_low, s2.place.zoom_x_high, s3.place.zoom_x_low, s3.place.zoom_x_high) || s2.axis_x.position != s3.axis_x.position || s2.axis_x.type != s3.axis_x.type) { @@ -869,7 +873,7 @@ namespace Gtk.CairoChart { protected virtual void draw_vertical_axis () { for (var si = series.length - 1, nskip = 0; si >=0; --si) { var s = series[si]; - if (!s.zoom_show) continue; + if (!s.show) continue; if (common_y_axes && si != zoom_first_show) continue; // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. double max_rec_width, max_rec_height; @@ -972,11 +976,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { var s2 = series[sj]; - if (!s2.zoom_show) continue; + if (!s2.show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { var s3 = series[sk]; - if (!s3.zoom_show) continue; + if (!s3.show) continue; if (are_intersect(s2.place.zoom_y_low, s2.place.zoom_y_high, s3.place.zoom_y_low, s3.place.zoom_y_high) || s2.axis_y.position != s3.axis_y.position) { has_intersection = true; @@ -1027,16 +1031,21 @@ namespace Gtk.CairoChart { return plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.zoom_y_low + (y - s.axis_y.zoom_min) / (s.axis_y.zoom_max - s.axis_y.zoom_min) * (s.place.zoom_y_high - s.place.zoom_y_low)); } + protected virtual Point get_scr_point (Series s, Point p) { + return Point (get_scr_x(s, p.x), get_scr_y(s, p.y)); + } protected virtual Float128 get_real_x (Series s, double scr_x) { return s.axis_x.zoom_min + ((scr_x - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) - s.place.zoom_x_low) * (s.axis_x.zoom_max - s.axis_x.zoom_min) / (s.place.zoom_x_high - s.place.zoom_x_low); } - protected virtual Float128 get_real_y (Series s, double scr_y) { return s.axis_y.zoom_min + ((plot_area_y_max - scr_y) / (plot_area_y_max - plot_area_y_min) - s.place.zoom_y_low) * (s.axis_y.zoom_max - s.axis_y.zoom_min) / (s.place.zoom_y_high - s.place.zoom_y_low); } + protected virtual Point get_real_point (Series s, Point p) { + return Point (get_real_x(s, p.x), get_real_y(s, p.y)); + } protected virtual bool point_in_rect (Point p, double x0, double x1, double y0, double y1) { if ( (x0 <= p.x <= x1 || x1 <= p.x <= x0) @@ -1072,7 +1081,7 @@ namespace Gtk.CairoChart { } delegate int PointComparator(Point a, Point b); - void sort_points(Point[] points, PointComparator compare) { + void sort_points_delegate(Point[] points, PointComparator compare) { for(var i = 0; i < points.length; ++i) { for(var j = i + 1; j < points.length; ++j) { if(compare(points[i], points[j]) > 0) { @@ -1121,28 +1130,33 @@ namespace Gtk.CairoChart { return false; } + protected virtual Point[] sort_points (Series s) { + var points = s.points.copy(); + switch(s.sort) { + case Series.Sort.BY_X: + sort_points_delegate(points, (a, b) => { + if (a.x < b.x) return -1; + if (a.x > b.x) return 1; + return 0; + }); + break; + case Series.Sort.BY_Y: + sort_points_delegate(points, (a, b) => { + if (a.y < b.y) return -1; + if (a.y > b.y) return 1; + return 0; + }); + break; + } + return points; + } + protected virtual void draw_series () { for (var si = 0; si < series.length; ++si) { var s = series[si]; - if (!s.zoom_show) continue; + if (!s.show) continue; if (s.points.length == 0) continue; - var points = s.points.copy(); - switch(s.sort) { - case Series.Sort.BY_X: - sort_points(points, (a, b) => { - if (a.x < b.x) return -1; - if (a.x > b.x) return 1; - return 0; - }); - break; - case Series.Sort.BY_Y: - sort_points(points, (a, b) => { - if (a.y < b.y) return -1; - if (a.y > b.y) return 1; - return 0; - }); - break; - } + var points = sort_points(s); set_line_style(s.line_style); // draw series line for (int i = 1; i < points.length; ++i) { @@ -1164,8 +1178,193 @@ namespace Gtk.CairoChart { } } + protected List cursors = new List (); + protected Point active_cursor = Point (); + protected bool is_cursor_active = false; + + public virtual void set_active_cursor (double x, double y, bool remove = false) { + active_cursor = Point (scr2rel_x(x), scr2rel_y(y)); + is_cursor_active = true; + } + + public virtual void add_active_cursor () { + cursors.append (active_cursor); + is_cursor_active = false; + } + + public enum CursorOrientation { + VERTICAL = 0, // default + HORIZONTAL + } + + public CursorOrientation cursors_orientation = CursorOrientation.VERTICAL; + + public double cursor_max_distance = 32; + + public virtual void remove_active_cursor () { + if (cursors.length() == 0) return; + var distance = width * width; + uint rm_indx = 0; + uint i = 0; + foreach (var c in cursors) { + double d = distance; + switch (cursors_orientation) { + case CursorOrientation.VERTICAL: + d = (c.x - active_cursor.x).abs(); + break; + case CursorOrientation.HORIZONTAL: + d = (c.y - active_cursor.y).abs(); + break; + default: + break; + } + if (distance > d) { + distance = d; + rm_indx = i; + } + ++i; + } + if (distance < cursor_max_distance) + cursors.delete_link(cursors.nth(rm_indx)); + } + + // TODO: + protected virtual bool get_cursor_limits (Series s, Point cursor, out Point low, out Point high) { + var points = sort_points (s); + bool ret = false; + low.x = plot_area_x_min; + low.y = plot_area_y_min; + high.x = plot_area_x_max; + high.y = plot_area_y_max; + for (var i = 0; i + 1 < points.length; ++i) { + switch (cursors_orientation) { + case CursorOrientation.VERTICAL: + Float128 y = 0.0; + if (vcross(get_scr_point(s, s.points[i]), get_scr_point(s, s.points[i+1]), cursor.x, plot_area_y_min, plot_area_y_max, out y)) { + if (y < low.y) low.y = y; + if (y > high.y) high.y = y; + ret = true; + } + break; + case CursorOrientation.HORIZONTAL: + Float128 x = 0.0; + if (hcross(get_scr_point(s, s.points[i]), get_scr_point(s, s.points[i+1]), cursor.y, plot_area_x_min, plot_area_x_max, out x)) { + if (x < low.x) low.x = x; + if (x > high.x) high.x = x; + ret = true; + } + break; + } + } + + if (common_x_axes) { + switch (s.axis_x.position) { + case Axis.Position.LOW: low.y = plot_area_y_max + s.axis_x.font_indent; break; + case Axis.Position.HIGH: high.y = plot_area_y_min - s.axis_x.font_indent; break; + case Axis.Position.BOTH: + low.y = plot_area_y_max + s.axis_x.font_indent; + high.y = plot_area_y_min - s.axis_x.font_indent; + break; + } + } + if (common_y_axes) { + switch (s.axis_y.position) { + case Axis.Position.LOW: low.x = plot_area_x_min - s.axis_y.font_indent; break; + case Axis.Position.HIGH: high.x = plot_area_x_max + s.axis_y.font_indent; break; + case Axis.Position.BOTH: + low.x = plot_area_x_min - s.axis_y.font_indent; + high.x = plot_area_x_max + s.axis_y.font_indent; + break; + } + } + + return false; + } + + protected virtual Float128 scr2rel_x (Float128 x) { + return (x - plot_area_x_min) / (plot_area_x_max - plot_area_x_min); + } + protected virtual Float128 scr2rel_y (Float128 y) { + return (y - plot_area_y_min) / (plot_area_y_max - plot_area_y_min); + } + protected virtual Point scr2rel_point (Point p) { + return Point (scr2rel_x(p.x), scr2rel_y(p.y)); + } + + protected virtual Float128 rel2scr_x(Float128 x) { + return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * x; + } + + protected virtual Float128 rel2scr_y(Float128 y) { + return plot_area_y_max - (plot_area_y_max - plot_area_y_min) * y; + } + + protected virtual Point rel2scr_point (Point p) { + return Point (rel2scr_x(p.x), rel2scr_y(p.y)); + } + + public LineStyle cursor_line_style = LineStyle(); + // TODO: protected virtual void draw_cursors () { + if (series.length == 0) return; + + var all_cursors = cursors.copy(); + all_cursors.append(active_cursor); + + foreach (var c in all_cursors) { + switch (cursors_orientation) { + case CursorOrientation.VERTICAL: + if (c.x <= rel_zoom_x_min || c.x >= rel_zoom_x_max) continue; break; + case CursorOrientation.HORIZONTAL: + if (c.y <= rel_zoom_y_min || c.y >= rel_zoom_y_max) continue; break; + } + + var low = Point(plot_area_x_max, plot_area_y_min); // low and high + var high = Point(plot_area_x_min, plot_area_y_max); // points of the cursor + foreach (var s in series) { + var l = Point(), h = Point(); + if (get_cursor_limits (s, c, out l, out h)) { + if (l.x < low.x) low.x = l.x; + if (l.y < low.y) low.y = l.y; + if (h.x > high.x) high.x = h.x; + if (h.y > high.y) high.y = h.y; + } + } + + switch (cursors_orientation) { + case CursorOrientation.VERTICAL: + // TODO: draw cursor line + set_line_style(cursor_line_style); + context.move_to (rel2scr_x(c.x), low.y); + context.line_to (rel2scr_x(c.x), high.y); + context.stroke(); + + // TODO: show values + if (common_x_axes) + // TODO: show only Y value + ; + else + // TODO: show [X;Y] + ; + break; + case CursorOrientation.HORIZONTAL: + // TODO: draw cursor line + set_line_style(cursor_line_style); + context.move_to (low.x, rel2scr_y(c.y)); + context.line_to (high.x, rel2scr_y(c.y)); + context.stroke(); + + // TODO: show values + if (common_y_axes) + // TODO: show only X value + ; + else + // TODO: show [X;Y] + ; + break; + } + } } public Chart copy () { @@ -1193,10 +1392,10 @@ namespace Gtk.CairoChart { chart.plot_area_x_min = this.plot_area_x_min; chart.plot_area_y_max = this.plot_area_y_max; chart.plot_area_y_min = this.plot_area_y_min; - chart.rel_zoom_x_min = this.rel_zoom_x_min; - chart.rel_zoom_x_max = this.rel_zoom_x_max; - chart.rel_zoom_y_min = this.rel_zoom_y_min; - chart.rel_zoom_y_max = this.rel_zoom_y_max; + chart._rel_zoom_x_min = this._rel_zoom_x_min; + chart._rel_zoom_x_max = this._rel_zoom_x_max; + chart._rel_zoom_y_min = this._rel_zoom_y_min; + chart._rel_zoom_y_max = this._rel_zoom_y_max; chart.selection_style = this.selection_style; chart.show_legend = this.show_legend; chart.title = this.title.copy().copy(); diff --git a/src/Point.vala b/src/Point.vala index 8710a75..30bee7b 100644 --- a/src/Point.vala +++ b/src/Point.vala @@ -3,7 +3,7 @@ namespace Gtk.CairoChart { Float128 x; Float128 y; - public Point (Float128 x, Float128 y) { + public Point (Float128 x = 0.0, Float128 y = 0.0) { this.x = x; this.y = y; } } diff --git a/src/Series.vala b/src/Series.vala index 2d4bc89..d4c37ad 100644 --- a/src/Series.vala +++ b/src/Series.vala @@ -49,7 +49,7 @@ namespace Gtk.CairoChart { default = Color (0.0, 0.0, 0.0, 1.0); } - public bool zoom_show = true; + public bool show = true; public Series copy () { var series = new Series (); @@ -63,7 +63,7 @@ namespace Gtk.CairoChart { series.points = this.points.copy(); series.sort = this.sort; series.title = this.title.copy(); - series.zoom_show = this.zoom_show; + series.show = this.show; return series; } diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 11c88a7..0008949 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -203,6 +203,13 @@ bool point_in_chart (Chart chart, double x, double y) { return true; } +enum MouseState { + FREE = 0, // default + DRAW_SELECTION, + MOVING_CHART, + CURSOR_SELECTION +} + int main (string[] args) { init (ref args); @@ -346,9 +353,40 @@ int main (string[] args) { } }); - bool draw_selection = false; + +/* var radio_button5 = new RadioButton.with_label (null, "Labels"); + var radio_button6 = new RadioButton.with_label_from_widget (radio_button5, "Cursors"); + radio_button5.toggled.connect ((button) => { + // TODO: set labels + if (button.get_active()) { + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + }); + radio_button6.toggled.connect ((button) => { + // TODO: set cursors + if (button.get_active()) { + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + });*/ + + var radio_button7 = new RadioButton.with_label (null, "Vertical Cursors"); + var radio_button8 = new RadioButton.with_label_from_widget (radio_button7, "Horizontal Cursors"); + radio_button7.toggled.connect ((button) => { + if (button.get_active()) { + chart.cursors_orientation = Chart.CursorOrientation.VERTICAL; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + }); + radio_button8.toggled.connect ((button) => { + if (button.get_active()) { + chart.cursors_orientation = Chart.CursorOrientation.HORIZONTAL; + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + } + }); + + MouseState mouse_state = MouseState.FREE; + double sel_x0 = 0, sel_x1 = 0, sel_y0 = 0, sel_y1 = 0; - bool moving_chart = false; double mov_x0 = 0, mov_y0 = 0; da.draw.connect((context) => { @@ -363,7 +401,7 @@ int main (string[] args) { /*var ret = */chart.draw(); // user's post draw operations here... - if (draw_selection) { + if (mouse_state == MouseState.DRAW_SELECTION) { context.set_source_rgba (0.5, 0.5, 0.5, 0.7); context.set_line_join(Cairo.LineJoin.MITER); context.set_line_cap(Cairo.LineCap.ROUND); @@ -381,18 +419,30 @@ int main (string[] args) { da.button_press_event.connect((event) => { if (!point_in_chart(chart, event.x, event.y)) return true; - if (event.button == 2) { - draw_selection = true; + switch (event.button) { + case 1: // start cursor position selection + if ((event.state & Gdk.ModifierType.SHIFT_MASK) != 0) { // remove cursor + chart.set_active_cursor (event.x, event.y, true); + } else { // add cursor + chart.set_active_cursor (event.x, event.y); + } + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + mouse_state = MouseState.CURSOR_SELECTION; + break; + + case 2: // start zoom area selection sel_x0 = sel_x1 = event.x; sel_y0 = sel_y1 = event.y; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); - } + mouse_state = MouseState.DRAW_SELECTION; + break; - if (event.button == 3) { - moving_chart = true; + case 3: // start moving mov_x0 = event.x; mov_y0 = event.y; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + mouse_state = MouseState.MOVING_CHART; + break; } return true; // return ret; @@ -401,9 +451,20 @@ int main (string[] args) { if (!point_in_chart(chart, event.x, event.y)) return true; - //var ret = chart.button_release_event(event); - if (event.button == 2) { - draw_selection = false; + switch (event.button) { + case 1: // start cursor position selection + if ((event.state & Gdk.ModifierType.SHIFT_MASK) != 0) { // remove cursor + chart.remove_active_cursor (); + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + mouse_state = MouseState.FREE; + } else { // add cursor + chart.add_active_cursor (); + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + mouse_state = MouseState.FREE; + } + break; + + case 2: sel_x1 = event.x; sel_y1 = event.y; if (sel_x1 > sel_x0 && sel_y1 > sel_y0) @@ -411,11 +472,13 @@ int main (string[] args) { else chart.zoom_out (); da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); - } + mouse_state = MouseState.FREE; + break; - if (event.button == 3) { - moving_chart = false; + case 3: da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + mouse_state = MouseState.FREE; + break; } return true; // return ret; @@ -423,20 +486,25 @@ int main (string[] args) { da.motion_notify_event.connect((event) => { if (!point_in_chart(chart, event.x, event.y)) return true; - //var ret = chart.motion_notify_event(event); - - if (draw_selection) { + switch (mouse_state) { + case MouseState.DRAW_SELECTION: sel_x1 = event.x; sel_y1 = event.y; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); - } + break; - if (moving_chart) { + case MouseState.MOVING_CHART: var delta_x = event.x - mov_x0, delta_y = event.y - mov_y0; chart.move (delta_x, delta_y); mov_x0 = event.x; mov_y0 = event.y; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + break; + + case MouseState.CURSOR_SELECTION: + chart.set_active_cursor (event.x, event.y, true); + da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + break; } return true; // return ret; @@ -462,6 +530,10 @@ int main (string[] args) { vbox2.pack_start(radio_button2, false, false, 0); vbox2.pack_start(radio_button3, false, false, 0); vbox2.pack_start(radio_button4, false, false, 0); + //vbox2.pack_start(radio_button5, false, false, 0); + //vbox2.pack_start(radio_button6, false, false, 0); + vbox2.pack_start(radio_button7, false, false, 0); + vbox2.pack_start(radio_button8, false, false, 0); var hbox = new Box(Orientation.HORIZONTAL, 0); hbox.pack_start(da, true, true, 0); From 22761fa04f940dc95aa37429a9f744337258db10 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Tue, 28 Nov 2017 21:51:58 +0300 Subject: [PATCH 20/40] Fixes in get_cursor_limits() and other methods. --- src/Chart.vala | 214 +++++++++++++++++++++++++++----------------- src/LineStyle.vala | 9 +- src/Series.vala | 4 +- test/ChartTest.vala | 32 +++++-- 4 files changed, 162 insertions(+), 97 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 51d62c3..c2575ba 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -101,7 +101,7 @@ namespace Gtk.CairoChart { public virtual void zoom_in (double x0, double y0, double x1, double y1) { for (var si = 0, max_i = series.length; si < max_i; ++si) { var s = series[si]; - if (!s.show) continue; + if (!s.zoom_show) continue; var real_x0 = get_real_x (s, x0); var real_x1 = get_real_x (s, x1); var real_y0 = get_real_y (s, y0); @@ -109,7 +109,7 @@ namespace Gtk.CairoChart { // if selected square does not intersect with the series's square if ( real_x1 <= s.axis_x.zoom_min || real_x0 >= s.axis_x.zoom_max || real_y0 <= s.axis_y.zoom_min || real_y1 >= s.axis_y.zoom_max) { - s.show = false; + s.zoom_show = false; continue; } if (real_x0 >= s.axis_x.zoom_min) { @@ -140,7 +140,7 @@ namespace Gtk.CairoChart { zoom_first_show = 0; for (var si = 0, max_i = series.length; si < max_i; ++si) - if (series[si].show) { + if (series[si].zoom_show) { zoom_first_show = si; break; } @@ -157,7 +157,7 @@ namespace Gtk.CairoChart { public virtual void zoom_out () { foreach (var s in series) { - s.show = true; + s.zoom_show = true; s.axis_x.zoom_min = s.axis_x.min; s.axis_x.zoom_max = s.axis_x.max; s.axis_y.zoom_min = s.axis_y.min; @@ -359,7 +359,7 @@ namespace Gtk.CairoChart { foreach (var s in series) { - if (!s.show) continue; + if (!s.zoom_show) continue; // carry switch (legend.position) { @@ -558,7 +558,7 @@ namespace Gtk.CairoChart { int nzoom_series_show = 0; for (var si = series.length - 1; si >=0; --si) { var s = series[si]; - if (!s.show) continue; + if (!s.zoom_show) continue; ++nzoom_series_show; if ( s.axis_x.position != series[0].axis_x.position || s.axis_x.zoom_min != series[0].axis_x.zoom_min @@ -579,7 +579,7 @@ namespace Gtk.CairoChart { // Join and calc X-axes for (var si = series.length - 1, nskip = 0; si >=0; --si) { var s = series[si]; - if (!s.show) continue; + if (!s.zoom_show) continue; if (nskip != 0) {--nskip; continue;} double max_rec_width = 0; double max_rec_height = 0; calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true); @@ -589,11 +589,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { var s2 = series[sj]; - if (!s2.show) continue; + if (!s2.zoom_show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { var s3 = series[sk]; - if (!s3.show) continue; + if (!s3.zoom_show) continue; if (are_intersect(s2.place.zoom_x_low, s2.place.zoom_x_high, s3.place.zoom_x_low, s3.place.zoom_x_high) || s2.axis_x.position != s3.axis_x.position || s2.axis_x.type != s3.axis_x.type) { @@ -627,7 +627,7 @@ namespace Gtk.CairoChart { // Join and calc Y-axes for (var si = series.length - 1, nskip = 0; si >=0; --si) { var s = series[si]; - if (!s.show) continue; + if (!s.zoom_show) continue; if (nskip != 0) {--nskip; continue;} double max_rec_width = 0; double max_rec_height = 0; calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false); @@ -637,11 +637,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { var s2 = series[sj]; - if (!s2.show) continue; + if (!s2.zoom_show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { var s3 = series[sk]; - if (!s3.show) continue; + if (!s3.zoom_show) continue; if (are_intersect(s2.place.zoom_y_low, s2.place.zoom_y_high, s3.place.zoom_y_low, s3.place.zoom_y_high) || s2.axis_y.position != s3.axis_y.position || s2.axis_x.type != s3.axis_x.type) { @@ -682,7 +682,7 @@ namespace Gtk.CairoChart { protected virtual void draw_horizontal_axis () { for (var si = series.length - 1, nskip = 0; si >=0; --si) { var s = series[si]; - if (!s.show) continue; + if (!s.zoom_show) continue; if (common_x_axes && si != zoom_first_show) continue; // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. double max_rec_width, max_rec_height; @@ -833,11 +833,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { var s2 = series[sj]; - if (!s2.show) continue; + if (!s2.zoom_show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { var s3 = series[sk]; - if (!s3.show) continue; + if (!s3.zoom_show) continue; if (are_intersect(s2.place.zoom_x_low, s2.place.zoom_x_high, s3.place.zoom_x_low, s3.place.zoom_x_high) || s2.axis_x.position != s3.axis_x.position || s2.axis_x.type != s3.axis_x.type) { @@ -873,7 +873,7 @@ namespace Gtk.CairoChart { protected virtual void draw_vertical_axis () { for (var si = series.length - 1, nskip = 0; si >=0; --si) { var s = series[si]; - if (!s.show) continue; + if (!s.zoom_show) continue; if (common_y_axes && si != zoom_first_show) continue; // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. double max_rec_width, max_rec_height; @@ -976,11 +976,11 @@ namespace Gtk.CairoChart { // join relative x-axes with non-intersect places for (int sj = si - 1; sj >= 0; --sj) { var s2 = series[sj]; - if (!s2.show) continue; + if (!s2.zoom_show) continue; bool has_intersection = false; for (int sk = si; sk > sj; --sk) { var s3 = series[sk]; - if (!s3.show) continue; + if (!s3.zoom_show) continue; if (are_intersect(s2.place.zoom_y_low, s2.place.zoom_y_high, s3.place.zoom_y_low, s3.place.zoom_y_high) || s2.axis_y.position != s3.axis_y.position) { has_intersection = true; @@ -1047,9 +1047,32 @@ namespace Gtk.CairoChart { return Point (get_real_x(s, p.x), get_real_y(s, p.y)); } + protected virtual bool x_in_range (double x, double x0, double x1) { + if (x0 <= x <= x1 || x1 <= x <= x0) + return true; + return false; + } + + protected virtual bool y_in_range (double y, double y0, double y1) { + if (y0 <= y <= y1 || y1 <= y <= y0) + return true; + return false; + } + + protected virtual bool x_in_plot_area (double x) { + if (x_in_range(x, plot_area_x_min, plot_area_x_max)) + return true; + return false; + } + + protected virtual bool y_in_plot_area (double y) { + if (y_in_range(y, plot_area_y_min, plot_area_y_max)) + return true; + return false; + } + protected virtual bool point_in_rect (Point p, double x0, double x1, double y0, double y1) { - if ( (x0 <= p.x <= x1 || x1 <= p.x <= x0) - && (y0 <= p.y <= y1 || y1 <= p.y <= y0)) + if (x_in_range(p.x, x0, x1) && y_in_range(p.y, y0, y1)) return true; return false; } @@ -1130,9 +1153,9 @@ namespace Gtk.CairoChart { return false; } - protected virtual Point[] sort_points (Series s) { + protected virtual Point[] sort_points (Series s, Series.Sort sort) { var points = s.points.copy(); - switch(s.sort) { + switch(sort) { case Series.Sort.BY_X: sort_points_delegate(points, (a, b) => { if (a.x < b.x) return -1; @@ -1154,9 +1177,9 @@ namespace Gtk.CairoChart { protected virtual void draw_series () { for (var si = 0; si < series.length; ++si) { var s = series[si]; - if (!s.show) continue; + if (!s.zoom_show) continue; if (s.points.length == 0) continue; - var points = sort_points(s); + var points = sort_points(s, s.sort); set_line_style(s.line_style); // draw series line for (int i = 1; i < points.length; ++i) { @@ -1184,7 +1207,7 @@ namespace Gtk.CairoChart { public virtual void set_active_cursor (double x, double y, bool remove = false) { active_cursor = Point (scr2rel_x(x), scr2rel_y(y)); - is_cursor_active = true; + is_cursor_active = ! remove; } public virtual void add_active_cursor () { @@ -1199,7 +1222,7 @@ namespace Gtk.CairoChart { public CursorOrientation cursors_orientation = CursorOrientation.VERTICAL; - public double cursor_max_distance = 32; + public double cursor_max_rm_distance = 32; public virtual void remove_active_cursor () { if (cursors.length() == 0) return; @@ -1210,47 +1233,81 @@ namespace Gtk.CairoChart { double d = distance; switch (cursors_orientation) { case CursorOrientation.VERTICAL: - d = (c.x - active_cursor.x).abs(); + d = (rel2scr_x(c.x) - rel2scr_x(active_cursor.x)).abs(); break; case CursorOrientation.HORIZONTAL: - d = (c.y - active_cursor.y).abs(); + d = (rel2scr_y(c.y) - rel2scr_y(active_cursor.y)).abs(); break; default: break; } - if (distance > d) { + if (d < distance) { distance = d; rm_indx = i; } ++i; } - if (distance < cursor_max_distance) + if (distance < cursor_max_rm_distance) cursors.delete_link(cursors.nth(rm_indx)); + is_cursor_active = false; } - // TODO: + protected virtual Float128 scr2rel_x (Float128 x) { + return _rel_zoom_x_min + (x - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (_rel_zoom_x_max - _rel_zoom_x_min); + } + protected virtual Float128 scr2rel_y (Float128 y) { + return _rel_zoom_y_max - (plot_area_y_max - y) / (plot_area_y_max - plot_area_y_min) * (_rel_zoom_y_max - _rel_zoom_y_min); + } + protected virtual Point scr2rel_point (Point p) { + return Point (scr2rel_x(p.x), scr2rel_y(p.y)); + } + + protected virtual Float128 rel2scr_x(Float128 x) { + return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (x - _rel_zoom_x_min) / (_rel_zoom_x_max - _rel_zoom_x_min); + } + + protected virtual Float128 rel2scr_y(Float128 y) { + return plot_area_y_min + (plot_area_y_max - plot_area_y_min) * (y - _rel_zoom_y_min) / (_rel_zoom_y_max - _rel_zoom_y_min); + } + + protected virtual Point rel2scr_point (Point p) { + return Point (rel2scr_x(p.x), rel2scr_y(p.y)); + } + + public LineStyle cursor_line_style = LineStyle(Color(0.2, 0.2, 0.2, 0.8)); + protected virtual bool get_cursor_limits (Series s, Point cursor, out Point low, out Point high) { - var points = sort_points (s); bool ret = false; - low.x = plot_area_x_min; - low.y = plot_area_y_min; - high.x = plot_area_x_max; - high.y = plot_area_y_max; + low.x = plot_area_x_max; + low.y = plot_area_y_max; + high.x = plot_area_x_min; + high.y = plot_area_y_min; + Point[] points = {}; + switch (cursors_orientation) { + case CursorOrientation.VERTICAL: + points = sort_points (s, s.sort); + break; + case CursorOrientation.HORIZONTAL: + points = sort_points (s, s.sort); + break; + } for (var i = 0; i + 1 < points.length; ++i) { switch (cursors_orientation) { case CursorOrientation.VERTICAL: Float128 y = 0.0; - if (vcross(get_scr_point(s, s.points[i]), get_scr_point(s, s.points[i+1]), cursor.x, plot_area_y_min, plot_area_y_max, out y)) { - if (y < low.y) low.y = y; - if (y > high.y) high.y = y; + if (vcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), rel2scr_x(cursor.x), + plot_area_y_min, plot_area_y_max, out y)) { + if (y < low.y /*&& y_in_plot_area(y)*/) low.y = y; + if (y > high.y /*&& y_in_plot_area(y)*/) high.y = y; ret = true; } break; case CursorOrientation.HORIZONTAL: Float128 x = 0.0; - if (hcross(get_scr_point(s, s.points[i]), get_scr_point(s, s.points[i+1]), cursor.y, plot_area_x_min, plot_area_x_max, out x)) { - if (x < low.x) low.x = x; - if (x > high.x) high.x = x; + if (hcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), + plot_area_x_min, plot_area_x_max, rel2scr_y(cursor.y), out x)) { + if (x < low.x /*&& x_in_plot_area(x)*/) low.x = x; + if (x > high.x /*&& x_in_plot_area(x)*/) high.x = x; ret = true; } break; @@ -1259,13 +1316,14 @@ namespace Gtk.CairoChart { if (common_x_axes) { switch (s.axis_x.position) { - case Axis.Position.LOW: low.y = plot_area_y_max + s.axis_x.font_indent; break; - case Axis.Position.HIGH: high.y = plot_area_y_min - s.axis_x.font_indent; break; + case Axis.Position.LOW: high.y = plot_area_y_max + s.axis_x.font_indent; break; + case Axis.Position.HIGH: low.y = plot_area_y_min - s.axis_x.font_indent; break; case Axis.Position.BOTH: - low.y = plot_area_y_max + s.axis_x.font_indent; - high.y = plot_area_y_min - s.axis_x.font_indent; + high.y = plot_area_y_max + s.axis_x.font_indent; + low.y = plot_area_y_min - s.axis_x.font_indent; break; } + ret = true; } if (common_y_axes) { switch (s.axis_y.position) { @@ -1276,53 +1334,31 @@ namespace Gtk.CairoChart { high.x = plot_area_x_max + s.axis_y.font_indent; break; } + ret = true; } - return false; + return ret; } - protected virtual Float128 scr2rel_x (Float128 x) { - return (x - plot_area_x_min) / (plot_area_x_max - plot_area_x_min); - } - protected virtual Float128 scr2rel_y (Float128 y) { - return (y - plot_area_y_min) / (plot_area_y_max - plot_area_y_min); - } - protected virtual Point scr2rel_point (Point p) { - return Point (scr2rel_x(p.x), scr2rel_y(p.y)); - } - - protected virtual Float128 rel2scr_x(Float128 x) { - return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * x; - } - - protected virtual Float128 rel2scr_y(Float128 y) { - return plot_area_y_max - (plot_area_y_max - plot_area_y_min) * y; - } - - protected virtual Point rel2scr_point (Point p) { - return Point (rel2scr_x(p.x), rel2scr_y(p.y)); - } - - public LineStyle cursor_line_style = LineStyle(); - - // TODO: protected virtual void draw_cursors () { if (series.length == 0) return; - var all_cursors = cursors.copy(); - all_cursors.append(active_cursor); + var all_cursors = cursors.copy_deep ((src) => { return src; }); + if (is_cursor_active) + all_cursors.append(active_cursor); foreach (var c in all_cursors) { switch (cursors_orientation) { case CursorOrientation.VERTICAL: - if (c.x <= rel_zoom_x_min || c.x >= rel_zoom_x_max) continue; break; + if (c.x <= _rel_zoom_x_min || c.x >= _rel_zoom_x_max) continue; break; case CursorOrientation.HORIZONTAL: - if (c.y <= rel_zoom_y_min || c.y >= rel_zoom_y_max) continue; break; + if (c.y <= _rel_zoom_y_min || c.y >= _rel_zoom_y_max) continue; break; } - var low = Point(plot_area_x_max, plot_area_y_min); // low and high - var high = Point(plot_area_x_min, plot_area_y_max); // points of the cursor + var low = Point(plot_area_x_max, plot_area_y_max); // low and high + var high = Point(plot_area_x_min, plot_area_y_min); // points of the cursor foreach (var s in series) { + if (!s.zoom_show) continue; var l = Point(), h = Point(); if (get_cursor_limits (s, c, out l, out h)) { if (l.x < low.x) low.x = l.x; @@ -1334,7 +1370,7 @@ namespace Gtk.CairoChart { switch (cursors_orientation) { case CursorOrientation.VERTICAL: - // TODO: draw cursor line + if (low.y > high.y) continue; set_line_style(cursor_line_style); context.move_to (rel2scr_x(c.x), low.y); context.line_to (rel2scr_x(c.x), high.y); @@ -1342,14 +1378,14 @@ namespace Gtk.CairoChart { // TODO: show values if (common_x_axes) - // TODO: show only Y value + // show only Y value ; else - // TODO: show [X;Y] + // show [X;Y] ; break; case CursorOrientation.HORIZONTAL: - // TODO: draw cursor line + if (low.x > high.x) continue; set_line_style(cursor_line_style); context.move_to (low.x, rel2scr_y(c.y)); context.line_to (high.x, rel2scr_y(c.y)); @@ -1357,10 +1393,10 @@ namespace Gtk.CairoChart { // TODO: show values if (common_y_axes) - // TODO: show only X value + // show only X value ; else - // TODO: show [X;Y] + // show [X;Y] ; break; } @@ -1369,6 +1405,7 @@ namespace Gtk.CairoChart { public Chart copy () { var chart = new Chart (); + chart.active_cursor = this.active_cursor; chart.axis_rec_npoints = this.axis_rec_npoints; chart.bg_color = this.bg_color; chart.border_color = this.border_color; @@ -1379,7 +1416,12 @@ namespace Gtk.CairoChart { chart.cur_x_min = this.cur_x_min; chart.cur_y_max = this.cur_y_max; chart.cur_y_min = this.cur_y_min; + chart.cursor_line_style = this.cursor_line_style; + chart.cursor_max_rm_distance = this.cursor_max_rm_distance; + chart.cursors = this.cursors.copy(); + chart.cursors_orientation = this.cursors_orientation; chart.height = this.height; + chart.is_cursor_active = this.is_cursor_active; chart.legend = this.legend.copy(); chart.legend_height = this.legend_height; chart.legend_line_length = this.legend_line_length; @@ -1397,12 +1439,16 @@ namespace Gtk.CairoChart { chart._rel_zoom_y_min = this._rel_zoom_y_min; chart._rel_zoom_y_max = this._rel_zoom_y_max; chart.selection_style = this.selection_style; + chart.series = this.series.copy(); chart.show_legend = this.show_legend; chart.title = this.title.copy().copy(); chart.title_height = this.title_height; chart.title_vindent = this.title_vindent; chart.title_width = this.title_width; chart.width = this.width; + chart.x_min = this.x_min; + chart.y_min = this.y_min; + chart.zoom_first_show = this.zoom_first_show; return chart; } } diff --git a/src/LineStyle.vala b/src/LineStyle.vala index 0efad62..4a58d1e 100644 --- a/src/LineStyle.vala +++ b/src/LineStyle.vala @@ -7,11 +7,12 @@ namespace Gtk.CairoChart { double dash_offset; Color color; - public LineStyle (double width = 1, - Cairo.LineJoin line_join = Cairo.LineJoin.MITER, - Cairo.LineCap line_cap = Cairo.LineCap.ROUND, + public LineStyle (Color color = Color(), + double width = 1, double[]? dashes = null, double dash_offset = 0, - Color color = Color()) { + Cairo.LineJoin line_join = Cairo.LineJoin.MITER, + Cairo.LineCap line_cap = Cairo.LineCap.ROUND + ) { this.width = width; this.line_join = line_join; this.line_cap = line_cap; diff --git a/src/Series.vala b/src/Series.vala index d4c37ad..2d4bc89 100644 --- a/src/Series.vala +++ b/src/Series.vala @@ -49,7 +49,7 @@ namespace Gtk.CairoChart { default = Color (0.0, 0.0, 0.0, 1.0); } - public bool show = true; + public bool zoom_show = true; public Series copy () { var series = new Series (); @@ -63,7 +63,7 @@ namespace Gtk.CairoChart { series.points = this.points.copy(); series.sort = this.sort; series.title = this.title.copy(); - series.show = this.show; + series.zoom_show = this.zoom_show; return series; } diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 0008949..33916a9 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -251,6 +251,8 @@ int main (string[] args) { var radio_button2 = new RadioButton.with_label (radio_button1.get_group(), "Right Legend"); var radio_button3 = new RadioButton.with_label_from_widget (radio_button1, "Left Legend"); var radio_button4 = new RadioButton.with_label_from_widget (radio_button1, "Bottom Legend"); + var radio_button7 = new RadioButton.with_label (null, "Vertical Cursors"); + var radio_button8 = new RadioButton.with_label_from_widget (radio_button7, "Horizontal Cursors"); button1.clicked.connect (() => { chart = chart1; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); @@ -261,6 +263,10 @@ int main (string[] args) { case Legend.Position.BOTTOM: radio_button4.set_active(true); break; default: break; } + switch (chart.cursors_orientation) { + case Chart.CursorOrientation.VERTICAL: radio_button7.set_active(true); break; + case Chart.CursorOrientation.HORIZONTAL: radio_button8.set_active(true); break; + } }); button2.clicked.connect (() => { chart = chart2; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); @@ -271,6 +277,10 @@ int main (string[] args) { case Legend.Position.BOTTOM: radio_button4.set_active(true); break; default: break; } + switch (chart.cursors_orientation) { + case Chart.CursorOrientation.VERTICAL: radio_button7.set_active(true); break; + case Chart.CursorOrientation.HORIZONTAL: radio_button8.set_active(true); break; + } }); button3.clicked.connect (() => { chart = chart3; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); @@ -281,6 +291,10 @@ int main (string[] args) { case Legend.Position.BOTTOM: radio_button4.set_active(true); break; default: break; } + switch (chart.cursors_orientation) { + case Chart.CursorOrientation.VERTICAL: radio_button7.set_active(true); break; + case Chart.CursorOrientation.HORIZONTAL: radio_button8.set_active(true); break; + } }); button4.clicked.connect (() => { chart = chart4; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); @@ -291,6 +305,10 @@ int main (string[] args) { case Legend.Position.BOTTOM: radio_button4.set_active(true); break; default: break; } + switch (chart.cursors_orientation) { + case Chart.CursorOrientation.VERTICAL: radio_button7.set_active(true); break; + case Chart.CursorOrientation.HORIZONTAL: radio_button8.set_active(true); break; + } }); button5.clicked.connect (() => { for (var i = 0; i < chart.series.length; ++i) { @@ -369,8 +387,6 @@ int main (string[] args) { } });*/ - var radio_button7 = new RadioButton.with_label (null, "Vertical Cursors"); - var radio_button8 = new RadioButton.with_label_from_widget (radio_button7, "Horizontal Cursors"); radio_button7.toggled.connect ((button) => { if (button.get_active()) { chart.cursors_orientation = Chart.CursorOrientation.VERTICAL; @@ -423,11 +439,13 @@ int main (string[] args) { case 1: // start cursor position selection if ((event.state & Gdk.ModifierType.SHIFT_MASK) != 0) { // remove cursor chart.set_active_cursor (event.x, event.y, true); + chart.remove_active_cursor(); + mouse_state = MouseState.FREE; } else { // add cursor chart.set_active_cursor (event.x, event.y); + mouse_state = MouseState.CURSOR_SELECTION; } da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); - mouse_state = MouseState.CURSOR_SELECTION; break; case 2: // start zoom area selection @@ -454,9 +472,9 @@ int main (string[] args) { switch (event.button) { case 1: // start cursor position selection if ((event.state & Gdk.ModifierType.SHIFT_MASK) != 0) { // remove cursor - chart.remove_active_cursor (); - da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); - mouse_state = MouseState.FREE; + //chart.remove_active_cursor (); + //da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); + //mouse_state = MouseState.FREE; } else { // add cursor chart.add_active_cursor (); da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); @@ -502,7 +520,7 @@ int main (string[] args) { break; case MouseState.CURSOR_SELECTION: - chart.set_active_cursor (event.x, event.y, true); + chart.set_active_cursor (event.x, event.y); da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); break; } From dc18d83414d4c851396ec886d5dc2f7c9cc23f0c Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Fri, 1 Dec 2017 16:25:07 +0300 Subject: [PATCH 21/40] CursorCrossings[] added. --- src/Chart.vala | 184 +++++++++++++++++++++++++++++-------------------- 1 file changed, 110 insertions(+), 74 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index c2575ba..aa154c2 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -55,6 +55,8 @@ namespace Gtk.CairoChart { set_vertical_axes_titles (); + get_cursors_crossings(); + calc_plot_area (); // Calculate plot area draw_horizontal_axis (); @@ -1276,78 +1278,31 @@ namespace Gtk.CairoChart { public LineStyle cursor_line_style = LineStyle(Color(0.2, 0.2, 0.2, 0.8)); - protected virtual bool get_cursor_limits (Series s, Point cursor, out Point low, out Point high) { - bool ret = false; - low.x = plot_area_x_max; - low.y = plot_area_y_max; - high.x = plot_area_x_min; - high.y = plot_area_y_min; - Point[] points = {}; - switch (cursors_orientation) { - case CursorOrientation.VERTICAL: - points = sort_points (s, s.sort); - break; - case CursorOrientation.HORIZONTAL: - points = sort_points (s, s.sort); - break; - } - for (var i = 0; i + 1 < points.length; ++i) { - switch (cursors_orientation) { - case CursorOrientation.VERTICAL: - Float128 y = 0.0; - if (vcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), rel2scr_x(cursor.x), - plot_area_y_min, plot_area_y_max, out y)) { - if (y < low.y /*&& y_in_plot_area(y)*/) low.y = y; - if (y > high.y /*&& y_in_plot_area(y)*/) high.y = y; - ret = true; - } - break; - case CursorOrientation.HORIZONTAL: - Float128 x = 0.0; - if (hcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), - plot_area_x_min, plot_area_x_max, rel2scr_y(cursor.y), out x)) { - if (x < low.x /*&& x_in_plot_area(x)*/) low.x = x; - if (x > high.x /*&& x_in_plot_area(x)*/) high.x = x; - ret = true; - } - break; - } - } - - if (common_x_axes) { - switch (s.axis_x.position) { - case Axis.Position.LOW: high.y = plot_area_y_max + s.axis_x.font_indent; break; - case Axis.Position.HIGH: low.y = plot_area_y_min - s.axis_x.font_indent; break; - case Axis.Position.BOTH: - high.y = plot_area_y_max + s.axis_x.font_indent; - low.y = plot_area_y_min - s.axis_x.font_indent; - break; - } - ret = true; - } - if (common_y_axes) { - switch (s.axis_y.position) { - case Axis.Position.LOW: low.x = plot_area_x_min - s.axis_y.font_indent; break; - case Axis.Position.HIGH: high.x = plot_area_x_max + s.axis_y.font_indent; break; - case Axis.Position.BOTH: - low.x = plot_area_x_min - s.axis_y.font_indent; - high.x = plot_area_x_max + s.axis_y.font_indent; - break; - } - ret = true; - } - - return ret; + protected struct CursorCross { + uint series_index; + Point point; + } + protected struct CursorCrossings { + uint cursor_index; + CursorCross[] crossings; } - protected virtual void draw_cursors () { - if (series.length == 0) return; + protected CursorCrossings[] cursors_crossings = {}; + protected List get_all_cursors () { var all_cursors = cursors.copy_deep ((src) => { return src; }); if (is_cursor_active) all_cursors.append(active_cursor); + return all_cursors; + } - foreach (var c in all_cursors) { + protected void get_cursors_crossings () { + var all_cursors = get_all_cursors(); + + CursorCrossings[] local_cursor_crossings = {}; + + for (var ci = 0, max_ci = all_cursors.length(); ci < max_ci; ++ci) { + var c = all_cursors.nth_data(ci); switch (cursors_orientation) { case CursorOrientation.VERTICAL: if (c.x <= _rel_zoom_x_min || c.x >= _rel_zoom_x_max) continue; break; @@ -1355,23 +1310,103 @@ namespace Gtk.CairoChart { if (c.y <= _rel_zoom_y_min || c.y >= _rel_zoom_y_max) continue; break; } - var low = Point(plot_area_x_max, plot_area_y_max); // low and high - var high = Point(plot_area_x_min, plot_area_y_min); // points of the cursor - foreach (var s in series) { + CursorCross[] crossings = {}; + for (var si = 0, max_si = series.length; si < max_si; ++si) { + var s = series[si]; if (!s.zoom_show) continue; - var l = Point(), h = Point(); - if (get_cursor_limits (s, c, out l, out h)) { - if (l.x < low.x) low.x = l.x; - if (l.y < low.y) low.y = l.y; - if (h.x > high.x) high.x = h.x; - if (h.y > high.y) high.y = h.y; + + Point[] points = {}; + switch (cursors_orientation) { + case CursorOrientation.VERTICAL: + points = sort_points (s, s.sort); + break; + case CursorOrientation.HORIZONTAL: + points = sort_points (s, s.sort); + break; + } + + for (var i = 0; i + 1 < points.length; ++i) { + switch (cursors_orientation) { + case CursorOrientation.VERTICAL: + Float128 y = 0.0; + if (vcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), rel2scr_x(c.x), + plot_area_y_min, plot_area_y_max, out y)) { + CursorCross cc = {si, Point(get_real_x(s, rel2scr_x(c.x)), get_real_y(s, y))}; + crossings += cc; +//stdout.printf("si = %d, rel2scr_x(c.x) = %f, y = %f\n", si, rel2scr_x(c.x), y); + } + break; + case CursorOrientation.HORIZONTAL: + Float128 x = 0.0; + if (hcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), + plot_area_x_min, plot_area_x_max, rel2scr_y(c.y), out x)) { + CursorCross cc = {si, Point(get_real_x(s, x), get_real_y(s, rel2scr_y(c.y)))}; + crossings += cc; + } + break; + } } } + if (crossings.length != 0) { + CursorCrossings ccs = {ci, crossings}; + local_cursor_crossings += ccs; + } + } + cursors_crossings = local_cursor_crossings; +//if (cursors_crossings.length != 0) +//stdout.printf("cursors_crossings[0].crossings.length = %d\n", cursors_crossings[0].crossings.length); + } + + protected virtual void draw_cursors () { + if (series.length == 0) return; + + var all_cursors = get_all_cursors(); + + for (var cci = 0, max_cci = cursors_crossings.length; cci < max_cci; ++cci) { + var low = Point(plot_area_x_max, plot_area_y_max); // low and high + var high = Point(plot_area_x_min, plot_area_y_min); // points of the cursor + var ccs = cursors_crossings[cci].crossings; + for (var ci = 0, max_ci = ccs.length; ci < max_ci; ++ci) { + var si = ccs[ci].series_index; + var s = series[si]; + var p = ccs[ci].point; + var scrx = get_scr_x(s, p.x); + var scry = get_scr_y(s, p.y); + if (scrx < low.x) low.x = scrx; +//stdout.printf("low.y = %f, high.y = %f\n", low.y, high.y); + if (scry < low.y) low.y = scry; + if (scrx > high.x) high.x = scrx; + if (scry > high.y) high.y = scry; + + if (common_x_axes) { + switch (s.axis_x.position) { + case Axis.Position.LOW: high.y = plot_area_y_max + s.axis_x.font_indent; break; + case Axis.Position.HIGH: low.y = plot_area_y_min - s.axis_x.font_indent; break; + case Axis.Position.BOTH: + high.y = plot_area_y_max + s.axis_x.font_indent; + low.y = plot_area_y_min - s.axis_x.font_indent; + break; + } + } + if (common_y_axes) { + switch (s.axis_y.position) { + case Axis.Position.LOW: low.x = plot_area_x_min - s.axis_y.font_indent; break; + case Axis.Position.HIGH: high.x = plot_area_x_max + s.axis_y.font_indent; break; + case Axis.Position.BOTH: + low.x = plot_area_x_min - s.axis_y.font_indent; + high.x = plot_area_x_max + s.axis_y.font_indent; + break; + } + } + } + + var c = all_cursors.nth_data(cursors_crossings[cci].cursor_index); switch (cursors_orientation) { case CursorOrientation.VERTICAL: if (low.y > high.y) continue; set_line_style(cursor_line_style); +//stdout.printf("rel2scr_x(c.x) = %f, low.y = %f\n", rel2scr_x(c.x), low.y); context.move_to (rel2scr_x(c.x), low.y); context.line_to (rel2scr_x(c.x), high.y); context.stroke(); @@ -1419,6 +1454,7 @@ namespace Gtk.CairoChart { chart.cursor_line_style = this.cursor_line_style; chart.cursor_max_rm_distance = this.cursor_max_rm_distance; chart.cursors = this.cursors.copy(); + chart.cursors_crossings = this.cursors_crossings.copy(); // no deep copying for .crossings chart.cursors_orientation = this.cursors_orientation; chart.height = this.height; chart.is_cursor_active = this.is_cursor_active; From 4e89e2682e7a9de9b64c684db15e74c7a57a574c Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Sun, 3 Dec 2017 16:03:51 +0300 Subject: [PATCH 22/40] Cursors: Move common axex. --- src/Chart.vala | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index aa154c2..178e574 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -665,12 +665,30 @@ namespace Gtk.CairoChart { } } + // 4.2. Cursor values for common X axis + if (common_x_axes && si == zoom_first_show && cursors_orientation == CursorOrientation.VERTICAL && cursors_crossings.length != 0) { + switch (s.axis_x.position) { + case Axis.Position.LOW: plot_area_y_max -= max_rec_height + s.axis_x.font_indent; break; + case Axis.Position.HIGH: plot_area_y_min += max_rec_height + s.axis_x.font_indent; break; + case Axis.Position.BOTH: break; + } + } + + // 4.2. Cursor values for common Y axis + if (common_y_axes && si == zoom_first_show && cursors_orientation == CursorOrientation.HORIZONTAL && cursors_crossings.length != 0) { + switch (s.axis_y.position) { + case Axis.Position.LOW: plot_area_x_min += max_rec_width + s.axis_y.font_indent; break; + case Axis.Position.HIGH: plot_area_x_max -= max_rec_width + s.axis_y.font_indent; break; + case Axis.Position.BOTH: break; + } + } + if (!common_y_axes || si == zoom_first_show) switch (s.axis_y.position) { - case Axis.Position.LOW: plot_area_x_min += max_rec_width + max_font_indent + max_axis_font_width; break; - case Axis.Position.HIGH: plot_area_x_max -= max_rec_width + max_font_indent + max_axis_font_width; break; - case Axis.Position.BOTH: break; - default: break; + case Axis.Position.LOW: plot_area_x_min += max_rec_width + max_font_indent + max_axis_font_width; break; + case Axis.Position.HIGH: plot_area_x_max -= max_rec_width + max_font_indent + max_axis_font_width; break; + case Axis.Position.BOTH: break; + default: break; } } } @@ -710,6 +728,15 @@ namespace Gtk.CairoChart { } if (x_min < s.axis_x.zoom_min) x_min += step; + // 4.2. Cursor values for common X axis + if (common_x_axes && cursors_orientation == CursorOrientation.VERTICAL && cursors_crossings.length != 0) { + switch (s.axis_x.position) { + case Axis.Position.LOW: cur_y_max -= max_rec_height + s.axis_x.font_indent; break; + case Axis.Position.HIGH: cur_y_min += max_rec_height + s.axis_x.font_indent; break; + case Axis.Position.BOTH: break; + } + } + // 4.5. Draw Axis title if (s.axis_x.title.text != "") switch (s.axis_x.position) { @@ -901,6 +928,15 @@ namespace Gtk.CairoChart { } if (y_min < s.axis_y.zoom_min) y_min += step; + // 4.2. Cursor values for common Y axis + if (common_y_axes && cursors_orientation == CursorOrientation.HORIZONTAL && cursors_crossings.length != 0) { + switch (s.axis_y.position) { + case Axis.Position.LOW: cur_x_min += max_rec_width + s.axis_y.font_indent; break; + case Axis.Position.HIGH: cur_x_max -= max_rec_width + s.axis_y.font_indent; break; + case Axis.Position.BOTH: break; + } + } + // 4.5. Draw Axis title if (s.axis_y.title.text != "") switch (s.axis_y.position) { From 7a1fd945327784006cefad5ab1bbe22e2a5b81d5 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Mon, 4 Dec 2017 16:07:59 +0300 Subject: [PATCH 23/40] Switch-case alignments. --- src/Chart.vala | 574 ++++++++++++++++++++++---------------------- test/ChartTest.vala | 7 +- 2 files changed, 285 insertions(+), 296 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 178e574..2501b17 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -268,9 +268,6 @@ namespace Gtk.CairoChart { x0 = width - legend_width; y0 = (height - legend_height) / 2; break; - - default: - break; } set_source_rgba(legend.bg_color); context.rectangle (x0, y0, legend_width, legend_height); @@ -294,47 +291,43 @@ namespace Gtk.CairoChart { double x, double y) { context.move_to (x, y); switch (marker_type) { - case Series.MarkerType.SQUARE: - context.rectangle (x - marker_size / 2, y - marker_size / 2, - marker_size, marker_size); - context.fill(); - break; + case Series.MarkerType.SQUARE: + context.rectangle (x - marker_size / 2, y - marker_size / 2, + marker_size, marker_size); + context.fill(); + break; - case Series.MarkerType.CIRCLE: - context.arc (x, y, marker_size / 2, 0, 2*Math.PI); - context.fill(); - break; + case Series.MarkerType.CIRCLE: + context.arc (x, y, marker_size / 2, 0, 2*Math.PI); + context.fill(); + break; - case Series.MarkerType.TRIANGLE: - context.move_to (x - marker_size / 2, y - marker_size / 2); - context.line_to (x + marker_size / 2, y - marker_size / 2); - context.line_to (x, y + marker_size / 2); - context.line_to (x - marker_size / 2, y - marker_size / 2); - context.fill(); - break; + case Series.MarkerType.TRIANGLE: + context.move_to (x - marker_size / 2, y - marker_size / 2); + context.line_to (x + marker_size / 2, y - marker_size / 2); + context.line_to (x, y + marker_size / 2); + context.line_to (x - marker_size / 2, y - marker_size / 2); + context.fill(); + break; - case Series.MarkerType.PRICLE_SQUARE: - context.rectangle (x - marker_size / 2, y - marker_size / 2, - marker_size, marker_size); - context.stroke(); - break; + case Series.MarkerType.PRICLE_SQUARE: + context.rectangle (x - marker_size / 2, y - marker_size / 2, + marker_size, marker_size); + context.stroke(); + break; - case Series.MarkerType.PRICLE_CIRCLE: - context.arc (x, y, marker_size / 2, 0, 2*Math.PI); - context.stroke(); - break; + case Series.MarkerType.PRICLE_CIRCLE: + context.arc (x, y, marker_size / 2, 0, 2*Math.PI); + context.stroke(); + break; - case Series.MarkerType.PRICLE_TRIANGLE: - context.move_to (x - marker_size / 2, y - marker_size / 2); - context.line_to (x + marker_size / 2, y - marker_size / 2); - context.line_to (x, y + marker_size / 2); - context.line_to (x - marker_size / 2, y - marker_size / 2); - context.stroke(); - break; - - case Series.MarkerType.NONE: - default: - break; + case Series.MarkerType.PRICLE_TRIANGLE: + context.move_to (x - marker_size / 2, y - marker_size / 2); + context.line_to (x + marker_size / 2, y - marker_size / 2); + context.line_to (x, y + marker_size / 2); + context.line_to (x - marker_size / 2, y - marker_size / 2); + context.stroke(); + break; } } @@ -348,15 +341,15 @@ namespace Gtk.CairoChart { // prepare switch (process_type) { - case LegendProcessType.CALC: - legend_width = 0.0; - legend_height = 0.0; - max_font_heights = {}; - heights_idx = 0; - break; - case LegendProcessType.DRAW: - draw_legend_rect(out legend_x0, out legend_y0); - break; + case LegendProcessType.CALC: + legend_width = 0.0; + legend_height = 0.0; + max_font_heights = {}; + heights_idx = 0; + break; + case LegendProcessType.DRAW: + draw_legend_rect(out legend_x0, out legend_y0); + break; } foreach (var s in series) { @@ -365,43 +358,43 @@ namespace Gtk.CairoChart { // carry switch (legend.position) { - case Legend.Position.TOP: - case Legend.Position.BOTTOM: - var ser_title_width = s.title.get_width(context) + legend_line_length; - if (leg_width_sum + (leg_width_sum == 0 ? 0 : legend_text_hspace) + ser_title_width > width) { // carry - leg_height_sum += max_font_h; - switch (process_type) { - case LegendProcessType.CALC: - max_font_heights += max_font_h; - legend_width = double.max(legend_width, leg_width_sum); - break; - case LegendProcessType.DRAW: - heights_idx++; - break; - } - leg_width_sum = 0.0; - max_font_h = 0; + case Legend.Position.TOP: + case Legend.Position.BOTTOM: + var ser_title_width = s.title.get_width(context) + legend_line_length; + if (leg_width_sum + (leg_width_sum == 0 ? 0 : legend_text_hspace) + ser_title_width > width) { // carry + leg_height_sum += max_font_h; + switch (process_type) { + case LegendProcessType.CALC: + max_font_heights += max_font_h; + legend_width = double.max(legend_width, leg_width_sum); + break; + case LegendProcessType.DRAW: + heights_idx++; + break; } - break; + leg_width_sum = 0.0; + max_font_h = 0; + } + break; } switch (process_type) { - case LegendProcessType.DRAW: - var x = legend_x0 + leg_width_sum + (leg_width_sum == 0.0 ? 0.0 : legend_text_hspace); - var y = legend_y0 + leg_height_sum + max_font_heights[heights_idx]; + case LegendProcessType.DRAW: + var x = legend_x0 + leg_width_sum + (leg_width_sum == 0.0 ? 0.0 : legend_text_hspace); + var y = legend_y0 + leg_height_sum + max_font_heights[heights_idx]; - // series title - context.move_to (x + legend_line_length - s.title.get_x_bearing(context), y); - set_source_rgba (s.title.color); - show_text(s.title); + // series title + context.move_to (x + legend_line_length - s.title.get_x_bearing(context), y); + set_source_rgba (s.title.color); + show_text(s.title); - // series line style - context.move_to (x, y - s.title.get_height(context) / 2); - set_line_style(s.line_style); - context.rel_line_to (legend_line_length, 0); - context.stroke(); - draw_marker_at_pos (s.marker_type, x + legend_line_length / 2, y - s.title.get_height(context) / 2); - break; + // series line style + context.move_to (x, y - s.title.get_height(context) / 2); + set_line_style(s.line_style); + context.rel_line_to (legend_line_length, 0); + context.stroke(); + draw_marker_at_pos (s.marker_type, x + legend_line_length / 2, y - s.title.get_height(context) / 2); + break; } switch (legend.position) { @@ -415,13 +408,13 @@ namespace Gtk.CairoChart { case Legend.Position.LEFT: case Legend.Position.RIGHT: switch (process_type) { - case LegendProcessType.CALC: - max_font_heights += s.title.get_height(context) + (leg_height_sum != 0 ? legend_text_vspace : 0); - legend_width = double.max (legend_width, s.title.get_width(context) + legend_line_length); - break; - case LegendProcessType.DRAW: - heights_idx++; - break; + case LegendProcessType.CALC: + max_font_heights += s.title.get_height(context) + (leg_height_sum != 0 ? legend_text_vspace : 0); + legend_width = double.max (legend_width, s.title.get_width(context) + legend_line_length); + break; + case LegendProcessType.DRAW: + heights_idx++; + break; } leg_height_sum += s.title.get_height(context) + (leg_height_sum != 0 ? legend_text_vspace : 0); break; @@ -430,38 +423,38 @@ namespace Gtk.CairoChart { // TOP, BOTTOM switch (legend.position) { - case Legend.Position.TOP: - case Legend.Position.BOTTOM: - if (leg_width_sum != 0) { - leg_height_sum += max_font_h; - switch (process_type) { - case LegendProcessType.CALC: - max_font_heights += max_font_h; - legend_width = double.max(legend_width, leg_width_sum); - break; - } + case Legend.Position.TOP: + case Legend.Position.BOTTOM: + if (leg_width_sum != 0) { + leg_height_sum += max_font_h; + switch (process_type) { + case LegendProcessType.CALC: + max_font_heights += max_font_h; + legend_width = double.max(legend_width, leg_width_sum); + break; } - break; + } + break; } switch (process_type) { - case LegendProcessType.CALC: - legend_height = leg_height_sum; - switch (legend.position) { - case Legend.Position.TOP: - cur_y_min += legend_height; - break; - case Legend.Position.BOTTOM: - cur_y_max -= legend_height; - break; - case Legend.Position.LEFT: - cur_x_min += legend_width; - break; - case Legend.Position.RIGHT: - cur_x_max -= legend_width; - break; - } - break; + case LegendProcessType.CALC: + legend_height = leg_height_sum; + switch (legend.position) { + case Legend.Position.TOP: + cur_y_min += legend_height; + break; + case Legend.Position.BOTTOM: + cur_y_max -= legend_height; + break; + case Legend.Position.LEFT: + cur_x_min += legend_width; + break; + case Legend.Position.RIGHT: + cur_x_max -= legend_width; + break; + } + break; } } @@ -502,8 +495,6 @@ namespace Gtk.CairoChart { } max_rec_height = double.max (max_rec_height, h); break; - default: - break; } } } @@ -617,12 +608,18 @@ namespace Gtk.CairoChart { } } + // for 4.2. Cursor values for common X axis + if (common_x_axes && si == zoom_first_show && cursors_orientation == CursorOrientation.VERTICAL && cursors_crossings.length != 0) { + switch (s.axis_x.position) { + case Axis.Position.LOW: plot_area_y_max -= max_rec_height + s.axis_x.font_indent; break; + case Axis.Position.HIGH: plot_area_y_min += max_rec_height + s.axis_x.font_indent; break; + } + } + if (!common_x_axes || si == zoom_first_show) switch (s.axis_x.position) { case Axis.Position.LOW: plot_area_y_max -= max_rec_height + max_font_indent + max_axis_font_height; break; case Axis.Position.HIGH: plot_area_y_min += max_rec_height + max_font_indent + max_axis_font_height; break; - case Axis.Position.BOTH: break; - default: break; } } @@ -665,30 +662,18 @@ namespace Gtk.CairoChart { } } - // 4.2. Cursor values for common X axis - if (common_x_axes && si == zoom_first_show && cursors_orientation == CursorOrientation.VERTICAL && cursors_crossings.length != 0) { - switch (s.axis_x.position) { - case Axis.Position.LOW: plot_area_y_max -= max_rec_height + s.axis_x.font_indent; break; - case Axis.Position.HIGH: plot_area_y_min += max_rec_height + s.axis_x.font_indent; break; - case Axis.Position.BOTH: break; - } - } - - // 4.2. Cursor values for common Y axis + // for 4.2. Cursor values for common Y axis if (common_y_axes && si == zoom_first_show && cursors_orientation == CursorOrientation.HORIZONTAL && cursors_crossings.length != 0) { switch (s.axis_y.position) { - case Axis.Position.LOW: plot_area_x_min += max_rec_width + s.axis_y.font_indent; break; - case Axis.Position.HIGH: plot_area_x_max -= max_rec_width + s.axis_y.font_indent; break; - case Axis.Position.BOTH: break; + case Axis.Position.LOW: plot_area_x_min += max_rec_width + s.axis_y.font_indent; break; + case Axis.Position.HIGH: plot_area_x_max -= max_rec_width + s.axis_y.font_indent; break; } } if (!common_y_axes || si == zoom_first_show) switch (s.axis_y.position) { - case Axis.Position.LOW: plot_area_x_min += max_rec_width + max_font_indent + max_axis_font_width; break; - case Axis.Position.HIGH: plot_area_x_max -= max_rec_width + max_font_indent + max_axis_font_width; break; - case Axis.Position.BOTH: break; - default: break; + case Axis.Position.LOW: plot_area_x_min += max_rec_width + max_font_indent + max_axis_font_width; break; + case Axis.Position.HIGH: plot_area_x_max -= max_rec_width + max_font_indent + max_axis_font_width; break; } } } @@ -699,11 +684,22 @@ namespace Gtk.CairoChart { return false; } + protected virtual double compact_rec_x_pos (Series s, Float128 x, Text text) { + return get_scr_x(s, x) - text.get_width(context) / 2.0 - text.get_x_bearing(context) + - text.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); + } + + protected virtual double compact_rec_y_pos (Series s, Float128 y, Text text) { + return get_scr_y(s, y) + text.get_height(context) / 2.0 + + text.get_height(context) * (y - (s.axis_y.zoom_min + s.axis_y.zoom_max) / 2.0) / (s.axis_y.zoom_max - s.axis_y.zoom_min); + } + protected virtual void draw_horizontal_axis () { for (var si = series.length - 1, nskip = 0; si >=0; --si) { var s = series[si]; if (!s.zoom_show) continue; if (common_x_axes && si != zoom_first_show) continue; + // 1. Detect max record width/height by axis_rec_npoints equally selected points using format. double max_rec_width, max_rec_height; calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true); @@ -731,34 +727,24 @@ namespace Gtk.CairoChart { // 4.2. Cursor values for common X axis if (common_x_axes && cursors_orientation == CursorOrientation.VERTICAL && cursors_crossings.length != 0) { switch (s.axis_x.position) { - case Axis.Position.LOW: cur_y_max -= max_rec_height + s.axis_x.font_indent; break; - case Axis.Position.HIGH: cur_y_min += max_rec_height + s.axis_x.font_indent; break; - case Axis.Position.BOTH: break; + case Axis.Position.LOW: cur_y_max -= max_rec_height + s.axis_x.font_indent; break; + case Axis.Position.HIGH: cur_y_min += max_rec_height + s.axis_x.font_indent; break; } } // 4.5. Draw Axis title - if (s.axis_x.title.text != "") + if (s.axis_x.title.text != "") { + var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.zoom_x_low + s.place.zoom_x_high) / 2.0; + double scr_y = 0.0; switch (s.axis_x.position) { - case Axis.Position.LOW: - var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.zoom_x_low + s.place.zoom_x_high) / 2.0; - var scr_y = cur_y_max - s.axis_x.font_indent; - context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y); - set_source_rgba(s.axis_x.color); - if (common_x_axes) set_source_rgba(Color(0,0,0,1)); - show_text(s.axis_x.title); - break; - case Axis.Position.HIGH: - var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.zoom_x_low + s.place.zoom_x_high) / 2.0; - var scr_y = cur_y_min + s.axis_x.font_indent + s.axis_x.title.get_height(context); - context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y); - set_source_rgba(s.axis_x.color); - if (common_x_axes) set_source_rgba(Color(0,0,0,1)); - show_text(s.axis_x.title); - break; - case Axis.Position.BOTH: - break; + case Axis.Position.LOW: scr_y = cur_y_max - s.axis_x.font_indent; break; + case Axis.Position.HIGH: scr_y = cur_y_min + s.axis_x.font_indent + s.axis_x.title.get_height(context); break; } + context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y); + set_source_rgba(s.axis_x.color); + if (common_x_axes) set_source_rgba(Color(0,0,0,1)); + show_text(s.axis_x.title); + } // 5. Draw records, update cur_{x,y}_{min,max}. for (Float128 x = x_min, x_max = s.axis_x.zoom_max; point_belong (x, x_min, x_max); x += step) { @@ -776,37 +762,27 @@ namespace Gtk.CairoChart { ("%."+(s.axis_x.dsec_signs.to_string())+"Lf").printf((LongDouble)(x - (int64)x)).offset(1); time_text = dt.format(s.axis_x.time_format) + dsec_str; break; - default: - break; } - var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) - * (s.place.zoom_x_low + (s.place.zoom_x_high - s.place.zoom_x_low) / (s.axis_x.zoom_max - s.axis_x.zoom_min) * (x - s.axis_x.zoom_min)); + var scr_x = get_scr_x (s, x); var text_t = new Text(text, s.axis_x.font_style, s.axis_x.color); switch (s.axis_x.position) { case Axis.Position.LOW: var print_y = cur_y_max - s.axis_x.font_indent - (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); + var print_x = compact_rec_x_pos (s, x, text_t); + context.move_to (print_x, print_y); switch (s.axis_x.type) { case Axis.Type.NUMBERS: - var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - - text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); - context.move_to (print_x, print_y); show_text(text_t); break; case Axis.Type.DATE_TIME: - var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - - text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); - context.move_to (print_x, print_y); if (s.axis_x.date_format != "") show_text(text_t); var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color); - print_x = scr_x - time_text_t.get_width(context) / 2.0 - time_text_t.get_x_bearing(context) - - time_text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); + print_x = compact_rec_x_pos (s, x, time_text_t); context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent)); if (s.axis_x.time_format != "") show_text(time_text_t); break; - default: - break; } - // 6. Draw grid lines to the s.place.zoom_y_high. + // 6. Draw grid lines to the s.place.zoom_y_low. var line_style = s.grid.line_style; if (common_x_axes) line_style.color = Color(0, 0, 0, 0.5); set_line_style(line_style); @@ -819,26 +795,20 @@ namespace Gtk.CairoChart { break; case Axis.Position.HIGH: var print_y = cur_y_min + max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); + var print_x = compact_rec_x_pos (s, x, text_t); + context.move_to (print_x, print_y); + switch (s.axis_x.type) { case Axis.Type.NUMBERS: - var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - - text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); - context.move_to (print_x, print_y); show_text(text_t); break; case Axis.Type.DATE_TIME: - var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context) - - text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); - context.move_to (print_x, print_y); if (s.axis_x.date_format != "") show_text(text_t); var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color); - print_x = scr_x - time_text_t.get_width(context) / 2.0 - time_text_t.get_x_bearing(context) - - time_text_t.get_width(context) * (x - (s.axis_x.zoom_min + s.axis_x.zoom_max) / 2.0) / (s.axis_x.zoom_max - s.axis_x.zoom_min); + print_x = compact_rec_x_pos (s, x, time_text_t); context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent)); if (s.axis_x.time_format != "") show_text(time_text_t); break; - default: - break; } // 6. Draw grid lines to the s.place.zoom_y_high. var line_style = s.grid.line_style; @@ -851,10 +821,6 @@ namespace Gtk.CairoChart { else context.line_to (scr_x, double.max (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.zoom_y_low)); break; - case Axis.Position.BOTH: - break; - default: - break; } context.stroke (); } @@ -892,9 +858,6 @@ namespace Gtk.CairoChart { cur_y_min += max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent); break; - case Axis.Position.BOTH: - break; - default: break; } } } @@ -931,51 +894,43 @@ namespace Gtk.CairoChart { // 4.2. Cursor values for common Y axis if (common_y_axes && cursors_orientation == CursorOrientation.HORIZONTAL && cursors_crossings.length != 0) { switch (s.axis_y.position) { - case Axis.Position.LOW: cur_x_min += max_rec_width + s.axis_y.font_indent; break; - case Axis.Position.HIGH: cur_x_max -= max_rec_width + s.axis_y.font_indent; break; - case Axis.Position.BOTH: break; + case Axis.Position.LOW: cur_x_min += max_rec_width + s.axis_y.font_indent; break; + case Axis.Position.HIGH: cur_x_max -= max_rec_width + s.axis_y.font_indent; break; } } // 4.5. Draw Axis title - if (s.axis_y.title.text != "") + if (s.axis_y.title.text != "") { + var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.zoom_y_low + s.place.zoom_y_high) / 2.0; switch (s.axis_y.position) { case Axis.Position.LOW: - var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.zoom_y_low + s.place.zoom_y_high) / 2.0; var scr_x = cur_x_min + s.axis_y.font_indent + s.axis_y.title.get_width(context); context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0); - set_source_rgba(s.axis_y.color); - if (common_y_axes) set_source_rgba(Color(0,0,0,1)); - show_text(s.axis_y.title); break; case Axis.Position.HIGH: - var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.zoom_y_low + s.place.zoom_y_high) / 2.0; var scr_x = cur_x_max - s.axis_y.font_indent; context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0); - set_source_rgba(s.axis_y.color); - if (common_y_axes) set_source_rgba(Color(0,0,0,1)); - show_text(s.axis_y.title); - break; - case Axis.Position.BOTH: break; } + set_source_rgba(s.axis_y.color); + if (common_y_axes) set_source_rgba(Color(0,0,0,1)); + show_text(s.axis_y.title); + } // 5. Draw records, update cur_{x,y}_{min,max}. for (Float128 y = y_min, y_max = s.axis_y.zoom_max; point_belong (y, y_min, y_max); y += step) { if (common_y_axes) set_source_rgba(Color(0,0,0,1)); else set_source_rgba(s.axis_y.color); var text = s.axis_y.format.printf((LongDouble)y); - var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) - * (s.place.zoom_y_low + (s.place.zoom_y_high - s.place.zoom_y_low) / (s.axis_y.zoom_max - s.axis_y.zoom_min) * (y - s.axis_y.zoom_min)); + var scr_y = get_scr_y (s, y); var text_t = new Text(text, s.axis_y.font_style, s.axis_y.color); switch (s.axis_y.position) { case Axis.Position.LOW: context.move_to (cur_x_min + max_rec_width - (new Text(text)).get_width(context) + s.axis_y.font_indent - text_t.get_x_bearing(context) + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent), - scr_y + (new Text(text)).get_height(context) / 2.0 - + text_t.get_height(context) * (y - (s.axis_y.zoom_min + s.axis_y.zoom_max) / 2.0) / (s.axis_y.zoom_max - s.axis_y.zoom_min)); + compact_rec_y_pos (s, y, new Text(text))); show_text(text_t); - // 6. Draw grid lines to the s.place.zoom_y_high. + // 6. Draw grid lines to the s.place.zoom_x_low. var line_style = s.grid.line_style; if (common_y_axes) line_style.color = Color(0, 0, 0, 0.5); set_line_style(line_style); @@ -989,10 +944,9 @@ namespace Gtk.CairoChart { case Axis.Position.HIGH: context.move_to (cur_x_max - (new Text(text)).get_width(context) - s.axis_y.font_indent - text_t.get_x_bearing(context) - (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent), - scr_y + (new Text(text)).get_height(context) / 2.0 - + text_t.get_height(context) * (y - (s.axis_y.zoom_min + s.axis_y.zoom_max) / 2.0) / (s.axis_y.zoom_max - s.axis_y.zoom_min)); + compact_rec_y_pos (s, y, new Text(text))); show_text(text_t); - // 6. Draw grid lines to the s.place.zoom_y_high. + // 6. Draw grid lines to the s.place.zoom_x_high. var line_style = s.grid.line_style; if (common_y_axes) line_style.color = Color(0, 0, 0, 0.5); set_line_style(line_style); @@ -1003,10 +957,6 @@ namespace Gtk.CairoChart { else context.line_to (double.min (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.zoom_x_low), scr_y); break; - case Axis.Position.BOTH: - break; - default: - break; } context.stroke (); } @@ -1034,7 +984,6 @@ namespace Gtk.CairoChart { if (nskip != 0) {--nskip; continue;} - switch (s.axis_y.position) { case Axis.Position.LOW: cur_x_min += max_rec_width + s.axis_y.font_indent @@ -1042,9 +991,6 @@ namespace Gtk.CairoChart { case Axis.Position.HIGH: cur_x_max -= max_rec_width + s.axis_y.font_indent + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent); break; - case Axis.Position.BOTH: - break; - default: break; } } } @@ -1276,8 +1222,6 @@ namespace Gtk.CairoChart { case CursorOrientation.HORIZONTAL: d = (rel2scr_y(c.y) - rel2scr_y(active_cursor.y)).abs(); break; - default: - break; } if (d < distance) { distance = d; @@ -1340,10 +1284,10 @@ namespace Gtk.CairoChart { for (var ci = 0, max_ci = all_cursors.length(); ci < max_ci; ++ci) { var c = all_cursors.nth_data(ci); switch (cursors_orientation) { - case CursorOrientation.VERTICAL: - if (c.x <= _rel_zoom_x_min || c.x >= _rel_zoom_x_max) continue; break; - case CursorOrientation.HORIZONTAL: - if (c.y <= _rel_zoom_y_min || c.y >= _rel_zoom_y_max) continue; break; + case CursorOrientation.VERTICAL: + if (c.x <= _rel_zoom_x_min || c.x >= _rel_zoom_x_max) continue; break; + case CursorOrientation.HORIZONTAL: + if (c.y <= _rel_zoom_y_min || c.y >= _rel_zoom_y_max) continue; break; } CursorCross[] crossings = {}; @@ -1353,33 +1297,32 @@ namespace Gtk.CairoChart { Point[] points = {}; switch (cursors_orientation) { - case CursorOrientation.VERTICAL: - points = sort_points (s, s.sort); - break; - case CursorOrientation.HORIZONTAL: - points = sort_points (s, s.sort); - break; + case CursorOrientation.VERTICAL: + points = sort_points (s, s.sort); + break; + case CursorOrientation.HORIZONTAL: + points = sort_points (s, s.sort); + break; } for (var i = 0; i + 1 < points.length; ++i) { switch (cursors_orientation) { - case CursorOrientation.VERTICAL: - Float128 y = 0.0; - if (vcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), rel2scr_x(c.x), - plot_area_y_min, plot_area_y_max, out y)) { - CursorCross cc = {si, Point(get_real_x(s, rel2scr_x(c.x)), get_real_y(s, y))}; - crossings += cc; -//stdout.printf("si = %d, rel2scr_x(c.x) = %f, y = %f\n", si, rel2scr_x(c.x), y); - } - break; - case CursorOrientation.HORIZONTAL: - Float128 x = 0.0; - if (hcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), - plot_area_x_min, plot_area_x_max, rel2scr_y(c.y), out x)) { - CursorCross cc = {si, Point(get_real_x(s, x), get_real_y(s, rel2scr_y(c.y)))}; - crossings += cc; - } - break; + case CursorOrientation.VERTICAL: + Float128 y = 0.0; + if (vcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), rel2scr_x(c.x), + plot_area_y_min, plot_area_y_max, out y)) { + CursorCross cc = {si, Point(get_real_x(s, rel2scr_x(c.x)), get_real_y(s, y))}; + crossings += cc; + } + break; + case CursorOrientation.HORIZONTAL: + Float128 x = 0.0; + if (hcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), + plot_area_x_min, plot_area_x_max, rel2scr_y(c.y), out x)) { + CursorCross cc = {si, Point(get_real_x(s, x), get_real_y(s, rel2scr_y(c.y)))}; + crossings += cc; + } + break; } } } @@ -1389,8 +1332,6 @@ namespace Gtk.CairoChart { } } cursors_crossings = local_cursor_crossings; -//if (cursors_crossings.length != 0) -//stdout.printf("cursors_crossings[0].crossings.length = %d\n", cursors_crossings[0].crossings.length); } protected virtual void draw_cursors () { @@ -1409,67 +1350,116 @@ namespace Gtk.CairoChart { var scrx = get_scr_x(s, p.x); var scry = get_scr_y(s, p.y); if (scrx < low.x) low.x = scrx; -//stdout.printf("low.y = %f, high.y = %f\n", low.y, high.y); if (scry < low.y) low.y = scry; if (scrx > high.x) high.x = scrx; if (scry > high.y) high.y = scry; if (common_x_axes) { switch (s.axis_x.position) { - case Axis.Position.LOW: high.y = plot_area_y_max + s.axis_x.font_indent; break; - case Axis.Position.HIGH: low.y = plot_area_y_min - s.axis_x.font_indent; break; - case Axis.Position.BOTH: - high.y = plot_area_y_max + s.axis_x.font_indent; - low.y = plot_area_y_min - s.axis_x.font_indent; - break; + case Axis.Position.LOW: high.y = plot_area_y_max + s.axis_x.font_indent; break; + case Axis.Position.HIGH: low.y = plot_area_y_min - s.axis_x.font_indent; break; + case Axis.Position.BOTH: + high.y = plot_area_y_max + s.axis_x.font_indent; + low.y = plot_area_y_min - s.axis_x.font_indent; + break; } } if (common_y_axes) { switch (s.axis_y.position) { - case Axis.Position.LOW: low.x = plot_area_x_min - s.axis_y.font_indent; break; - case Axis.Position.HIGH: high.x = plot_area_x_max + s.axis_y.font_indent; break; - case Axis.Position.BOTH: - low.x = plot_area_x_min - s.axis_y.font_indent; - high.x = plot_area_x_max + s.axis_y.font_indent; - break; + case Axis.Position.LOW: low.x = plot_area_x_min - s.axis_y.font_indent; break; + case Axis.Position.HIGH: high.x = plot_area_x_max + s.axis_y.font_indent; break; + case Axis.Position.BOTH: + low.x = plot_area_x_min - s.axis_y.font_indent; + high.x = plot_area_x_max + s.axis_y.font_indent; + break; } } } var c = all_cursors.nth_data(cursors_crossings[cci].cursor_index); + var s = series[zoom_first_show]; switch (cursors_orientation) { - case CursorOrientation.VERTICAL: - if (low.y > high.y) continue; - set_line_style(cursor_line_style); -//stdout.printf("rel2scr_x(c.x) = %f, low.y = %f\n", rel2scr_x(c.x), low.y); - context.move_to (rel2scr_x(c.x), low.y); - context.line_to (rel2scr_x(c.x), high.y); - context.stroke(); + case CursorOrientation.VERTICAL: + if (low.y > high.y) continue; + set_line_style(cursor_line_style); + context.move_to (rel2scr_x(c.x), low.y); + context.line_to (rel2scr_x(c.x), high.y); + context.stroke(); - // TODO: show values - if (common_x_axes) - // show only Y value - ; - else - // show [X;Y] - ; - break; - case CursorOrientation.HORIZONTAL: - if (low.x > high.x) continue; - set_line_style(cursor_line_style); - context.move_to (low.x, rel2scr_y(c.y)); - context.line_to (high.x, rel2scr_y(c.y)); - context.stroke(); + // TODO: show values + if (common_x_axes) { + // show common X value + var x = get_real_x(s, rel2scr_x(c.x)); + string text = "", time_text = ""; + switch (s.axis_x.type) { + case Axis.Type.NUMBERS: + text = s.axis_x.format.printf((LongDouble)x); + break; + case Axis.Type.DATE_TIME: + var dt = new DateTime.from_unix_utc((int64)x); + text = dt.format(s.axis_x.date_format); + var dsec_str = + ("%."+(s.axis_x.dsec_signs.to_string())+"Lf").printf((LongDouble)(x - (int64)x)).offset(1); + time_text = dt.format(s.axis_x.time_format) + dsec_str; + break; + default: + break; + } + var text_t = new Text(text, s.axis_x.font_style, s.axis_x.color); + var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color); + var print_y = 0.0; + switch (s.axis_x.position) { + case Axis.Position.LOW: print_y = y_min + height - s.axis_x.font_indent + - (legend.position == Legend.Position.BOTTOM ? legend_height : 0); + break; + case Axis.Position.HIGH: print_y = y_min + title_height + s.axis_x.font_indent + + (legend.position == Legend.Position.TOP ? legend_height : 0) + + (s.axis_x.date_format == "" ? 0 : text_t.get_height(context)) + + (s.axis_x.time_format == "" ? 0 : time_text_t.get_height(context)) + + (s.axis_x.date_format == "" || s.axis_x.time_format == "" ? 0 : s.axis_x.font_indent); + break; + } + var print_x = compact_rec_x_pos (s, x, text_t); + context.move_to (print_x, print_y); - // TODO: show values - if (common_y_axes) - // show only X value - ; - else - // show [X;Y] - ; - break; + switch (s.axis_x.type) { + case Axis.Type.NUMBERS: + show_text(text_t); + break; + case Axis.Type.DATE_TIME: + if (s.axis_x.date_format != "") show_text(text_t); + print_x = compact_rec_x_pos (s, x, time_text_t); + context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent)); + if (s.axis_x.time_format != "") show_text(time_text_t); + break; + } + + context.stroke (); + + // show only Y value + ; + } else { + // show [X;Y] + } + break; + case CursorOrientation.HORIZONTAL: + if (low.x > high.x) continue; + set_line_style(cursor_line_style); + context.move_to (low.x, rel2scr_y(c.y)); + context.line_to (high.x, rel2scr_y(c.y)); + context.stroke(); + + // TODO: show values + if (common_y_axes) { + // show common Y value + + // show only X value + ; + } else + // show [X;Y] + ; + break; } } } diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 33916a9..1f51ca3 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -83,6 +83,9 @@ void plot_chart2 (Chart chart) { s3.axis_x.title = new Text("All Series: Axis X."); s3.axis_y.title = new Text("Series 3: Axis Y."); + //s1.axis_x.position = s2.axis_x.position = s3.axis_x.position = Axis.Position.HIGH; + //s1.axis_x.type = s2.axis_x.type = s3.axis_x.type = Axis.Type.DATE_TIME; + chart.series = { s1, s2, s3 }; } @@ -261,7 +264,6 @@ int main (string[] args) { case Legend.Position.RIGHT: radio_button2.set_active(true); break; case Legend.Position.LEFT: radio_button3.set_active(true); break; case Legend.Position.BOTTOM: radio_button4.set_active(true); break; - default: break; } switch (chart.cursors_orientation) { case Chart.CursorOrientation.VERTICAL: radio_button7.set_active(true); break; @@ -275,7 +277,6 @@ int main (string[] args) { case Legend.Position.RIGHT: radio_button2.set_active(true); break; case Legend.Position.LEFT: radio_button3.set_active(true); break; case Legend.Position.BOTTOM: radio_button4.set_active(true); break; - default: break; } switch (chart.cursors_orientation) { case Chart.CursorOrientation.VERTICAL: radio_button7.set_active(true); break; @@ -289,7 +290,6 @@ int main (string[] args) { case Legend.Position.RIGHT: radio_button2.set_active(true); break; case Legend.Position.LEFT: radio_button3.set_active(true); break; case Legend.Position.BOTTOM: radio_button4.set_active(true); break; - default: break; } switch (chart.cursors_orientation) { case Chart.CursorOrientation.VERTICAL: radio_button7.set_active(true); break; @@ -303,7 +303,6 @@ int main (string[] args) { case Legend.Position.RIGHT: radio_button2.set_active(true); break; case Legend.Position.LEFT: radio_button4.set_active(true); break; case Legend.Position.BOTTOM: radio_button4.set_active(true); break; - default: break; } switch (chart.cursors_orientation) { case Chart.CursorOrientation.VERTICAL: radio_button7.set_active(true); break; From f4b89194d61d38716715a0c062f7aca107227cc3 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Thu, 7 Dec 2017 04:21:27 +0300 Subject: [PATCH 24/40] Fix cursors value on common axes. --- src/Chart.vala | 33 +++++++++++++++++++++++++++++---- test/ChartTest.vala | 2 ++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 2501b17..3a3a866 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -1414,10 +1414,17 @@ namespace Gtk.CairoChart { - (legend.position == Legend.Position.BOTTOM ? legend_height : 0); break; case Axis.Position.HIGH: print_y = y_min + title_height + s.axis_x.font_indent - + (legend.position == Legend.Position.TOP ? legend_height : 0) - + (s.axis_x.date_format == "" ? 0 : text_t.get_height(context)) - + (s.axis_x.time_format == "" ? 0 : time_text_t.get_height(context)) - + (s.axis_x.date_format == "" || s.axis_x.time_format == "" ? 0 : s.axis_x.font_indent); + + (legend.position == Legend.Position.TOP ? legend_height : 0); + switch (s.axis_x.type) { + case Axis.Type.NUMBERS: + print_y += text_t.get_height(context); + break; + case Axis.Type.DATE_TIME: + print_y += (s.axis_x.date_format == "" ? 0 : text_t.get_height(context)) + + (s.axis_x.time_format == "" ? 0 : time_text_t.get_height(context)) + + (s.axis_x.date_format == "" || s.axis_x.time_format == "" ? 0 : s.axis_x.font_indent); + break; + } break; } var print_x = compact_rec_x_pos (s, x, text_t); @@ -1453,6 +1460,24 @@ namespace Gtk.CairoChart { // TODO: show values if (common_y_axes) { // show common Y value + var y = get_real_y(s, rel2scr_y(c.y)); + var text_t = new Text(s.axis_y.format.printf((LongDouble)y)); + var print_y = compact_rec_y_pos (s, y, text_t); + var print_x = 0.0; + switch (s.axis_y.position) { + case Axis.Position.LOW: + print_x = x_min + s.axis_y.font_indent + + (legend.position == Legend.Position.LEFT ? legend_width : 0); + break; + case Axis.Position.HIGH: + print_x = x_min + width - text_t.get_width(context) - s.axis_y.font_indent + - (legend.position == Legend.Position.RIGHT ? legend_width : 0); + break; + } + context.move_to (print_x, print_y); + show_text(text_t); + + context.stroke (); // show only X value ; diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 1f51ca3..6ef3eec 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -131,6 +131,8 @@ void plot_chart3 (Chart chart) { s3.axis_x.title = new Text("Series 3: Axis X."); s3.axis_y.title = new Text("Series 3: Axis Y."); + //s1.axis_y.position = s2.axis_y.position = s3.axis_y.position = Axis.Position.LOW; + chart.series = { s1, s2, s3 }; } From c676f7eea0c30cd66be4290c24e4f91583733ccd Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Sun, 10 Dec 2017 03:42:29 +0300 Subject: [PATCH 25/40] Show cursors crossings values. --- src/Chart.vala | 233 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 183 insertions(+), 50 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 3a3a866..f3f7ab4 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -463,12 +463,12 @@ namespace Gtk.CairoChart { process_legend (LegendProcessType.DRAW); } - int axis_rec_npoints = 128; + protected int axis_rec_npoints = 128; protected virtual void calc_axis_rec_sizes (Axis axis, out double max_rec_width, out double max_rec_height, bool is_horizontal = true) { max_rec_width = max_rec_height = 0; for (var i = 0; i < axis_rec_npoints; ++i) { - Float128 x = axis.zoom_min + (axis.zoom_max - axis.zoom_min) / axis_rec_npoints * i; + Float128 x = (int64)(axis.zoom_min + (axis.zoom_max - axis.zoom_min) / axis_rec_npoints * i) + 1.0/3.0; switch (axis.type) { case Axis.Type.NUMBERS: var text = new Text (axis.format.printf((LongDouble)x) + (is_horizontal ? "_" : "")); @@ -477,18 +477,19 @@ namespace Gtk.CairoChart { max_rec_height = double.max (max_rec_height, text.get_height(context)); break; case Axis.Type.DATE_TIME: - var dt = new DateTime.from_unix_utc((int64)x); + string date, time; + format_date_time(axis, x, out date, out time); + var text = new Text(""); var h = 0.0; if (axis.date_format != "") { - text = new Text (dt.format(axis.date_format) + (is_horizontal ? "_" : "")); + text = new Text (date + (is_horizontal ? "_" : "")); text.style = axis.font_style; max_rec_width = double.max (max_rec_width, text.get_width(context)); h = text.get_height(context); } if (axis.time_format != "") { - var dsec_str = ("%."+(axis.dsec_signs.to_string())+"f").printf(1.0/3.0).offset(1); - text = new Text (dt.format(axis.time_format) + (is_horizontal ? "_" : "") + dsec_str); + text = new Text (time + (is_horizontal ? "_" : "")); text.style = axis.font_style; max_rec_width = double.max (max_rec_width, text.get_width(context)); h += text.get_height(context); @@ -541,14 +542,14 @@ namespace Gtk.CairoChart { } protected virtual void calc_plot_area () { - plot_area_x_min = cur_x_min + legend.indent; - plot_area_x_max = cur_x_max - legend.indent; - plot_area_y_min = cur_y_min + legend.indent; - plot_area_y_max = cur_y_max - legend.indent; + plot_area_x_min = cur_x_min + legend.indent; + plot_area_x_max = cur_x_max - legend.indent; + plot_area_y_min = cur_y_min + legend.indent; + plot_area_y_max = cur_y_max - legend.indent; - // Check for common axes - common_x_axes = common_y_axes = true; - int nzoom_series_show = 0; + // Check for common axes + common_x_axes = common_y_axes = true; + int nzoom_series_show = 0; for (var si = series.length - 1; si >=0; --si) { var s = series[si]; if (!s.zoom_show) continue; @@ -694,6 +695,15 @@ namespace Gtk.CairoChart { + text.get_height(context) * (y - (s.axis_y.zoom_min + s.axis_y.zoom_max) / 2.0) / (s.axis_y.zoom_max - s.axis_y.zoom_min); } + protected virtual void format_date_time (Axis axis, Float128 x, out string date, out string time) { + date = time = ""; + var dt = new DateTime.from_unix_utc((int64)x); + date = dt.format(axis.date_format); + var dsec_str = + ("%."+(axis.dsec_signs.to_string())+"Lf").printf((LongDouble)(x - (int64)x)).offset(1); + time = dt.format(axis.time_format) + dsec_str; + } + protected virtual void draw_horizontal_axis () { for (var si = series.length - 1, nskip = 0; si >=0; --si) { var s = series[si]; @@ -756,11 +766,7 @@ namespace Gtk.CairoChart { text = s.axis_x.format.printf((LongDouble)x); break; case Axis.Type.DATE_TIME: - var dt = new DateTime.from_unix_utc((int64)x); - text = dt.format(s.axis_x.date_format); - var dsec_str = - ("%."+(s.axis_x.dsec_signs.to_string())+"Lf").printf((LongDouble)(x - (int64)x)).offset(1); - time_text = dt.format(s.axis_x.time_format) + dsec_str; + format_date_time(s.axis_x, x, out text, out time_text); break; } var scr_x = get_scr_x (s, x); @@ -1261,6 +1267,13 @@ namespace Gtk.CairoChart { protected struct CursorCross { uint series_index; Point point; + Point size; + bool show_x; + bool show_date; + bool show_time; + bool show_y; + Point scr_point; + Point scr_value_point; } protected struct CursorCrossings { uint cursor_index; @@ -1311,16 +1324,24 @@ namespace Gtk.CairoChart { Float128 y = 0.0; if (vcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), rel2scr_x(c.x), plot_area_y_min, plot_area_y_max, out y)) { - CursorCross cc = {si, Point(get_real_x(s, rel2scr_x(c.x)), get_real_y(s, y))}; - crossings += cc; + var point = Point(get_real_x(s, rel2scr_x(c.x)), get_real_y(s, y)); + Point size; bool show_x, show_date, show_time, show_y; + cross_what_to_show(s, out show_x, out show_time, out show_date, out show_y); + calc_cross_sizes (s, point, out size, show_x, show_time, show_date, show_y); + CursorCross cc = {si, point, size, show_x, show_date, show_time, show_y}; + crossings += cc; } break; case CursorOrientation.HORIZONTAL: Float128 x = 0.0; if (hcross(get_scr_point(s, points[i]), get_scr_point(s, points[i+1]), plot_area_x_min, plot_area_x_max, rel2scr_y(c.y), out x)) { - CursorCross cc = {si, Point(get_real_x(s, x), get_real_y(s, rel2scr_y(c.y)))}; - crossings += cc; + var point = Point(get_real_x(s, x), get_real_y(s, rel2scr_y(c.y))); + Point size; bool show_x, show_date, show_time, show_y; + cross_what_to_show(s, out show_x, out show_time, out show_date, out show_y); + calc_cross_sizes (s, point, out size, show_x, show_time, show_date, show_y); + CursorCross cc = {si, point, size, show_x, show_date, show_time, show_y}; + crossings += cc; } break; } @@ -1334,15 +1355,79 @@ namespace Gtk.CairoChart { cursors_crossings = local_cursor_crossings; } + protected virtual void calc_cursors_value_positions () { + for (var ccsi = 0, max_ccsi = cursors_crossings.length; ccsi < max_ccsi; ++ccsi) { + for (var cci = 0, max_cci = cursors_crossings[ccsi].crossings.length; cci < max_cci; ++cci) { + // TODO: Ticket #142: find smart algorithm of cursors values placements + unowned CursorCross[] cr = cursors_crossings[ccsi].crossings; + cr[cci].scr_point = get_scr_point (series[cr[cci].series_index], cr[cci].point); + var d_max = double.max (cr[cci].size.x / 1.5, cr[cci].size.y / 1.5); + cr[cci].scr_value_point = Point (cr[cci].scr_point.x + d_max, cr[cci].scr_point.y - d_max); + } + } + } + + protected virtual void cross_what_to_show (Series s, out bool show_x, out bool show_time, + out bool show_date, out bool show_y) { + show_x = show_time = show_date = show_y = false; + switch (cursors_orientation) { + case CursorOrientation.VERTICAL: + show_y = true; + if (!common_x_axes) + switch (s.axis_x.type) { + case Axis.Type.NUMBERS: show_x = true; break; + case Axis.Type.DATE_TIME: + if (s.axis_x.date_format != "") show_date = true; + if (s.axis_x.time_format != "") show_time = true; + break; + } + break; + case CursorOrientation.HORIZONTAL: + if (!common_y_axes) show_y = true; + switch (s.axis_x.type) { + case Axis.Type.NUMBERS: show_x = true; break; + case Axis.Type.DATE_TIME: + if (s.axis_x.date_format != "") show_date = true; + if (s.axis_x.time_format != "") show_time = true; + break; + } + break; + } + } + + protected virtual void calc_cross_sizes (Series s, Point p, out Point size, + bool show_x = false, bool show_time = false, + bool show_date = false, bool show_y = false) { + if (show_x == show_time == show_date == show_y == false) + cross_what_to_show(s, out show_x, out show_time, out show_date, out show_y); + size = Point (); + double x_w = 0.0, x_h = 0.0, y_w = 0.0, y_h = 0.0; + string date, time; + format_date_time(s.axis_x, p.x, out date, out time); + var date_t = new Text (date, s.axis_x.font_style, s.axis_x.color); + var time_t = new Text (time, s.axis_x.font_style, s.axis_x.color); + var x_t = new Text (s.axis_x.format.printf((LongDouble)p.x), s.axis_x.font_style, s.axis_x.color); + var y_t = new Text (s.axis_y.format.printf((LongDouble)p.y), s.axis_y.font_style, s.axis_y.color); + double h_x = 0.0, h_y = 0.0; + if (show_x) { size.x = x_t.get_width(context); h_x = x_t.get_height(context); } + if (show_date) { size.x = date_t.get_width(context); h_x = date_t.get_height(context); } + if (show_time) { size.x = double.max(size.x, time_t.get_width(context)); h_x += time_t.get_height(context); } + if (show_y) { size.x += y_t.get_width(context); h_y = y_t.get_height(context); } + if ((show_x || show_date || show_time) && show_y) size.x += double.max(s.axis_x.font_indent, s.axis_y.font_indent); + if (show_date && show_time) h_x += s.axis_x.font_indent; + size.y = double.max (h_x, h_y); + } + protected virtual void draw_cursors () { if (series.length == 0) return; var all_cursors = get_all_cursors(); + calc_cursors_value_positions(); for (var cci = 0, max_cci = cursors_crossings.length; cci < max_cci; ++cci) { var low = Point(plot_area_x_max, plot_area_y_max); // low and high var high = Point(plot_area_x_min, plot_area_y_min); // points of the cursor - var ccs = cursors_crossings[cci].crossings; + unowned CursorCross[] ccs = cursors_crossings[cci].crossings; for (var ci = 0, max_ci = ccs.length; ci < max_ci; ++ci) { var si = ccs[ci].series_index; var s = series[si]; @@ -1374,10 +1459,14 @@ namespace Gtk.CairoChart { break; } } + + set_line_style(cursor_line_style); + context.move_to (ccs[ci].scr_point.x, ccs[ci].scr_point.y); + context.line_to (ccs[ci].scr_value_point.x, ccs[ci].scr_value_point.y); + context.stroke (); } var c = all_cursors.nth_data(cursors_crossings[cci].cursor_index); - var s = series[zoom_first_show]; switch (cursors_orientation) { case CursorOrientation.VERTICAL: @@ -1387,9 +1476,9 @@ namespace Gtk.CairoChart { context.line_to (rel2scr_x(c.x), high.y); context.stroke(); - // TODO: show values + // show common X value if (common_x_axes) { - // show common X value + var s = series[zoom_first_show]; var x = get_real_x(s, rel2scr_x(c.x)); string text = "", time_text = ""; switch (s.axis_x.type) { @@ -1397,11 +1486,7 @@ namespace Gtk.CairoChart { text = s.axis_x.format.printf((LongDouble)x); break; case Axis.Type.DATE_TIME: - var dt = new DateTime.from_unix_utc((int64)x); - text = dt.format(s.axis_x.date_format); - var dsec_str = - ("%."+(s.axis_x.dsec_signs.to_string())+"Lf").printf((LongDouble)(x - (int64)x)).offset(1); - time_text = dt.format(s.axis_x.time_format) + dsec_str; + format_date_time(s.axis_x, x, out text, out time_text); break; default: break; @@ -1411,19 +1496,19 @@ namespace Gtk.CairoChart { var print_y = 0.0; switch (s.axis_x.position) { case Axis.Position.LOW: print_y = y_min + height - s.axis_x.font_indent - - (legend.position == Legend.Position.BOTTOM ? legend_height : 0); + - (legend.position == Legend.Position.BOTTOM ? legend_height : 0); break; case Axis.Position.HIGH: print_y = y_min + title_height + s.axis_x.font_indent - + (legend.position == Legend.Position.TOP ? legend_height : 0); + + (legend.position == Legend.Position.TOP ? legend_height : 0); switch (s.axis_x.type) { case Axis.Type.NUMBERS: print_y += text_t.get_height(context); break; case Axis.Type.DATE_TIME: print_y += (s.axis_x.date_format == "" ? 0 : text_t.get_height(context)) - + (s.axis_x.time_format == "" ? 0 : time_text_t.get_height(context)) - + (s.axis_x.date_format == "" || s.axis_x.time_format == "" ? 0 : s.axis_x.font_indent); - break; + + (s.axis_x.time_format == "" ? 0 : time_text_t.get_height(context)) + + (s.axis_x.date_format == "" || s.axis_x.time_format == "" ? 0 : s.axis_x.font_indent); + break; } break; } @@ -1443,11 +1528,6 @@ namespace Gtk.CairoChart { } context.stroke (); - - // show only Y value - ; - } else { - // show [X;Y] } break; case CursorOrientation.HORIZONTAL: @@ -1457,9 +1537,9 @@ namespace Gtk.CairoChart { context.line_to (high.x, rel2scr_y(c.y)); context.stroke(); - // TODO: show values + // show common Y value if (common_y_axes) { - // show common Y value + var s = series[zoom_first_show]; var y = get_real_y(s, rel2scr_y(c.y)); var text_t = new Text(s.axis_y.format.printf((LongDouble)y)); var print_y = compact_rec_y_pos (s, y, text_t); @@ -1478,14 +1558,67 @@ namespace Gtk.CairoChart { show_text(text_t); context.stroke (); - - // show only X value - ; - } else - // show [X;Y] - ; + } break; } + + // show value (X, Y or [X;Y]) + for (var ci = 0, max_ci = ccs.length; ci < max_ci; ++ci) { + var si = ccs[ci].series_index; + var s = series[si]; + var point = ccs[ci].point; + var size = ccs[ci].size; + var svp = ccs[ci].scr_value_point; + var rp = get_real_point(s, rel2scr_point(point)); + var show_x = ccs[ci].show_x; + var show_date = ccs[ci].show_date; + var show_time = ccs[ci].show_time; + var show_y = ccs[ci].show_y; + + set_source_rgba(bg_color); + context.rectangle (svp.x - size.x / 2, svp.y - size.y / 2, size.x, size.y); + context.fill(); + + if (show_x) { + set_source_rgba(s.axis_x.color); + var text_t = new Text(s.axis_x.format.printf((LongDouble)point.x)); + context.move_to (svp.x - size.x / 2, svp.y + text_t.get_height(context) / 2); + show_text(text_t); + context.stroke(); + } + + if (show_time) { + set_source_rgba(s.axis_x.color); + string date = "", time = ""; + format_date_time(s.axis_x, point.x, out date, out time); + var text_t = new Text(time); + var y = svp.y + text_t.get_height(context) / 2; + if (show_date) y -= text_t.get_height(context) / 2 + s.axis_x.font_indent / 2; + context.move_to (svp.x - size.x / 2, y); + show_text(text_t); + context.stroke(); + } + + if (show_date) { + set_source_rgba(s.axis_x.color); + string date = "", time = ""; + format_date_time(s.axis_x, point.x, out date, out time); + var text_t = new Text(date); + var y = svp.y + text_t.get_height(context) / 2; + if (show_time) y += text_t.get_height(context) / 2 + s.axis_x.font_indent / 2; + context.move_to (svp.x - size.x / 2, y); + show_text(text_t); + context.stroke(); + } + + if (show_y) { + set_source_rgba(s.axis_y.color); + var text_t = new Text(s.axis_y.format.printf((LongDouble)point.y)); + context.move_to (svp.x + size.x / 2 - text_t.get_width(context), svp.y + text_t.get_height(context) / 2); + show_text(text_t); + context.stroke(); + } + } } } @@ -1528,7 +1661,7 @@ namespace Gtk.CairoChart { chart.selection_style = this.selection_style; chart.series = this.series.copy(); chart.show_legend = this.show_legend; - chart.title = this.title.copy().copy(); + chart.title = this.title.copy(); chart.title_height = this.title_height; chart.title_vindent = this.title_vindent; chart.title_width = this.title_width; From f80401bd762751b58748ad8e8580fa956ca9e6df Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Fri, 15 Dec 2017 17:15:15 +0300 Subject: [PATCH 26/40] Font style fixed in Text() calls. --- src/Chart.vala | 28 ++++++++++++---------------- src/FontStyle.vala | 4 +++- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index f3f7ab4..1707e01 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -471,8 +471,7 @@ namespace Gtk.CairoChart { Float128 x = (int64)(axis.zoom_min + (axis.zoom_max - axis.zoom_min) / axis_rec_npoints * i) + 1.0/3.0; switch (axis.type) { case Axis.Type.NUMBERS: - var text = new Text (axis.format.printf((LongDouble)x) + (is_horizontal ? "_" : "")); - text.style = axis.font_style; + var text = new Text (axis.format.printf((LongDouble)x) + (is_horizontal ? "_" : ""), axis.font_style); max_rec_width = double.max (max_rec_width, text.get_width(context)); max_rec_height = double.max (max_rec_height, text.get_height(context)); break; @@ -480,17 +479,14 @@ namespace Gtk.CairoChart { string date, time; format_date_time(axis, x, out date, out time); - var text = new Text(""); var h = 0.0; if (axis.date_format != "") { - text = new Text (date + (is_horizontal ? "_" : "")); - text.style = axis.font_style; + var text = new Text (date + (is_horizontal ? "_" : ""), axis.font_style); max_rec_width = double.max (max_rec_width, text.get_width(context)); h = text.get_height(context); } if (axis.time_format != "") { - text = new Text (time + (is_horizontal ? "_" : "")); - text.style = axis.font_style; + var text = new Text (time + (is_horizontal ? "_" : ""), axis.font_style); max_rec_width = double.max (max_rec_width, text.get_width(context)); h += text.get_height(context); } @@ -932,9 +928,9 @@ namespace Gtk.CairoChart { var text_t = new Text(text, s.axis_y.font_style, s.axis_y.color); switch (s.axis_y.position) { case Axis.Position.LOW: - context.move_to (cur_x_min + max_rec_width - (new Text(text)).get_width(context) + s.axis_y.font_indent - text_t.get_x_bearing(context) + context.move_to (cur_x_min + max_rec_width - text_t.get_width(context) + s.axis_y.font_indent - text_t.get_x_bearing(context) + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent), - compact_rec_y_pos (s, y, new Text(text))); + compact_rec_y_pos (s, y, text_t)); show_text(text_t); // 6. Draw grid lines to the s.place.zoom_x_low. var line_style = s.grid.line_style; @@ -948,9 +944,9 @@ namespace Gtk.CairoChart { context.line_to (double.max (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.zoom_x_high), scr_y); break; case Axis.Position.HIGH: - context.move_to (cur_x_max - (new Text(text)).get_width(context) - s.axis_y.font_indent - text_t.get_x_bearing(context) + context.move_to (cur_x_max - text_t.get_width(context) - s.axis_y.font_indent - text_t.get_x_bearing(context) - (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent), - compact_rec_y_pos (s, y, new Text(text))); + compact_rec_y_pos (s, y, text_t)); show_text(text_t); // 6. Draw grid lines to the s.place.zoom_x_high. var line_style = s.grid.line_style; @@ -1541,7 +1537,7 @@ namespace Gtk.CairoChart { if (common_y_axes) { var s = series[zoom_first_show]; var y = get_real_y(s, rel2scr_y(c.y)); - var text_t = new Text(s.axis_y.format.printf((LongDouble)y)); + var text_t = new Text(s.axis_y.format.printf((LongDouble)y, s.axis_y.font_style)); var print_y = compact_rec_y_pos (s, y, text_t); var print_x = 0.0; switch (s.axis_y.position) { @@ -1581,7 +1577,7 @@ namespace Gtk.CairoChart { if (show_x) { set_source_rgba(s.axis_x.color); - var text_t = new Text(s.axis_x.format.printf((LongDouble)point.x)); + var text_t = new Text(s.axis_x.format.printf((LongDouble)point.x), s.axis_x.font_style); context.move_to (svp.x - size.x / 2, svp.y + text_t.get_height(context) / 2); show_text(text_t); context.stroke(); @@ -1591,7 +1587,7 @@ namespace Gtk.CairoChart { set_source_rgba(s.axis_x.color); string date = "", time = ""; format_date_time(s.axis_x, point.x, out date, out time); - var text_t = new Text(time); + var text_t = new Text(time, s.axis_x.font_style); var y = svp.y + text_t.get_height(context) / 2; if (show_date) y -= text_t.get_height(context) / 2 + s.axis_x.font_indent / 2; context.move_to (svp.x - size.x / 2, y); @@ -1603,7 +1599,7 @@ namespace Gtk.CairoChart { set_source_rgba(s.axis_x.color); string date = "", time = ""; format_date_time(s.axis_x, point.x, out date, out time); - var text_t = new Text(date); + var text_t = new Text(date, s.axis_x.font_style); var y = svp.y + text_t.get_height(context) / 2; if (show_time) y += text_t.get_height(context) / 2 + s.axis_x.font_indent / 2; context.move_to (svp.x - size.x / 2, y); @@ -1613,7 +1609,7 @@ namespace Gtk.CairoChart { if (show_y) { set_source_rgba(s.axis_y.color); - var text_t = new Text(s.axis_y.format.printf((LongDouble)point.y)); + var text_t = new Text(s.axis_y.format.printf((LongDouble)point.y), s.axis_y.font_style); context.move_to (svp.x + size.x / 2 - text_t.get_width(context), svp.y + text_t.get_height(context) / 2); show_text(text_t); context.stroke(); diff --git a/src/FontStyle.vala b/src/FontStyle.vala index df4b8de..685fd0e 100644 --- a/src/FontStyle.vala +++ b/src/FontStyle.vala @@ -15,11 +15,13 @@ namespace Gtk.CairoChart { public FontStyle (string family = "Sans", Cairo.FontSlant slant = Cairo.FontSlant.NORMAL, Cairo.FontWeight weight = Cairo.FontWeight.NORMAL, - double size = 10) { + double size = 10, + FontOrient orientation = FontOrient.HORIZONTAL) { this.family = family; this.slant = slant; this.weight = weight; this.size = size; + this.orientation = orientation; } } } From a8d5b1658a528fe1a15ff4be1aab486accd4e2b5 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Fri, 15 Dec 2017 20:50:13 +0300 Subject: [PATCH 27/40] Uneccessary .copy() calls removed. --- src/Chart.vala | 6 +++--- src/Series.vala | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 1707e01..ad362a7 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -1634,7 +1634,7 @@ namespace Gtk.CairoChart { chart.cursor_line_style = this.cursor_line_style; chart.cursor_max_rm_distance = this.cursor_max_rm_distance; chart.cursors = this.cursors.copy(); - chart.cursors_crossings = this.cursors_crossings.copy(); // no deep copying for .crossings + chart.cursors_crossings = this.cursors_crossings; chart.cursors_orientation = this.cursors_orientation; chart.height = this.height; chart.is_cursor_active = this.is_cursor_active; @@ -1645,7 +1645,7 @@ namespace Gtk.CairoChart { chart.legend_text_vspace = this.legend_text_vspace; chart.legend_width = this.legend_width; chart.marker_size = this.marker_size; - chart.max_font_heights = this.max_font_heights.copy(); + chart.max_font_heights = this.max_font_heights; chart.plot_area_x_max = this.plot_area_x_max; chart.plot_area_x_min = this.plot_area_x_min; chart.plot_area_y_max = this.plot_area_y_max; @@ -1655,7 +1655,7 @@ namespace Gtk.CairoChart { chart._rel_zoom_y_min = this._rel_zoom_y_min; chart._rel_zoom_y_max = this._rel_zoom_y_max; chart.selection_style = this.selection_style; - chart.series = this.series.copy(); + chart.series = this.series; chart.show_legend = this.show_legend; chart.title = this.title.copy(); chart.title_height = this.title_height; diff --git a/src/Series.vala b/src/Series.vala index 2d4bc89..284921e 100644 --- a/src/Series.vala +++ b/src/Series.vala @@ -60,7 +60,7 @@ namespace Gtk.CairoChart { series.line_style = this.line_style; series.marker_type = this.marker_type; series.place = this.place.copy(); - series.points = this.points.copy(); + series.points = this.points; series.sort = this.sort; series.title = this.title.copy(); series.zoom_show = this.zoom_show; From 5402757336769a9c6519bd15be17d6eeac3ac2fd Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Sun, 17 Dec 2017 01:20:07 +0300 Subject: [PATCH 28/40] Closes #143: DrawSelection -> src/, Chart.selection_style. --- src/Chart.vala | 10 ++++++++-- test/ChartTest.vala | 14 ++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index ad362a7..f06c941 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -18,8 +18,6 @@ namespace Gtk.CairoChart { public Series[] series = {}; - protected LineStyle selection_style = LineStyle (); - public Chart () { bg_color = Color (1, 1, 1); } @@ -463,6 +461,14 @@ namespace Gtk.CairoChart { process_legend (LegendProcessType.DRAW); } + public LineStyle selection_style = LineStyle (); + + public virtual void draw_selection (double x0, double y0, double x1, double y1) { + set_line_style (selection_style); + context.rectangle (x0, y0, x1 - x0, y1 - y0); + context.stroke(); + } + protected int axis_rec_npoints = 128; protected virtual void calc_axis_rec_sizes (Axis axis, out double max_rec_width, out double max_rec_height, bool is_horizontal = true) { diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 6ef3eec..98d55da 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -244,6 +244,8 @@ int main (string[] args) { plot_chart3 (chart3); plot_chart4 (chart4); + chart1.selection_style = LineStyle(Color(0.3, 0.3, 0.3, 0.7), 1); + var da = new DrawingArea(); da.set_events ( Gdk.EventMask.BUTTON_PRESS_MASK |Gdk.EventMask.BUTTON_RELEASE_MASK @@ -418,16 +420,8 @@ int main (string[] args) { /*var ret = */chart.draw(); // user's post draw operations here... - if (mouse_state == MouseState.DRAW_SELECTION) { - context.set_source_rgba (0.5, 0.5, 0.5, 0.7); - context.set_line_join(Cairo.LineJoin.MITER); - context.set_line_cap(Cairo.LineCap.ROUND); - context.set_line_width(1); - //context.set_dash(null, 0); - context.rectangle (sel_x0, sel_y0, sel_x1 - sel_x0, sel_y1 - sel_y0); - //context.fill(); - context.stroke(); - } + if (mouse_state == MouseState.DRAW_SELECTION) + chart.draw_selection (sel_x0, sel_y0, sel_x1, sel_y1); return true;//ret; }); From 5db6a7dfd62ba3c9f403a73bd1cd6562d1ddca8c Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Sun, 17 Dec 2017 01:32:30 +0300 Subject: [PATCH 29/40] ChartTest: spare redraw() removed. --- test/ChartTest.vala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/ChartTest.vala b/test/ChartTest.vala index 98d55da..badf72d 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -425,7 +425,6 @@ int main (string[] args) { return true;//ret; }); - da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); da.button_press_event.connect((event) => { if (!point_in_chart(chart, event.x, event.y)) return true; @@ -446,14 +445,12 @@ int main (string[] args) { case 2: // start zoom area selection sel_x0 = sel_x1 = event.x; sel_y0 = sel_y1 = event.y; - da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); mouse_state = MouseState.DRAW_SELECTION; break; case 3: // start moving mov_x0 = event.x; mov_y0 = event.y; - da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); mouse_state = MouseState.MOVING_CHART; break; } @@ -472,7 +469,6 @@ int main (string[] args) { //mouse_state = MouseState.FREE; } else { // add cursor chart.add_active_cursor (); - da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); mouse_state = MouseState.FREE; } break; @@ -489,7 +485,6 @@ int main (string[] args) { break; case 3: - da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height()); mouse_state = MouseState.FREE; break; } From db2ac267ce3ad21ebe21a7035efc079ae83a2710 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Sun, 17 Dec 2017 18:10:22 +0300 Subject: [PATCH 30/40] Use .common_axis_color to show common axis values in cursors. --- src/Chart.vala | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index f06c941..9a6e22a 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -528,6 +528,7 @@ namespace Gtk.CairoChart { bool common_x_axes = false; bool common_y_axes = false; + public Color common_axis_color = Color (0, 0, 0, 1); bool are_intersect (double a_min, double a_max, double b_min, double b_max) { if ( a_min < a_max <= b_min < b_max @@ -754,13 +755,13 @@ namespace Gtk.CairoChart { } context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y); set_source_rgba(s.axis_x.color); - if (common_x_axes) set_source_rgba(Color(0,0,0,1)); + if (common_x_axes) set_source_rgba(common_axis_color); show_text(s.axis_x.title); } // 5. Draw records, update cur_{x,y}_{min,max}. for (Float128 x = x_min, x_max = s.axis_x.zoom_max; point_belong (x, x_min, x_max); x += step) { - if (common_x_axes) set_source_rgba(Color(0,0,0,1)); + if (common_x_axes) set_source_rgba(common_axis_color); else set_source_rgba(s.axis_x.color); string text = "", time_text = ""; switch (s.axis_x.type) { @@ -921,13 +922,13 @@ namespace Gtk.CairoChart { break; } set_source_rgba(s.axis_y.color); - if (common_y_axes) set_source_rgba(Color(0,0,0,1)); + if (common_y_axes) set_source_rgba(common_axis_color); show_text(s.axis_y.title); } // 5. Draw records, update cur_{x,y}_{min,max}. for (Float128 y = y_min, y_max = s.axis_y.zoom_max; point_belong (y, y_min, y_max); y += step) { - if (common_y_axes) set_source_rgba(Color(0,0,0,1)); + if (common_y_axes) set_source_rgba(common_axis_color); else set_source_rgba(s.axis_y.color); var text = s.axis_y.format.printf((LongDouble)y); var scr_y = get_scr_y (s, y); @@ -1585,6 +1586,7 @@ namespace Gtk.CairoChart { set_source_rgba(s.axis_x.color); var text_t = new Text(s.axis_x.format.printf((LongDouble)point.x), s.axis_x.font_style); context.move_to (svp.x - size.x / 2, svp.y + text_t.get_height(context) / 2); + if (common_x_axes) set_source_rgba (common_axis_color); show_text(text_t); context.stroke(); } @@ -1597,6 +1599,7 @@ namespace Gtk.CairoChart { var y = svp.y + text_t.get_height(context) / 2; if (show_date) y -= text_t.get_height(context) / 2 + s.axis_x.font_indent / 2; context.move_to (svp.x - size.x / 2, y); + if (common_x_axes) set_source_rgba (common_axis_color); show_text(text_t); context.stroke(); } @@ -1609,6 +1612,7 @@ namespace Gtk.CairoChart { var y = svp.y + text_t.get_height(context) / 2; if (show_time) y += text_t.get_height(context) / 2 + s.axis_x.font_indent / 2; context.move_to (svp.x - size.x / 2, y); + if (common_x_axes) set_source_rgba (common_axis_color); show_text(text_t); context.stroke(); } @@ -1617,6 +1621,7 @@ namespace Gtk.CairoChart { set_source_rgba(s.axis_y.color); var text_t = new Text(s.axis_y.format.printf((LongDouble)point.y), s.axis_y.font_style); context.move_to (svp.x + size.x / 2 - text_t.get_width(context), svp.y + text_t.get_height(context) / 2); + if (common_y_axes) set_source_rgba (common_axis_color); show_text(text_t); context.stroke(); } From fe6dfa190123466adf16ff8d752c7e815cda5300 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Mon, 18 Dec 2017 04:26:06 +0300 Subject: [PATCH 31/40] Unnecessary _rel_zoom_{x,y}_m{in,ax} removed. --- src/Chart.vala | 58 +++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 9a6e22a..c017356 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -87,14 +87,10 @@ namespace Gtk.CairoChart { } } - double _rel_zoom_x_min = 0.0; - double _rel_zoom_x_max = 1.0; - double _rel_zoom_y_min = 0.0; - double _rel_zoom_y_max = 1.0; - public double rel_zoom_x_min { get { return _rel_zoom_x_min; } default = 0.0; } - public double rel_zoom_x_max { get { return _rel_zoom_x_max; } default = 1.0; } - public double rel_zoom_y_min { get { return _rel_zoom_y_min; } default = 0.0; } - public double rel_zoom_y_max { get { return _rel_zoom_y_max; } default = 1.0; } + protected double rel_zoom_x_min = 0.0; + protected double rel_zoom_x_max = 1.0; + protected double rel_zoom_y_min = 0.0; + protected double rel_zoom_y_max = 1.0; int zoom_first_show = 0; @@ -145,14 +141,14 @@ namespace Gtk.CairoChart { break; } - var new_rel_zoom_x_min = _rel_zoom_x_min + (x0 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (_rel_zoom_x_max - _rel_zoom_x_min); - var new_rel_zoom_x_max = _rel_zoom_x_min + (x1 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (_rel_zoom_x_max - _rel_zoom_x_min); - var new_rel_zoom_y_min = _rel_zoom_y_min + (y0 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (_rel_zoom_y_max - _rel_zoom_y_min); - var new_rel_zoom_y_max = _rel_zoom_y_min + (y1 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (_rel_zoom_y_max - _rel_zoom_y_min); - _rel_zoom_x_min = new_rel_zoom_x_min; - _rel_zoom_x_max = new_rel_zoom_x_max; - _rel_zoom_y_min = new_rel_zoom_y_min; - _rel_zoom_y_max = new_rel_zoom_y_max; + var new_rel_zoom_x_min = rel_zoom_x_min + (x0 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (rel_zoom_x_max - rel_zoom_x_min); + var new_rel_zoom_x_max = rel_zoom_x_min + (x1 - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (rel_zoom_x_max - rel_zoom_x_min); + var new_rel_zoom_y_min = rel_zoom_y_min + (y0 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (rel_zoom_y_max - rel_zoom_y_min); + var new_rel_zoom_y_max = rel_zoom_y_min + (y1 - plot_area_y_min) / (plot_area_y_max - plot_area_y_min) * (rel_zoom_y_max - rel_zoom_y_min); + rel_zoom_x_min = new_rel_zoom_x_min; + rel_zoom_x_max = new_rel_zoom_x_max; + rel_zoom_y_min = new_rel_zoom_y_min; + rel_zoom_y_max = new_rel_zoom_y_max; } public virtual void zoom_out () { @@ -167,10 +163,10 @@ namespace Gtk.CairoChart { s.place.zoom_y_low = s.place.y_low; s.place.zoom_y_high = s.place.y_high; } - _rel_zoom_x_min = 0; - _rel_zoom_x_max = 1; - _rel_zoom_y_min = 0; - _rel_zoom_y_max = 1; + rel_zoom_x_min = 0; + rel_zoom_x_max = 1; + rel_zoom_y_min = 0; + rel_zoom_y_max = 1; zoom_first_show = 0; } @@ -178,7 +174,7 @@ namespace Gtk.CairoChart { public virtual void move (double delta_x, double delta_y) { delta_x /= plot_area_x_max - plot_area_x_min; delta_x *= - 1.0; delta_y /= plot_area_y_max - plot_area_y_min; delta_y *= - 1.0; - var rzxmin = _rel_zoom_x_min, rzxmax = _rel_zoom_x_max, rzymin = _rel_zoom_y_min, rzymax = _rel_zoom_y_max; + var rzxmin = rel_zoom_x_min, rzxmax = rel_zoom_x_max, rzymin = rel_zoom_y_min, rzymax = rel_zoom_y_max; zoom_out(); //draw(); // TODO: optimize here delta_x *= plot_area_x_max - plot_area_x_min; @@ -1244,21 +1240,21 @@ namespace Gtk.CairoChart { } protected virtual Float128 scr2rel_x (Float128 x) { - return _rel_zoom_x_min + (x - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (_rel_zoom_x_max - _rel_zoom_x_min); + return rel_zoom_x_min + (x - plot_area_x_min) / (plot_area_x_max - plot_area_x_min) * (rel_zoom_x_max - rel_zoom_x_min); } protected virtual Float128 scr2rel_y (Float128 y) { - return _rel_zoom_y_max - (plot_area_y_max - y) / (plot_area_y_max - plot_area_y_min) * (_rel_zoom_y_max - _rel_zoom_y_min); + return rel_zoom_y_max - (plot_area_y_max - y) / (plot_area_y_max - plot_area_y_min) * (rel_zoom_y_max - rel_zoom_y_min); } protected virtual Point scr2rel_point (Point p) { return Point (scr2rel_x(p.x), scr2rel_y(p.y)); } protected virtual Float128 rel2scr_x(Float128 x) { - return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (x - _rel_zoom_x_min) / (_rel_zoom_x_max - _rel_zoom_x_min); + return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (x - rel_zoom_x_min) / (rel_zoom_x_max - rel_zoom_x_min); } protected virtual Float128 rel2scr_y(Float128 y) { - return plot_area_y_min + (plot_area_y_max - plot_area_y_min) * (y - _rel_zoom_y_min) / (_rel_zoom_y_max - _rel_zoom_y_min); + return plot_area_y_min + (plot_area_y_max - plot_area_y_min) * (y - rel_zoom_y_min) / (rel_zoom_y_max - rel_zoom_y_min); } protected virtual Point rel2scr_point (Point p) { @@ -1301,9 +1297,9 @@ namespace Gtk.CairoChart { var c = all_cursors.nth_data(ci); switch (cursors_orientation) { case CursorOrientation.VERTICAL: - if (c.x <= _rel_zoom_x_min || c.x >= _rel_zoom_x_max) continue; break; + if (c.x <= rel_zoom_x_min || c.x >= rel_zoom_x_max) continue; break; case CursorOrientation.HORIZONTAL: - if (c.y <= _rel_zoom_y_min || c.y >= _rel_zoom_y_max) continue; break; + if (c.y <= rel_zoom_y_min || c.y >= rel_zoom_y_max) continue; break; } CursorCross[] crossings = {}; @@ -1661,10 +1657,10 @@ namespace Gtk.CairoChart { chart.plot_area_x_min = this.plot_area_x_min; chart.plot_area_y_max = this.plot_area_y_max; chart.plot_area_y_min = this.plot_area_y_min; - chart._rel_zoom_x_min = this._rel_zoom_x_min; - chart._rel_zoom_x_max = this._rel_zoom_x_max; - chart._rel_zoom_y_min = this._rel_zoom_y_min; - chart._rel_zoom_y_max = this._rel_zoom_y_max; + chart.rel_zoom_x_min = this.rel_zoom_x_min; + chart.rel_zoom_x_max = this.rel_zoom_x_max; + chart.rel_zoom_y_min = this.rel_zoom_y_min; + chart.rel_zoom_y_max = this.rel_zoom_y_max; chart.selection_style = this.selection_style; chart.series = this.series; chart.show_legend = this.show_legend; From 0093299e093e621ec00e1a18b1a4a625d7d12c64 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Sun, 17 Dec 2017 17:21:39 +0300 Subject: [PATCH 32/40] =?UTF-8?q?Closes=20#144:=20Show=20=CE=94=20X=20or?= =?UTF-8?q?=20=CE=94=20Y=20for=202=20cursors.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Chart.vala | 62 ++++++++++++++++++++++++++++++++++++++++++--- test/ChartTest.vala | 18 +++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index c017356..4861227 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -75,7 +75,7 @@ namespace Gtk.CairoChart { return true; } - protected virtual void set_source_rgba (Color color) { + public virtual void set_source_rgba (Color color) { context.set_source_rgba (color.red, color.green, color.blue, color.alpha); } @@ -522,8 +522,8 @@ namespace Gtk.CairoChart { public double plot_area_y_min = 0; public double plot_area_y_max = 0; - bool common_x_axes = false; - bool common_y_axes = false; + public bool common_x_axes { get; protected set; default = false; } + public bool common_y_axes { get; protected set; default = false; } public Color common_axis_color = Color (0, 0, 0, 1); bool are_intersect (double a_min, double a_max, double b_min, double b_max) { @@ -1625,6 +1625,62 @@ namespace Gtk.CairoChart { } } + public bool get_cursors_delta (out Float128 delta) { + delta = 0.0; + if (series.length == 0) return false; + if (cursors.length() + (is_cursor_active ? 1 : 0) != 2) return false; + if (common_x_axes && cursors_orientation == CursorOrientation.VERTICAL) { + Float128 val1 = get_real_x (series[zoom_first_show], rel2scr_x(cursors.nth_data(0).x)); + Float128 val2 = 0; + if (is_cursor_active) + val2 = get_real_x (series[zoom_first_show], rel2scr_x(active_cursor.x)); + else + val2 = get_real_x (series[zoom_first_show], rel2scr_x(cursors.nth_data(1).x)); + if (val2 > val1) + delta = val2 - val1; + else + delta = val1 - val2; + return true; + } + if (common_y_axes && cursors_orientation == CursorOrientation.HORIZONTAL) { + Float128 val1 = get_real_y (series[zoom_first_show], rel2scr_y(cursors.nth_data(0).y)); + Float128 val2 = 0; + if (is_cursor_active) + val2 = get_real_y (series[zoom_first_show], rel2scr_y(active_cursor.y)); + else + val2 = get_real_y (series[zoom_first_show], rel2scr_y(cursors.nth_data(1).y)); + if (val2 > val1) + delta = val2 - val1; + else + delta = val1 - val2; + return true; + } + return false; + } + + public string get_cursors_delta_str () { + Float128 delta = 0.0; + if (!get_cursors_delta(out delta)) return ""; + var str = ""; + var s = series[zoom_first_show]; + if (common_x_axes) + switch (s.axis_x.type) { + case Axis.Type.NUMBERS: + str = s.axis_x.format.printf((LongDouble)delta); + break; + case Axis.Type.DATE_TIME: + var date = "", time = ""; + int64 days = (int64)(delta / 24 / 3600); + format_date_time(s.axis_x, delta, out date, out time); + str = days.to_string() + " + " + time; + break; + } + if (common_y_axes) { + str = s.axis_y.format.printf((LongDouble)delta); + } + return str; + } + public Chart copy () { var chart = new Chart (); chart.active_cursor = this.active_cursor; diff --git a/test/ChartTest.vala b/test/ChartTest.vala index badf72d..a3e4f15 100644 --- a/test/ChartTest.vala +++ b/test/ChartTest.vala @@ -85,6 +85,7 @@ void plot_chart2 (Chart chart) { //s1.axis_x.position = s2.axis_x.position = s3.axis_x.position = Axis.Position.HIGH; //s1.axis_x.type = s2.axis_x.type = s3.axis_x.type = Axis.Type.DATE_TIME; + //s1.axis_x.max = s2.axis_x.max = s3.axis_x.max = 5*24*3600; chart.series = { s1, s2, s3 }; } @@ -423,6 +424,23 @@ int main (string[] args) { if (mouse_state == MouseState.DRAW_SELECTION) chart.draw_selection (sel_x0, sel_y0, sel_x1, sel_y1); + // show delta + var str = chart.get_cursors_delta_str(); + if (str != "") { + var text = "Δ = " + str; + var text_t = new Text(text); + var w = text_t.get_width(context); + var h = text_t.get_height(context); + var x0 = chart.plot_area_x_max - w - 5; + var y0 = chart.plot_area_y_min + h + 5; + chart.set_source_rgba(chart.legend.bg_color); + context.rectangle (x0, y0 - h, w, h); + context.fill(); + context.move_to (x0, y0); + chart.set_source_rgba(chart.common_axis_color); + context.show_text(text); + } + return true;//ret; }); From 004381ed7a9f1f11c713c28b5ff5fad04927f146 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Mon, 18 Dec 2017 15:49:19 +0300 Subject: [PATCH 33/40] Spare vars removed which never used. --- src/Chart.vala | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Chart.vala b/src/Chart.vala index 4861227..49b2b77 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -1400,7 +1400,6 @@ namespace Gtk.CairoChart { if (show_x == show_time == show_date == show_y == false) cross_what_to_show(s, out show_x, out show_time, out show_date, out show_y); size = Point (); - double x_w = 0.0, x_h = 0.0, y_w = 0.0, y_h = 0.0; string date, time; format_date_time(s.axis_x, p.x, out date, out time); var date_t = new Text (date, s.axis_x.font_style, s.axis_x.color); @@ -1568,7 +1567,6 @@ namespace Gtk.CairoChart { var point = ccs[ci].point; var size = ccs[ci].size; var svp = ccs[ci].scr_value_point; - var rp = get_real_point(s, rel2scr_point(point)); var show_x = ccs[ci].show_x; var show_date = ccs[ci].show_date; var show_time = ccs[ci].show_time; From 74cbbb78c8599e8c79b829b75ae0b24158bc7bca Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Tue, 19 Dec 2017 07:45:42 +0300 Subject: [PATCH 34/40] Module cmake/backbone updated: GNUIntallDirs: lib -> CMAKE_INSTALL_FULL_LIBDIR. --- cmake/backbone | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/backbone b/cmake/backbone index 784ca10..be702a8 160000 --- a/cmake/backbone +++ b/cmake/backbone @@ -1 +1 @@ -Subproject commit 784ca10a530e629885236e9df7fdf5647ddffa7c +Subproject commit be702a8ef89f8aef65a6555256c5cd33817cad7c From 5f254b8810e5d61e94d79068850c55dd3ef9e058 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Tue, 19 Dec 2017 07:55:19 +0300 Subject: [PATCH 35/40] Module cmake/backbone updated: CMAKE_INSTALL_FULL_LIBDIR brokes CPack --- cmake/backbone | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/backbone b/cmake/backbone index be702a8..f4fd363 160000 --- a/cmake/backbone +++ b/cmake/backbone @@ -1 +1 @@ -Subproject commit be702a8ef89f8aef65a6555256c5cd33817cad7c +Subproject commit f4fd3637a3d4ad08f04416fc9396943c5493bede From 9792aa6c392abaf7c058ced0d9125ba15958b26d Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Tue, 19 Dec 2017 08:24:39 +0300 Subject: [PATCH 36/40] Module cmake/backbone updated: Check LibPkgModules for empty value. --- cmake/backbone | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/backbone b/cmake/backbone index f4fd363..7797a9d 160000 --- a/cmake/backbone +++ b/cmake/backbone @@ -1 +1 @@ -Subproject commit f4fd3637a3d4ad08f04416fc9396943c5493bede +Subproject commit 7797a9d1ccf03bf80e8e8b5048a6193a87328eac From ed3e82269466abcf9b917a64cd024b9fce2a8072 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Tue, 19 Dec 2017 08:47:23 +0300 Subject: [PATCH 37/40] Module cmake/backbone updated: Check BinPkgModules for empty value. --- cmake/backbone | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/backbone b/cmake/backbone index 7797a9d..5aec8e8 160000 --- a/cmake/backbone +++ b/cmake/backbone @@ -1 +1 @@ -Subproject commit 7797a9d1ccf03bf80e8e8b5048a6193a87328eac +Subproject commit 5aec8e830a52e88f61c8844ac15dca72b1ef7f06 From 6c693fa1fe845c6b08eb7c9fa7f525414c8cb001 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Mon, 25 Dec 2017 02:14:31 +0300 Subject: [PATCH 38/40] README.md added. --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..90eac89 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Cairo-Chart (GTK3+) + +Gtk+ Chart for DrawingArea widget with multiaxes, dates and cursors. + +Project Page: https://redmine.backbone.ws/projects/cairo-chart + +Git: https://git.backbone.ws/gtk/cairo-chart + +## View YouTube movie + +[![View video](https://img.youtube.com/vi/wmZAw2csjQg/0.jpg)](https://www.youtube.com/watch?v=wmZAw2csjQg) + +## Screenshots +![Screenshot1](https://redmine.backbone.ws/attachments/download/424/20171225_020441.png) +![Screenshot2](https://redmine.backbone.ws/attachments/download/425/20171225_020414.png) +![Screenshot3](https://redmine.backbone.ws/attachments/download/426/20171225_020350.png) +![Screenshot4](https://redmine.backbone.ws/attachments/download/427/20171225_020330.png) +![Screenshot5](https://redmine.backbone.ws/attachments/download/428/20171225_020315.png) +![Screenshot6](https://redmine.backbone.ws/attachments/download/429/20171225_020301.png) +![Screenshot7](https://redmine.backbone.ws/attachments/download/430/20171225_020237.png) From 2be8374cb03fa7b48837d5e3762eed57dfc7070c Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Mon, 25 Dec 2017 13:18:50 +0300 Subject: [PATCH 39/40] Spare .copy() for array removed. --- src/Chart.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chart.vala b/src/Chart.vala index 49b2b77..b493bc6 100644 --- a/src/Chart.vala +++ b/src/Chart.vala @@ -1143,7 +1143,7 @@ namespace Gtk.CairoChart { } protected virtual Point[] sort_points (Series s, Series.Sort sort) { - var points = s.points.copy(); + var points = s.points; switch(sort) { case Series.Sort.BY_X: sort_points_delegate(points, (a, b) => { From 6160203a666738a7900e2d6a27f64694a9f693a3 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Mon, 25 Dec 2017 13:45:24 +0300 Subject: [PATCH 40/40] Bumped version number to 0.1.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 33d21b9..0383a4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ SET (PROJECT_LOWERCASE_NAME "cairo-chart") SET (PROJECT_DESCRIPTION "GtkChart for Gtk.DrawingArea (Cairo).") SET (MAJOR 0) -SET (MINOR 0) +SET (MINOR 1) SET (PATCH 0) LIST (APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/backbone)