Merge branch '2-define-indexes-and-metrics-to-be-displayed' into 'master'

Resolve "Define indexes and metrics to be displayed"

Closes #2

See merge request usi-si-teaching/msde/2022-2023/visual-analytics-atelier/group-projects/group-1!9
This commit is contained in:
Claudio Maggioni 2023-05-19 17:53:32 +00:00
commit ff56370b38
5 changed files with 104 additions and 16 deletions

View file

@ -2,7 +2,10 @@ import os
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from scraper.top100_extractor import programming_crime_list from scraper.top100_extractor import programming_crime_list
COMPANIES_CSV_PATH: str = 'scraper/companies.csv' COMPANIES_CSV_PATH: str = 'scraper/companies.csv'
COMPANY_DATA_CSV_PATH: str = 'Elaborated_Data/normalized_data.csv'
def non_nan(a: list[any]) -> list[any]: def non_nan(a: list[any]) -> list[any]:
return list(filter(lambda a: type(a) == str or not np.isnan(a), a)) return list(filter(lambda a: type(a) == str or not np.isnan(a), a))
@ -21,4 +24,13 @@ def get_companies(root_dir: str) -> list[dict]:
del df['tag 1'] del df['tag 1']
del df['tag 2'] del df['tag 2']
del df['tag 3'] del df['tag 3']
# Include company metrics
df_data = pd.read_csv(os.path.join(root_dir, COMPANY_DATA_CSV_PATH), index_col='Ticker') \
.loc[:, ['Valuation', 'Financial Health', 'Estimated Growth', 'Past Performance']]
# Compute limits of metrics
# print(df_data.agg([min, max]).to_dict('records'))
df = df.join(df_data)
return df.reset_index().replace({ np.nan: None }).to_dict('records') return df.reset_index().replace({ np.nan: None }).to_dict('records')

View file

@ -14,6 +14,7 @@
"debounce": "^1.2.1", "debounce": "^1.2.1",
"fuse.js": "^6.6.2", "fuse.js": "^6.6.2",
"roboto-fontface": "*", "roboto-fontface": "*",
"round-to": "^6.0.0",
"vue": "^3.2.0", "vue": "^3.2.0",
"vue-router": "^4.0.0", "vue-router": "^4.0.0",
"vuetify": "^3.2.3", "vuetify": "^3.2.3",

View file

