% WEBWEB is a tool for visualizing complex networks and their metadata. % It creates intuitive and lightweight visualizations in your web browser. % See https://webwebpage.github.io for full documentation % ------------------------------------------------------------------------ % Contributors: % Dan Larremore http://danlarremore.com % K. Hunter Wapman http://hneutr.github.io % Michael Iuzzolino https://michael-iuzzolino.github.io/ % ------------------------------------------------------------------------ % % webweb for MATLAB accepts a few input types: % % webweb(adjacency_matrix) % adjacency_matrix must be at least 4x4; may be directed or undirected. % Note: any 3x3 or 2x2 matrix will be parsed as an edge_list instead. % % webweb(edge_list) % edge_list representing M edges must be Mx3 [from, to, weight] or % Mx2 [from,to]. Passing 2xM or 3xM is also ok. % % webweb(webweb_struct) % webweb_struct is a MATLAB struct enabling full webweb functionality. % all elements of its hierarchical struct is: % .networks % .networkObject % - and/or - % .layers % .networkObject % .display % .metadataObject % .[optional display parameters] % % networkObject % .edgeList [2 or 3] % .metadataObject % % metadataObject % .values % .type (optional generally; required 'binary' if metadata are 0,1) % .categories (optional) % % [optional display parameters] % w, h, c, g, l, r, linkStrength, % scaleLinkWidth, scaleLinkOpacity % colorPalette % freezeNodeMovement, showNodeNames, invertBinaryColors, invertBinarySizes % see https://webwebpage.github.io for full documentation function [] = webweb(x) if isstruct(x) %%% PARSE A webweb STRUCT %%% % Basically, if you're passing in a struct, we're going to assume that % you have set all the parameters already. :) % % However, we're still going to go through and check that you're not % sending in weights unnecessarily, and that all adjacency matrices % have been changed to edge lists (which is what the js expects). % % We will also check all edgelists to make sure that they are % zero-indexed instead of one-indexed. % % We're also going to look for layers and check THOSE the same as we % checked single layers above. % Get the names of the networks. We'll iterate through each. mynets = fieldnames(x.networks); for i = 1:length(mynets) % Does this network have an .edgeList or .layers? if isfield(x.networks.(mynets{i}),'edgeList') % edgeList! x.networks.(mynets{i}).edgeList = ... clean_edgeList(x.networks.(mynets{i}).edgeList); if isfield(x.networks.(mynets{i}),'metadata') myMetadata = fieldnames(x.networks.(mynets{i}).metadata); for k = 1:length(myMetadata) x.networks.(mynets{i}).metadata.(myMetadata{k}).values = ... force_brackets(x.networks.(mynets{i}).metadata.(myMetadata{k}).values); end end elseif isfield(x.networks.(mynets{i}),'layers') % layers! for j=1:length(x.networks.(mynets{i}).layers) x.networks.(mynets{i}).layers{j}.edgeList = ... clean_edgeList(x.networks.(mynets{i}).layers{j}.edgeList); if isfield(x.networks.(mynets{i}).layers{j},'metadata') myMetadata = fieldnames(x.networks.(mynets{i}).layers{j}.metadata); for k = 1:length(myMetadata) x.networks.(mynets{i}).layers{j}.metadata.(myMetadata{k}).values = ... force_brackets(x.networks.(mynets{i}).layers{j}.metadata.(myMetadata{k}).values); end end end end end webwebWrite(x) else %%% PARSE A MATRIX OR TENSOR %%% % Is it a matrix or a tensor? if size(x, 3)==1 % It's a matrix wwstruct.networks.network.edgeList = to_edgeList(x); else % It's a tensor wwstruct.networks.network.layers = {}; for m=1:size(x,3) netObj.edgeList = to_edgeList(x); wwstruct.networks.network.layers{end+1} = netObj; end end webwebWrite(wwstruct); end end %%%%%%%%%% %%%%%%%%%% function webwebWrite(wwstruct) % if you want all your webwebs written elsewhere, you can... % Default = here. webwebloc = ''; % Alternative = uncomment and customize the line below. % webwebloc = '~/Desktop/webweb/'; % Open the file. if isfield(wwstruct,'title') fid = fopen([webwebloc wwstruct.title '.html'],'w'); fname = wwstruct.title; else fid = fopen([webwebloc 'webweb.html'],'w'); fname = 'webweb'; end %%%%%%%%%% WRITE THE DATA TO HTML/JSON %%%%%%%%%% fprintf(fid,'\n'); fprintf(fid,'\n'); fprintf(fid,'\n'); if isfield(wwstruct,'title') fprintf(fid,'\twebweb %s\n',wwstruct.title); %TITLE else fprintf(fid,'\twebweb\n'); %TITLE end fprintf(fid,'\t\n'); fprintf(fid,'\t\n'); fprintf(fid,'\t\n'); fprintf(fid,'\n'); fprintf(fid,'\n'); fprintf(fid,'\t\n'); fprintf(fid,'\t\n'); fprintf(fid,'\t\n'); fprintf(fid,'\t\n',... jsonencode(wwstruct)); %JSON fprintf(fid,'\t\n'); fprintf(fid,'\n'); fprintf(fid,'\n'); fclose('all'); %%%%%%%%%% OPEN THE FILE %%%%%%%%%% comp = computer; if ~isempty(strfind(comp,'PCWIN')) dos([webwebloc fname '.html'],'-echo'); else unix(['open ' webwebloc fname '.html']); end end %%%%% %%%%% % function rcv = zero_index(rcv) % % take an edgelist, and if the minimum node id is 1, subtract to 0 % minIndex = min(min(rcv(:,1:2))); % if minIndex==1 % rcv(:,1:2) = rcv(:,1:2)-1; % return % else % return % end % end %%%%% function rcv = adj_to_edgeList(A) % we'll simply return a Mx3 edgelist if the network is weighted % and a Mx2 edgelist if the network is unweighted [r,c,v] = find(remove_undirected_redundancy(A)); if sum(v==1)==length(v) rcv = [r-1,c-1]; return else rcv = [r-1,c-1,v]; return end end %%%%% function B = remove_undirected_redundancy(A) if ~sum(sum(A~=A')) B = triu(A); else B = A; end return end %%%%% function rcv = to_edgeList(myArray) nrows = size(myArray, 1); ncols = size(myArray, 2); if ((nrows <= 3) || (ncols <= 3)) rcv = clean_edgeList(myArray); return else rcv = adj_to_edgeList(myArray); return end end %%%%% function rcv = clean_edgeList(myEdges) % if the edges are just empty, return nothing. if size(myEdges,1)==0 rcv = {[]}; return end % if the edges are sizeways (3xM instead of Mx3 for instance) flip them if size(myEdges,1) < size(myEdges,2) myEdges = myEdges'; end % if there are no edge weights, return if size(myEdges,2)==2 rcv = myEdges; return end % if there are edge weights... if size(myEdges,2)==3 % are they all ones, and can be discarded? if sum(myEdges(:,3)==1)==size(myEdges,1) % yes rcv = myEdges(:,1:2); return else % no rcv = myEdges; return end end error('Unable to parse edgeList. Possible cause: did you pass in an adjacency matrix?'); end %%%%% function md = force_brackets(metadata) if length(metadata)==1 md = {metadata}; else md = metadata; end return end