Added
This commit is contained in:
		
							
								
								
									
										353
									
								
								HomePage.md
									
									
									
									
									
								
							
							
						
						
									
										353
									
								
								HomePage.md
									
									
									
									
									
								
							@@ -319,3 +319,356 @@ dv.paragraph(md);
 | 
			
		||||
![[Wohnorte]]
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
# Stats
 | 
			
		||||
```dataviewjs
 | 
			
		||||
// === Config ===
 | 
			
		||||
const splitOnSlash = false; // true -> merge hierarchical tags (#project/obsidian -> project)
 | 
			
		||||
const topN = 50;            // show top N tags (null for all)
 | 
			
		||||
 | 
			
		||||
// === Catppuccin Mocha Palette ===
 | 
			
		||||
const catppuccin = {
 | 
			
		||||
    rosewater: '#f5e0dc',
 | 
			
		||||
    flamingo: '#f2cdcd',
 | 
			
		||||
    pink: '#f5c2e7',
 | 
			
		||||
    mauve: '#cba6f7',
 | 
			
		||||
    red: '#f38ba8',
 | 
			
		||||
    maroon: '#eba0ac',
 | 
			
		||||
    peach: '#fab387',
 | 
			
		||||
    yellow: '#f9e2af',
 | 
			
		||||
    green: '#a6e3a1',
 | 
			
		||||
    teal: '#94e2d5',
 | 
			
		||||
    sky: '#89dceb',
 | 
			
		||||
    sapphire: '#74c7ec',
 | 
			
		||||
    blue: '#89b4fa',
 | 
			
		||||
    lavender: '#b4befe',
 | 
			
		||||
    text: '#cdd6f4',
 | 
			
		||||
    subtext1: '#bac2de',
 | 
			
		||||
    base: '#1e1e2e'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const catppuccinColors = [
 | 
			
		||||
    catppuccin.rosewater,
 | 
			
		||||
    catppuccin.flamingo,
 | 
			
		||||
    catppuccin.pink,
 | 
			
		||||
    catppuccin.mauve,
 | 
			
		||||
    catppuccin.red,
 | 
			
		||||
    catppuccin.maroon,
 | 
			
		||||
    catppuccin.peach,
 | 
			
		||||
    catppuccin.yellow,
 | 
			
		||||
    catppuccin.green,
 | 
			
		||||
    catppuccin.teal,
 | 
			
		||||
    catppuccin.sky,
 | 
			
		||||
    catppuccin.sapphire,
 | 
			
		||||
    catppuccin.blue,
 | 
			
		||||
    catppuccin.lavender
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// === Helpers ===
 | 
			
		||||
function flattenTags(input, out = []) {
 | 
			
		||||
    if (!input) return out;
 | 
			
		||||
    if (Array.isArray(input)) {
 | 
			
		||||
        for (const v of input) flattenTags(v, out);
 | 
			
		||||
    } else if (typeof input === 'string') {
 | 
			
		||||
        out.push(input);
 | 
			
		||||
    } else if (typeof input === 'object') {
 | 
			
		||||
        if (typeof input.tag === 'string') out.push(input.tag);
 | 
			
		||||
        else if (typeof input.path === 'string') out.push(input.path);
 | 
			
		||||
        else {
 | 
			
		||||
            const s = String(input);
 | 
			
		||||
            if (s && s !== '[object Object]') out.push(s);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        out.push(String(input));
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Convert a string to Title Case (preserving slashes/hyphens)
 | 
			
		||||
function toTitleCase(str) {
 | 
			
		||||
    return str.replace(/[\w]+/g, w =>
 | 
			
		||||
        w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === Collect tags from all pages ===
 | 
			
		||||
const pages = dv.pages();
 | 
			
		||||
let allTags = [];
 | 
			
		||||
for (const p of pages) {
 | 
			
		||||
    if (p.file?.tags) flattenTags(p.file.tags, allTags);
 | 
			
		||||
    if (p.tags) flattenTags(p.tags, allTags);
 | 
			
		||||
    if (p.tags_list) flattenTags(p.tags_list, allTags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === Normalize and count ===
 | 
			
		||||
const counts = {};
 | 
			
		||||
for (let tag of allTags) {
 | 
			
		||||
    if (!tag) continue;
 | 
			
		||||
    tag = String(tag).replace(/^#/, '').trim().toLowerCase();
 | 
			
		||||
    if (!tag) continue;
 | 
			
		||||
    if (splitOnSlash && tag.includes('/')) tag = tag.split('/')[0];
 | 
			
		||||
    counts[tag] = (counts[tag] || 0) + 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === Handle no tags ===
 | 
			
		||||
const tagEntries = Object.entries(counts);
 | 
			
		||||
if (tagEntries.length === 0) {
 | 
			
		||||
    dv.el("p", "⚠️ No tags found in your vault.");
 | 
			
		||||
} else {
 | 
			
		||||
    // === Sort + limit ===
 | 
			
		||||
    tagEntries.sort((a, b) => b[1] - a[1]);
 | 
			
		||||
    const limited = (topN && tagEntries.length > topN)
 | 
			
		||||
        ? tagEntries.slice(0, topN)
 | 
			
		||||
        : tagEntries;
 | 
			
		||||
 | 
			
		||||
    // Convert to title case for display
 | 
			
		||||
    const labels = limited.map(e => toTitleCase(e[0].replace(/[-_]/g, ' ')));
 | 
			
		||||
    const dataValues = limited.map(e => e[1]);
 | 
			
		||||
 | 
			
		||||
    // === Color setup ===
 | 
			
		||||
    const bgColors = [];
 | 
			
		||||
    const borderColors = [];
 | 
			
		||||
    for (let i = 0; i < labels.length; i++) {
 | 
			
		||||
        const color = catppuccinColors[i % catppuccinColors.length];
 | 
			
		||||
        bgColors.push(color + "55");
 | 
			
		||||
        borderColors.push(color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // === Chart.js config ===
 | 
			
		||||
    const chartData = {
 | 
			
		||||
        type: 'bar',
 | 
			
		||||
        data: {
 | 
			
		||||
            labels: labels,
 | 
			
		||||
            datasets: [{
 | 
			
		||||
                label: 'Tag Usage',
 | 
			
		||||
                data: dataValues,
 | 
			
		||||
                backgroundColor: bgColors,
 | 
			
		||||
                borderColor: borderColors,
 | 
			
		||||
                borderWidth: 1
 | 
			
		||||
            }]
 | 
			
		||||
        },
 | 
			
		||||
        options: {
 | 
			
		||||
            plugins: {
 | 
			
		||||
                legend: { display: false },
 | 
			
		||||
                title: {
 | 
			
		||||
                    display: true,
 | 
			
		||||
                    text: `Tag Usage (Top ${labels.length})`,
 | 
			
		||||
                    color: catppuccin.text,
 | 
			
		||||
                    font: { size: 16 }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            scales: {
 | 
			
		||||
                x: {
 | 
			
		||||
                    ticks: { color: catppuccin.subtext1, autoSkip: false, maxRotation: 60, minRotation: 30 },
 | 
			
		||||
                    title: { display: true, text: 'Tags', color: catppuccin.text },
 | 
			
		||||
                    grid: { color: catppuccin.base }
 | 
			
		||||
                },
 | 
			
		||||
                y: {
 | 
			
		||||
                    ticks: { color: catppuccin.subtext1 },
 | 
			
		||||
                    title: { display: true, text: 'Count', color: catppuccin.text },
 | 
			
		||||
                    beginAtZero: true,
 | 
			
		||||
                    grid: { color: catppuccin.base }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            maintainAspectRatio: false
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // === Render ===
 | 
			
		||||
    const wrapper = this.container.createEl('div');
 | 
			
		||||
    wrapper.style.minHeight = '320px';
 | 
			
		||||
    wrapper.style.maxHeight = '60vh';
 | 
			
		||||
    wrapper.style.overflow = 'auto';
 | 
			
		||||
    wrapper.style.borderRadius = '8px';
 | 
			
		||||
    wrapper.style.padding = '8px';
 | 
			
		||||
 | 
			
		||||
    window.renderChart(chartData, wrapper);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
```dataviewjs
 | 
			
		||||
