Create a WPF Seismic Spectrogram chart with LightningChart .NET

Tutorial

Written by a Human

In this tutorial, we will learn how to create a WPF seismic spectrogram application in WPF using LightningChart .NET charting library.
Roy Liu

Omar Urbano

Software Engineer

LinkedIn icon
Seismic-spectrogram-Project-Cover

Introduction

Hello, I’m Omar, and welcome to a new article on LightningChart and .NET. In this article, we will work with a 3D chart in which we will simulate real-time study. To achieve this, we will use a dataset obtained from Kaggle, which contains records of various events in India. 

In this dataset, we will find location, magnitude, and depth data. Since we will be simulating real-time data, we will generate a time range based on our actual time. 

We will use the magnitude data to establish a range of peaks, while the Z-axis will be simulated using a brief calculation based on magnitude. This calculation will serve as a variable to simulate greater “noise” in the displayed peaks. 

The goal of this chart is to demonstrate how we can read a dataset and stream it in real-time on a three-axis chart. 

What is a seismic spectrogram chart? 

A spectrogram is a visual representation of how the frequency spectrum of a signal varies over time. It is commonly used in the analysis of audio signals, vibrations, seismic signals, and other signal processing applications. 

Components of a Spectrogram: 

  1. X-axis (horizontal): Represents time. 
  2. Y-axis (vertical): Represents frequency. 
  3. Z-axis (or color in 2D): Amplitude or signal power (dB or arbitrary units). 
  4. Colors or Intensity: Represent the amplitude or power of each frequency at a given moment. 

How a Spectrogram Generated

A spectrogram is obtained by applying the Short-Time Fourier Transform (STFT) to segments of the signal to calculate its spectral content at different moments.

Examples of Spectrogram Applications

  • Audio and speech analysis: To detect speech patterns, identify sounds, or study acoustic signal quality. 
  • Predictive maintenance: To analyze vibrations in motors and detect early failures. 
  • Medicine: Used in EEG (electroencephalograms) and brain wave analysis. 
  • Seismology: To study earthquakes and geological activity. 

What is a seismic spectrogram? 

A seismic spectrogram is a visual representation of the energy of ground vibrations in relation to time and frequency. It is used in seismology to analyze seismic activity, detect earthquakes, and study phenomena such as volcanic eruptions or underground explosions. 

Properties: 

  • X-axis (horizontal): Time (seconds, minutes, hours, or days). 
  • Y-axis (vertical): Frequency (Hz). 
  • Color or Z-axis: Amplitude of the seismic signal (vibration intensity, usually in dB). 

To obtain a seismic spectrogram, the signal is processed using the Short-Time Fourier Transform (STFT), which allows analysis of how frequencies change over time. More intense colors in the spectrogram indicate higher energy at certain frequencies. 

This helps identify seismic events, such as earthquakes or volcanic eruptions, and differentiate between natural and artificial events, like explosions. This type of analysis is crucial in seismology and can also be applied to other fields, such as predictive maintenance in industrial machinery. 

Project Overview

Feel free to download the ZIP file to follow this semiconductor measurement system project.

Seismic-spectrogram-Chart-Example

zip icon
Download the project to follow the tutorial

Local Setup

For this project, we need to consider the following requirements to compile the project.

  1. OS: 32-bit or 64-bit Windows Vista or later, Windows Server 2008 R2 or later.
  2. DirectX: 9.0c (Shader model 3 and higher) or 11.0 compatible graphics adapter.
  3. Visual Studio: 2022 for development, not required for deployment.
  4. Platform .NET Framework: installed version 8.0 or newer.

Now go to the next URL and download LightningChart .NET. You’ll then be redirected to a sign-in form where you’ll have to complete a simple sign-up process to get access to your LightningChart account.

Example-LightningChart-Account

After signing in to your account, you can download the SDK “free trial” version that allows you to use important features for this tutorial. When you download the SDK, you’ll have a .exe file like this:

LightningChart-exe-installation

The installation will be a typical Windows process, so please continue with it until it is finished. After the installation, you will see the following programs:

LightningChart-.NET-Installed-Programs

License Manager

In this application, you will see the purchase options. All the projects that you will create with this trial SDK will be available for future developments with all features enabled.

