Skip to main content

Linkchart Styling

The Linkchart is a standard component in Octostar. However, linkchart styling can be defined using JavaScript as part of an app, to allow maximum flexibility and customizability.

If a linkchart styling app is in a workspace that is closed, that app (and the corresponding styling) will not be visible.

Stylers​

Stylers are custom functions to apply styling on linkcharts.

The general contract of stylers is best understood by the typescript interfaces:

export type Styler = {
getG6NodeStyle?: (e: Entity, curr: any) => Promise<any>;
getG6EdgeStyle?: (e: EdgeSpec, curr: any) => Promise<any>;
};

export interface StylerContext {
graph: GraphSpec;
selected: GraphSpec;
OntologyAPI: Ontology;
DesktopAPI: Desktop;
ee: EventEmitterType;
eventTopicPrefix: string;
onTaskComplete?: (x: Styler) => void;
onTaskCanceled?: () => void;
taskID?: string;
}
export type StylerProducer = (context: StylerContext) => Promise<Styler>;

The functions of stylers correspond to the StylerProducer, that is they accept any properties of the StylerContext interface, and return a Styler . Styler function getG6NodeStyle should return style object which consists only of updates to style. Eg.:

styler which changes the fill of a node to red :

        const getG6NodeStyle = async (entity, currStyle) => {
// you can use currStyle for calculations etc. but do not merge your updates with
// your changes as it will be handled automatically later
return {
keyshape: {
fill: 'rgb(255,0,0),
},
};
};

Merge to existing style will be performed automatically. If this merge will be done inside styler's function, all changes applied by another stylers will be lost.

Full Example​

stylers:

"Color by Type":
description: 'color linkchart nodes by their entity_type attribute'
type: javascript
code: |
function producer(){


const stringToHash = (string) => {
let hash = 0;
for (let i = 0; i < string.length; i++) {
const character = string.charCodeAt(i);
hash = ((hash << 5) - hash) + character;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
};

const hashToColor = (hash) => {
const hue = Math.abs(hash) % 360; // Get a hue value between 0 and 359
const darkColor = `hsl(${hue}, 80%, 40%)`;
const lightColor = `hsl(${hue}, 70%, 75%)`;
return [darkColor, lightColor];
};

const getG6NodeStyle = async (entity, currStyle) => {
// you can use currStyle for calculations etc. but do not merge your updates with
// your changes as it will be handled automatically later

// Retrieve the entity_type
const entityType = entity.entity_type;

// Generate a color based on the entity_type
const [color1,color2] = hashToColor(stringToHash(entityType));
// return changes (updates) to style
return {
keyshape: {
fill: color2,
stroke: color1,
lineWidth: 1

},
icon: {
fill: color1
},
halo: {
fill: color2
}

};
};

// Return the Styler object
return {
getG6NodeStyle
};

}

"Size by links":
description: 'apply sizing to the linkchart nodes'
type: javascript
code: |
function producer(graph){
const {edges} = graph;
// Define the edges array


// Function to count edges per node
const countEdges = (edges) => {
const edgeCount = {};
edges.forEach(edge => {
edgeCount[edge.from] = (edgeCount[edge.from] || 0) + 1;
edgeCount[edge.to] = (edgeCount[edge.to] || 0) + 1;
});
return edgeCount;
};

// Function to generate first n Fibonacci numbers
const generateFibonacci = (n) => {
const fib = [1, 1];
while (fib.length < n) {
fib.push(fib[fib.length - 1] + fib[fib.length - 2]);
}
return fib;
};

// Function to calculate relative size using a logarithmic scale
const calculateSize = (count, min, max, fib) => {
if (count === min) return fib[0];
const logMax = Math.log(max - min + 1);
const logCount = Math.log(count - min + 1);
const index = Math.round((logCount / logMax) * (fib.length - 1));
return fib[index];
};

// Calculate edges for each node
const edgeCounts = countEdges(edges);
const counts = Object.values(edgeCounts);
const minCount = Math.min(...counts);
const maxCount = Math.max(...counts);

// Generate the Fibonacci sequence
const fib = generateFibonacci(5); // n=5

// Function to get size for a node
const getNodeSize = (nodeId) => {
const count = edgeCounts[nodeId] || 0;
return count?calculateSize(count, minCount, maxCount, fib):0;
};


const getG6NodeStyle = async (entity, currStyle) => {
// you can use currStyle for calculations etc. but do not merge your updates with
// your changes as it will be handled automatically later
const size = getNodeSize(entity.entity_id) * 2;
// return changes (updates) to style
return {
icon: {
fontSize: 13 + size,
},
keyshape: {
r: 13 + size,
},
halo: {
r: 28 + size,
}

};
};

// Return the Styler object
return {
getG6NodeStyle
};
}

"Outline by Workspace":
description: it's brilliant
type: javascript
code: |
function producer(OntologyAPI){
const stringToHash = (string) => {
let hash = 0;
for (let i = 0; i < string.length; i += 1) {
const character = string.charCodeAt(i);
hash = (hash << 5) - hash + character;
hash &= hash; // Convert to 32bit integer
}
return hash;
};

const hashToColor = (hash) => {
const hue = Math.abs(hash) % 360; // Get a hue value between 0 and 359
const lightColor = `hsl(${hue}, 70%, 75%)`;
return lightColor;
};

const wsPromises = {};
const getWorkspaceColor = async (entity) => {
const os_workspace = entity.os_workspace;
let promise = wsPromises[os_workspace || ''];
if (!promise) {
if (!os_workspace) {
return `hsl(0, 0%, 0%)`; // big data black
}
promise = OntologyAPI.getEntity({
entity_type: 'os_workspace',
entity_id: os_workspace,
entity_label: 'ws',
}).then(
ws =>
ws.os_item_content.color || hashToColor(stringToHash(os_workspace)),
);
wsPromises[os_workspace] = promise;
}
return promise;
};

const getG6NodeStyle = async (entity) => {
const color = await getWorkspaceColor(entity);
const style = {
keyshape: {
stroke: color,
lineWidth: 2
},
halo: {
fill: color,
},
};
return style;
};

return {
getG6NodeStyle,
};
}