Skip to main content

How to Build Corva Frontend Apps with Highcharts - Complete Guide

What You'll Build

In this guide, you'll learn how to build Corva frontend applications with Highcharts integration for powerful data visualization. You'll create a real-time drilling data visualization app that displays WITS (Wellsite Information Transfer Specification) data using interactive Highcharts. The app will show drilling parameters like hole depth updated in real-time, demonstrating the complete workflow of building Corva FE apps with advanced charting capabilities.

Final App Screenshot

The completed drilling WITS app showing real-time data visualization

Key Features You'll Implement:

  • ✅ Real-time drilling data visualization
  • ✅ Interactive Highcharts integration
  • ✅ Multiple time interval selection (6h, 30m, 1m, 1ft)
  • ✅ Responsive modern UI using Corva Design System
  • ✅ Live data streaming via Corva SDK

Prerequisites

System Requirements

  • Node.js: Version 18 or higher (required for @corva/ui 3.12.0+)
  • Yarn: Package manager
  • Git: Version control
  • Corva Account: Access to Corva platform

Verify Your Environment

# Check Node version (should be 18+)
node --version

# Check if yarn is installed
yarn --version

# Install yarn if needed
npm install -g yarn

If you need Node 18, use nvm:

# Install nvm if needed
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# Install and use Node 18
nvm install 18
nvm use 18

Step 1: Generate the Base App Using Corva CLI

Create your new drilling WITS app using the Corva CLI:

npx @corva/create-app drilling-wits-app \
--appName "Drilling WITS Dashboard" \
--segments "drilling" \
--category "analytics" \
--appKey "your-company.drilling_wits_dashboard.ui" \
--appType "ui" \
--runtime "ui"

This command creates a basic React app structure with these files:

# Navigate to your new app
cd drilling-wits-app

# Install dependencies
yarn install

Step 2: Understanding the Generated App Structure

After generation, your project structure looks like this:

drilling-wits-app/
├── src/
│ ├── App.js # Basic component showing rig/well info
│ ├── AppSettings.js # Simple settings with checkbox
│ ├── constants.js # Basic default settings
│ ├── index.js # App entry point
│ ├── App.css # Basic styling
│ └── assets/ # Static assets (logo, etc.)
├── config-overrides.js # Webpack configuration
├── package.json # Dependencies and scripts
├── manifest.json # Corva app manifest
├── .nvmrc # Node version specification
├── .eslintrc # ESLint configuration
└── README.md # Project documentation

Generated Files Explained:

src/App.js - Basic component that displays rig and well information:

// Generated basic App.js
function App({ isExampleCheckboxChecked, rig, well }) {
return (
<AppContainer header={<AppHeader />}>
<div>
<img src={logo} alt="logo" />
<p>Rig: {rig.name}</p>
<p>Well: {well.name}</p>
<p>Settings checkbox is {isExampleCheckboxChecked ? 'checked' : 'unchecked'}</p>
</div>
</AppContainer>
);
}

src/AppSettings.js - Simple settings component with one checkbox:

// Generated basic AppSettings.js
function AppSettings({ settings, onSettingChange }) {
return (
<div>
<FormControlLabel
label="Example checkbox"
control={
<Checkbox
checked={settings.isExampleCheckboxChecked}
onChange={e => onSettingChange('isExampleCheckboxChecked', e.target.checked)}
/>
}
/>
</div>
);
}

src/constants.js - Basic default settings:

// Generated basic constants.js
export const DEFAULT_SETTINGS = {
isExampleCheckboxChecked: false,
};

Step 3: Install Additional Dependencies for WITS Visualization

Add the required packages for data visualization and real-time functionality:

yarn add highcharts highcharts-react-official lodash prop-types

Important: Currently, the maximum supported Highcharts version is 8.1.2. Support for higher versions will be added in future releases.

Update your package.json to match our target configuration:

{
"name": "DrillingWITS",
"version": "0.0.1",
"main": "src/index.js",
"scripts": {
"build": "webpack --config=./config-overrides.js --mode production",
"start": "webpack-dev-server --config=./config-overrides.js --open --mode development",
"zip": "create-corva-app --zip ui",
"lint": "eslint --cache ./src/"
},
"dependencies": {
"@corva/ui": "^3.12.0",
"@material-ui/core": "4.11.2",
"@material-ui/icons": "4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"@material-ui/pickers": "3.2.10",
"classnames": "2.2.6",
"corva-convert-units": "1.10.33",
"highcharts": "8.1.2",
"highcharts-react-official": "2.2.2",
"lodash": "^4.17.21",
"mapbox.js": "3.2.1",
"moment": "2.29.4",
"moment-timezone": "0.5.43",
"prop-types": "^15.6.1",
"react": "17.0.1",
"react-dom": "17.0.1"
},
"devDependencies": {
"postcss-loader": "4.2.0",
"webpack": "5.94.0",
"webpack-dev-server": "4.15.2",
"webpack-cli": "5.1.4",
"@corva/dc-platform-shared": "latest",
"babel-loader": "8.1.0",
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"postcss": "8.4.31"
}
}

Step 4: Transform Constants for WITS Data

Replace the basic constants with WITS-specific dataset definitions:

Replace src/constants.js:

export const DATASETS = [
'wits.summary-6h',
'wits.summary-30m',
'wits.summary-1m',
'wits.summary-1ft',
];

These represent different time intervals for WITS data aggregation, allowing users to view drilling data at various granularities.


Step 5: Create the Data Fetching Hook

Create a new directory structure and build a custom React hook for WITS data management:

Create directory: src/effects/

Create src/effects/useWITSSummaryData.js:

import { useState, useEffect } from 'react';
import { corvaDataAPI, socketClient } from '@corva/ui/clients';
import { reverse } from 'lodash';

async function fetchWITSSummaryData({ assetId, dataset }) {
try {
return await corvaDataAPI.get(`/api/v1/data/corva/${dataset}/`, {
limit: 1000,
skip: 0, // NOTE: Required for pagination
// NOTE: Make sure the sort field hit database indexes. Otherwise the request will take too long
sort: JSON.stringify({ timestamp: -1 }),
query: JSON.stringify({ asset_id: assetId }),
// NOTE: To make efficient request - fetch only fields used by the app
fields: ['timestamp', 'data.hole_depth', 'data.state'].join(','),
});
} catch (e) {
console.log(e);
return [];
}
}

function useWITSSummaryData({ assetId, dataset }) {
// NOTE: Define state to store subscription data
const [witsSummaryData, setWITSSummaryData] = useState([]);

const [loading, setLoading] = useState(false);

useEffect(() => {
let unsubscribe;
setLoading(true);

// Make initial request first
fetchWITSSummaryData({ assetId, dataset })
.then(response => {
setWITSSummaryData(reverse(response));

const subscription = { provider: 'corva', dataset, assetId }; // Subscription params
// Concatenate new records to state
const onDataReceive = event => setWITSSummaryData(prevData => prevData.concat(event.data));

// NOTE: Subscribe to updates after initial data request
unsubscribe = socketClient.subscribe(subscription, { onDataReceive });
})
.finally(() => setLoading(false));

// NOTE: Unsubscribe to prevent memory leaks and crashes in your app.
return () => unsubscribe?.();
}, [assetId, dataset]);

return { loading, witsSummaryData };
}

export default useWITSSummaryData;

Create src/effects/index.js:

export { default as useWITSSummaryData } from './useWITSSummaryData';

How This Hook Works:

  1. Initial HTTP Request: Fetches the last 1000 WITS records for the selected dataset
  2. WebSocket Subscription: Establishes real-time connection for live updates
  3. State Management: Manages loading states and data updates
  4. Memory Management: Properly cleans up subscriptions to prevent leaks

Step 6: Build the Chart Component System

Create the Highcharts integration components:

Create directory: src/components/WITSSummaryChart/

Create src/components/WITSSummaryChart/options.js:

const defaultHighchartsOptions = {
chart: {
backgroundColor: '#201f1f',
type: 'line',
},
plotOptions: {
series: {
marker: {
enabled: false,
},
},
},
xAxis: {
type: 'datetime',
title: { text: 'Date' },
},
yAxis: {
title: { text: 'Hole Depth' },
gridLineColor: '#3b3b3b',
min: 0,
},
credits: { enabled: false },
exporting: { enabled: false },
};

export function getHighchartsOptions({ data, dataset }) {
const series = [
{
name: dataset,
data: data.map(witsRecord => ({
x: witsRecord.timestamp * 1000,
y: witsRecord.data.hole_depth,
name: witsRecord.data.state,
})),
turboThreshold: 20000,
},
];

return {
...defaultHighchartsOptions,
title: { text: `Example ${dataset} chart`, style: { color: 'white' } },
series,
};
}

Create src/components/WITSSummaryChart/index.js:

import { useMemo, useRef, useEffect } from 'react';
import Highcharts from 'highcharts';
import HighchartsReact from 'highcharts-react-official';

import { getHighchartsOptions } from './options';
import styles from './styles.css';

function WITSSummaryChart({ data, dataset, coordinates }) {
const chartRef = useRef();

useEffect(() => {
// NOTE: Update chart size when container size has changed
chartRef.current?.chart.setSize();
}, [coordinates]);

// NOTE: Use memoization technique
// Calculate chart options ONLY when data changed.
// This will also help avoid choppy animation
return useMemo(() => {
const options = getHighchartsOptions({ data, dataset });

return (
<HighchartsReact
highcharts={Highcharts}
options={options}
// NOTE: Pass className to style highchart
containerProps={{ className: styles.chartContainer }}
ref={chartRef}
/>
);
}, [data]);
}

export default WITSSummaryChart;

Create src/components/WITSSummaryChart/styles.css:

.chartContainer {
height: 400px;
width: 100%;
margin: 20px 0;
}

Create src/components/index.js:

export { default as WITSSummaryChart } from './WITSSummaryChart';

Step 7: Transform the Main App Component

Replace the basic generated App.js with our sophisticated drilling visualization interface:

Replace src/App.js:

/*
This example is a simple app which consists of: AppHeader, Select and Highcharts Chart
*/
import { useState } from 'react';
import { LoadingIndicator, AppHeader, Select } from '@corva/ui/components';
import { MenuItem } from '@material-ui/core';

import { useWITSSummaryData } from './effects';
import { WITSSummaryChart } from './components';
import { DATASETS } from './constants';

import styles from './styles.css';

function App(props) {
// NOTE: Read asset_id from well. Most datasets are indexed by asset_id.
const {
well: { asset_id: assetId },
coordinates,
appHeaderProps,
} = props;

const [dataset, setDataset] = useState(DATASETS[0]);
// NOTE: Use custom react hook to encapsulate data fetching/subscriptions logic
const { witsSummaryData, loading } = useWITSSummaryData({ assetId, dataset });

return (
<div className={styles.container}>
<AppHeader {...appHeaderProps} />

<div className={styles.content}>
<Select
value={dataset}
onChange={e => setDataset(e.target.value)}
FormControlProps={{ className: styles.select }}
>
{DATASETS.map(dataset => (
<MenuItem key={dataset} value={dataset}>
{dataset}
</MenuItem>
))}
</Select>

{/*Show loading indicator while data is loading*/}
{loading && <LoadingIndicator />}
{!loading && (
<WITSSummaryChart data={witsSummaryData} coordinates={coordinates} dataset={dataset} />
)}
</div>
</div>
);
}

// Important: Do not change root component default export (App.js). Use it as container
// for your App. It's required to make build and zip scripts work as expected;
export default App;

Key Transformations Made:

  1. Removed basic rig/well display → Added sophisticated data visualization
  2. Added dataset selection → Dropdown for choosing time intervals
  3. Integrated custom hook → Real-time data fetching and management
  4. Added chart component → Highcharts visualization
  5. Enhanced layout → Professional container and content structure

Step 8: Add Professional Styling

Create new styling to replace the basic generated styles:

Create src/styles.css:

.container {
display: flex;
flex-direction: column;
height: 100%;
background-color: #1a1a1a;
}

.content {
flex: 1;
padding: 20px;
overflow: auto;
}

.select {
margin-bottom: 20px;
min-width: 200px;
}

Update src/App.css:

.App {
text-align: center;
background-color: #1a1a1a;
color: white;
min-height: 100vh;
}

.App-header {
background-color: #282c34;
padding: 20px;
color: white;
}

.App-link {
color: #61dafb;
}

/* Additional styling for drilling app */
.drilling-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}

.dataset-selector {
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}

.chart-wrapper {
background-color: #2a2a2a;
border-radius: 8px;
padding: 20px;
margin-top: 20px;
}

Step 9: Update App Settings

Transform the basic settings to work with our drilling app:

Replace src/AppSettings.js:

import { Checkbox, FormControlLabel } from '@material-ui/core';
import PropTypes from 'prop-types';

import { DATASETS } from './constants';

function AppSettings({
settings: apiSettings,
onSettingChange,
// appData,
// app,
// user,
// company,
}) {
const settings = { ...DATASETS, ...apiSettings };
return (
<div>
<FormControlLabel
label="Example checkbox"
control={
<Checkbox
checked={settings.isExampleCheckboxChecked}
onChange={e => onSettingChange('isExampleCheckboxChecked', e.target.checked)}
/>
}
/>
</div>
);
}

