Wired filter
Этот коммит содержится в:
		
							родитель
							
								
									c9a418f0b7
								
							
						
					
					
						Коммит
						64e741ce69
					
				| @ -1,100 +1,41 @@ | ||||
| <script> | ||||
|     import Dropdown from '$lib/components/Dropdown.svelte' | ||||
|     import { | ||||
|         platformFilter, | ||||
|     } from '../../stores/filters'; | ||||
| 
 | ||||
|     let items = [ | ||||
|     { | ||||
|         "id": "Facebook", | ||||
|         "name": "Facebook", | ||||
|         "selected": true, | ||||
|         "count": 34, | ||||
|         "liveCount": 34 | ||||
|     }, | ||||
|     { | ||||
|         "id": "Instagram", | ||||
|         "name": "Instagram", | ||||
|         "selected": true, | ||||
|         "count": 21, | ||||
|         "liveCount": 21 | ||||
|     }, | ||||
|     { | ||||
|         "id": "Twitter", | ||||
|         "name": "Twitter", | ||||
|         "selected": true, | ||||
|         "count": 34, | ||||
|         "liveCount": 34 | ||||
|     }, | ||||
|     { | ||||
|         "id": "YouTube", | ||||
|         "name": "YouTube", | ||||
|         "selected": true, | ||||
|         "count": 16, | ||||
|         "liveCount": 16 | ||||
|     }, | ||||
|     { | ||||
|         "id": "unspecified", | ||||
|         "name": "unspecified", | ||||
|         "selected": true, | ||||
|         "count": 33, | ||||
|         "liveCount": 33 | ||||
|     }, | ||||
|     { | ||||
|         "id": "Reddit", | ||||
|         "name": "Reddit", | ||||
|         "selected": true, | ||||
|         "count": 13, | ||||
|         "liveCount": 13 | ||||
|     }, | ||||
|     { | ||||
|         "id": "WhatsApp", | ||||
|         "name": "WhatsApp", | ||||
|         "selected": true, | ||||
|         "count": 2, | ||||
|         "liveCount": 2 | ||||
|     }, | ||||
|     { | ||||
|         "id": "SMS", | ||||
|         "name": "SMS", | ||||
|         "selected": true, | ||||
|         "count": 2, | ||||
|         "liveCount": 2 | ||||
|     }, | ||||
|     { | ||||
|         "id": "Telegram", | ||||
|         "name": "Telegram", | ||||
|         "selected": true, | ||||
|         "count": 1, | ||||
|         "liveCount": 1 | ||||
|     }, | ||||
|     { | ||||
|         "id": "Quora", | ||||
|         "name": "Quora", | ||||
|         "selected": true, | ||||
|         "count": 1, | ||||
|         "liveCount": 1 | ||||
|     }, | ||||
|     { | ||||
|         "id": "Forum Board", | ||||
|         "name": "Forum Board", | ||||
|         "selected": true, | ||||
|         "count": 1, | ||||
|         "liveCount": 1 | ||||
|     }, | ||||
|     { | ||||
|         "id": "Parler", | ||||
|         "name": "Parler", | ||||
|         "selected": true, | ||||
|         "count": 1, | ||||
|         "liveCount": 1 | ||||
|     }, | ||||
|     { | ||||
|         "id": "Gab", | ||||
|         "name": "Gab", | ||||
|         "selected": true, | ||||
|         "count": 1, | ||||
|         "liveCount": 1 | ||||
|     } | ||||
| ] | ||||
|     export let cases; | ||||
| 
 | ||||
|     function handleButtonClick() { | ||||
|     selectAllFilters(); | ||||
|     /*contextData.unselectAll(); | ||||
|     $highlightPolarization = false; | ||||
|     $highlightCib = false; | ||||
|     if ($originalTimeDomain) { | ||||
|       $timeScale.domain($originalTimeDomain); | ||||
|       $timeScale = $timeScale; | ||||
|       $originalTimeDomain = null; | ||||
|     }*/ | ||||
|   } | ||||
|   $: console.log($platformFilter) | ||||
| 
 | ||||
