Appearance
Timeline
This is the example of custom timeline controls used in combination with Ogma filter transformation.
ts
import Ogma from '@linkurious/ogma';
import { Controller as TimelinePlugin } from '@linkurious/ogma-timeline-plugin';
const ogma = new Ogma<{
type: string;
label: string;
start: number;
}>({
container: 'graph-container'
});
const range = Date.now() - new Date('August 19, 1975 23:15:30').getTime();
ogma.setOptions({
backgroundColor: '#FDFAF7'
});
// Create a <style> tag to hold the dynamically generated CSS
const styleTag = document.createElement('style');
document.head.appendChild(styleTag);
const typeToColor = {
'Climate-change': {
textColor: '#027C47',
background: '#E7F3BA',
nodeColor: '#00B064'
},
Geography: {
textColor: '#067E9F',
background: '#CFF2FC',
nodeColor: '#067E9F'
},
Physics: {
textColor: '#46037D',
background: '#F6EDFE',
nodeColor: '#9961C7'
},
Geology: {
textColor: '#A64700',
background: '#FFF7D6',
nodeColor: '#F4AF44'
},
Ice: {
textColor: '#037C7D',
background: '#E5FFFF',
nodeColor: '#00CCCC'
},
Economy: {
textColor: '#990000',
background: '#FFE8D7',
nodeColor: '#990000'
},
Politics: {
textColor: '#9B0032',
background: '#FFECEA',
nodeColor: '#EB5153'
},
Biology: {
textColor: '#404A1F',
background: '#F6F7DF',
nodeColor: '#A0C32C'
}
};
ogma.styles.setTheme({
selectedNodeAttributes: {
radius: 10,
outerStroke: {
width: 4,
color: '#FF720D'
}
},
hoveredNodeAttributes: {
radius: 10,
outerStroke: {
width: 4,
color: '#FF720D'
},
duration: 500,
easing: 'quadraticInOut'
}
});
const highlightedLight = ogma.styles.createClass({
name: 'highlightedLight',
edgeAttributes: {
color: '#F9C198',
width: 1.2
},
nodeAttributes: {
outerStroke: {
width: 4,
color: '#F9C198'
}
}
});
const highlighted = ogma.styles.createClass({
name: 'highlighted',
edgeAttributes: {
color: '#FF720D',
width: 1.2
},
nodeAttributes: {
outerStroke: {
width: 4,
color: '#FF720D'
}
}
});
ogma.events.on('mouseover', evt => {
if (!evt.target || !evt.target.isNode) return;
const node = evt.target;
const neighbors = node.getAdjacentElements();
neighbors.nodes.addClass('highlightedLight', {
duration: 500,
easing: 'quadraticInOut'
});
neighbors.edges
.filter(e => !e.isSelected())
.addClass('highlightedLight', {
duration: 500,
easing: 'quadraticInOut'
});
});
ogma.events.on('mouseout', () => {
highlightedLight.clearNodes();
highlightedLight.clearEdges();
});
ogma.events.on(['nodesSelected', 'nodesUnselected'], evt => {
const nodes = ogma.getSelectedNodes();
const neighbors = nodes.getAdjacentElements();
highlighted.clearNodes();
highlighted.clearEdges();
neighbors.nodes.addClass('highlighted', {
duration: 500,
easing: 'quadraticInOut'
});
neighbors.edges
.filter(e => !e.isSelected())
.addClass('highlighted', {
duration: 500,
easing: 'quadraticInOut'
});
});
ogma.styles.addNodeRule({
color: node => typeToColor[node.getData('type')].nodeColor,
text: {
content: node => ` ${node.getData('label')} `,
// padding: 10,
size: 14
},
radius: 8,
opacity: 1
});
ogma.styles.setHoveredNodeAttributes({
text: {
color: '#fff',
backgroundColor: '#272727'
}
});
ogma.styles.setSelectedNodeAttributes({
text: {
color: '#fff',
backgroundColor: '#272727'
}
});
ogma.styles.addEdgeRule({
width: 0.5
});
ogma.styles.setHoveredEdgeAttributes({
width: 2,
color: '#FF720D'
});
ogma.styles.setSelectedEdgeAttributes({
width: 2,
color: '#FF720D'
});
Ogma.parse
.gexfFromUrl('files/arctic-small.gexf')
.then(graph => {
graph.nodes.forEach((node, i) => {
// Add a random date to the node
const start = Date.now() - Math.random() * range;
node.data.start = start;
});
Object.entries(typeToColor).forEach(([type, { textColor, background }]) => {
// Inject the CSS rule for the backgroundoup
styleTag.sheet?.insertRule(
`.${type} { background-color: ${background}; }`,
styleTag.sheet.cssRules.length
);
styleTag.sheet?.insertRule(
`.${type}.vis-item.timeline-item.vis-selected { background-color: ${textColor}; color: white;}`,
styleTag.sheet.cssRules.length
);
styleTag.sheet?.insertRule(
`.${type}.vis-item.timeline-item.vis-line.vis-selected,
.${type}.vis-item.timeline-item.vis-dot.vis-selected
{ border-color: ${textColor};}
`,
styleTag.sheet.cssRules.length
);
styleTag.sheet?.insertRule(
`.${type} { border-color: ${textColor}; color: ${textColor}; }`,
styleTag.sheet.cssRules.length
);
});
return ogma.setGraph(
{
nodes: graph.nodes.slice(50, 100),
edges: graph.edges
},
{ ignoreInvalid: true }
);
})
.then(() => ogma.layouts.force({ locate: true, gpu: true }))
.then(() => {
const container = document.getElementById('timeline');
const timelinePlugin = new TimelinePlugin(ogma, container, {
minTime: new Date('January 1, 1975 00:00:00').getTime(),
maxTime: new Date('January 1, 2022 00:00:00').getTime(),
timeBars: [
new Date('January 1, 1975 00:00:00'),
new Date('January 1, 2025 00:00:00')
],
barchart: {
graph2dOptions: {
drawPoints: false,
barChart: { width: 30, align: 'center' }
}
},
timeline: {
timelineOptions: {},
nodeItemGenerator: node => ({
content: `${node.getData('label')}`
}),
getNodeClass: node => {
return node.getData('type');
}
}
});
window.timelinePlugin = timelinePlugin;
/* let isSelecting = false;
timelinePlugin.on(
'select',
({ nodes, edges }: { nodes: NodeList; edges: EdgeList }) => {
isSelecting = true;
ogma.getNodes().setSelected(false);
ogma.getEdges().setSelected(false);
if (nodes) nodes.setSelected(true);
if (edges) edges.setSelected(true);
if (!nodes.size && !edges.size) return;
const bounds = nodes ? nodes.getBoundingBox() : edges.getBoundingBox();
if (bounds.width < 256) {
bounds.pad(256 - bounds.width);
}
ogma.view.moveToBounds(bounds, {
easing: 'quadraticInOut',
duration: 1000
});
isSelecting = false;
}
);
ogma.events.on(
['nodesSelected', 'edgesSelected', 'nodesUnselected', 'edgesUnselected'],
() => {
if (isSelecting) return;
timelinePlugin.setSelection({
nodes: ogma.getSelectedNodes(),
edges: ogma.getSelectedEdges()
});
const minMax = ogma
.getSelectedNodes()
.getData('start')
.concat(ogma.getSelectedEdges().getData('start'))
.reduce(
(acc, time) => {
if (time < acc.min) acc.min = time;
if (time > acc.max) acc.max = time;
return acc;
},
{ min: Infinity, max: -Infinity }
);
const year = 1000 * 60 * 60 * 24 * 365;
// add one year margin between min and max
minMax.min -= year;
minMax.max += year;
timelinePlugin.setWindow(minMax.min, minMax.max);
}
);
const filter = ogma.transformations.addNodeFilter({
criteria: node => {
return timelinePlugin.filteredNodes.has(node.getId());
}
});
// Hook it to the timeline events
timelinePlugin.on('timechange', () => {
filter.refresh();
});
timelinePlugin.showBarchart();*/
});
html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link
href="https://cdn.jsdelivr.net/npm/vis-timeline@latest/styles/vis-timeline-graph2d.min.css"
rel="stylesheet"
type="text/css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@linkurious/ogma-timeline-plugin@latest/dist/style.css"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap"
rel="stylesheet"
/>
<link type="text/css" rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="graph-container"></div>
<div id="timeline"></div>
<script src="index.ts"></script>
</body>
</html>
css
body {
display: grid;
grid-template-rows: auto 33%;
height: 100vh;
margin: 0;
padding: 0;
}
#timeline > div {
height: 100%;
}
/* vis-item vis-box timeline-item nodes 95 node Economy vis-readonly */
.vis-item.vis-box {
border-radius: 4px;
}
.vis-item {
font-weight: 700;
padding: 4px 4px;
border-width: 1px;
}
.rect.vis-bar,
rect.vis-group.nodes.node.vis-bar {
fill-opacity: 1 !important;
}
.vis-item.vis-box.timeline-item.vis-selected {
border: 0;
}
.vis-timeline {
background-color: #fdfaf7;
}
.vis-bar {
fill: #177356;
stroke: none;
}
.vis-bar.vis-selected {
fill: #00b064;
stroke: #10503c;
}
.vis-custom-time {
cursor: ew-resize;
cursor: url('files/arrow-left-right-circle-fill.svg'), auto;
}
.vis-filtered {
opacity: 0.5;
}
json
{
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@linkurious/ogma": "^5.2.3",
"vis-data": "7.1.16",
"vis-timeline": "7.7.2",
"leaflet": "1.9.4",
"@linkurious/ogma-timeline-plugin": "0.2.15"
}
}