Per fare tutto a mano hai due strade, essenzialmente:

1) Estendere la classe JPanel in modo da farle implementare Scrollable (e, di conseguenza, implementarne tutti i metodi + getPreferredSize() )

2) Tenere traccia, ad ogni add sul pannello, della posizione e dimensione del nuovo oggetto aggiunto, mantenendo le informazioni sulla dimensione minima che il pannello deve avere per poter contenere tutti gli oggetti aggiunti, quindi notificando il ViewPort del JScrollPane di questa dimensione.

La prima strada è più semplice e pulita... questo è un esempio banale di un pannello scrollabile, in cui gli oggetti possono essere posizionati in modo assoluto (il LayoutManager è nullo):

codice:
public class ScrollablePanel extends JPanel implements Scrollable {
   private Dimension minDimension;

   public ScrollablePanel() {
      this( new Dimension(0, 0) );
   }

   public ScrollablePanel(Dimension minDimension) {
      this.minDimension = minDimension;
      setLayout( null );
   }
   
   @Override
   public Dimension getPreferredSize() { return getPreferredScrollableViewportSize(); }

   @Override
   public Dimension getPreferredScrollableViewportSize() {
      Dimension ret = (minDimension != null) ? minDimension : new Dimension(0, 0);

      Component[] content = getComponents();
      for(Component c : content) {
         Point loc = c.getLocation();
         Dimension dim = c.getSize();
         if ((loc.x + dim.width) > ret.width) ret.width = loc.x + dim.width;
         if ((loc.y + dim.height) > ret.height) ret.height = loc.y + dim.height;
      }

      return ret;
   }

   @Override
   public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
       return 10;
   }
   
   @Override
   public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
       return 1;
   }
   
   @Override
   public boolean getScrollableTracksViewportHeight() { return false; }
   
   @Override
   public boolean getScrollableTracksViewportWidth() { return false; }
   
}
Ciao.