Purchase-Options-LightningChart-.NET

Creating the project

Once you run the example in the “interactive examples” application, you will see the following option

Seismic-spectrogram-interactive-example
Semiconductor-measurement-system-interactive-examples

Lightning Chart can generate a project for the current selected chart, using Windows Presentation Foundation (WPF), WinForms, and their NET6 versions. 

Semiconductor-measurement-system-project-type

Once you select the extract option, you will have to create a new folder for the project

Seismic-spectrogram-Project-Folder

Once the project is saved, Visual Studio will open by itself, and you’ll see a project like this

Seismic-spectrogram-Project-Tree

About Dataset: Visualizing India’s Seismic activity 

For this project, you can use the India’s Seismic Activity Kaggle dataset. This dataset includes a record of the date, time, location, depth, and magnitude of every earthquake since 1st August 2019.

The magnitude refers to the amplitude or size of the seismic waves generated by an earthquake source. This dataset corresponds to the study of the geographical area of India and is designed to be used on a 2D map, so we will focus solely on the magnitude data. Within the project of this article, you will find a CSV file with all the information and its JSON format for reading on .NET.

Seismic-spectrogram-Dataset-Example

MainWindow 

Chart Initialization and Cleanup

We will create the LightningChart graph, but first, we ensure that no previous graph remains in the gridChart container. If a previous graph exists, it is destroyed to avoid resource conflicts.

