Wired filter
Этот коммит содержится в:
		
							родитель
							
								
									c9a418f0b7
								
							
						
					
					
						Коммит
						64e741ce69
					
				| @ -1,100 +1,41 @@ | |||||||
| <script> | <script> | ||||||
|     import Dropdown from '$lib/components/Dropdown.svelte' |     import Dropdown from '$lib/components/Dropdown.svelte' | ||||||
|  |     import { | ||||||
|  |         platformFilter, | ||||||
|  |     } from '../../stores/filters'; | ||||||
| 
 | 
 | ||||||
|     let items = [ |     export let cases; | ||||||
|     { | 
 | ||||||
|         "id": "Facebook", |     function handleButtonClick() { | ||||||
|         "name": "Facebook", |     selectAllFilters(); | ||||||
|         "selected": true, |     /*contextData.unselectAll(); | ||||||
|         "count": 34, |     $highlightPolarization = false; | ||||||
|         "liveCount": 34 |     $highlightCib = false; | ||||||
|     }, |     if ($originalTimeDomain) { | ||||||
|     { |       $timeScale.domain($originalTimeDomain); | ||||||
|         "id": "Instagram", |       $timeScale = $timeScale; | ||||||
|         "name": "Instagram", |       $originalTimeDomain = null; | ||||||
|         "selected": true, |     }*/ | ||||||
|         "count": 21, |   } | ||||||
|         "liveCount": 21 |   $: console.log($platformFilter) | ||||||
|     }, | 
 | ||||||
|     { |   function addCount(filter, property, cases) { | ||||||
|         "id": "Twitter", |     return filter.map((d) => ({ | ||||||
|         "name": "Twitter", |       ...d, | ||||||
|         "selected": true, |       count: cases.map((d) => d[property]).flat().filter((a) => a === d.id).length, | ||||||
|         "count": 34, |       liveCount: cases.filter((d) => d.show).map((d) => d[property]).flat().filter((a) => a === d.id).length | ||||||
|         "liveCount": 34 |     })); | ||||||
|     }, |   } | ||||||
|     { | 
 | ||||||
|         "id": "YouTube", |   $: console.log(addCount($platformFilter, 'platform', cases)) | ||||||
|         "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 |  | ||||||
|     } |  | ||||||
| ] |  | ||||||
| 
 | 
 | ||||||
| </script> | </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 hideOneHitWonders = false; | ||||||
|   export let superior = false; |   export let superior = false; | ||||||
| 
 | 
 | ||||||
|  |   //$: console.log(items) | ||||||
|  | 
 | ||||||
|   const dispatch = createEventDispatcher(); |   const dispatch = createEventDispatcher(); | ||||||
| 
 | 
 | ||||||