// === Config ===
 | 
			
		||||
const splitOnSlash = false; // true -> merge hierarchical tags (#project/obsidian -> project)
 | 
			
		||||
const topN = 20;            // show top N tags (null for all)
 | 
			
		||||
 | 
			
		||||
// === Catppuccin Mocha Palette ===
 | 
			
		||||
const catppuccin = {
 | 
			
		||||
    rosewater: '#f5e0dc',
 | 
			
		||||
    flamingo: '#f2cdcd',
 | 
			
		||||
    pink: '#f5c2e7',
 | 
			
		||||
    mauve: '#cba6f7',
 | 
			
		||||
    red: '#f38ba8',
 | 
			
		||||
    maroon: '#eba0ac',
 | 
			
		||||
    peach: '#fab387',
 | 
			
		||||
    yellow: '#f9e2af',
 | 
			
		||||
    green: '#a6e3a1',
 | 
			
		||||
    teal: '#94e2d5',
 | 
			
		||||
    sky: '#89dceb',
 | 
			
		||||
    sapphire: '#74c7ec',
 | 
			
		||||
    blue: '#89b4fa',
 | 
			
		||||
    lavender: '#b4befe',
 | 
			
		||||
    text: '#cdd6f4',
 | 
			
		||||
    subtext1: '#bac2de',
 | 
			
		||||
    base: '#1e1e2e'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const catppuccinColors = [
 | 
			
		||||
    catppuccin.rosewater,
 | 
			
		||||
    catppuccin.flamingo,
 | 
			
		||||
    catppuccin.pink,
 | 
			
		||||
    catppuccin.mauve,
 | 
			
		||||
    catppuccin.red,
 | 
			
		||||
    catppuccin.maroon,
 | 
			
		||||
    catppuccin.peach,
 | 
			
		||||
    catppuccin.yellow,
 | 
			
		||||
    catppuccin.green,
 | 
			
		||||
    catppuccin.teal,
 | 
			
		||||
    catppuccin.sky,
 | 
			
		||||
    catppuccin.sapphire,
 | 
			
		||||
    catppuccin.blue,
 | 
			
		||||
    catppuccin.lavender
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// === Helpers ===
 | 
			
		||||
function flattenTags(input, out = []) {
 | 
			
		||||
    if (!input) return out;
 | 
			
		||||
    if (Array.isArray(input)) {
 | 
			
		||||
        for (const v of input) flattenTags(v, out);
 | 
			
		||||
    } else if (typeof input === 'string') {
 | 
			
		||||
        out.push(input);
 | 
			
		||||
    } else if (typeof input === 'object') {
 | 
			
		||||
        if (typeof input.tag === 'string') out.push(input.tag);
 | 
			
		||||
        else if (typeof input.path === 'string') out.push(input.path);
 | 
			
		||||
        else {
 | 
			
		||||
            const s = String(input);
 | 
			
		||||
            if (s && s !== '[object Object]') out.push(s);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        out.push(String(input));
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toTitleCase(str) {
 | 
			
		||||
    return str.replace(/[\w]+/g, w =>
 | 
			
		||||
        w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === Collect tags ===
 | 
			
		||||
const pages = dv.pages();
 | 
			
		||||
let allTags = [];
 | 
			
		||||
for (const p of pages) {
 | 
			
		||||
    if (p.file?.tags) flattenTags(p.file.tags, allTags);
 | 
			
		||||
    if (p.tags) flattenTags(p.tags, allTags);
 | 
			
		||||
    if (p.tags_list) flattenTags(p.tags_list, allTags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === Normalize + count ===
 | 
			
		||||
const counts = {};
 | 
			
		||||
for (let tag of allTags) {
 | 
			
		||||
    if (!tag) continue;
 | 
			
		||||
    tag = String(tag).replace(/^#/, '').trim().toLowerCase();
 | 
			
		||||
    if (!tag) continue;
 | 
			
		||||
    if (splitOnSlash && tag.includes('/')) tag = tag.split('/')[0];
 | 
			
		||||
    counts[tag] = (counts[tag] || 0) + 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const tagEntries = Object.entries(counts);
 | 
			
		||||
if (tagEntries.length === 0) {
 | 
			
		||||
    dv.el("p", "⚠️ No tags found in your vault.");
 | 
			
		||||
} else {
 | 
			
		||||
    // === Sort + limit ===
 | 
			
		||||
    tagEntries.sort((a, b) => b[1] - a[1]);
 | 
			
		||||
    const limited = (topN && tagEntries.length > topN)
 | 
			
		||||
        ? tagEntries.slice(0, topN)
 | 
			
		||||
        : tagEntries;
 | 
			
		||||
 | 
			
		||||
    const labels = limited.map(e => toTitleCase(e[0].replace(/[-_]/g, ' ')));
 | 
			
		||||
    const dataValues = limited.map(e => e[1]);
 | 
			
		||||
    const total = dataValues.reduce((a, b) => a + b, 0);
 | 
			
		||||
 | 
			
		||||
    // === Colors ===
 | 
			
		||||
    const bgColors = [], borderColors = [];
 | 
			
		||||
    for (let i = 0; i < labels.length; i++) {
 | 
			
		||||
        const color = catppuccinColors[i % catppuccinColors.length];
 | 
			
		||||
        bgColors.push(color + "aa");
 | 
			
		||||
        borderColors.push(color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // === Create container ===
 | 
			
		||||
    const wrapper = this.container.createEl('div');
 | 
			
		||||
    wrapper.style.minHeight = '400px';
 | 
			
		||||
    wrapper.style.maxHeight = '70vh';
 | 
			
		||||
    wrapper.style.position = 'relative';
 | 
			
		||||
    wrapper.style.borderRadius = '8px';
 | 
			
		||||
    wrapper.style.padding = '8px';
 | 
			
		||||
    wrapper.style.overflow = 'auto';
 | 
			
		||||
 | 
			
		||||
    // === Center label ===
 | 
			
		||||
    const centerLabel = document.createElement('div');
 | 
			
		||||
    centerLabel.style.position = 'absolute';
 | 
			
		||||
    centerLabel.style.top = '50%';
 | 
			
		||||
    centerLabel.style.left = '44%';
 | 
			
		||||
    centerLabel.style.transform = 'translate(-50%, -50%)';
 | 
			
		||||
    centerLabel.style.color = catppuccin.text;
 | 
			
		||||
    centerLabel.style.fontSize = '14px';
 | 
			
		||||
    centerLabel.style.fontWeight = '600';
 | 
			
		||||
    centerLabel.style.textAlign = 'center';
 | 
			
		||||
    centerLabel.innerText = 'Tag Usage';
 | 
			
		||||
    wrapper.appendChild(centerLabel);
 | 
			
		||||
 | 
			
		||||
    // === Chart.js config ===
 | 
			
		||||
    const chartData = {
 | 
			
		||||
        type: 'doughnut',
 | 
			
		||||
        data: {
 | 
			
		||||
            labels: labels,
 | 
			
		||||
            datasets: [{
 | 
			
		||||
                data: dataValues,
 | 
			
		||||
                backgroundColor: bgColors,
 | 
			
		||||
                borderColor: borderColors,
 | 
			
		||||
                borderWidth: 1
 | 
			
		||||
            }]
 | 
			
		||||
        },
 | 
			
		||||
        options: {
 | 
			
		||||
            cutout: '60%',
 | 
			
		||||
            plugins: {
 | 
			
		||||
                legend: {
 | 
			
		||||
                    display: true,
 | 
			
		||||
                    position: 'right',
 | 
			
		||||
                    labels: { color: catppuccin.subtext1, font: { size: 12 } }
 | 
			
		||||
                },
 | 
			
		||||
                title: {
 | 
			
		||||
                    display: true,
 | 
			
		||||
                    text: `Tag Distribution (Top ${labels.length})`,
 | 
			
		||||
                    color: catppuccin.text,
 | 
			
		||||
                    font: { size: 16 }
 | 
			
		||||
                },
 | 
			
		||||
                tooltip: {
 | 
			
		||||
                    callbacks: {
 | 
			
		||||
                        label: ctx => {
 | 
			
		||||
                            const val = ctx.raw;
 | 
			
		||||
                            const pct = ((val / total) * 100).toFixed(1);
 | 
			
		||||
                            return `${ctx.label}: ${val} (${pct}%)`;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            onHover: (evt, elements) => {
 | 
			
		||||
                if (elements.length > 0) {
 | 
			
		||||
                    const el = elements[0];
 | 
			
		||||
                    const label = labels[el.index];
 | 
			
		||||
                    const value = dataValues[el.index];
 | 
			
		||||
                    const pct = ((value / total) * 100).toFixed(1);
 | 
			
		||||
                    centerLabel.innerText = `${label}\n${pct}%`;
 | 
			
		||||
                } else {
 | 
			
		||||
                    centerLabel.innerText = 'Tag Usage';
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            maintainAspectRatio: false
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    window.renderChart(chartData, wrapper);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user