private void CreateChart() 
{ 
    gridChart.Children.Clear(); 
    if (_chart != null) 
    { 
        _chart.Dispose(); 
        _chart = null; 
    } 
    _chart = new LightningChart 
    { 
        ChartName = "Spectrogram chart" 
    }; 
 

  • gridChart.Children.Clear(): Ensures that any previous chart components are removed from the gridChart container.
  • _chart.Dispose(): If _chart is not null, it releases the memory of the previous chart.
  • Creating a new chart: The _chart object of type LightningChart is instantiated and assigned a name identifying it as “Spectrogram chart.”

Chart Configuration (Appearance and Axes)

In this section, the visual options of the chart are configured, including the background, axes, legends, and the appearance of values. Everything is organized in a 3D environment.  Additionally, properties are adjusted to ensure the chart looks appealing and remains functional. 

_chart.BeginUpdate(); 
_chart.ActiveView = ActiveView.View3D; 
_chart.ChartBackground.GradientColor = Colors.Black; 
_chart.ChartBackground.Color = Colors.DimGray; 
_chart.ChartBackground.GradientFill = GradientFill.Radial; 
foreach (var wall in _chart.View3D.GetWalls()) 
{ 
    wall.Visible = false; 
} 
_chart.View3D.XAxisPrimary3D.Orientation = PlaneXAxis3D.XZ; 
_chart.View3D.XAxisPrimary3D.CornerAlignment = AxisAlignment3D.Outside; 
_chart.View3D.XAxisPrimary3D.MajorDivTickStyle.Alignment = Alignment.Far; 
_chart.View3D.XAxisPrimary3D.ValueType = AxisValueType.Time; 
_chart.View3D.XAxisPrimary3D.LabelsColor = Color.FromArgb(200, 255, 255, 255); 
_chart.View3D.XAxisPrimary3D.MajorDivTickStyle.Color = Colors.Orange; 
_chart.View3D.XAxisPrimary3D.MinorDivTickStyle.Visible = false; 
_chart.View3D.XAxisPrimary3D.Title.Text = "Time"; 
_chart.View3D.XAxisPrimary3D.Title.Color = Colors.Yellow; 
  • X, Y, and Z Axes: Configured to represent time and magnitudes, with different ranges and colors for each. 
  • Legend: The legend’s display is adjusted with titles, colors, and positioning. 

Interactive Adjustments and Finalization

This section ensures that the chart includes interactive options and is added to the user interface container, providing a better user experience.

_chart.View3D.Camera.SetPredefinedCamera(PredefinedCamera.TopOrthographicXZ); 
_chart.View3D.ZoomPanOptions.DeviceSecondaryButtonAction = UserInteractiveDeviceButtonAction3D.None; 
_chart.SizeChanged += _chart_SizeChanged; 
_chart.EndUpdate(); 
gridChart.Children.Add(_chart); 
  • Camera: Configured to view the chart from a top orthographic perspective. 
  • Zoom and Pan: Interaction options are adjusted, disabling some default actions. 
  • Resize Event: The chart is subscribed to an event that handles size changes. 
  • Finalization: The chart update is completed and added to the gridChart user interface container. 

MaximizeViewPort 

This code is designed to adjust the dimensions of the chart’s 3D view based on the actual size of its container (_chart.ActualWidth and _chart.ActualHeight). The goal is to maximize the visible area of the chart while maintaining the correct aspect ratio for optimal visualization.  

Initial Configuration 

The code starts by updating the chart and preparing some variables that will be used for dimension calculations. 

float MarginPixels = 70f; 
float fAspectRatio = (float)(_chart.ActualWidth / _chart.ActualHeight); 
  • MarginPixels: Defines the margin left at the edges of the chart to prevent it from occupying the entire container space. 
  • fAspectRatio: Calculates the chart’s aspect ratio (the ratio between the chart’s width and height) to proportionally adjust the 3D view dimensions. 

Horizontal or Vertical Condition 

The code then decides which dimensions to assign based on whether the chart is oriented horizontally or vertically, controlled by the m_bIsHorizontal variable. Depending on this orientation, the Width and Depth dimensions of the chart’s 3D view are adjusted accordingly. 

if (fAspectRatio >= 1) 
{ 
    _chart.View3D.Dimensions.Depth = fAspectRatio * 200f * ((float)_chart.ActualWidth - 2f * MarginPixels) / (float)_chart.ActualWidth; 
    _chart.View3D.Dimensions.Width = 200f * ((float)_chart.ActualHeight - 2f * MarginPixels) / (float)_chart.ActualHeight; 
} 
else 
{ 
    _chart.View3D.Dimensions.Width = 200f * ((float)_chart.ActualHeight - 2f * MarginPixels) / (float)_chart.ActualHeight / fAspectRatio; 
    _chart.View3D.Dimensions.Depth = 200f * ((float)_chart.ActualWidth - 2f * MarginPixels) / (float)_chart.ActualWidth; 
} 
  • Aspect Ratio Greater Than or Equal to 1: If the chart is wider than it is tall, the depth (Depth) and width (Width) are calculated based on the margins and the actual size of the chart, ensuring the proper aspect ratio is maintained. 
  • Aspect Ratio Less Than 1: If the chart is taller than it is wide, the calculation is reversed, adjusting the dimensions so that the visual proportions are maintained correctly. 
  • If m_bIsHorizontal is false (vertical): Here, the process is like the horizontal case, but the calculations for width Width and depth Depth are reversed depending on whether the aspect ratio is greater than or less than 1. 

The key difference is the assignment of dimensions, which adjusts based on whether the chart is taller than it is wide (aspect ratio less than 1) or vice versa.

RowDataGenerator_started 

This code manages the initialization and configuration of a 3D chart, related to the real-time data visualization of seismic values. 

Initial Configuration of the X Axis and Data Range 

The values for the X axis of the chart (time) and other visualization parameters are calculated and configured.

int iXWidthSec = 5; 
int resolution = 128; 
DeleteAllSeries(); 
_chart.BeginUpdate(); 
double dAxisXMin = _rowDataGenerator1.IntervalMs / 1000.0 - iXWidthSec; 
double dAxisXMax = 0; 
m_dStepX = _rowDataGenerator1.IntervalMs / 1000.0; 
_chart.View3D.XAxisPrimary3D.SetRange(dAxisXMin, dAxisXMax); 
m_dCurrentX = dAxisXMax; 
  • iXWidthSec: Controls the width of the chart’s time window in seconds.  
  • dAxisXMin and dAxisXMax: Set the range for the X axis (in this case, the range is centered around the data interval). 
  • m_dStepX: Represents the time step between each data point to be visualized. 

Configuration of the 3D Chart Surface (SurfaceGridSeries3D) 

A 3D surface series is created and configured to represent the data on the chart. 

if (_surface == null) 
{ 
    _surface = new SurfaceGridSeries3D(_chart.View3D, Axis3DBinding.Primary, Axis3DBinding.Primary, Axis3DBinding.Primary); 
    _surface.ContourPalette = CreatePalette(_surface); 
    _surface.Title.Text = "Earthquake Magnitude"; 
    _surface.DisableDepthTest = true; 
    _surface.ColorSaturation = 100.0; 
    _chart.View3D.SurfaceGridSeries3D.Add(_surface); 
} 
_surface.InitialValue = 10; 
_surface.WireframeType = SurfaceWireframeType3D.None; 
_surface.ContourLineType = ContourLineType3D.None; 
int iSizeX = (int)(iXWidthSec * 1000.0 / _rowDataGenerator1.IntervalMs); 
_surface.SetSize(iSizeX, resolution); 
_surface.SuppressLighting = true; 
_surface.SetRangesXZ(_chart.View3D.XAxisPrimary3D.Minimum, _chart.View3D.XAxisPrimary3D.Maximum, _chart.View3D.ZAxisPrimary3D.Minimum, _chart.View3D.ZAxisPrimary3D.Maximum); 
_surface.Material.EmissiveColor = Color.FromArgb(255, 0, 40, 0); 
_surface.BaseColor = Colors.White; 
  • SurfaceGridSeries3D: Used to create a 3D data representation in the form of a surface. 
  • Contour and Color Palette: The color palette and visual properties of the surface are set. 
  • Axis Ranges: The ranges for the X and Z axes are adjusted based on the chart to ensure that the data is represented correctly. 

Configuration of Data Generator (RowDataGenerator) 

Finally, the parameters of the data generator are adjusted to define how the data series will be generated. This includes settings for data frequency, intervals, and how data points are created for the chart’s 3D visualization. 

_chart.EndUpdate(); 
_rowDataGenerator1.RowLength = resolution; 
_rowDataGenerator1.MinValue = 1; 
_rowDataGenerator1.MaxValue = 95; 
_rowDataGenerator1.Variation = 6;  
  • _rowDataGenerator1: The object responsible for generating the data to be visualized on the chart. 
  • Data Generation Parameters: The characteristics of the generated data are adjusted, such as the row size, minimum and maximum values, and the variation they will have. 

RowDataGenerator_started 

This code defines a class called DataGenerator that simulates the generation of real-time seismic data for visualization. The class loads seismic data from a JSON file and generates it at periodic intervals using a DispatcherTimer. 

Load and Store Seismic Data from a JSON File

The code loads seismic data from a JSON file in the EarthquakeData format. If the file does not exist, an exception is thrown. The data is deserialized into a list of EarthquakeData objects.

string projectRoot = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory).Parent.Parent.Parent.FullName; 
string jsonFilePath = Path.Combine(projectRoot, "SeismicData.json"); 
_earthquakeData = LoadJsonData(jsonFilePath); 
_currentIndex = 0; 
_dispatcherTimer = new DispatcherTimer(); 
_dispatcherTimer.Tick += m_dispatcherTimer_Tick; 
_rowLength = 10; 
_minValue = 0.0; 
_maxValue = 8.0; 
_variation = 0.0; 

