(defun modes-tree ()
(cl-loop
with result = (make-hash-table)
for maybe-mode in (apropos-internal "-mode$")
;; This doesn't really filter all non-modes, but it gets close
when (fboundp maybe-mode) do
(setf (gethash maybe-mode result)
(get maybe-mode 'derived-mode-parent))
finally (cl-return result)))
Something to get you started. This will produce a hash-map containing every symbol which looks like it is a mode as a key. And, if it is a derived, mode, then the value pointed by that symbol will be the parent mode.
But printing this is a much, much harder problem :) Graph printing is in general very non-trivial. Maybe you could export this as Dot and then use something like OGDF to print it nicely (Graphiz can also print graphs, but, quite frankly, it doesn't look very good / it's hard to get it to lay out the graph exactly the way you want).