|   let elem; |   let elem; | ||||||
|  | |||||||
| @ -38,6 +38,7 @@ | |||||||
| 					<text x={xScale(tick)} y={32} text-anchor={'middle'}>{utcFormat('%b %d')(tick)}</text> | 					<text x={xScale(tick)} y={32} text-anchor={'middle'}>{utcFormat('%b %d')(tick)}</text> | ||||||
| 				{/each} | 				{/each} | ||||||
| 				{#each cases as attrCase} | 				{#each cases as attrCase} | ||||||
|  | 				{#if attrCase.show} | ||||||
| 					<a href={'#case-' + attrCase.attribution_id}> | 					<a href={'#case-' + attrCase.attribution_id}> | ||||||
| 						<circle | 						<circle | ||||||
| 							cx={xScale(new Date(attrCase.attribution_date))} | 							cx={xScale(new Date(attrCase.attribution_date))} | ||||||
| @ -48,6 +49,7 @@ | |||||||
| 							stroke-width={2} | 							stroke-width={2} | ||||||
| 						></circle> | 						></circle> | ||||||
| 					</a> | 					</a> | ||||||
|  | 				{/if} | ||||||
| 				{/each} | 				{/each} | ||||||
| 			</g> | 			</g> | ||||||
| 		{/if} | 		{/if} | ||||||
|  | |||||||
| @ -19,3 +19,16 @@ export const sortConsistently = (itemA, itemB, property, key) => { | |||||||
|   } |   } | ||||||
|   return r; |   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 Timeline from '$lib/components/Timeline.svelte'; | ||||||
| 	//import Select from 'svelte-select'; | 	//import Select from 'svelte-select'; | ||||||
|     import Controls from '$lib/components/Controls.svelte'; |     import Controls from '$lib/components/Controls.svelte'; | ||||||
|  |     import { splitString, haveOverlap } from '$lib/utils/misc' | ||||||
|  | 
 | ||||||
|  |     import { | ||||||
|  |         platformFilter, | ||||||
|  |     } from '../stores/filters'; | ||||||
| 
 | 
 | ||||||
| 	let cases = []; | 	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(`https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/Demo_Attribution_Data.csv`); | ||||||
| 		const response = await csv(`${base}/Demo_Attribution_Data.csv`); | 		const response = await csv(`${base}/Demo_Attribution_Data.csv`); | ||||||
| 		cases = response; | 		cases = response; | ||||||
|  |         cases.forEach(d => { | ||||||
|  |             d.platform = splitString(d.platform) | ||||||
|  |             d.show = false | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         platformFilter.init(cases, 'platform'); | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	$: actorNations = |     $: if (cases) { | ||||||
| 		cases.length > 0 ? [...new Set(cases.map((d) => d.actor_nation.split(', ')).flat())] : []; |         cases = cases.map(d => ({ | ||||||
| 	$: actorNationsUnique = [...new Set(actorNations)]; |             ...d, | ||||||
| 	let selectedActorNations = null; |             show: haveOverlap($platformFilter, d.platform) | ||||||
| 
 |         })) | ||||||
| 	$: filteredCases = selectedActorNations |     } | ||||||
| 		? cases.filter((d) => d.actor_nation.includes(selectedActorNations[0].value)) |  | ||||||
| 		: cases; |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <section class="section"> | <section class="section"> | ||||||
| @ -39,30 +48,24 @@ | |||||||
| </section> | </section> | ||||||
| 
 | 
 | ||||||
| <section class="section"> | <section class="section"> | ||||||
|     <Controls></Controls> |     <Controls {cases}></Controls> | ||||||
| 	<!--div class="select-container"> |  | ||||||
| 		<Select |  | ||||||
| 			items={actorNationsUnique} |  | ||||||
| 			multiple={true} |  | ||||||
| 			bind:value={selectedActorNations} |  | ||||||
| 			placeholder={'Select 1 or more actor countries'} |  | ||||||
| 		></Select> |  | ||||||
| 	</div--> |  | ||||||
| </section> | </section> | ||||||
| 
 | 
 | ||||||
| <section class="section"> | <section class="section"> | ||||||
| 	<div> | 	<div> | ||||||
| 		<Timeline cases={filteredCases}></Timeline> | 		<Timeline {cases}></Timeline> | ||||||
| 	</div> | 	</div> | ||||||
| </section> | </section> | ||||||
| 
 | 
 | ||||||
| <section class="section"> | <section class="section"> | ||||||
| 	<div class="container"> | 	<div class="container"> | ||||||
| 		<div class="grid is-col-min-12"> | 		<div class="grid is-col-min-12"> | ||||||
| 			{#each filteredCases as attrCase} | 			{#each cases as attrCase} | ||||||
|  |             {#if attrCase.show} | ||||||
| 				<div class="cell"> | 				<div class="cell"> | ||||||
| 					<CaseCard cardData={attrCase}></CaseCard> | 					<CaseCard cardData={attrCase}></CaseCard> | ||||||
| 				</div> | 				</div> | ||||||
|  |                 {/if} | ||||||
| 			{/each} | 			{/each} | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| @ -70,7 +73,7 @@ | |||||||
| 
 | 
 | ||||||
| <section class="section"> | <section class="section"> | ||||||
| 	<div class="container"> | 	<div class="container"> | ||||||
| 		<CaseTable cases={filteredCases}></CaseTable> | 		<CaseTable {cases}></CaseTable> | ||||||
| 	</div> | 	</div> | ||||||
| </section> | </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