' + title + ' on ' + 1038 | 'Linkurious.
'; 1039 | 1040 | if (description.length) { 1041 | iframe += '' + description + '
'; 1042 | } 1043 | return iframe; 1044 | } 1045 | 1046 | /** 1047 | * Format the node or edge data for Mustache. 1048 | * @param item 1049 | * @param type 'node' or 'edge' 1050 | * @return The modified item. 1051 | */ 1052 | function mustachPrepare(item, type) { 1053 | // see http://stackoverflow.com/a/9058774/ 1054 | item.mustacheProperties = []; 1055 | item.mustacheCategories = []; 1056 | 1057 | if(item.data) { 1058 | for (var prop in item.data.properties){ 1059 | if (item.data.properties.hasOwnProperty(prop)){ 1060 | item.mustacheProperties.push({ 1061 | 'key' : prop, 1062 | 'value' : item.data.properties[prop] 1063 | }); 1064 | } 1065 | } 1066 | if (type === 'node') { 1067 | for (var prop in item.data.categories){ 1068 | if (item.data.categories.hasOwnProperty(prop)){ 1069 | item.mustacheCategories.push(item.data.categories[prop]); 1070 | } 1071 | } 1072 | } 1073 | else { 1074 | item.mustacheCategories = [item.data.type]; 1075 | } 1076 | } 1077 | 1078 | return item; 1079 | } 1080 | 1081 | /** 1082 | * Search on sigma nodes or sigma edges. 1083 | * @param {string} q The query string. 1084 | * @param {array} items Nodes or edges. 1085 | * @returns {array} 1086 | */ 1087 | function graphSearch(q, items) { 1088 | q = q.trim().toLowerCase(); 1089 | 1090 | return items.map(function(item) { 1091 | return { 1092 | item: item, 1093 | score: fuzzysearch(q, (item.label || '').trim().toLowerCase(), 3) 1094 | }; 1095 | }).filter(function(res) { 1096 | return res.score != Number.POSITIVE_INFINITY; 1097 | }).sort(function(a, b) { 1098 | return a.score - b.score; 1099 | }); 1100 | } 1101 | 1102 | 1103 | /** 1104 | * Fuzzy searching allows for flexibly matching a string with partial input, 1105 | * useful for filtering data very quickly based on lightweight user input. 1106 | * Returns score lower than Infinity if needle matches haystack using a fuzzy-searching algorithm. 1107 | * Note that this program doesn't implement levenshtein distance, but rather 1108 | * a simplified version where there's no approximation. The method will return a score lower 1109 | * than Infinity only if each character in the needle can be found in the haystack and 1110 | * occurs after the preceding matches. To sum up: 1111 | * - score = +Inf: not a match. 1112 | * - score > 0: number of characters read before finding the last character of needle 1113 | * (doesn't count the characters of needle). 1114 | * - score = 0: the needle is a substring of haystack. 1115 | * - score < 0: perfect match. 1116 | * 1117 | * @see https://github.com/bevacqua/fuzzysearch (modified) 1118 | * @param {string} needle 1119 | * @param {string} haystack 1120 | * @param {number} cutoff The maximum score before Infinity. 1121 | * @returns {number} score 1122 | */ 1123 | function fuzzysearch (needle, haystack, cutoff) { 1124 | var 1125 | hlen = haystack.length, 1126 | nlen = needle.length, 1127 | hops = - needle.length + 1, 1128 | hops_limit = cutoff || 10, 1129 | incr = 0, 1130 | nch, i, j; 1131 | 1132 | if (nlen > hlen) { 1133 | return Number.POSITIVE_INFINITY; 1134 | } 1135 | if (nlen === hlen) { 1136 | return (needle === haystack) ? -1 : Number.POSITIVE_INFINITY; 1137 | } 1138 | if (nlen == 1) { 1139 | hops = haystack.indexOf(needle); 1140 | return (hops < 0) ? Number.POSITIVE_INFINITY : hops; 1141 | } 1142 | outer: for (i = 0, j = 0; i < nlen; i++) { 1143 | nch = needle.charCodeAt(i); 1144 | while (j < hlen) { 1145 | hops += incr; 1146 | if (incr && hops > hops_limit) { 1147 | return Number.POSITIVE_INFINITY; 1148 | } 1149 | if (haystack.charCodeAt(j++) === nch) { 1150 | incr = 1; 1151 | continue outer; 1152 | } 1153 | } 1154 | return Number.POSITIVE_INFINITY; 1155 | } 1156 | 1157 | return hops; 1158 | } 1159 | 1160 | /** 1161 | * Select a node from the list of search results. It should be called by an event on the item. 1162 | * @returns {boolean} 1163 | */ 1164 | function nodeResultHandler() { 1165 | var id = this.dataset.id; 1166 | activeStatePlugin.dropNodes(); 1167 | activeStatePlugin.dropEdges(); 1168 | activeStatePlugin.addNodes(id); 1169 | LK.closeTooltip(); 1170 | LK.clearSearch(); 1171 | LK.locateNode(id, { onComplete: function() { 1172 | LK.openNodeTooltip(sigmaInstance.graph.nodes(id)); 1173 | }}); 1174 | return false; 1175 | } 1176 | 1177 | /** 1178 | * Select an edge from the list of search results. It should be called by an event on the item. 1179 | * @returns {boolean} 1180 | */ 1181 | function edgeResultHandler() { 1182 | var id = this.dataset.id; 1183 | activeStatePlugin.dropNodes(); 1184 | activeStatePlugin.dropEdges(); 1185 | activeStatePlugin.addEdges(id); 1186 | LK.closeTooltip(); 1187 | LK.clearSearch(); 1188 | LK.locateEdge(id, { onComplete: function() { 1189 | LK.openEdgeTooltip(sigmaInstance.graph.edges(id)); 1190 | }}); 1191 | return false; 1192 | } 1193 | 1194 | /** 1195 | * Parse the query string and returns an object of parameter<>value. 1196 | * @see http://jsperf.com/querystring-with-javascript 1197 | * @param q 1198 | * @returns {{}} 1199 | */ 1200 | function getQueryString (q) { 1201 | return (function(a) { 1202 | if (a == "") return {}; 1203 | var b = {}; 1204 | for (var i = 0; i < a.length; ++i) { 1205 | var p = a[i].split('='); 1206 | if (p.length != 2) continue; 1207 | b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); 1208 | } 1209 | return b; 1210 | })(q.split("&")); 1211 | } 1212 | 1213 | /** 1214 | * Handle the onDrop event to either load a graph of a design. 1215 | * @param e 1216 | */ 1217 | function onDropHandler(e) { 1218 | e.preventDefault(); 1219 | e.stopPropagation(); // stop browser from redirecting 1220 | 1221 | for (var i = 0; i < e.dataTransfer.files.length; i++) { 1222 | var file = e.dataTransfer.files[i]; 1223 | var reader = new FileReader(file); 1224 | 1225 | if (file.name.slice(-4) === 'gexf') { 1226 | reader.onload = function(e) { 1227 | designPlugin.reset(); 1228 | var parser = new DOMParser(); 1229 | var domElement = parser.parseFromString(e.target.result, "text/xml"); 1230 | sigma.parsers.gexf( 1231 | domElement, 1232 | sigmaInstance, 1233 | function() { 1234 | // Fix data: 1235 | sigmaInstance.graph.nodes().forEach(function(node) { 1236 | node.size = node.size || 1; 1237 | node.color = node.color || '#ddd'; 1238 | }); 1239 | sigmaInstance.refresh(); 1240 | locatePlugin.center(); 1241 | } 1242 | ); 1243 | LK.dom.hide('#drag-overlay'); 1244 | }; 1245 | reader.readAsText(file); 1246 | } 1247 | else if (file.name.slice(-4) === 'json') { 1248 | reader.onload = function(e) { 1249 | var data = JSON.parse(e.target.result); 1250 | if (data.nodes) { 1251 | // Load graph 1252 | sigmaInstance.graph.clear(); 1253 | designPlugin.reset(); 1254 | sigmaInstance.graph.read(data); 1255 | sigmaInstance.refresh(); 1256 | locatePlugin.center(); 1257 | } 1258 | else { 1259 | LK.setDesign(data.styles, data.palette); 1260 | } 1261 | LK.dom.hide('#drag-overlay'); 1262 | }; 1263 | reader.readAsText(file); 1264 | } 1265 | else { 1266 | console.error('Wrong file type.'); 1267 | } 1268 | } 1269 | } 1270 | 1271 | }).call(this); 1272 | --------------------------------------------------------------------------------