Here, we will create variables that will be useful for adding variations to our magnitudes. These variables will help simulate real-time fluctuations in seismic data for a more dynamic and realistic visualization. 

Periodic Seismic Data Generation 

This code is inside an event handler for the timer (DispatcherTimer). The timer is triggered at regular intervals defined by the IntervalMs property. Each time the timer “ticks” (i.e., each time the time interval passes), the GenerateData() method is called, which is responsible for generating a set of seismic data.

public double[] GenerateData() 
{ 
    if (_earthquakeData == null || _earthquakeData.Count == 0) 
        return Array.Empty<double>(); 
    var currentData = _earthquakeData[_currentIndex]; 
    double[] result = new double[_rowLength]; 
    for (int i = 0; i < _rowLength; i++) 
    { 
        result[i] = Math.Clamp(currentData.Magnitude + (_variation / 100.0 * (_maxValue - _minValue) * (new Random().NextDouble() - 0.5)), _minValue, _maxValue); 
        //result[i] = Math.Clamp(currentData.Magnitude, _minValue, _maxValue); 
    } 
    NewDataGenerated?.Invoke(new NewDataGeneratedEventArgs(result)); 
    _currentIndex = (_currentIndex + 1) % _earthquakeData.Count; 
    return result; 
} 

GenerateData returns an array of generated values (based on the magnitude of an earthquake and other defined parameters), which are used for updates in the interface or for further processing. This allows the system to continuously simulate and display updated seismic data at regular intervals.

