Appearance
Export selection
This example show how to export a part of your visualisation. It uses the layer API to create the UI for selecting the area to export. The export API is then used to export the selected area.
ts
import Ogma from '@linkurious/ogma';
import { handles } from './handles';
const ogma = new Ogma({
container: 'graph-container'
});
Ogma.parse
.jsonFromUrl('files/solarCity.json')
.then(graph => ogma.setGraph(graph))
.then(() => ogma.view.locateGraph())
.then(() =>
ogma.setOptions({
detect: { nodes: false, edges: false }
})
);
// building the UI
const button = document.getElementById('export') as HTMLButtonElement;
button.classList.add('snapshot');
const overlay = document.createElement('div');
const viewbox = document.createElement('div');
viewbox.classList.add('viewbox');
overlay.classList.add('overlay');
handles.forEach(({ cssClass, x, y }) => {
const line = document.createElement('div');
line.classList.add('handle');
line.classList.add(...cssClass);
let isDragging = false;
let startX = 0;
let startY = 0;
let startRect: DOMRect;
line.addEventListener('mousedown', evt => {
startRect = viewbox.getBoundingClientRect();
startX = evt.clientX;
startY = evt.clientY;
ogma.setOptions({
interactions: {
drag: { enabled: false },
pan: { enabled: false }
}
});
isDragging = true;
});
document.addEventListener('mouseup', () => {
if (!isDragging) return;
ogma.setOptions({
interactions: {
drag: { enabled: true },
pan: { enabled: true }
}
});
isDragging = false;
});
overlay.addEventListener('mousemove', evt => {
if (!isDragging) return;
const ogmaRect = overlay.getBoundingClientRect();
const rect = viewbox.getBoundingClientRect();
let newWidth = rect.width;
let newHeight = rect.height;
if (x !== 0) {
const delta = evt.clientX - startX;
newWidth = startRect.width + x * delta;
}
if (y !== 0) {
const delta = evt.clientY - startY;
newHeight = startRect.height + y * delta;
}
viewbox.style.width = `${newWidth}px`;
viewbox.style.height = `${newHeight}px`;
viewbox.style.left = `${(ogmaRect.width - newWidth) / 2}px`;
viewbox.style.top = `${(ogmaRect.height - newHeight) / 2}px`;
evt.stopPropagation();
evt.preventDefault();
});
viewbox.appendChild(line);
return line;
});
const exportCanvas = document.createElement('canvas');
ogma.layers.addLayer(exportCanvas);
exportCanvas.classList.add('ogmaCanvas');
overlay.appendChild(viewbox);
overlay.appendChild(button);
ogma.layers.addLayer(overlay);
ogma.view.set({ zoom: 12.65, x: 10, y: 0 });
// Export the cropped image.
button.addEventListener('click', () => {
const rect = viewbox.getBoundingClientRect();
ogma.export.png({
width: rect.width,
height: rect.height,
margin: 0,
clip: true
});
});
html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="graph-container"></div>
<div class="controls">
<button id="export">
<svg
width="24"
height="24"
stroke-width="1.5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
color="#000"
>
<path
d="M2 19V9a2 2 0 0 1 2-2h.5a2 2 0 0 0 1.6-.8l2.22-2.96A.6.6 0 0 1 8.8 3h6.4a.6.6 0 0 1 .48.24L17.9 6.2a2 2 0 0 0 1.6.8h.5a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2Z"
stroke="#000"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 17a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z"
stroke="#000"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button>
</div>
<script type="module" src="index.ts"></script>
</body>
</html>
css
#graph-container {
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
margin: 0;
overflow: hidden;
}
.overlay {
position: absolute;
width: 100%;
height: 100%;
background-color: #999;
opacity: 0.5;
z-index: 10;
}
.viewbox {
position: relative;
background: white;
opacity: 1;
margin: 0;
top: calc(50% - 100px);
left: calc(50% - 100px);
width: 200px;
height: 200px;
}
.handle {
position: absolute;
background-color: #38e;
}
.top-handle {
cursor: ns-resize;
height: 2px;
left: 0;
width: 100%;
top: 0;
}
.bottom-handle {
cursor: ns-resize;
height: 2px;
width: 100%;
left: 0;
bottom: 0;
}
.left-handle {
cursor: ew-resize;
height: 100%;
top: 0;
left: 0;
width: 2px;
}
.right-handle {
cursor: ew-resize;
height: 100%;
right: -2px;
top: 0;
width: 2px;
}
.point-handle {
width: 10px;
height: 10px;
}
.point-handle-top {
left: 50%;
top: -4px;
}
.point-handle-bottom {
left: 50%;
bottom: -4px;
}
.point-handle-left {
top: 50%;
left: -4px;
}
.point-handle-right {
top: 50%;
right: -4px;
}
.point-handle-top-left,
.point-handle-bottom-right {
cursor: nwse-resize;
}
.point-handle-top-right,
.point-handle-bottom-left {
cursor: nesw-resize;
}
.point-handle-bottom-left,
.point-handle-bottom-right {
top: calc(100% - 10px);
}
.snapshot {
position: absolute;
top: 2em;
right: 2em;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
padding: 1em;
border-radius: 5px;
border: 1px solid #555;
}
ts
export const handles = [
{
cssClass: ['top-handle'],
x: 0,
y: -1
},
{
cssClass: ['bottom-handle'],
x: 0,
y: 1
},
{
cssClass: ['top-handle', 'point-handle', 'point-handle-top'],
x: 0,
y: -1
},
{
cssClass: ['bottom-handle', 'point-handle', 'point-handle-bottom'],
x: 0,
y: 1
},
{
cssClass: ['left-handle'],
x: -1,
y: 0
},
{
cssClass: ['right-handle'],
x: 1,
y: 0
},
{
cssClass: ['right-handle', 'point-handle', 'point-handle-right'],
x: 1,
y: 0
},
{
cssClass: ['left-handle', 'point-handle', 'point-handle-left'],
x: 1,
y: 0
},
{
cssClass: ['right-handle', 'point-handle', 'point-handle-top-right'],
x: 1,
y: -1
},
{
cssClass: ['left-handle', 'point-handle', 'point-handle-top-left'],
x: -1,
y: -1
},
{
cssClass: ['right-handle', 'point-handle', 'point-handle-bottom-right'],
x: 1,
y: 1
},
{
cssClass: ['left-handle', 'point-handle', 'point-handle-bottom-left'],
x: -1,
y: 1
}
];