This yells out "Polymorphic" to me, pretty much the default use case ;)
Tag Model:
belongs_to :taggable, :polymorphic => true
belongs_to :project
Project Model:
has_many :tags
has_many :datasets, :through => :tags, :source => :taggable, :source_type => 'Dataset'
has_many :graphs, :through => :tags, :source => :taggable, :source_type => 'Graph'
Graph Model:
belongs_to :dataset
has_many :tags, :dependent => :destroy
has_many :projects, :as => :taggable
Dataset Model:
has_many :graphs
has_many :tags, :dependent => :destroy
has_many :projects, :as => :taggable
If you do NOT want to go with a polymorphic model, your approach seems correct.