AppSettings.propTypes = {
app: PropTypes.shape({}).isRequired,
appData: PropTypes.shape({}).isRequired,
company: PropTypes.shape({}),
onSettingChange: PropTypes.func.isRequired,
settings: PropTypes.shape({
isExampleCheckboxChecked: PropTypes.bool
}).isRequired,
user: PropTypes.shape({}),
};

AppSettings.defaultProps = {
user: {},
company: {},
};

// Important: Do not change root component default export (AppSettings.js). Use it as container
// for your App Settings. It's required to make build and zip scripts work as expected;
export default AppSettings;

Step 10: Test Your Transformed Application

Start the development server to see your transformation:

yarn start

What You Should See:

  1. Dataset Selection: Dropdown with WITS time intervals (6h, 30m, 1m, 1ft)
  2. Loading States: Professional loading indicators during data fetch
  3. Real-time Chart: Highcharts visualization showing hole depth over time
  4. Responsive Design: Chart adjusts to window size changes

Testing Your App:

  1. Dataset Selection: Switch between different time intervals
  2. Loading States: Verify loading indicators appear during data fetch
  3. Real-time Updates: Check browser console for WebSocket connections
  4. Chart Visualization: Confirm hole depth data displays correctly
  5. Responsive Design: Resize browser window to test chart responsiveness

Debugging:

  • Console Logs: Check for API call logs and WebSocket connection status
  • Network Tab: Monitor HTTP requests to /api/v1/data/corva/{dataset}/
  • Data Verification: Ensure asset_id is being extracted from well context

Step 11: Understanding the Complete Data Flow

How Everything Works Together:

  1. App Initialization:

    • Component mounts and extracts asset_id from well props
    • Sets initial dataset to first option (6h)
  2. Data Fetching Hook Activation:

    • useWITSSummaryData hook triggers with asset ID and dataset
    • Makes HTTP request to /api/v1/data/corva/wits.summary-6h/
    • Fetches last 1000 records with optimized field selection
  3. Initial Data Processing:

    • Response data is reversed (oldest first for time series)
    • Loading state is set to false
    • Chart component receives data
  4. WebSocket Subscription:

    • Real-time subscription established for live updates
    • New data automatically appends to existing dataset
    • Chart updates in real-time
  5. Chart Rendering:

    • Highcharts transforms WITS data into time series format
    • Displays hole depth over time with professional styling
    • Memoization prevents unnecessary re-renders
  6. User Interactions:

    • Dataset selection triggers new API calls
    • Loading states provide user feedback
    • Chart updates with new time interval data

API Endpoints Used:

  • HTTP: /api/v1/data/corva/wits.summary-{interval}/ for initial data
  • WebSocket: Real-time subscription for live drilling data updates

Data Structure:

// WITS record structure
{
timestamp: 1640995200,
asset_id: "drilling-rig-123",
data: {
hole_depth: 1250.5,
state: "drilling"
}
}

Step 12: Build and Deploy

Create Production Build:

yarn build

Package for Deployment:

yarn zip

Deploy to Corva Platform:

yarn release

Your complete drilling WITS visualization app will be available in the Corva platform's drilling segment.


Step 13: Summary of Transformation

What We Changed:

From Basic Generated App:

  • Simple rig/well display
  • Basic checkbox setting
  • Static content
  • Minimal styling

To Sophisticated WITS App:

  • Real-time data visualization
  • Multiple dataset selection
  • Custom React hooks for data management
  • Highcharts integration
  • Professional UI with loading states
  • WebSocket subscriptions for live updates

Files Added/Modified:

New Files:

  • src/effects/useWITSSummaryData.js - Custom data hook
  • src/effects/index.js - Effects export
  • src/components/WITSSummaryChart/index.js - Chart component
  • src/components/WITSSummaryChart/options.js - Chart configuration
  • src/components/WITSSummaryChart/styles.css - Chart styling
  • src/components/index.js - Components export
  • src/styles.css - App styling

Modified Files:

  • src/App.js - Complete transformation to data visualization
  • src/constants.js - WITS datasets instead of basic settings
  • src/AppSettings.js - Updated with PropTypes
  • src/App.css - Enhanced styling
  • package.json - Added visualization dependencies

This transformation demonstrates how to evolve a basic Corva app into a sophisticated, production-ready drilling data visualization application with real-time capabilities.