Variation Calculation 

This calculation aims to generate a numerical value based on the magnitude of an earthquake (currentData.Magnitude), adjusted by a variation value (_variation), ensuring that the generated value stays within a specific range (_minValue to _maxValue). This helps simulate realistic fluctuations in seismic data.

result[i] = Math.Clamp(currentData.Magnitude + (_variation / 100.0 * (_maxValue - _minValue) * (new Random().NextDouble() - 0.5)), _minValue, _maxValue); 

If you want to display the real magnitude data, you can use the commented line

//result[i] = Math.Clamp(currentData.Magnitude, _minValue, _maxValue); 

The visual result will be more stable reading

Seismic-spectrogram-Example-Result

Start and Stop Data Generation 

public void Start() 
{ 
    if (!_dispatcherTimer.IsEnabled) 
    { 
        _dispatcherTimer.Interval = TimeSpan.FromMilliseconds(_interval); 
        _dispatcherTimer.Start(); 
        Started?.Invoke(this, EventArgs.Empty); 
    } 
} 
public void Stop() 
{ 
    if (_dispatcherTimer.IsEnabled) 
    { 
        _dispatcherTimer.Stop(); 
        Stopped?.Invoke(this, EventArgs.Empty); 
    } 
} 

Start Method 

This method starts with the timer if it is not already running. It first checks if the timer is enabled (i.e., if it is already running) using !_dispatcherTimer.IsEnabled. 

If it is not active, it sets the time interval for each “tick” of the timer (based on the IntervalMs value) and then starts the timer with _dispatcherTimer.Start(). It also triggers the Started event, allowing other parts of the code (such as the user interface or processing logic) to know that data generation has begun. 

Stop Method 

This method stops the timer if it is running. It checks if the timer is active (_dispatcherTimer.IsEnabled) and, if so, stops it with _dispatcherTimer.Stop(). Then, it triggers the Stopped event, indicating that data generation has ended. 

Conclusion

LightningChart is an excellent choice for data visualization in .NET, especially when dealing with complex and dynamic charts like earthquake spectrograms. Its straightforward integration, optimization for 3D charts, and high performance make it the ideal tool for developing visually appealing and efficient applications. 

The ability to manage large data volumes, combined with ease of customizing the interactive experience, ensures that any project using LightningChart will have a robust and efficient solution. Here are some important points to consider: 

  • Ease of Implementation: Its integration with .NET is direct and simple, allowing for quick creation of interactive charts. 
  • Performance Optimization: It handles large data volumes and complex charts smoothly, ensuring real-time experience fast. 
  • High Customization: It offers extensive options for customizing the visualization, from axes to the legend, enhancing user interactivity. 
  • Scalability: It is ideal for projects that need to scale and handle large amounts of data without compromising performance. 

The most complex part of this project was calculating the data and generating the timers, allowing us to easily and simply implement a 3D chart. The implementation of a 3D chart doesn’t differ much between this and other examples, so if you’ve already worked with 3D charts, this implementation will surely feel very familiar. 

Thank you for getting this far, I hope this article was of interest to you. I invite you to visit all our content on YouTube and our blog. Bye! 

Continue learning with LightningChart

Data Visualization Components for React Applications

Data Visualization Components for React Applications

Written by a human | Updated on April 9th, 2025React Data Visualization Components  React is one of the most popular front-end development frameworks on the web in the year 2022. It is a free and open-source front-end JS library that is used to build interactive...

What Is LiDAR Data Visualization?

What Is LiDAR Data Visualization?

LiDAR Data Visualization  The LiDAR method conveys another yet complex level of data visualization as it is widely used in demanding industries that require plotting, e.g., topographic or cartography 3D data. But the use of LiDAR goes further as it is also used...

Android

Android

Written by a human | Updated on April 9th, 2025Android Charts  Here's a new article I'm really excited about...this time, we will create an android charts data visualization application. For this application, we will work with Android Studio and LightningChart JS...