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.
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:
- Initial HTTP Request: Fetches the last 1000 WITS records for the selected dataset
- WebSocket Subscription: Establishes real-time connection for live updates
- State Management: Manages loading states and data updates
- 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:
- Removed basic rig/well display → Added sophisticated data visualization
- Added dataset selection → Dropdown for choosing time intervals
- Integrated custom hook → Real-time data fetching and management
- Added chart component → Highcharts visualization
- 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:
- Dataset Selection: Dropdown with WITS time intervals (6h, 30m, 1m, 1ft)
- Loading States: Professional loading indicators during data fetch
- Real-time Chart: Highcharts visualization showing hole depth over time
- Responsive Design: Chart adjusts to window size changes
Testing Your App:
- Dataset Selection: Switch between different time intervals
- Loading States: Verify loading indicators appear during data fetch
- Real-time Updates: Check browser console for WebSocket connections
- Chart Visualization: Confirm hole depth data displays correctly
- 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:
App Initialization:
- Component mounts and extracts
asset_id
from well props - Sets initial dataset to first option (6h)
- Component mounts and extracts
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
Initial Data Processing:
- Response data is reversed (oldest first for time series)
- Loading state is set to false
- Chart component receives data
WebSocket Subscription:
- Real-time subscription established for live updates
- New data automatically appends to existing dataset
- Chart updates in real-time
Chart Rendering:
- Highcharts transforms WITS data into time series format
- Displays hole depth over time with professional styling
- Memoization prevents unnecessary re-renders
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 hooksrc/effects/index.js
- Effects exportsrc/components/WITSSummaryChart/index.js
- Chart componentsrc/components/WITSSummaryChart/options.js
- Chart configurationsrc/components/WITSSummaryChart/styles.css
- Chart stylingsrc/components/index.js
- Components exportsrc/styles.css
- App styling
Modified Files:
src/App.js
- Complete transformation to data visualizationsrc/constants.js
- WITS datasets instead of basic settingssrc/AppSettings.js
- Updated with PropTypessrc/App.css
- Enhanced stylingpackage.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.