The whole discipline is about not issuing a draw call per data point.
Instancing. One geometry, one material, uploaded once, drawn many times with a per-instance matrix and colour. A hundred thousand cubes cost roughly one draw call rather than a hundred thousand. In Three.js this is InstancedMesh, and it is the single technique that makes browser data visualization possible.
Attribute-driven geometry. Above a few hundred thousand marks even instancing strains, and the data moves into buffer attributes read directly by the vertex shader. The position of a point becomes a value in a texture rather than an object in JavaScript. At that point the visualisation is a shader and the data is its input.
Picking. Making a rendered point clickable is not free, because the GPU has drawn pixels rather than objects. The usual answer is a second render pass into an off-screen buffer where each mark is drawn in a unique colour, then reading a single pixel under the cursor to identify it. It is a well-understood trick and it needs planning for, because it doubles the draw work if implemented carelessly.
Where the data lives. The interesting constraint is rarely rendering, it is transfer. A million rows of JSON is tens of megabytes and blocks the main thread while it parses. Binary formats, typed arrays, aggregation on the server, and streaming in chunks are what make the difference between a two-second load and a thirty-second freeze.