Voor het onderzoeksinstituut Drift hebben we gewerkt aan de ontwikkeling van de website voor Water4Change (W4C), een project dat inzicht biedt in stedelijk waterbeheer in Indiase steden.
Een belangrijk doel van de website was het visueel representeren van complexe netwerken van processen en hun onderlinge relaties. Maar hoe ontwerp je een systeem waarin gebruikers willekeurige processen en verbindingen kunnen toevoegen zonder dat ze hoeven na te denken over de layout? Dat is precies waar wij mee aan de slag gingen. Het resultaat hiervan is het pathways component.
Waarom we een slim algoritme nodig hadden
Het pathways component bestaat uit kaarten die met lijnen aan elkaar verbonden zijn. Elk proces is een schakel in een groter geheel, en de lijnen representeren de relaties tussen die schakels. Het component moest voldoen aan een aantal eisen die een hoop interessante uitdagingen met zich mee brengen. Ten eerste moest het systeem flexibel genoeg zijn om willekeurige data vanuit een CMS (content management system) te verwerken en om te zetten in een dynamische visualisatie. Daarnaast moesten de processen zodanig gepositioneerd worden dat lijnen elkaar niet overlappen en de visualisatie overzichtelijk blijft, ongeacht de hoeveelheid data. Maar hoe organiseer je die gegevens automatisch, zonder dat ze elkaar overlappen of visueel chaotisch worden? Met deze vraag in gedachten begonnen we aan de ontwikkeling van een algoritme dat deze uitdagingen kon oplossen.
Hoe we pathways bouwden
Het hart van dit component is een algoritme dat automatisch de beste positionering en layout berekent. Dit algoritme analyseert bijvoorbeeld hoe ver processen uit elkaar moeten staan, waar lijnen moeten worden getrokken en hoe overlappingen van verbindingen kunnen worden vermeden. De berekeningen garanderen een duidelijke en gebruiksvriendelijke visualisatie, ongeacht de complexiteit van de data. Hieronder zie je een voorbeeld van een functie die de verticale afstanden tussen processen berekent:
export const getVerticalDistances = (refs: RefItem[],): { id: string; distances: Record<string, number> }[] => {const verticalDistanceArray = refs.map(ref => {const distances: Record<string, number> = {};ref.relationIds.forEach(relationId => {const relatedRef = refs.find(item => item.id === relationId);if (relatedRef) {const distance = relatedRef.rect.bottom - ref.rect.bottom;distances[relationId] = distance;}});return { id: ref.id, distances };});return verticalDistanceArray;};
Om de posities van de lijnen te bepalen, hebben we een reeks slimme technieken en functies ingebouwd. Een voorbeeld hiervan is hoe het algoritme offsets berekent om de lijnen zo te positioneren dat ze elkaar niet kruizen. Het algoritme werkt met variabele afstandsberekeningen en telt het aantal lijnen van en naar iedere kaart om deze evenredig over de bepaalde afstand te verdelen. Door dynamische aanpassingen blijft de presentatie intuïtief en schaalbaar.
export const getLeftOffsets = (process: LinkedProcess, pathwayCardHeight: number): number[] => {const offsetArray = process.relationIds.map((_, i) =>-pathwayCardHeight / 2 +(pathwayCardHeight / (process.relationIds.length + 1)) * (i + 1));return offsetArray;};
Uiteindelijk worden deze berekeningen als variabelen meegegeven aan een React component dat hier vervolgens een SVG van maakt door de lijnen te genereren:
<svg xmlns="http://www.w3.org/2000/svg" height={verticalHeight}><linex1={0}x2={24 - horizontalOffset + strokeWidth / 2}y1={yStart + leftOffset}y2={yStart + leftOffset}/><linex1={24 - horizontalOffset}x2={24 - horizontalOffset}y1={yStart + leftOffset}y2={yStart + difference + rightOffset + strokeWidth / 2}/><linex1={24 - horizontalOffset - strokeWidth / 2}x2={48}y2={yStart + difference + rightOffset}y1={yStart + difference + rightOffset}/></svg>
Het resultaat: een intuïtief en dynamisch component
De afbeelding hieronder toont een voorbeeld van de output die met Pathways wordt gegenereerd. Hier zie je hoe processen (weergegeven als gekleurde kaarten) logisch worden gepositioneerd, terwijl lijnen de relaties tussen deze processen duidelijk aangeven.
Elk proces is interactief, en gebruikers kunnen erop klikken voor meer informatie of om gerelateerde processen te ontdekken. De dynamische lay-out zorgt ervoor dat alle data overzichtelijk wordt weergegeven, zelfs bij een groot aantal processen en relaties.