|   function addCount(filter, property, cases) { | ||||
|     return filter.map((d) => ({ | ||||
|       ...d, | ||||
|       count: cases.map((d) => d[property]).flat().filter((a) => a === d.id).length, | ||||
|       liveCount: cases.filter((d) => d.show).map((d) => d[property]).flat().filter((a) => a === d.id).length | ||||
|     })); | ||||
|   } | ||||
| 
 | ||||
|   $: console.log(addCount($platformFilter, 'platform', cases)) | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <Dropdown items={items} label='Platform'></Dropdown> | ||||
| {#if cases} | ||||
| <Dropdown items={addCount($platformFilter, 'platform', cases)} | ||||
|                 label="Platform" | ||||
|                 on:itemsAdded={(e) => platformFilter.select(e.detail)} | ||||
|                 on:itemsRemoved={(e) => platformFilter.unselect(e.detail)}> | ||||
| </Dropdown> | ||||
| <!--Dropdown items={items} label='Platform'></Dropdown--> | ||||
| {/if} | ||||
| @ -12,6 +12,8 @@ | ||||
|   export let hideOneHitWonders = false; | ||||
|   export let superior = false; | ||||
| 
 | ||||
|   //$: console.log(items) | ||||
| 
 | ||||
|   const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
|   let elem; | ||||
|  | ||||
| @ -38,6 +38,7 @@ | ||||
| 					<text x={xScale(tick)} y={32} text-anchor={'middle'}>{utcFormat('%b %d')(tick)}</text> | ||||
| 				{/each} | ||||
| 				{#each cases as attrCase} | ||||
| 				{#if attrCase.show} | ||||
| 					<a href={'#case-' + attrCase.attribution_id}> | ||||
| 						<circle | ||||
| 							cx={xScale(new Date(attrCase.attribution_date))} | ||||
| @ -48,6 +49,7 @@ | ||||
| 							stroke-width={2} | ||||
| 						></circle> | ||||
| 					</a> | ||||
| 				{/if} | ||||
| 				{/each} | ||||
| 			</g> | ||||
| 		{/if} | ||||
|  | ||||
| @ -18,4 +18,17 @@ export const sortConsistently = (itemA, itemB, property, key) => { | ||||
|         : 0; | ||||
|   } | ||||
|   return r; | ||||
| }; | ||||
| }; | ||||
| 
 | ||||
| // split string into array
 | ||||
| export const splitString = (s) => { | ||||
|   if (s === '' || s === ',') return ['unspecified']; | ||||
|   return(s | ||||
|           .split(';') | ||||
|           .map((d) => d.trim()) | ||||
|           .filter((d) => d !== '')); | ||||
| }; | ||||
| 
 | ||||
| // check if there's overlap between array and filter
 | ||||
| export const haveOverlap = (filter, arr) => | ||||
|   filter.filter((d) => d.selected).map((d) => d.id).some((item) => arr.includes(item)); | ||||
| @ -8,6 +8,11 @@ | ||||
| 	import Timeline from '$lib/components/Timeline.svelte'; | ||||
| 	//import Select from 'svelte-select'; | ||||
|     import Controls from '$lib/components/Controls.svelte'; | ||||
|     import { splitString, haveOverlap } from '$lib/utils/misc' | ||||
| 
 | ||||
|     import { | ||||
|         platformFilter, | ||||
|     } from '../stores/filters'; | ||||
| 
 | ||||
| 	let cases = []; | ||||
| 
 | ||||
| @ -15,16 +20,20 @@ | ||||
| 		//const response = await csv(`https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/Demo_Attribution_Data.csv`); | ||||
| 		const response = await csv(`${base}/Demo_Attribution_Data.csv`); | ||||
| 		cases = response; | ||||
|         cases.forEach(d => { | ||||
|             d.platform = splitString(d.platform) | ||||
|             d.show = false | ||||
|         }) | ||||
| 
 | ||||
|         platformFilter.init(cases, 'platform'); | ||||
| 	}); | ||||
| 
 | ||||
