Like you said, you aren't looking for THE optimum solution.
When a human goes about planting a bed, they have a primary plant in mind for a given area, and then support plants. So we tend to assign some form of weighting / ranking system to each type of plant, beginning with the highest ranked plant and filling in the available space.
My initial approach would be a greedy algorithm based on the weighting previously assigned to each plant type in the compatibility set. I would also make use of a randomizing element that makes sure the packing isn't 100% optimal and there is more space left available for the lower weighted plants. That randomizing element will influence the overall balance of species, and at some point you may wish to replace it as a preset or configurable variable. Either way, it is necessary to avoid a "monocrop with a few extras" scenarios.
Once I had that basic approach more or less working and verified it does as I expected. I would start focusing on making that randomizing element more sophisticated. For example, an auto-tune / sweep algorithm that runs the packing algorithm over and over, each time incrementally changing this variable until the closest result to a target blend of species representation was achieved.
Another approach entirely:
Each plant species has an optimal growing density that can be represented by a grid. Starting with the largest grid spacing from the set of grids that are selected based on the plant species and working to the smallest, find the rotation angle of each grid such that when overlaid on the grow bed, the number of bisecting lines is maximized.
This provides the maximum number of each type of plant that can fit in the bed and is pretty quick using some matrix arithmetic.
Now start stacking each grid as a layer on top of the base garden bed shape and since we're really only interested in where the lines bisect, we can represent them as a set of points. As you do so, you will need to make a decision about what to do when a point gets closer to another previously laid down point by a previously determined threshold. Answering the question "do I remove the existing point, or do I not place this point" is where the real complexity lies, but hopefully this is enough info to communicate the concept.