@ -13,10 +13,14 @@ export interface Company {
tags: string[]; tags: string[];
ticker: string; ticker: string;
website: string; website: string;
logoSrc: string logoSrc: string;
'Valuation': number;
'Financial Health': number;
'Estimated Growth': number;
'Past Performance': number;
} }
export const getCompanies = (): Promise<Company[]> => export const getCompanies = (): Promise<Company[]> =>
fetch(BACKEND_URL + '/companies').then(r => r.json()).then(list => list.map((e: Company) => ({ fetch(BACKEND_URL + '/companies').then(r => r.json()).then(list => list.map((e: Company) => ({
...e, ...e,
logoSrc: `${BACKEND_URL}/companies/logos/${e.ticker}` logoSrc: `${BACKEND_URL}/companies/logos/${e.ticker}`

View file

@ -39,7 +39,7 @@
<strong>{{ m.title }}</strong> <strong>{{ m.title }}</strong>
<span class="text-right">{{ m.value(company) }}{{ m.symbol ?? '' }}</span> <span class="text-right">{{ m.value(company) }}{{ m.symbol ?? '' }}</span>
</div> </div>
<v-progress-linear :color="m.color" :model-value="m.percentage(company)"></v-progress-linear> <v-progress-linear :color="m.color(company)" :model-value="m.percentage(company)"></v-progress-linear>
</div> </div>
</v-card-text> </v-card-text>
@ -88,6 +88,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { getCompanies, Company } from '@/api'; import { getCompanies, Company } from '@/api';
import { ref, reactive, computed, watch } from 'vue'; import { ref, reactive, computed, watch } from 'vue';
import { roundTo } from 'round-to';
import debounce from 'debounce'; import debounce from 'debounce';
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
@ -133,36 +134,101 @@ const setSearch = (text: string) => searchText.value = text;
watch(filteredCompanies, () => loading.value = false); watch(filteredCompanies, () => loading.value = false);
type ColorScale = { gt?: number, color: string }[];
interface Metric { interface Metric {
title: string, title: string,
color: string,
minValue: number, minValue: number,
maxValue: number, maxValue: number,
value: (c: Company) => number // in [0, 100], value: (c: Company) => number // in [0, 100],
symbol?: string symbol?: string,
decimals?: number
scale: ColorScale
} }
// COLORS from Pietro:
// Valuation: < 1 ( GREEN); > 1 (RED); = 1 (ORANGE)
// Financial Health: < 1 (GREEN); > 1 (RED); = 1 (ORANGE)
// Growth: < 0 (RED); 0 < x < 8% (ORANGE); < 8 % (GREEN)
// Past performance: -100 < x < 0 (RED); = 0 (ORANGE); 0 < x < 100 (GREEN)
const COLORS = {
good: 'success',
warning: 'warning',
bad: 'error'
};
const metricsData = reactive<Metric[]>([ const metricsData = reactive<Metric[]>([
{ {
title: 'Metric 1', title: 'Valuation',
color: 'green',
minValue: 0, minValue: 0,
maxValue: 100, maxValue: 150,
value: _ => 20 value: c => c['Valuation'],
scale: [
{ gt: 1.1, color: COLORS.bad },
{ gt: 0.9, color: COLORS.warning },
{ color: COLORS.good }
]
}, },
{ {
title: 'Length of ticker', title: 'Financial Health',
color: 'orange',
minValue: 0, minValue: 0,
maxValue: 5, maxValue: 500,
value: c => c.ticker.length, value: c => c['Financial Health'],
symbol: ' chars' scale: [
{ gt: 1.1, color: COLORS.bad },
{ gt: 0.9, color: COLORS.warning },
{ color: COLORS.good }
]
}, },
{
title: 'Estimated Growth',
minValue: 0,
maxValue: 200,
value: c => c['Estimated Growth'],
symbol: ' %',
scale: [
{ gt: 8, color: COLORS.good },
{ gt: 0, color: COLORS.warning },
{ color: COLORS.bad }
]
},
{
title: 'Past Performance',
minValue: -100,
maxValue: 200,
value: c => c['Past Performance'],
symbol: ' %',
scale: [
{ gt: 5, color: COLORS.good },
{ gt: -5, color: COLORS.warning },
{ gt: -Infinity, color: COLORS.bad }
]
}
]); ]);
const metrics = computed<(Metric & { percentage: (c: Company) => number })[]>(() => metricsData.map(e => ({ type ExtendedMetric = Metric & {
percentage: (c: Company) => number,
color: (c: Company) => string
}
const metrics = computed<ExtendedMetric[]>(() => metricsData.map(e => ({
...e, ...e,
percentage: (c: Company) => (e.value(c) - e.minValue) * 100 / (e.maxValue - e.minValue) percentage: (c: Company) => (e.value(c) - e.minValue) * 100 / (e.maxValue - e.minValue),
value: (c: Company) => roundTo(e.value(c), e.decimals ?? 2),
color: (c: Company) => {
console.log(JSON.parse(JSON.stringify(e.scale)));
const value = e.value(c);
console.log(c.ticker, value);
for (const s of e.scale) {
console.log('checking', s.gt, s.color);
if (typeof s.gt !== 'number' || value > s.gt) {
console.log(s.gt, s.color);
return s.color;
}
}
return 'blue-grey' // default value, should never be displayed
}
}))); })));
getCompanies().then(cs => { getCompanies().then(cs => {

View file

@ -1446,6 +1446,11 @@ rollup@^3.21.0:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
round-to@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/round-to/-/round-to-6.0.0.tgz#c12a8dee3c78cbc981d161ba8ff0214abd6cae53"
integrity sha512-jFvBgyRueGU0QVa7EqXZOkarkzrqEnF3VTCzATRcBkzxXJ4/+pzDf1iouqOqGsx6ZpnIIu5gvFDGnyzoX58ldQ==
run-parallel@^1.1.9: run-parallel@^1.1.9:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"