David Pomerenke commited on
Commit
7cdde63
·
1 Parent(s): 92b2164

Plot carousel

Browse files
frontend/src/App.js CHANGED
@@ -6,6 +6,8 @@ import LanguageTable from './components/LanguageTable'
6
  import DatasetTable from './components/DatasetTable'
7
  import WorldMap from './components/WorldMap'
8
  import AutoComplete from './components/AutoComplete'
 
 
9
 
10
  function App () {
11
  const [data, setData] = useState(null)
@@ -33,9 +35,30 @@ function App () {
33
 
34
  useEffect(() => {
35
  if (data) {
36
- const models = data.model_table.map(item => ({ type: 'Model', value: item.model, detail: item.provider, searchText: item.provider.toLowerCase() + ' ' + item.model.toLowerCase() }))
37
- const languages = data.language_table.map(item => ({ type: 'Language', value: item.autonym, detail: item.language_name, searchText: item.language_name.toLowerCase() + ' ' + item.autonym.toLowerCase() }))
38
- const datasets = data.dataset_table.map(item => ({ type: 'Dataset', value: item.name, detail: item.tasks, searchText: item.author.toLowerCase() + ' ' + item.name.toLowerCase() + ' ' + item.tasks.map(task => task.toLowerCase()).join(' ') }))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  const allSuggestions = [...models, ...languages, ...datasets]
40
  setAllSuggestions(allSuggestions)
41
  }
@@ -43,89 +66,103 @@ function App () {
43
 
44
  return (
45
  <PrimeReactProvider>
46
- <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
47
- <header
48
- style={{
49
- display: 'flex',
50
- flexDirection: 'column',
51
- alignItems: 'center',
52
- justifyContent: 'center',
53
- padding: '5vh 0'
54
- }}
55
- >
56
- <div>
57
- <span
58
- role='img'
59
- aria-label='Globe Emoji'
60
- style={{ fontSize: '70px' }}
61
- >
62
- 🌍
63
- </span>
64
- </div>
65
- <h1 style={{ fontSize: '2.5rem', fontWeight: '700' }}>
66
- Global AI Language Monitor
67
- </h1>
68
- <p style={{ fontSize: '1.15rem', color: '#555', marginTop: '0' }}>
69
- Tracking language proficiency of AI models for every language
70
- </p>
71
- <AutoComplete allSuggestions={allSuggestions} onComplete={item => setFilters(prevFilters => [item])} />
72
- </header>
73
- <main
74
- style={{
75
- display: 'flex',
76
- flexDirection: 'row',
77
- flexWrap: 'wrap',
78
- gap: '2rem',
79
- alignItems: 'center',
80
- width: '100%',
81
- height: '100%',
82
- justifyContent: 'center',
83
- paddingBottom: '5vh'
84
- }}
85
- >
86
- {loading && <i className='pi pi-spinner pi-spin' style={{ fontSize: '4rem' }} />}
87
- {error && <p>Error: {error}</p>}
88
- {data && (
89
- <>
90
- <div
91
- id='figure'
92
- style={{
93
- flex: '100vw 100vw 100vw',
94
- maxWidth: 'min(100vw, 800px)',
95
- alignItems: 'center',
96
- justifyContent: 'center',
97
- }}
98
  >
99
- <WorldMap data={data} />
100
- </div>
101
- <div
102
- style={{
103
- flex: '60vw 100vw 40vw',
104
- maxWidth: 'min(100vw, 800px)',
105
- }}
106
- >
107
- <ModelTable data={data} />
108
- </div>
109
- <div
110
- style={{
111
- flex: '60vw 100vw 40vw',
112
- maxWidth: 'min(100vw, 800px)'
113
- }}
114
- >
115
- <LanguageTable data={data} />
116
- </div>
117
- <div
118
- style={{
119
- flex: '60vw 100vw 40vw',
120
- maxWidth: 'min(100vw, 800px)'
121
- }}
122
- >
123
- <DatasetTable data={data} />
124
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  </>
126
- )}
127
- </main>
128
- </div>
129
  </PrimeReactProvider>
130
  )
131
  }
 
6
  import DatasetTable from './components/DatasetTable'
7
  import WorldMap from './components/WorldMap'
8
  import AutoComplete from './components/AutoComplete'
9
+ import LanguagePlot from './components/LanguagePlot'
10
+ import { Carousel } from 'primereact/carousel'
11
 
12
  function App () {
13
  const [data, setData] = useState(null)
 
35
 
36
  useEffect(() => {
37
  if (data) {
38
+ const models = data.model_table.map(item => ({
39
+ type: 'Model',
40
+ value: item.model,
41
+ detail: item.provider,
42
+ searchText: item.provider.toLowerCase() + ' ' + item.model.toLowerCase()
43
+ }))
44
+ const languages = data.language_table.map(item => ({
45
+ type: 'Language',
46
+ value: item.autonym,
47
+ detail: item.language_name,
48
+ searchText:
49
+ item.language_name.toLowerCase() + ' ' + item.autonym.toLowerCase()
50
+ }))
51
+ const datasets = data.dataset_table.map(item => ({
52
+ type: 'Dataset',
53
+ value: item.name,
54
+ detail: item.tasks,
55
+ searchText:
56
+ item.author.toLowerCase() +
57
+ ' ' +
58
+ item.name.toLowerCase() +
59
+ ' ' +
60
+ item.tasks.map(task => task.toLowerCase()).join(' ')
61
+ }))
62
  const allSuggestions = [...models, ...languages, ...datasets]
63
  setAllSuggestions(allSuggestions)
64
  }
 
66
 
67
  return (
68
  <PrimeReactProvider>
69
+ <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
70
+ <header
71
+ style={{
72
+ display: 'flex',
73
+ flexDirection: 'column',
74
+ alignItems: 'center',
75
+ justifyContent: 'center',
76
+ padding: '5vh 0'
77
+ }}
78
+ >
79
+ <div>
80
+ <span
81
+ role='img'
82
+ aria-label='Globe Emoji'
83
+ style={{ fontSize: '70px' }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  >
85
+ 🌍
86
+ </span>
87
+ </div>
88
+ <h1 style={{ fontSize: '2.5rem', fontWeight: '700' }}>
89
+ Global AI Language Monitor
90
+ </h1>
91
+ <p style={{ fontSize: '1.15rem', color: '#555', marginTop: '0' }}>
92
+ Tracking language proficiency of AI models for every language
93
+ </p>
94
+ <AutoComplete
95
+ allSuggestions={allSuggestions}
96
+ onComplete={item => setFilters(prevFilters => [item])}
97
+ />
98
+ </header>
99
+ <main
100
+ style={{
101
+ display: 'flex',
102
+ flexDirection: 'row',
103
+ flexWrap: 'wrap',
104
+ gap: '2rem',
105
+ alignItems: 'center',
106
+ width: '100%',
107
+ height: '100%',
108
+ justifyContent: 'center',
109
+ paddingBottom: '5vh'
110
+ }}
111
+ >
112
+ {loading && (
113
+ <i className='pi pi-spinner pi-spin' style={{ fontSize: '4rem' }} />
114
+ )}
115
+ {error && <p>Error: {error}</p>}
116
+ {data && (
117
+ <>
118
+ <div
119
+ style={{
120
+ flex: '60vw 100vw 40vw',
121
+ maxWidth: 'min(100vw, 800px)'
122
+ }}
123
+ >
124
+ <ModelTable data={data} />
125
+ </div>
126
+ <div
127
+ style={{
128
+ flex: '60vw 100vw 40vw',
129
+ maxWidth: 'min(100vw, 800px)'
130
+ }}
131
+ >
132
+ <LanguageTable data={data} />
133
+ </div>
134
+ <div
135
+ style={{
136
+ flex: '60vw 100vw 40vw',
137
+ maxWidth: 'min(100vw, 800px)'
138
+ }}
139
+ >
140
+ <DatasetTable data={data} />
141
+ </div>
142
+ <div
143
+ id='figure'
144
+ style={{
145
+ flex: '100vw 100vw 100vw',
146
+ maxWidth: 'min(100vw, 800px)',
147
+ alignItems: 'center',
148
+ justifyContent: 'center'
149
+ }}
150
+ >
151
+ <Carousel
152
+ value={[
153
+ <WorldMap data={data} />,
154
+ <LanguagePlot data={data} />
155
+ ]}
156
+ numScroll={1}
157
+ numVisible={1}
158
+ itemTemplate={item => item}
159
+ circular
160
+ />
161
+ </div>
162
  </>
163
+ )}
164
+ </main>
165
+ </div>
166
  </PrimeReactProvider>
167
  )
168
  }
frontend/src/components/LanguagePlot.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef, useEffect } from 'react'
2
+ import * as Plot from '@observablehq/plot'
3
+
4
+ const LanguagePlot = ({ data }) => {
5
+ const containerRef = useRef()
6
+ const languages = data.language_table.filter (a => a.average > 0)
7
+ const families = [...new Set(languages.map(a => a.family))]
8
+
9
+ useEffect(() => {
10
+ const plot = Plot.plot({
11
+ width: 750,
12
+ height: 500,
13
+ // title: 'Proficiency of Languages by Number of Speakers',
14
+ x: {
15
+ label: 'Number of Speakers',
16
+ type: 'log'
17
+ },
18
+ y: {
19
+ label: 'Language Proficiency Score',
20
+ },
21
+ marks: [
22
+ Plot.dot(languages, {
23
+ x: 'speakers',
24
+ y: d => d.average,
25
+ r: "speakers",
26
+ fill: 'family',
27
+ fillOpacity: 0.5,
28
+ title: d => d.language_name,
29
+ tip: true
30
+ }),
31
+ ],
32
+ })
33
+ containerRef.current.append(plot)
34
+ return () => plot.remove()
35
+ }, [])
36
+
37
+ return (
38
+ <div
39
+ ref={containerRef}
40
+ style={{
41
+ width: '100%',
42
+ height: '100%',
43
+ display: 'flex',
44
+ alignItems: 'center',
45
+ justifyContent: 'center',
46
+ }}
47
+ />
48
+ )
49
+ }
50
+
51
+ export default LanguagePlot
frontend/src/components/WorldMap.js CHANGED
@@ -1,15 +1,29 @@
1
  import { useRef, useEffect, useState } from 'react'
2
- import * as topojson from 'topojson-client'
3
  import * as Plot from '@observablehq/plot'
4
 
5
- function smoothProgressBar(fraction, { color = false } = {}) {
6
- const blocks = ['▏','▎','▍','▌','▋','▊','▉','█'];
7
- const width = 10;
8
- const totalUnits = width * 8;
9
- const filledUnits = Math.round(fraction * totalUnits);
10
- const fullBlocks = Math.floor(filledUnits / 8);
11
- const remainder = filledUnits % 8;
12
- return '█'.repeat(fullBlocks) + (remainder > 0 ? blocks[remainder - 1] : '')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
 
15
  const WorldMap = ({ data }) => {
@@ -25,42 +39,44 @@ const WorldMap = ({ data }) => {
25
  useEffect(() => {
26
  if (mapData === undefined) return
27
  const countries = mapData
28
- // const countries = topojson.feature(mapData, mapData.objects["world.geo"])
29
- console.log(countries)
30
- const codes = countries.features.map(d => d.properties?.ISO_A2_EH)
31
- console.log(codes.toSorted().join(', '))
32
  const plot = Plot.plot({
33
  width: 750,
34
- height: 400,
35
  projection: 'equal-earth',
36
  marks: [
37
  Plot.geo(countries, {
38
- fill: d => {
39
- const score = data.countries[d.properties?.ISO_A2_EH]?.score
40
- return score
41
- },
42
- title: d => {
43
- const languages = data.countries[d.properties?.ISO_A2_EH]?.languages.toSorted((a, b) => b.population - a.population)
44
- const pop = languages?.map(a => a.population).reduce((prev, a) => prev + a, 0)
45
- const langstring = languages?.slice(0, 10).map(a=> `${smoothProgressBar(a.population / pop)} ${a.name}`).join('\n\n') + (languages?.length > 10 ? `\n\n...` : '')
46
- return `${d.properties.ADMIN}\n\n${langstring}`
47
- },
48
  tip: true
49
  })
50
  ],
51
  color: {
52
- scheme: 'Blues',
53
  unknown: 'gray',
54
  label: 'Score',
55
  legend: true,
56
  domain: [0, 0.5]
 
 
 
57
  }
58
  })
59
  containerRef.current.append(plot)
60
  return () => plot.remove()
61
  }, [mapData])
62
 
63
- return <div ref={containerRef} style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }} />
 
 
 
 
 
 
 
 
 
 
 
64
  }
65
 
66
  export default WorldMap
 
1
  import { useRef, useEffect, useState } from 'react'
 
2
  import * as Plot from '@observablehq/plot'
3
 
4
+ const smoothProgressBar = fraction => {
5
+ const blocks = ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '█']
6
+ const width = 10
7
+ const totalUnits = width * 8
8
+ const filledUnits = Math.round(fraction * totalUnits)
9
+ const fullBlocks = Math.floor(filledUnits / 8)
10
+ const remainder = filledUnits % 8
11
+ return ('█'.repeat(fullBlocks) + (remainder > 0 ? blocks[remainder - 1] : '')) || '▏'
12
+ }
13
+
14
+ const makeTitle = data => d => {
15
+ const languages = data.countries[
16
+ d.properties?.ISO_A2_EH
17
+ ]?.languages.toSorted((a, b) => b.population - a.population)
18
+ const pop = languages
19
+ ?.map(a => a.population)
20
+ .reduce((prev, a) => prev + a, 0)
21
+ const langstring =
22
+ languages
23
+ ?.slice(0, 10)
24
+ .map(a => `${smoothProgressBar(a.population / pop)} ${a.name}`)
25
+ .join('\n\n') + (languages?.length > 10 ? `\n\n...` : '')
26
+ return `${d.properties.ADMIN}\n\n${langstring}`
27
  }
28
 
29
  const WorldMap = ({ data }) => {
 
39
  useEffect(() => {
40
  if (mapData === undefined) return
41
  const countries = mapData
 
 
 
 
42
  const plot = Plot.plot({
43
  width: 750,
44
+ height: 500,
45
  projection: 'equal-earth',
46
  marks: [
47
  Plot.geo(countries, {
48
+ fill: d => data.countries[d.properties?.ISO_A2_EH]?.score,
49
+ title: makeTitle(data),
 
 
 
 
 
 
 
 
50
  tip: true
51
  })
52
  ],
53
  color: {
54
+ scheme: 'Greens',
55
  unknown: 'gray',
56
  label: 'Score',
57
  legend: true,
58
  domain: [0, 0.5]
59
+ },
60
+ style: {
61
+ fontFamily: 'monospace'
62
  }
63
  })
64
  containerRef.current.append(plot)
65
  return () => plot.remove()
66
  }, [mapData])
67
 
68
+ return (
69
+ <div
70
+ ref={containerRef}
71
+ style={{
72
+ width: '100%',
73
+ height: '100%',
74
+ display: 'flex',
75
+ alignItems: 'center',
76
+ justifyContent: 'center',
77
+ }}
78
+ />
79
+ )
80
  }
81
 
82
  export default WorldMap
frontend/src/index.css CHANGED
@@ -74,3 +74,10 @@ html, body, #root {
74
  border: 1px solid rgba(0, 0, 0, 0.7);
75
  box-shadow: 3px 3px 1px 0px rgba(0, 0, 0, 0.2);;
76
  }
 
 
 
 
 
 
 
 
74
  border: 1px solid rgba(0, 0, 0, 0.7);
75
  box-shadow: 3px 3px 1px 0px rgba(0, 0, 0, 0.2);;
76
  }
77
+
78
+ .p-carousel-indicators button.p-link {
79
+ border-radius: 5px;
80
+ width: 10px;
81
+ height: 10px;
82
+ background-color: #000000;
83
+ }