| 	$: actorNations = | ||||
| 		cases.length > 0 ? [...new Set(cases.map((d) => d.actor_nation.split(', ')).flat())] : []; | ||||
| 	$: actorNationsUnique = [...new Set(actorNations)]; | ||||
| 	let selectedActorNations = null; | ||||
| 
 | ||||
| 	$: filteredCases = selectedActorNations | ||||
| 		? cases.filter((d) => d.actor_nation.includes(selectedActorNations[0].value)) | ||||
| 		: cases; | ||||
|     $: if (cases) { | ||||
|         cases = cases.map(d => ({ | ||||
|             ...d, | ||||
|             show: haveOverlap($platformFilter, d.platform) | ||||
|         })) | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <section class="section"> | ||||
| @ -39,30 +48,24 @@ | ||||
| </section> | ||||
| 
 | ||||
| <section class="section"> | ||||
|     <Controls></Controls> | ||||
| 	<!--div class="select-container"> | ||||
| 		<Select | ||||
| 			items={actorNationsUnique} | ||||
| 			multiple={true} | ||||
| 			bind:value={selectedActorNations} | ||||
| 			placeholder={'Select 1 or more actor countries'} | ||||
| 		></Select> | ||||
| 	</div--> | ||||
|     <Controls {cases}></Controls> | ||||
| </section> | ||||
| 
 | ||||
| <section class="section"> | ||||
| 	<div> | ||||
| 		<Timeline cases={filteredCases}></Timeline> | ||||
| 		<Timeline {cases}></Timeline> | ||||
| 	</div> | ||||
| </section> | ||||
| 
 | ||||
| <section class="section"> | ||||
| 	<div class="container"> | ||||
| 		<div class="grid is-col-min-12"> | ||||
| 			{#each filteredCases as attrCase} | ||||
| 			{#each cases as attrCase} | ||||
|             {#if attrCase.show} | ||||
| 				<div class="cell"> | ||||
| 					<CaseCard cardData={attrCase}></CaseCard> | ||||
| 				</div> | ||||
|                 {/if} | ||||
| 			{/each} | ||||
| 		</div> | ||||
| 	</div> | ||||
| @ -70,7 +73,7 @@ | ||||
| 
 | ||||
| <section class="section"> | ||||
| 	<div class="container"> | ||||
| 		<CaseTable cases={filteredCases}></CaseTable> | ||||
| 		<CaseTable {cases}></CaseTable> | ||||
| 	</div> | ||||
| </section> | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										106
									
								
								src/stores/filters.js
									
									
									
									
									
										Обычный файл
									
								
							
							
						
						
									
										106
									
								
								src/stores/filters.js
									
									
									
									
									
										Обычный файл
									
								
							| @ -0,0 +1,106 @@ | ||||
| import { writable } from 'svelte/store'; | ||||
| //import { uniq } from 'lodash';
 | ||||
| 
 | ||||
| function createRangeFilter() { | ||||
|   const { subscribe, set, update } = writable([0, 0]); | ||||
| 
 | ||||
|   return { | ||||
|     subscribe, | ||||
|     set, | ||||
|     setMin: (value) => update((f) => f[0] = value), | ||||
|     setMax: (value) => update((f) => f[1] = value) | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function createInclusiveFilter() { | ||||
|   const { subscribe, set, update } = writable([]); | ||||
| 
 | ||||
|   const select = (id) => update((f) => f.map((d) => ({...d, selected: [id].flat().includes(d.id) ? true : d.selected}))); | ||||
|   const unselectAll = () => update((f) => f.map((d) => ({...d, selected: false}))); | ||||
| 
 | ||||
|   const applyBoolArray = (arr) => { | ||||
|     const tmpArr = [...arr].reverse(); | ||||
|     update((f) => f.reverse().map((d, i) => ({...d, selected: tmpArr[i] !== undefined ? tmpArr[i] : false})).reverse()); | ||||
|   }; | ||||
| 
 | ||||
|   return { | ||||
|     subscribe, | ||||
|     set: (value) => set(value), | ||||
|     //init: (values, id) => set(uniq(values.map((d) => d[id]).flat()).map((id) => ({id, name: id, selected: true}))),
 | ||||
|     init: (values, id) => set([...new Set(values.map((d) => d[id]).flat())].map((id) => ({id, name: id, selected: true}))), | ||||
|     select, | ||||
|     selectOne: (id) => { | ||||
|       unselectAll(); | ||||
|       select(id) | ||||
|     }, | ||||
|     selectAll: () => update((f) => f.map((d) => ({...d, selected: true}))), | ||||
|     unselect: (id) => update((f) => f.map((d) => ({...d, selected: [id].flat().includes(d.id) ? false : d.selected}))), | ||||
|     unselectAll, | ||||
|     applyBoolArray | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function createTextSearchFilter() { | ||||
|   const { subscribe, set } = writable(''); | ||||
| 
 | ||||
|   return { | ||||
|     subscribe, | ||||
|     set, | ||||
|     reset: () => set('') | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export const disinformantNationFilter = createInclusiveFilter(); | ||||
| 
 | ||||
| export const platformFilter = createInclusiveFilter(); | ||||
| 
 | ||||
| export const methodFilter = createInclusiveFilter(); | ||||
| 
 | ||||
| export const sourceFilter = createInclusiveFilter(); | ||||
| 
 | ||||
| export const sourceCategoryFilter = createInclusiveFilter(); | ||||
| 
 | ||||
| export const tagFilter = createInclusiveFilter(); | ||||
| 
 | ||||
| export const attributionScoreFilter = createRangeFilter(); | ||||
| export const attributionScoreDef = [0, 18]; | ||||
| 
 | ||||
| export const polarizationFilter = createRangeFilter(); | ||||
| export const polarizationDef = [-2, 2]; | ||||
| 
 | ||||
| export const unselectAllFilters = (disinformantNation = true) => { | ||||
|   if (disinformantNation) disinformantNationFilter.unselectAll(); | ||||
|   platformFilter.unselectAll(); | ||||
|   methodFilter.unselectAll(); | ||||
|   sourceFilter.unselectAll(); | ||||
|   sourceCategoryFilter.unselectAll(); | ||||
|   tagFilter.unselectAll(); | ||||
|   attributionScoreFilter.set(attributionScoreDef); | ||||
|   polarizationFilter.set(polarizationDef); | ||||
| }; | ||||
| 
 | ||||
| export const selectAllFilters = (disinformantNation = true) => { | ||||
|   if (disinformantNation) disinformantNationFilter.selectAll(); | ||||
|   platformFilter.selectAll(); | ||||
|   methodFilter.selectAll(); | ||||
|   sourceFilter.selectAll(); | ||||
|   sourceCategoryFilter.selectAll(); | ||||
|   tagFilter.selectAll(); | ||||
|   attributionScoreFilter.set(attributionScoreDef); | ||||
|   polarizationFilter.set(polarizationDef); | ||||
|   textSearchFilter.reset(); | ||||
|   caseIdFilter.set(undefined); | ||||
| }; | ||||
| 
 | ||||
| export const textSearchFilter = createTextSearchFilter(); | ||||
| 
 | ||||
| export const contextData = createInclusiveFilter(); | ||||
| 
 | ||||
| export const brushed = writable(false); | ||||
| export const originalTimeDomain = writable(null); | ||||
| 
 | ||||
| export const caseIdFilter = writable(); | ||||
| 
 | ||||
| export const highlightPolarization = writable(false); | ||||
| 
 | ||||
| export const highlightCib = writable(false); | ||||
		Загрузка…
	
	
			
			x
			
			
		
	
		Ссылка в новой задаче
	
	Block a user
	 Maarten
						Maarten