File size: 3,293 Bytes
dd079af
 
 
 
 
 
c7b04d0
dd079af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5208b65
 
 
 
 
 
 
 
 
 
dd079af
 
 
 
 
 
 
 
 
 
 
 
 
 
5208b65
 
 
 
 
dd079af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
export const DASH = '—';

export function fmtInt(n: number | null | undefined): string {
  return n == null ? DASH : n.toLocaleString('en-US');
}

/** 113581394 → "113.6M", 24673 → "24.7K" */
export function fmtCompact(n: number | null | undefined, digits = 1): string {
  if (n == null) return DASH;
  const abs = Math.abs(n);
  const trim = (v: number) => v.toFixed(digits).replace(/\.0+$/, '');
  if (abs >= 1e9) return `${trim(n / 1e9)}B`;
  if (abs >= 1e6) return `${trim(n / 1e6)}M`;
  if (abs >= 1e4) return `${trim(n / 1e3)}K`;
  return n.toLocaleString('en-US');
}

export function fmtNum(n: number | null | undefined, digits = 2): string {
  return n == null || !Number.isFinite(n) ? DASH : n.toFixed(digits);
}

export function fmtSigned(n: number | null | undefined, digits = 1): string {
  if (n == null || !Number.isFinite(n)) return DASH;
  const s = Math.abs(n) >= 1000 ? n.toFixed(0) : n.toFixed(digits);
  return n > 0 ? `+${s}` : s;
}

export function fmtPct(n: number | null | undefined, digits = 0): string {
  return n == null ? DASH : `${(n * 100).toFixed(digits)}%`;
}

export function fmtShare(part: number, total: number, digits = 1): string {
  if (!total) return DASH;
  return `${((part / total) * 100).toFixed(digits)}%`;
}

export function chainLabel(pdb: string, chain: string): string {
  return `${pdb.toUpperCase()}.${chain}`;
}

export function chainPath(pdb: string, chain: string): string {
  return `/chain/${pdb.toLowerCase()}/${encodeURIComponent(chain)}`;
}

export function sequencePath(id: string): string {
  return `/sequence/${id}`;
}

const METHOD_SHORT: Record<string, string> = {
  'X-RAY DIFFRACTION': 'X-ray',
  // PDB's 'ELECTRON MICROSCOPY' also covers negative-stain maps; the specimen
  // labels below (pipeline step 13) say which, when known
  'ELECTRON MICROSCOPY': 'EM',
  'cryo-EM': 'Cryo-EM',
  'negative-stain EM': 'Neg-stain EM',
  EM: 'EM',
  'ELECTRON CRYSTALLOGRAPHY': 'Electron cryst.',
  MicroED: 'MicroED',
  '2D electron crystallography': '2D electron cryst.',
  'electron crystallography': 'Electron cryst.',
  'SOLUTION NMR': 'NMR',
  'SOLID-STATE NMR': 'ssNMR',
  'NEUTRON DIFFRACTION': 'Neutron',
  'SOLUTION SCATTERING': 'SAXS',
  'POWDER DIFFRACTION': 'Powder',
  'FIBER DIFFRACTION': 'Fiber',
  'INFRARED SPECTROSCOPY': 'IR',
};

export function methodShort(m: string | null | undefined): string {
  if (!m) return DASH;
  return METHOD_SHORT[m] ?? titleCase(m);
}

/** The method to show for an observation: its EM specimen label when it has one. */
export function methodOf(x: { experimental_method?: string | null; em_label?: string | null }): string | null {
  return x.em_label ?? x.experimental_method ?? null;
}

export function titleCase(s: string): string {
  return s.toLowerCase().replace(/\b\w/g, (c) => c.toUpperCase());
}

export function capitalize(s: string | null | undefined): string {
  if (!s) return DASH;
  return s.charAt(0).toUpperCase() + s.slice(1);
}

export function year(date: string | null | undefined): string {
  return date ? date.slice(0, 4) : DASH;
}

/** Remove trailing evidence codes like "{ECO:0000256|ARBA...}" from function text. */
export function cleanFunction(text: string): string {
  return text.replace(/\s*\{ECO:[^}]*\}\.?/g, '').replace(/\s+/g, ' ').trim();
}