It looks to me like you can use a variant on a Poisson disc sampling algorithm to achieve this.
Check out Mike Bostock's wonderful visual demonstration of these algorithms, particularly the explanation of Bridson's algorithm about a quarter of the way in.
Here you store your points in a grid where the cell size is
r/sqrt(2) - that way if all your points are a distance of at least
r apart, then there is at most one point in each grid cell. This speeds up our neighbour checks.
Start with at least one point, classified as a frontier point (those are the red dots in the diagram above)
In each iteration, select a point on the frontier. Randomly generate candidate points around it, at a range of at least
r (the Poisson disc example uses a range from
2r but you can tune this). To get an "upward" growth like in your example, you might want to bias this random sample vertically.
As you generate each candidate, check to see if it's out of bounds, or if any other points are within
r of it. (Using the grid, you only need to check certain cells)
If the candidate is too close to an existing point, reject it and generate a new candidate. If you exceed a maximum number of attempts without success, give up and mark the parent point as no longer part of the frontier. (This will terminate a branch)
If the candidate has no other points within a distance of
r, add it to the grid as a new frontier point.
Here's where we'll break from the normal Poisson disc sampling algorithm: instead of leaving the parent point as part of the frontier, we can choose to immediately remove it from the frontier once it's spawned a single child point. This will produce chains of points instead of a dense cloud.
To get branching, we can occasionally allow a point to spawn 2 child points before it's removed from the frontier. (We can balance our tolerance based on the number of branches, ie. the size of the frontier set, to avoid spawning too dense a shrub)
This should get you pointed in a useful direction. Some improvements you can make from here:
make your peer repulsion distance larger than your parent-child spawning distance, to keep branches spaced apart
refine the branch termination condition - maybe if a candidate point is almost equidistant between the parent point and a point from a different branch, you place it as a non-frontier point to "merge" the two branches back together rather than rejecting it.