How to combine WinForms UI and Python charting in minutes using Javonet

  • Date: 2025/06/26
  • |
  • Category news

image

Introduction

Creating modern desktop applications often involves choosing between languages and ecosystems: C# for fast GUI development with WinForms, or Python for its rich ecosystem of data processing and visualization libraries. But what if you could combine the strengths of both—without rewriting code or setting up complex interop layers?

In this article, we’ll show you how to build a .NET WinForms application that leverages Python logic to generate dynamic SVG charts. Using Javonet, a cross-language integration bridge, you can seamlessly call Python code from your C# application with zero hassle. The result is a clean and maintainable hybrid architecture where C# handles the interface, and Python does the heavy lifting for data visualization.

Let’s walk through a practical example: a chart editor in WinForms that takes X and Y values from the user and visualizes them using Python’s svgwrite library. You’ll see how easy it is to pass data between the two worlds—and how little boilerplate is needed.

Prerequisites

Before you get started, make sure you have the following tools and packages installed:

.NET (C#/WinForms) side

  • Visual Studio 2019+ (or any IDE with .NET WinForms support)
  • A .NET Framework or .NET Core WinForms project
  • The following NuGet packages added to your project:
Install-Package SkiaSharp Install-Package SkiaSharp.Views.WindowsForms Install-Package Svg.Skia Install-Package Javonet.Netcore.Sdk #or Javonet.Clr.Sdk for .NET Framework

Python side

  • Python 3.8 or later
  • Install the svgwrite package:
pip install svgwrite

Creating the Python Charts Class

We’ll now create the core component on the Python side: a class called Charts that generates an SVG line chart using the svgwrite library.

This class will expose a method named generate_line_chart_svg(...) which accepts a title, a list of X and Y values, and returns a raw SVG string ready to be rendered inside a WinForms PictureBox.

charts.py

import tempfile import svgwrite class Charts: __type__ = "Charts" # Optional identifier (useful for Javonet interop) def generate_line_chart_svg(self, title, x_vals, y_vals, width=800, height=600): # Create an SVG drawing canvas with a fixed width and height dwg = svgwrite.Drawing(size=(width, height)) # Set margins around the chart area to leave space for axes and labels margin = 50 plot_width = width - 2 * margin plot_height = height - 2 * margin # Draw Y axis (left vertical line) dwg.add(dwg.line( start=(margin, margin), end=(margin, height - margin), stroke='black' )) # Draw X axis (bottom horizontal line) dwg.add(dwg.line( start=(margin, height - margin), end=(width - margin, height - margin), stroke='black' )) # Calculate data ranges max_x = max(x_vals) min_x = min(x_vals) max_y = max(y_vals) min_y = min(y_vals) # Avoid division by zero by defaulting to 1 if min == max scale_x = plot_width / (max_x - min_x if max_x != min_x else 1) scale_y = plot_height / (max_y - min_y if max_y != min_y else 1) # Function to map data points (x, y) to pixel coordinates in the SVG canvas def transform(x, y): tx = margin + (x - min_x) * scale_x ty = height - margin - (y - min_y) * scale_y # invert Y-axis for correct chart direction return (tx, ty) # Apply transformation to all data points points = [transform(x, y) for x, y in zip(x_vals, y_vals)] # Draw the polyline connecting all points dwg.add(dwg.polyline( points=points, fill='none', stroke='black', stroke_width=2 )) # Draw a red dot at each point for better visibility for point in points: dwg.add(dwg.circle(center=point, r=3, fill='red')) # Add the chart title centered at the top of the canvas dwg.add(dwg.text( title, insert=(width / 2, margin / 2), text_anchor="middle", font_size=20 )) # Return the full SVG content as a string return dwg.tostring()

Before wiring this up to your C# application, you can easily test the chart generator on its own. The following helper function allows you to render the SVG in a temporary HTML file and open it in your default browser.

def preview_svg_in_browser(svg_content): # Save the SVG content to a temporary HTML file with tempfile.NamedTemporaryFile(delete=False, suffix=".html", mode="w", encoding="utf-8") as f: html = f"""<!DOCTYPE html> <html> <head><meta charset="utf-8"><title>SVG Preview</title></head> <body> {svg_content} </body> </html>""" f.write(html) file_path = f.name # Open the file in the default system browser webbrowser.open(f"file://{file_path}")

You can call this test locally using the standard Python __main__ entry point:

if __name__ == "__main__": charts = Charts() # Provide sample X and Y data svg = charts.generate_line_chart_svg("Demo Chart", [1, 2, 3, 4], [10, 5, 30, 15]) # Open the generated chart in your browser preview_svg_in_browser(svg)

What to Expect
Running the script will open your browser with a simple line chart. This ensures that your Python environment, the svgwrite package, and the SVG rendering logic all work before integrating with the .NET layer through Javonet.

Building the WinForms UI in C#

On the .NET side, we’ll use WinForms to build a minimal user interface that lets the user input chart data (as comma-separated values) and displays the generated SVG chart.

Our UI is composed of two main components:

  1. ChartInputPanel – A panel with input fields and a button to trigger chart generation.
  2. ChartDisplayPanel – A picture box capable of rendering SVG content returned from Python.

ChartInputPanel.cs
This panel handles user input and parses it into numeric arrays.

public class ChartInputPanel : Panel { private readonly TextBox inputX; private readonly TextBox inputY; private readonly Button generateButton; public event EventHandler<ChartInputEventArgs> GenerateChartClicked; public ChartInputPanel() { Padding = new Padding(10); // Create and add the "Generate Chart" button generateButton = new Button { Text = "Generate Chart", Dock = DockStyle.Top, Height = 40 }; generateButton.Click += (s, e) => OnGenerateChartClicked(); Controls.Add(generateButton); // Input field for Y-values Controls.Add(new Label { Text = "Values Y (e.g. 10,20,30)", Dock = DockStyle.Top }); inputY = new TextBox { Dock = DockStyle.Top }; Controls.Add(inputY); // Input field for X-values Controls.Add(new Label { Text = "Values X (e.g. 1,2,3)", Dock = DockStyle.Top }); inputX = new TextBox { Dock = DockStyle.Top }; Controls.Add(inputX); } // Handles the button click and raises an event with parsed input private void OnGenerateChartClicked() { try { // Split input values and convert to arrays of doubles string[] xStr = inputX.Text.Split(','); string[] yStr = inputY.Text.Split(','); double[] x = Array.ConvertAll(xStr, double.Parse); double[] y = Array.ConvertAll(yStr, double.Parse); // Fire the event to inform the main form that a chart should be generated GenerateChartClicked?.Invoke(this, new ChartInputEventArgs(x, y)); } catch (Exception ex) { MessageBox.Show("Input data error: " + ex.Message); } } }

ChartInputEventArgs.cs
This simple data container class is used to pass chart data (X and Y arrays) when the user clicks “Generate Chart”.

public class ChartInputEventArgs : EventArgs { public double[] XValues { get; } public double[] YValues { get; } public ChartInputEventArgs(double[] x, double[] y) { XValues = x; YValues = y; } }

ChartDisplayPanel.cs
This component is a customized PictureBox that knows how to render an SVG image using Svg.Skia and SkiaSharp.

public class ChartDisplayPanel : PictureBox { public ChartDisplayPanel() { this.SizeMode = PictureBoxSizeMode.Zoom; this.BorderStyle = BorderStyle.FixedSingle; } // Accepts an SVG string and renders it into a Bitmap public void DisplayImage(string svgContent) { Image = RenderSvgToBitmap(svgContent, Width, Height); } private Bitmap RenderSvgToBitmap(string svg, int width, int height) { using (var svgStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(svg))) { var skSvg = new SKSvg(); skSvg.Load(svgStream); var bitmap = new SKBitmap(width, height); using (var canvas = new SKCanvas(bitmap)) { canvas.Clear(SKColors.Transparent); var scale = Math.Min( width / skSvg.Picture.CullRect.Width, height / skSvg.Picture.CullRect.Height); canvas.Scale((float)scale); canvas.DrawPicture(skSvg.Picture); } return SkiaSharp.Views.Desktop.Extensions.ToBitmap(bitmap); } } }

Calling Python from C# with Javonet

Designing the MainForm.cs

The MainForm class is the entry point of your WinForms application. It brings together the UI components and the logic required to communicate with Python through Javonet. Here’s how it works step-by-step.

Layout and Components

The form is divided into two main sections:

  1. Left Panel – Contains the ChartInputPanel with fields to enter X and Y values.
  2. Main Area – A ChartDisplayPanel that renders the SVG chart based on the Python output.

These components are added using DockStyle so the layout is responsive and clean.

MainForm.cs – Constructor Overview

using JN = Javonet.Netcore.Sdk; namespace Charts { public partial class MainForm : Form { private readonly ChartInputPanel _inputPanel; private readonly ChartDisplayPanel _chartDisplay; private readonly JN.RuntimeContext _pythonRuntime; public MainForm() { Text = "Python Chart Generator via Javonet"; Size = new Size(800, 600); _inputPanel = new ChartInputPanel { Dock = DockStyle.Left, Width = 250 }; _chartDisplay = new ChartDisplayPanel { Dock = DockStyle.Fill }; _inputPanel.GenerateChartClicked += OnGenerateChart; Controls.Add(_chartDisplay); Controls.Add(_inputPanel); [...] InitializeComponent(); } private void OnGenerateChart(object sender, ChartInputEventArgs e) { [...] } } }

Explanation:

  • Text and Size set the window title and dimensions.
  • _inputPanel is docked to the left, taking fixed width.
  • _chartDisplay fills the remaining space, scaling automatically.
  • The event GenerateChartClicked is subscribed to by OnGenerateChart, which will later call Python and render the result.
  • The [ ... ] placeholder will contain Javonet runtime initialization (we’ll fill it in the next section).

Completing the MainForm Constructor

Python Runtime Initialization
To finish the MainForm setup, we need to initialize the Javonet runtime for Python and load the Charts class from your Python script.

This initialization should happen directly inside the constructor, after adding UI controls but before the call to InitializeComponent() (or right after, depending on your layout preference).

Here’s what this final initialization block should do:

  1. Activate the Javonet using your API key.
  2. Initialize the Python runtime context.
  3. Set the RuntimeClasspath to point to the folder where charts.py is located.
  4. Load the Charts class from Python and create its instance.
  5. Store that instance in a private field so it can be used later to generate charts.

This instance (chartsInstance) will then be used in the OnGenerateChart(...) method to call the generate_line_chart_svg(...) function and get the resulting SVG.

⚠️ Make sure the path to your charts.py is correct and Python is installed and accessible from the system PATH.

Our constructor should look like this:

public MainForm() { Text = "Python Chart Generator via Javonet"; Size = new Size(800, 600); _inputPanel = new ChartInputPanel { Dock = DockStyle.Left, Width = 250 }; _chartDisplay = new ChartDisplayPanel { Dock = DockStyle.Fill }; _inputPanel.GenerateChartClicked += OnGenerateChart; Controls.Add(_chartDisplay); Controls.Add(_inputPanel); JN.Javonet.Activate("your-API-key"); _pythonRuntime = JN.Javonet.InMemory().Python(); var path = @"your charts.py path"; _pythonRuntime.LoadLibrary(path); InitializeComponent(); }

Implementing OnGenerateChart – Calling Python and Rendering the Chart
With the UI layout complete and the Python Charts class successfully loaded using Javonet, it’s time to implement the heart of the interaction: the OnGenerateChart method.

This method will be triggered whenever the user clicks the “Generate Chart” button. Here’s what it needs to accomplish:

  1. Read the X and Y values from the event arguments (ChartInputEventArgs).
  2. Call the Python method generate_line_chart_svg(...) using the Javonet instance of the Charts class.
  3. Receive the SVG content as a string.
  4. Render the SVG into a Bitmap using Svg.Skia and show it in the ChartDisplayPanel.

By the end of this step, you’ll have a fully working hybrid desktop application — a native .NET UI powered by Python logic behind the scenes.

Final implementation of the OnGenerateChart() method should look like this:

try { if (e.XValues.Length != e.YValues.Length) { MessageBox.Show("X and Y must have the same number of elements."); return; } var instance = _pythonRuntime.GetType("charts.Charts").CreateInstance(); var x = e.XValues; var y = e.YValues; var svg = instance.InvokeInstanceMethod("generate_line_chart_svg", "Demo Chart", x, y).Execute(); var result = (string)svg.GetValue(); _chartDisplay.DisplayImage(result); } catch (Exception ex) { MessageBox.Show("Error: " + ex.Message); }

Conclusion

By combining the strengths of .NET WinForms for user interfaces and Python for expressive data visualization, you unlock the best of both worlds. Thanks to Javonet, integrating Python logic into your C# application becomes a straightforward and elegant process—no external processes, no manual data serialization, and no glue code.

Whether you want to reuse existing Python models, call scientific libraries, or offload chart generation like in our example, Javonet makes cross-language integration effortless. In just a few lines of code, you can call Python methods, receive SVG strings, and render them natively inside your .NET application.

This approach not only saves time and effort but also encourages modular, maintainable, and multi-language development—ideal for teams that already rely on Python for logic or analytics.

Try it yourself and see how Javonet opens up a whole new layer of flexibility in desktop application development.