{"id":18234200,"url":"https://github.com/dmrokan/gdmatplot","last_synced_at":"2026-01-18T07:14:52.664Z","repository":{"id":241958801,"uuid":"808300484","full_name":"dmrokan/gdmatplot","owner":"dmrokan","description":" This plugin adds GNUPlot terminal to the Godot engine for displaying plots on a 2D canvas layer without requiring the GNUPlot executable","archived":false,"fork":false,"pushed_at":"2024-09-16T11:14:45.000Z","size":4776,"stargazers_count":21,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-04T20:02:52.315Z","etag":null,"topics":["gnuplot","godot-engine","math-art","matplot","plugin"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmrokan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-30T19:30:18.000Z","updated_at":"2024-11-03T14:40:36.000Z","dependencies_parsed_at":"2024-05-30T22:54:24.214Z","dependency_job_id":"f7c148aa-a4ec-4425-b0b8-c8dbb65ca411","html_url":"https://github.com/dmrokan/gdmatplot","commit_stats":null,"previous_names":["dmrokan/gdmatplot"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmrokan%2Fgdmatplot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmrokan%2Fgdmatplot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmrokan%2Fgdmatplot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmrokan%2Fgdmatplot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmrokan","download_url":"https://codeload.github.com/dmrokan/gdmatplot/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247097653,"owners_count":20883122,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gnuplot","godot-engine","math-art","matplot","plugin"],"created_at":"2024-11-04T20:01:40.222Z","updated_at":"2026-01-18T07:14:52.659Z","avatar_url":"https://github.com/dmrokan.png","language":"C++","readme":"# GDMatPlot\nThis native [Godot](https://github.com/godotengine/godot) extension embeds [GNUPlot](http://www.gnuplot.info/) command line into the game engine by exploiting GNUPlot's flexibility for declaring custom graphical output terminals. You can create static or dynamic plots for games or GUI applications and it does not require GNUPlot executable. Plots can be embedded into 3D world by using sub viewports. It can plot custom dataframes defined in scripts. Also, you can provide a `PackedFloat64Array` as dataframe which is a more performant method. Please, see examples in `demo` folder.\n\n## How it works\nI created a custom graphical terminal named by *gdmp.trm* which redirects GNUPlot's drawing calls to Godot's CanvasItem drawing methods. *gdmp.trm* is based on SVG terminal implemented in `gnuplot/term/svg.trm`. The common method to embed GNUPlot into an application is running it as a separate process and communicating through an Inter Process Communication (IPC) method. However, I came up with a hack to embed it into the plugin itself as a separate shared library after some tedious work. GDMP terminal and mentioned hack can be found in `gdmatplot-gnuplot.patch`.\n\nThe main issue that can not be solved programmatically is handling the global program state used by GNUPlot while drawing multiple plots concurrently. The common method to load a shared library on Linux is reading it by `dlopen`. If you load the same dynamic library with [`dlopen`](https://man7.org/linux/man-pages/man3/dlopen.3.html) several times all symbols will be allocated at the same memory address (similar to global variables in the same namespace) in the application. Changing a global symbol in a plot will effect the others and cause race conditions in a threaded application. GNU linker on Linux provides a function called `dlmopen` which overcomes this problem up to a point by separating subsequent library loads into separate memory regions. However, it supports a maximum of 16 different namespaces and it is not a multiplatform solution. After some research and trial-error, found that fooling the dynamic library loader by changing dynamic library file name each time before loading makes the loader create separate GNUPlot instances. This method seems like working for both Linux and Windows.\n\nBuild steps of GNUPlot is declared in `SConscript`. It first compiles GNUPlot source as a shared library, then dumps it into a C array to be packed in GDMatPlot extension. The C array written to a '.so' or '.dll' file to be read by dynamic library loader and deleted afterwards.\n\n## Beta warning\nAs explained above, this software is based on a hack that drags it into a indefinitely long beta stage despite it is based on GNUPlot's 6.0.0 release. In the tests provided in demo projects, I didn't experience illegal memory access or memory leak. However, it is not easy to cover all code paths. Users of this library should notice the comments below.\n\n### Known (but not limited) regressions\n- Dashed plot types is not working\n- Unicode text is not working\n- Can not change font family by using GNUPlot font commands\n- Arrow plots are not working\n- Spider plots are not working\n- Command line breaks by using backslash character is not working\n- Clean termination after error is not working\n- Z-ordering of line segments in 3D plots is not working\n- Can not load dataframes from files, GNUPlot's I/O functionality is disabled\n\n### Note\nYou should first test your sequence of GNUPlot commands on an original build with version number '6.0.0' by selecting svg terminal as the graphical output option and make sure it does not emit warning or error messages. Providing erroneous commands to GDMatPlot may cause SEGFAULT or memory leaks.\n\n## Demos\n\n### 2D demo\n\nIt includes several test plots which utilizes a large aspect of GNUPlot of functionality. A test view is shown below.\n\n![Demo2D screenshot](https://raw.githubusercontent.com/dmrokan/gdmatplot/main/docs/demo2d_screenshot.png)\n\n### 3D demo\n\nIn this demo, similar test plots are embedded into 3D game world by using subviewports.\n\n![Demo3D screenshot](https://raw.githubusercontent.com/dmrokan/gdmatplot/main/docs/demo3d_screenshot.png)\n\nhttps://github.com/user-attachments/assets/0b9b067d-0e99-45da-b536-b9213b482bc7\n\n## Classes\n### GDMatPlotNative\n\nBase class of GDMatPlot derived from Node2D.\n\n#### Methods\n- `draw_plot()`: Overrides `Node2D` `_draw` method. See the example below for its usage.\n- `load_gnuplot(p_path: String = \"user://libgnuplot.so\")`: Loads GNUPlot shared library by using `p_path` as a temporary loading location. This function must be called initially, preferably in `_ready()` method. It returns `0` on success, a negative value on error. Note that, if there is already a file at `p_path`, it will be overwritten.\n\n| :warning: WARNING | This function creates 2MB sized temporary file when called. It is better not to call it frequently. |\n|-------------------|-----------------------------------------------------------------------------------------------------|\n\n- `run_command(p_cmd: String)`: Redirects `p_cmd` to GNUPlot's command line parser.\n- `set_dataframe(p_data: PackedFloat64Array, p_column_count: int)`: Set dataframe to be parsed and plotted. `p_data` is assumed to be in row major format and its size must be divisible by `p_column_count`. Imitates GNUPlot's numeric dataframe loading from a text file.\n```gdscript\nvar df: PackedFloat64Array = PackedFloat64Array([ 1, 2, 3, 4, 5, 6, 7... ])\nset_dataframe(df, 3)\n# This will be interpreted by GNUPlot as\n# Column index: 1   2   3\n# -------------------------\n#               1   2   3\n#               4   5   6\n#               7   .   .\n# Check gnuplot documentation and examples how to plot dataframe columns\n```\n- `load_dataframe()`: Reset dataframe parser's internal state. It should be called at the beginning of each draw call.\n- `start_renderer(p_loop_func: Callable)`: GNUplot command parser and renderer should be run in a separate thread beacuse it introduces an easily recognizable performance decrease. You should call `run_command` in the renderer loop function `p_loop_func`.\n- `stop_renderer()`: Stops renderer thread.\n- `set_rendering_period(p_loop_period: int)`: Set the time rate of calling `p_loop_func` defined by `start_renderer`. The unit of `p_loop_period` is milliseconds.\n\n#### Properties\n- `transparency: float`: Sets plot's background transparency.\n- `antialiasing: bool`: Enable/disable antialiased line drawing.\n\n## Usage\n\nSince version 0.2.0 GDMatPlot imposes a specific programming pattern because GNUPlot's command parsing and rendering add an overhead which does not fit the performace requirements of a game engine. Instead of directly drawing the results of `run_command` in `_draw` function, `run_command` it is called in a renderer loop which is called in a separate thread. In the renderer thread GNUPlot processes commands and generates draw line, polygon, point etc. calls which are encoded in an efficient data structure. Then, a new draw call is queued by calling `queue_redraw` as shown in the example below. When new drawing triggered, encoded draw calls are decoded and converted to `CanvasItem`s `draw_polyline`, `draw_polygon`, etc. calls. This method cancels the negative effect of GNUPlot's command parsing on the frame rate (FPS).\n\n```gdscript\nextends GDMatPlotNative\n\nvar dataframe: PackedFloat64Array\nvar lib_loaded: bool = false\nvar figure_size: Vector2i = Vector2i(640, 480)\nvar renderer_period: int = 100\nvar update_df_period: float = 0.1\nvar col_count: int = 4\nvar sample_count: int = 100\n# Protect access to dataframe from GNUPlot renderer thread\nvar df_lock: Mutex = Mutex.new()\nfunc _ready():\n\ttransparency = 0.75 # Set plot's transparency\n\tantialiasing = true # Enable antialized lines\n\tdataframe.resize(sample_count)\n\tfor i in range(0, sample_count, col_count):\n\t\tvar x: float = i - sample_count / 2.0\n\t\tdataframe[i+0] = x # 1st column of dataframe\n\t\tdataframe[i+1] = x / pow(sample_count / 2.0, 1) # 2nd column\n\t\tdataframe[i+2] = x * x / pow(sample_count / 2.0, 2) # 3rd column\n\t\tdataframe[i+3] = x * x * x / pow(sample_count / 2.0, 3) # 4th column\n\n\tvar error: int = load_gnuplot()\n\tif error == 0:\n\t\tlib_loaded = true\n\t\tset_dataframe(dataframe, col_count) # 4 columns 100/4 = 25 rows\n\t\tstart_renderer(_draw_commands)\n\t\tset_rendering_period(renderer_period)\n\nfunc _draw_commands():\n\tif lib_loaded:\n\t\tdf_lock.lock()\n\t\tload_dataframe()\n\t\tdf_lock.unlock()\n\t\t\"\"\"GNUPlot commands\"\"\"\n\t\trun_command(\"set terminal gdmp size %d,%d\" % [figure_size.x, figure_size.y])\n\t\trun_command(\"set grid on\")\n\t\t# 'df' is a placeholder, it can be any alphanumeric string.\n\t\trun_command(\"plot 'df' using 1:2 with lines, 'dfasd' using 1:3 with lines, 'df' using 1:4 with lines\")\n\nfunc update_dataframe():\n\t# ... process dataframe ...#\n\tfor i in range(sample_count):\n\t\tdataframe[i] *= 0.99\n\tif lib_loaded:\n\t\tdf_lock.lock()\n\t\tset_dataframe(dataframe, col_count)\n\t\tdf_lock.unlock()\n\n\"\"\"\nYou don't need to define _draw function. GDMatPlotNative overrides it\nand draw_plot will be autmatically called. You should define _draw here\nif you want to draw some other stuff (e.g. an image) before and after draw_plot.\n\"\"\"\nfunc _draw():\n\t# draw some stuff behind the plot\n\tdraw_circle(Vector2(), 250, Color.AQUA)\n\tif lib_loaded:\n\t\tdraw_plot()\n\t# draw other stuff over the plot\n\tdraw_circle(figure_size, 250, Color(0.0, 0.2, 0.0, 0.5))\n\nvar some_condition: bool = false\nvar total_time: float = 0.0\n\"\"\"\nYou should call `queue_redraw` manually after an event or at a desired period.\nOtherwise, plot will not be redrawn.\n\"\"\"\nfunc _process(delta):\n\tif total_time \u003e update_df_period:\n\t\tupdate_dataframe()\n\t\tqueue_redraw()\n\t\ttotal_time = 0\n\ttotal_time += delta\n\tif lib_loaded and some_condition:\n\t\t# Pause renderer when ever you want by setting a very large period.\n\t\tset_rendering_period(1000000000)\n```\n\n**Output of the script:**\n\n![Example plot](https://raw.githubusercontent.com/dmrokan/gdmatplot/main/docs/example_plot.png)\n\n## Build from source\n\n### Requirements\n\nCurrently, it can be built on a Linux host. Building the Windows release requires MinGW 12.2.0 or newer.\n\n```sh\ngit clone https://github.com/dmrokan/gdmatplot.git\ncd gdmatplot\ngit submodule update --init --recursive\nscons -f SConscript platform='\u003clinux|windows\u003e'\ncd godot_cpp\nscons target='\u003ctemplate_debug|template_release\u003e' platform='\u003clinux|windows\u003e' arch='x86_64'\ncd ..\nscons target='\u003ctemplate_debug|template_release\u003e' platform='\u003clinux|windows\u003e' arch='x86_64'\n```\nYou can visit Godot's [build system](https://docs.godotengine.org/en/stable/contributing/development/compiling/introduction_to_the_buildsystem.html) documentation for more information.\n","funding_links":[],"categories":["Uncategorized"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmrokan%2Fgdmatplot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmrokan%2Fgdmatplot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmrokan%2Fgdmatplot/lists"}