Appearance
Import progressbar
This example shows how to display a loader while Ogma is parsing and adding the graph to the visualisation.
It uses batchSize option to add the graph by chunks and display a loader between each chunk.
ts
import Ogma from '@linkurious/ogma';
import { Progressbar } from './progressbar';
const ogma = new Ogma({
container: 'graph-container'
});
ogma.setOptions({
detect: {
// disable the detection of edges and texts, making them impossible to hover or select.
// this improves performances for large graphs
edges: false,
nodeTexts: false,
edgeTexts: false
},
interactions: {
drag: {
enabled: false // disable node dragging
}
}
});
// create progressbar UI
const progressbar = new Progressbar();
const response = await fetch('files/github.json');
const reader = response.body!.getReader();
// Step 2: get total length
const contentLength = +response!.headers.get('Content-Length')!;
// Step 3: read the data
let receivedLength = 0; // received that many bytes at the moment
const chunks: Uint8Array[] = []; // array of received binary chunks (comprises the body)
progressbar.setText('Loading...').show();
const read = (): Promise<string | undefined> => {
return reader.read().then(({ done, value }) => {
if (done) {
progressbar.hide();
const buffer = new Uint8Array(receivedLength);
chunks.reduce((position, chunk) => {
buffer.set(chunk, position); // (4.2)
return position + chunk.length;
}, 0);
// Step 5: decode into a string
const result = new TextDecoder('utf-8').decode(buffer);
return result;
} else {
chunks.push(value!);
receivedLength += value!.length;
progressbar.setValue((receivedLength / contentLength) * 100);
}
if (!done) return read();
});
};
const json = await read();
const graph = await Ogma.parse.json(json!);
progressbar.setText('Rendering...').setValue(0).show();
const totalSize = graph.nodes.length + graph.edges.length;
// add progress listener
const onProgress = () => {
const currentSize = ogma.getNodes().size + ogma.getEdges().size;
progressbar.setValue((currentSize / totalSize) * 100);
if (currentSize === totalSize) onReady();
};
const onReady = () => {
progressbar.hide();
ogma.events.off(onProgress);
const nodes = ogma.getNodes().size;
const edges = ogma.getEdges().size;
// show graph size
document.getElementById('n')!.textContent = `nodes: ${nodes}`;
document.getElementById('e')!.textContent = `edges: ${edges}`;
};
ogma.events.on(['addNodes', 'addEdges'], onProgress);
// predict the camera location and then add the graph
await ogma.view.locateRawGraph(graph);
await ogma.setGraph(graph, { batchSize: 500 });
html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link
href="https://fonts.googleapis.com/css?family=Roboto:400,700"
rel="stylesheet"
type="text/css"
/>
<link type="text/css" rel="stylesheet" href="styles.css" />
<link type="text/css" rel="stylesheet" href="progressbar.css" />
</head>
<body>
<div id="graph-container"></div>
<div id="n" class="info n">
loading a large graph, it can take a few seconds...
</div>
<div id="e" class="info e"></div>
<script type="module" src="index.ts"></script>
</body>
</html>
css
body {
margin: 0;
font-family: 'Roboto', sans-serif;
}
#graph-container {
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
margin: 0;
overflow: hidden;
}
.info {
position: absolute;
color: #fff;
background: #141229;
font-size: 12px;
font-family: monospace;
padding: 5px;
}
.info.n {
top: 0;
left: 0;
}
.info.e {
top: 20px;
left: 0;
}
css
.progressbar {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 200px;
margin-top: -100px;
margin-left: -100px;
}
.progressbar--card {
position: relative;
background: rgba(255, 255, 255, 0.5);
border-radius: 10px;
padding: 10px;
text-align: center;
overflow: hidden;
}
.progressbar--box {
display: inline-block;
}
.progressbar--percent {
position: relative;
width: 150px;
height: 150px;
border-radius: 50%;
box-shadow: inset 0 0 50px #000;
background: #222;
z-index: 1000;
}
.progressbar--num {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
}
.progressbar--num h2 {
color: #777;
font-weight: 700;
font-size: 30px;
transition: 0.5s;
}
.progressbar--num h2 span {
color: #777;
font-size: 24px;
transition: 0.5s;
}
.progressbar--text {
position: relative;
color: #777;
margin-top: 20px;
font-weight: 700;
font-size: 18px;
letter-spacing: 1px;
text-transform: uppercase;
transition: 0.5s;
}
.progressbar--indicator {
position: relative;
width: 150px;
height: 150px;
z-index: 1000;
}
.progressbar--circle {
width: 100%;
height: 100%;
fill: none;
stroke: #191919;
stroke-width: 10;
stroke-linecap: round;
transform: translate(5px, 145px) rotate(-90deg);
}
.progressbar--progress {
stroke-dasharray: 440;
stroke-dashoffset: 440;
stroke: #ea5565;
}
ts
interface ProgressbarOptions {
/** Gauge radius */
radius?: number;
/** Text to display. Default "progress" */
text?: string;
/** CSS class name. Default "progressbar" */
className?: string;
}
export class Progressbar {
private _container: HTMLElement;
private _text: HTMLElement;
private _num: HTMLElement;
private _progress: SVGCircleElement;
private _circumference: number;
/**
* @param {object} options
* @param {number} options.radius=70 Gauge radius
* @param {string} options.text="Progress" Text to display
* @param {string} className="progressbar"
*/
constructor({
radius = 70,
text = 'Progress',
className = 'progressbar'
}: ProgressbarOptions = {}) {
this._container = this._renderTemplate({ text, radius, name: className });
this._text = this._container.querySelector(`.${className}--text`)!;
this._num = this._container.querySelector(`.${className}--num`)!;
this._progress = this._container.querySelector(`.${className}--progress`)!;
this._circumference = radius * 2 * Math.PI;
}
/**
* Show the progressbar above all other UI
*/
show() {
document.body.appendChild(this._container);
return this;
}
/**
* Remove the progressbar from the DOM
*/
hide() {
document.body.removeChild(this._container);
return this;
}
/**
* @param percent Number between 0 and 100
*/
setValue(percent: number) {
if (percent >= 0 && percent <= 100) {
const c = this._circumference;
const value = c - (c * percent) / 100;
this._progress.style.strokeDashoffset = value.toString();
this._num.innerHTML = `<h2>${percent.toFixed(2)}<span>%</span></h2>`;
}
return this;
}
/**
* @param text
* @returns
*/
setText(text: string) {
this._text.innerText = text;
return this;
}
private _renderTemplate({
text,
radius,
name
}: {
text: string;
radius: number;
name: string;
}) {
const container = document.createElement('div');
container.className = name;
container.innerHTML = `
<div class="${name}--card">
<div class="${name}--box">
<div class="${name}--percent">
<svg class="${name}--indicator">
<circle
class="${name}--circle"
cx="${radius}"
cy="${radius}"
r="${radius}" />
<circle
class="${name}--circle ${name}--progress"
cx="${radius}"
cy="${radius}"
r="${radius}" />
</svg>
<div class="${name}--num">
<h2>0<span>%</span></h2>
</div>
</div>
<h2 class="${name}--text">${text}</h2>
</div>
</div>
`;
return container;
}
}