How to count the placeholder links

What I’m trying to do

I want to have a line of text somewhere which will say:

Internal links across the vault: $m$ placeholders / $n$ total

It is a must that this line will be updated automatically. It will be elating to have such stats per-note, right at the “outgoing links” linked view.

Is there any way, doesn’t matter which one, to achieve that? I don’t care where exactly it will be rendered, in the statusbar, on the special (auto-updated) note, in the settings UI, just want these numbers to be visible.

Things I have tried

I know Dataview plugin exists but don’t know the exact capabilities and/or API so don’t know whether it’s possible to dig this info out of the vault index.

I have found the Vault Full Statistics plugin which is already pretty nice, but it shows only the total amount of links, it doesn’t show the amount of placeholder links.

Thank you beforehand.

per-note stats are just a nice-to-have afterthought, I’m wondering more about the stats for the whole vault

Exactly what are you trying to count or keep track of?

I can rephraze my question as “I want to count the gray nodes in the Graph view of the whole vault”.

So basically the count of all files having no in- or out-links in them versus the total number of files.

Try the following queries in a note of its own:

## Count of files with a given set of links

```dataview
TABLE fileCount
GROUP BY length(file.inlinks) + length(file.outlinks) as AllLinkCount
FLATTEN length(rows) as fileCount
```

## Count of file with no or some links

```dataview
TABLE sum(rows.fileCount) as "Number of files"
GROUP BY length(file.inlinks) + length(file.outlinks) as AllLinkCount
FLATTEN length(rows) as fileCount
GROUP BY choice(key = 0, "No links", "Some links") as hasNoLinks
```

Total number of files: `$= dv.pages().length `

In my test vault this gives:

Which should equate to 1262 grey nodes, and 803 no-grey nodes, where the total number of files in my test vault is 2065. I reckon you don’t really need the first query, it’s just there to hopefully help you understand what I do in the queries.

The gist is simply to keep the number of links in and out of any file, and then group together the files having that number of links. Those having 0 links will be the nodes with no links in the graph view.

( PS! This doesn’t account for tags being nodes, it’s just the file nodes)

Do I understand correctly that simply by doing [[Name of the note to be created sometime later]] I’m creating a “file” as far as Dataview plugin is concerned?

Also, I don’t really think that counting the “files which have no in-links pointing to them” is the query I need. The whole point of the “placeholder link” concept as far as I understand is that I create a reference to a note which doesn’t exist yet - the link is there but the note is not.

Ehh… I might have overthinked this one. Are you talking about unresolved file links? That is a different story, yes.

The simple count is:

Unresolved link count: `$= Object.keys(dv.app.metadataCache.unresolvedLinks).length `

This utilises an inline dataviewjs query, which you might need to turn on in the settings.

Here is a post to view some information related to unresolved links:

1 Like

dv.app.metadataCache.unresolvedLinks

superb, exactly what I hoped to exist

thank you so much

For the completeness’s sake, here is what I actually wanted to do.

The raw Object.keys(dv.app.metadataCache.unresolvedLinks).length gives a different number which is useless for me. It gives the amount of pages which contain the “unresolved links” this is not what I want. I want the length of the list of these links!

The following is the code which aggregates this number from the raw data from Dataview API, and solves my problem completely:

const existing = dv.pages().length;

const names = new Set();
Object.entries(dv.app.metadataCache.unresolvedLinks)
	.forEach(
		([page, links]) => 
			Object.entries(links).forEach(
				([name,]) => names.add(name)
			)
	)
const unresolved = names.size;

dv.paragraph(`Pages in vault: ${unresolved} unresolved / ${existing} existing / ${unresolved + existing} total`);

Which renders the following line:

Pages in vault: 969 unresolved / 391 existing / 1360 total

Which is all I wanted.

Of course we can do a lot of interesting things inside the final forEach where we have the full context. For example, we can build a Wikipedia-style list of “most wanted pages”.

do not blindly put this code into your vault it is seriously slow to render
like mere 900+ lines in the list will render for seconds and rendering it as a table hangs the UI for around a minute.

// Native JS object to accumulate the counts for each name
const counts = {};

Object.entries(dv.app.metadataCache.unresolvedLinks)
	.forEach(
		([page, links]) => 
			Object.entries(links).forEach(
				([name, amount]) => {
					// clever tricks to increase a possibly null or undefined value on a single line
					counts[name] = (counts[name] ?? 0) + amount;
				}
			)
	)

// we used a native JS object to deduplicate the names
// to render in table/list we need an array of pairs (name, count)
// `Object.entries` gives us exactly that
const countsArray = Object.entries(counts);

// two-level sort: by count decreasing, by name alphabetical
countsArray.sort(
	(left, right) => {
		const diff = right[1] - left[1];
		if (diff === 0) {
			return left[0].localeCompare(right[0]);
		} else {
			return diff;
		}
	}
);

dv.header(2, 'Most wanted pages');
// table is ultra slow so use at your own risk
// dv.table(['link', 'count'], countsArray.map(([link, count]) => [`[[${link}]]`, count])); 

dv.list(
	countsArray
		.map(
			// render names as links to be able to immediately create these pages
			// render the count only if larger than 1
			([link, count]) => `[[${link}]]` + (count > 1 ? ` (${count})` : '')
		)
)
1 Like