1const SettingFilterPane = { 2 filterList: {}, 3 4 show: function() { 5 const self = this; 6 $('#setting-pane > .settings').removeClass('visualize'); 7 $('#setting-pane > .settings').removeClass('group'); 8 $('#setting-pane > .settings').removeClass('filter'); 9 $('#setting-pane > .settings').removeClass('encode'); 10 $('#setting-pane > .settings').removeClass('highlight'); 11 $('#setting-pane > .settings').removeClass('other'); 12 $('#setting-pane > .settings').addClass('filter'); 13 self.filterList = {}; 14 self.showSearchBox(); 15 self.addCountryFiltersToList(); 16 self.addEventFiltersToList(); 17 self.drawFilters(); 18 self.initClickListFilterHeaderBehaviour(); 19 self.initClickRangeFilterHeaderBehaviour(); 20 self.initHoverTitleBehaviour(); 21 self.initHoverFilterHeaderBehaviour(); 22 self.initSearchBoxBehaviour(); 23 $('#setting-pane > .settings').scrollTop(0); 24 }, 25 showSearchBox: function() { 26 $('#setting-pane > .search-box') 27 .css('display', ''); 28 }, 29 addCountryFiltersToList: function() { 30 const self = this; 31 var countryAttributeList = Database.entityList.country; 32 var filterList = self.filterList; 33 34 filterList.country = []; 35 36 for (var i = 0; i < countryAttributeList.length; i++) { 37 var attributeData = countryAttributeList[i]; 38 var attributeName = attributeData.attributeName; 39 var attributeType = attributeData.attributeType; 40 var filterObject = null; 41 var previousFilterObjectList = State.getPreviousFilterObjectList({ 42 databaseQueryType: 'entity', 43 databaseQueryName: 'country', 44 attributeName: attributeName, 45 attributeType: attributeType 46 }); 47 48 if (previousFilterObjectList.length == 0) { // not previously stored 49 filterObject = {}; 50 filterObject.databaseQueryType = 'entity'; 51 filterObject.databaseQueryName = 'country'; 52 filterObject.attributeName = attributeName; 53 filterObject.attributeType = attributeType; 54 filterObject.displayName = attributeName; 55 filterObject.isFiltered = false; 56 self.addDataToFilterObject(filterObject, attributeType, attributeData); 57 filterList.country.push(filterObject); 58 } 59 else if (previousFilterObjectList.length > 0) { 60 filterObject = previousFilterObjectList[0]; // only one match 61 filterList.country.push(filterObject); 62 } 63 } 64 }, 65 addEventFiltersToList: function() { 66 const self = this; 67 var filterList = self.filterList; 68 var visualizeState = State.visualize; 69 var selectedEvents = visualizeState.filter(function(d) { 70 return (d.type == 'pointEvent' || d.type == 'intervalEvent'); 71 }); 72 73 for (var i = 0; i < selectedEvents.length; i++) { 74 var eventData = selectedEvents[i].data; 75 var eventName = eventData.eventName; 76 var eventAttributeList = eventData.eventAttributeList; 77 78 filterList[eventName] = []; 79 80 for (var j = 0; j < eventAttributeList.length; j++) { 81 var eventAttributeData = eventAttributeList[j]; 82 var eventAttributeName = eventAttributeData.eventAttributeName; 83 var eventAttributeType = eventAttributeData.eventAttributeType; 84 var eventAttributeAttributeList = eventAttributeData.eventAttributeAttributeList; 85 var filterObject = null; 86 var previousFilterObjectList = State.getPreviousFilterObjectList({ 87 databaseQueryType: 'event', 88 databaseQueryName: eventName, 89 eventAttributeName: eventAttributeName, 90 eventAttributeType: eventAttributeType, 91 displayName: eventAttributeName // to differentiate from event attribute attribute 92 }); 93 94 // handle event attribute 95 if (previousFilterObjectList.length == 0) { // not previously stored 96 filterObject = {}; 97 filterObject.databaseQueryType = 'event'; 98 filterObject.databaseQueryName = eventName; 99 filterObject.eventAttributeName = eventAttributeName; 100 filterObject.eventAttributeType = eventAttributeType; 101 filterObject.displayName = eventAttributeName; 102 filterObject.isFiltered = false; 103 self.addDataToFilterObject(filterObject, eventAttributeType, eventAttributeData); 104 filterList[eventName].push(filterObject); 105 } 106 else if (previousFilterObjectList.length > 0) { 107 filterObject = previousFilterObjectList[0]; // only one match 108 filterList[eventName].push(filterObject); 109 } 110 111 // handle event attribute attribute 112 for (var k = 0; k < eventAttributeAttributeList.length; k++) { 113 var eventAttributeAttributeData = eventAttributeAttributeList[k]; 114 var eventAttributeAttributeName = eventAttributeAttributeData.attributeName; 115 var eventAttributeAttributeType = eventAttributeAttributeData.attributeType; 116 var eventAttributEntityKey = eventAttributeData.entityKey; 117 var shortEventAttributeName = eventAttributeName.length > 10 ? eventAttributeName.substring(0, 10) + '...' : eventAttributeName; 118 var filterObject = null;
119 var previousFilterObjectList = State.getPreviousFilterObjectList({ 120 databaseQueryType: 'event', 121 databaseQueryName: eventName, 122 eventAttributeName: eventAttributeName, 123 eventAttributeType: eventAttributeType, 124 eventAttributeAttributeName: eventAttributeAttributeName, 125 eventAttributeAttributeType: eventAttributeAttributeType 126 }); 127 128 if (previousFilterObjectList.length == 0) { // not previously stored 129 filterObject = {}; 130 filterObject.databaseQueryType = 'event'; 131 filterObject.databaseQueryName = eventName; 132 filterObject.eventAttributeName = eventAttributeName; 133 filterObject.eventAttributeType = eventAttributeType; 134 filterObject.eventAttributeAttributeName = eventAttributeAttributeName; 135 filterObject.eventAttributeAttributeType = eventAttributeAttributeType; 136 filterObject.entityKey = eventAttributEntityKey; 137 filterObject.displayName = shortEventAttributeName + ' : ' + eventAttributeAttributeName; 138 filterObject.isFiltered = false; 139 self.addDataToFilterObject(filterObject, eventAttributeAttributeType, eventAttributeAttributeData); 140 filterList[eventName].push(filterObject); 141 } 142 else if (previousFilterObjectList.length > 0) { 143 filterObject = previousFilterObjectList[0]; // only one match 144 filterList[eventName].push(filterObject); 145 } 146 } 147 } 148 } 149 }, 150 drawFilters: function() { 151 const self = this; 152 var filterList = self.filterList; 153 154 for (var filterItem in filterList) { 155 var title = 'Filter ' + filterItem; 156 157 $('#setting-pane > .settings') 158 .append('<div class="title">' + title + '</div>'); // add name span for showing tooltip 159 160 for (var i = 0; i < filterList[filterItem].length; i++) { 161 var filterObject = filterList[filterItem][i]; 162 var attributeName = filterObject.displayName; 163 var isFiltered = filterObject.isFiltered; 164 var attributeType = null; 165 var filterCapsuleHTML = null; 166 var addedFilterCapsuleEl = null; 167 168 if ('eventAttributeAttributeType' in filterObject) attributeType = filterObject.eventAttributeAttributeType; 169 else if ('eventAttributeType' in filterObject) attributeType = filterObject.eventAttributeType; 170 else if ('attributeType' in filterObject) attributeType = filterObject.attributeType; 171 filterCapsuleHTML = self.generateFilterCapsuleHTML(attributeName, attributeType, isFiltered); 172 $('#setting-pane > .settings').append(filterCapsuleHTML); 173 addedFilterCapsuleEl = $('#setting-pane > .settings > .filter-capsule').last()[0]; 174 d3.select(addedFilterCapsuleEl).datum(filterObject); 175 } 176 } 177 }, 178 initClickListFilterHeaderBehaviour: function() { 179 const self = this; 180 181 $('#setting-pane > .settings.filter > .filter-capsule.list > .header') 182 .on('click', onClickFilterPaneListFilterHeader); 183 184 function onClickFilterPaneListFilterHeader(event) { 185 var filterCapsuleEl = this.parentNode; 186 var needToExpand = !$(filterCapsuleEl).hasClass('expanded'); 187 var filterContentEl = $(filterCapsuleEl).find('.content')[0]; 188 var filterObject = d3.select(filterCapsuleEl).datum(); 189 var clickedSelectAllButton = $(event.target).hasClass('select-all-button'); 190 var clickedSelectNoneButton = $(event.target).hasClass('select-none-button'); 191 192 // expand 193 if (needToExpand && !clickedSelectAllButton && !clickedSelectNoneButton) { 194 $(filterCapsuleEl).addClass('expanded'); 195 $(filterContentEl).empty(); 196 $(this).find('.expand-button').removeClass('fa-caret-square-down'); 197 $(this).find('.expand-button').addClass('fa-caret-square-up'); 198 for (var i = 0; i < filterObject.categories.length; i++) { 199 var categoryName = filterObject.categories[i]; 200 var categoryIsChecked = filterObject.filteredCategories.indexOf(categoryName) != -1; 201 var categoryHTML = self.generateCategoryHTML(categoryName, categoryIsChecked); 202 var addedCategoryEl = null; 203 $(filterContentEl).append(categoryHTML); 204 addedCategoryEl = $(filterContentEl).find('.category').last()[0]; 205 d3.select(addedCategoryEl).datum(categoryName); 206 } 207 self.initClickListFilterCategoryBehaviour(filterContentEl); 208 self.initHoverListFilterCategoryBehaviour(filterContentEl); 209 } 210 211 // collapse 212 else if (!needToExpand && !clickedSelectAllButton && !clickedSelectNoneButton) { 213 $(filterCapsuleEl).removeClass('expanded'); 214 $(filterContentEl).empty(); 215 $(this).find('.expand-button').removeClass('fa-caret-square-up'); 216 $(this).find('.expand-button').addClass('fa-caret-square-down'); 217 } 218 219 // select all 220 else if (clickedSelectAllButton) { 221 ListFilter.clickSelectAll(filterCapsuleEl); // update states 222 RightColumn.update(); 223 } 224 225 // select none 226 else if (clickedSelectNoneButton) { 227 ListFilter.clickSelectNone(filterCapsuleEl); // update states 228 RightColumn.update(); 229 } 230 } 231 }, 232 initClickRangeFilterHeaderBehaviour: function() { 233 const self = this; 234 235 $('#setting-pane > .settings.filter > .filter-capsule.range > .header') 236 .on('click', onClickFilterPaneRangeFilter); 237 238 function onClickFilterPaneRangeFilter() { 239 var filterCapsuleEl = this.parentNode; 240 var needToExpand = !$(filterCapsuleEl).hasClass('expanded'); 241 var filterContentEl = $(filterCapsuleEl).find('.content')[0]; 242 var filterObject = d3.select(filterCapsuleEl).datum(); 243 var clickedSelectAllButton = $(event.target).hasClass('select-all-button'); 244 var clickedSelectNoneButton = $(event.target).hasClass('select-none-button'); 245 246 // expand 247 if (needToExpand && !clickedSelectAllButton && !clickedSelectNoneButton) { 248 var sliderHTML = self.generateSliderHTML(); 249 var sliderObject = new FilterSlider(filterContentEl); 250 251 var filterMin = filterObject.range[0]; 252 var filterMax = filterObject.range[1]; 253 var filterHandleMin = filterObject.filteredRange[0]; 254 var filterHandleMax = filterObject.filteredRange[1]; 255 let realMaxNumberOfDecimal = Math.max(self.countDecimals(filterMin), self.countDecimals(filterMax)); 256 let maxNumberOfDecimal = (realMaxNumberOfDecimal > 2) ? 2 : realMaxNumberOfDecimal; 257 let step = 1 / Math.pow(10, maxNumberOfDecimal); 258 259 $(filterCapsuleEl).addClass('expanded'); 260 $(filterContentEl).empty(); 261 $(this).find('.expand-button').removeClass('fa-caret-square-down'); 262 $(this).find('.expand-button').addClass('fa-caret-square-up'); 263 $(filterContentEl).append(sliderHTML); 264 265 sliderObject.init(); 266 sliderObject.initHandleValues(); 267 sliderObject.initDragBehaviour(); 268 sliderObject.updateMinMax(filterMin, filterMax); 269 sliderObject.updateValues([ filterHandleMin, filterHandleMax ]); 270 sliderObject.updateStep(step); 271 sliderObject.updateHandles(); 272 sliderObject.updateMinMaxText(); 273 } 274 275 // collapse 276 else if (!needToExpand && !clickedSelectAllButton && !clickedSelectNoneButton) { 277 $(filterCapsuleEl).removeClass('expanded'); 278 $(filterContentEl).empty(); 279 $(this).find('.expand-button').removeClass('fa-caret-square-up'); 280 $(this).find('.expand-button').addClass('fa-caret-square-down'); 281 } 282 } 283 }, 284 initHoverTitleBehaviour: function() { 285 $('#setting-pane > .settings.filter > .title') 286 .unbind('mouseover').unbind('mouseout') 287 .on('mouseover', onMouseoverTitle) 288 .on('mouseout', onMouseoutTitle); 289 290 function onMouseoverTitle() { 291 var name = $(this).text(); 292 SettingPaneAttributeTooltip.show(name, this); 293 } 294 295 function onMouseoutTitle() { 296 SettingPaneAttributeTooltip.hide(); 297 } 298 }, 299 initHoverFilterHeaderBehaviour: function() { 300 $('#setting-pane > .settings.filter > .filter-capsule > .header') 301 .unbind('mouseover').unbind('mouseout') 302 .on('mouseover', onMouseoverFilterHeader) 303 .on('mouseout', onMouseoutFilterHeader); 304 305 function onMouseoverFilterHeader(event) { 306 var data = d3.select(this.parentNode).datum(); 307 var name = null; 308 var hoveredSelectNoneButton = $(event.target).hasClass('select-none-button'); 309 var hoveredSelectAllButton = $(event.target).hasClass('select-all-button'); 310 311 if (data.databaseQueryType == 'entity') name = data.attributeName; 312 else if ('eventAttributeAttributeName' in data) name = data.eventAttributeName + ' : ' + data.eventAttributeAttributeName; 313 else name = data.eventAttributeName; 314 315 if (!hoveredSelectNoneButton && !hoveredSelectAllButton) { 316 $(this).addClass('hover'); 317 SettingPaneAttributeTooltip.show(name, this); 318 } 319 else if (hoveredSelectNoneButton) { 320 var offset = $(this).offset(); 321 var width = $(this).width(); 322 323 $(this).removeClass('hover'); 324 SettingPaneAttributeTooltip.simplyShow( 325 left = offset.left + width + 8 + 5, 326 top = offset.top + 8, 327 text = 'Click to deselect all categories below' 328 ); 329 } 330 else if (hoveredSelectAllButton) { 331 var offset = $(this).offset(); 332 var width = $(this).width(); 333 334 $(this).removeClass('hover'); 335 SettingPaneAttributeTooltip.simplyShow( 336 left = offset.left + width + 8 + 5, 337 top = offset.top + 8, 338 text = 'Click to select all categories below' 339 ); 340 } 341 } 342 343 function onMouseoutFilterHeader() { 344 $(this).removeClass('hover'); 345 SettingPaneAttributeTooltip.hide(); 346 } 347 }, 348 initSearchBoxBehaviour: function() { 349 const self = this; 350 351 $('#setting-pane > .search-box > input') 352 .unbind('input') 353 .on('input', onInputSearchQuery); 354 355 function onInputSearchQuery() { 356 var searchQueryInLowerCase = $(this).val().toLowerCase(); 357 358 if (searchQueryInLowerCase == '') { 359 SettingPane.empty(); 360 self.filterList = {}; 361 self.addCountryFiltersToList(); 362 self.addEventFiltersToList(); 363 self.drawFilters(); 364 self.initClickListFilterHeaderBehaviour(); 365 self.initClickRangeFilterHeaderBehaviour(); 366 self.initHoverTitleBehaviour(); 367 self.initHoverFilterHeaderBehaviour(); 368 } 369 370 else if (searchQueryInLowerCase != '') { 371 self.expandListFilters(searchQueryInLowerCase); 372 self.markVariables(searchQueryInLowerCase); 373 self.filterFilters(searchQueryInLowerCase); 374 self.filterTitles(); 375 } 376 } 377 }, 378 379 // initSearchBoxBehaviour 380 381 expandListFilters: function(searchQueryInLowerCase) { 382 const self = this; 383 384 $('#setting-pane > .settings.filter > .filter-capsule.list').each(function() { 385 var filterCapsuleEl = this; 386 var filterContentEl = $(filterCapsuleEl).find('.content')[0]; 387 var filterObject = d3.select(filterCapsuleEl).datum(); 388 var allCategoryList = filterObject.categories; 389 var filteredCategoryList = filterObject.filteredCategories; 390 var matchedCategoryList = []; 391 392 // get matchedCategoryList 393 for (var i = 0; i < allCategoryList.length; i++) { 394 var categoryName = allCategoryList[i]; 395 var categoryNameInLowerCase = categoryName.toLowerCase(); 396 var categoryNameMatchedQuery = categoryNameInLowerCase.indexOf(searchQueryInLowerCase) != -1; 397 if (categoryNameMatchedQuery) matchedCategoryList.push(categoryName); 398 } 399 400 // expand if matchedCategoryList is not empty 401 if (matchedCategoryList.length > 0) { 402 $(filterCapsuleEl).addClass('expanded'); 403 $(filterContentEl).empty(); 404 $(this).find('.expand-button').removeClass('fa-caret-square-down'); 405 $(this).find('.expand-button').addClass('fa-caret-square-up'); 406 } 407 408 // collapse if matchedCategoryList is empty 409 else if (matchedCategoryList.length == 0) { 410 $(filterCapsuleEl).removeClass('expanded'); 411 $(filterContentEl).empty(); 412 $(this).find('.expand-button').removeClass('fa-caret-square-up'); 413 $(this).find('.expand-button').addClass('fa-caret-square-down'); 414 } 415 416 // draw matchedCategoryList 417 for (var i = 0; i < matchedCategoryList.length; i++) { 418 var categoryName = matchedCategoryList[i]; 419 var categoryIsChecked = filteredCategoryList.indexOf(categoryName) != -1; 420 var categoryHTML = self.generateCategoryHTML(categoryName, categoryIsChecked); 421 var addedCategoryEl = null; 422 $(filterContentEl).append(categoryHTML); 423 addedCategoryEl = $(filterContentEl).find('.category').last()[0]; 424 d3.select(addedCategoryEl).datum(categoryName); 425 } 426 self.initClickListFilterCategoryBehaviour(filterContentEl); 427 self.initHoverListFilterCategoryBehaviour(filterContentEl); 428 }); 429 }, 430 markVariables: function(searchQueryInLowerCase) { 431 var markInstance = new Mark(document.querySelectorAll('#setting-pane > .settings.filter')); 432 433 markInstance.unmark({ 434 done: function(){ 435 markInstance.mark(searchQueryInLowerCase, { 436 'separateWordSearch': false, 437 'diacritics': false 438 }); 439 } 440 }); 441 }, 442 filterFilters: function(searchQueryInLowerCase) { 443 $('#setting-pane > .settings.filter > .filter-capsule').each(function() { 444 var filterCapsuleEl = this; 445 var filterObject = d3.select(filterCapsuleEl).datum(); 446 var attributeName = filterObject.displayName; 447 var attributeNameInLowerCase = attributeName.toLowerCase(); 448 var attributeNameMatchedQuery = attributeNameInLowerCase.indexOf(searchQueryInLowerCase) != -1; 449 var categoriesMatchedQuery = $(filterCapsuleEl).hasClass('expanded'); 450
451 if (attributeNameMatchedQuery || categoriesMatchedQuery) 452 $(filterCapsuleEl).css('display', ''); 453 else if (!attributeNameMatchedQuery && !categoriesMatchedQuery) 454 $(filterCapsuleEl).css('display', 'none'); 455 }); 456 }, 457 filterTitles: function() { 458 $('#setting-pane > .settings.filter > .title').each(function() { 459 var currentTitleEl = this; 460 var currentEl = $(this).next()[0]; 461 var currentTitleHasContent = false; 462 var isCurrentFilterCapsule = $(currentEl).hasClass('filter-capsule'); 463 464 while (isCurrentFilterCapsule) { 465 if ($(currentEl).is(':visible')) { 466 currentTitleHasContent = true; break; 467 } 468 currentEl = $(currentEl).next()[0]; 469 isCurrentFilterCapsule = $(currentEl).hasClass('filter-capsule'); 470 } 471 472 if (currentTitleHasContent) $(currentTitleEl).css('display', ''); 473 else if (!currentTitleHasContent) $(currentTitleEl).css('display', 'none'); 474 }); 475 }, 476 477 // helpers 478 addDataToFilterObject: function(filterObject, attributeType, data) { 479 const self = this; 480 481 if (attributeType == 'categorical') { 482 filterObject.categories = self.shallowCopyList(data.categories); 483 filterObject.filteredCategories = self.shallowCopyList(data.categories); 484 } 485 else if (attributeType == 'nominal') { 486 filterObject.range = d3.extent(data.levels); 487 filterObject.filteredRange = d3.extent(data.levels); 488 } 489 else if (attributeType == 'numerical') { 490 filterObject.range = self.shallowCopyList(data.range); 491 filterObject.filteredRange = self.shallowCopyList(data.range); 492 } 493 }, 494 shallowCopyList: function(list) { 495 var newList = []; 496 497 for (var i = 0; i < list.length; i++) 498 newList.push(list[i]); 499 500 return newList; 501 }, 502 initClickListFilterCategoryBehaviour: function(filterContentEl) { 503 $(filterContentEl).find('.category') 504 .on('click', onClickListFilterCategory); 505 506 function onClickListFilterCategory() { 507 var filterCapsuleEl = filterContentEl.parentNode; 508 ListFilter.clickCategory(filterCapsuleEl, this); // update states 509 RightColumn.update(); 510 } 511 }, 512 initHoverListFilterCategoryBehaviour: function(filterContentEl) { 513 $(filterContentEl).find('.category') 514 .unbind('mouseover').unbind('mouseout') 515 .on('mouseover', onMouseoverListFilterCategory) 516 .on('mouseout', onMouseoutListFilterCategory); 517 518 function onMouseoverListFilterCategory() { 519 var name = d3.select(this).datum(); 520 SettingPaneAttributeTooltip.show(name, this); 521 } 522 523 function onMouseoutListFilterCategory() { 524 SettingPaneAttributeTooltip.hide(); 525 } 526 }, 527 generateFilterCapsuleHTML: function(attributeName, attributeType, isFiltered) { 528 var typeClass = attributeType == 'categorical' ? 'fa-font' 529 : (attributeType == 'temporal' ? 'fa-chart-line' : 'fa-hashtag'); 530 var filterClass = attributeType == 'categorical' ? 'list' : 'range'; 531 var isFilteredClass = isFiltered ? ' filtered' : ''; 532 533 return '<div class="filter-capsule ' + filterClass + isFilteredClass + '">' + 534 '<div class="header">' + 535 '<span class="fa fa-filter filter-icon"></span>' + 536 '<span class="fa ' + typeClass + ' type"></span>' + 537 '<span class="name">' + attributeName + '</span>' + 538 '<span class="far fa-square select-none-button"></span>' + 539 '<span class="far fa-check-square select-all-button"></span>' + 540 '<span class="far fa-caret-square-down expand-button"></span>' + 541 '</div>' + 542 '<div class="content"></div>' + 543 '</div>'; 544 }, 545 generateCategoryHTML: function(categoryName, categoryIsChecked) { 546 return '<div class="category">' + 547 '<span class="round-check-box">' + 548 '<input type="checkbox"' + (categoryIsChecked ? ' checked' : '') + '/>' + 549 '<label></label>' + 550 '</span>' + 551 '<span class="name">' + categoryName + '</span>' + 552 '</div>'; 553 }, 554 generateSliderHTML: function() { 555 return '<span class="min-text">min</span>' + 556 '<input type="text"/>' + 557 '<span class="max-text">max</span>'; 558 }, 559 countDecimals: function(value) { 560 if (Math.floor(value) !== value) 561 return value.toString().split(".")[1].length || 0; 562 return 0; 563 } 564}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.