I wanted to expand upon what Anon is getting at and provide some math as I've just gone through this process for a similar 9-axis device.
First, it's good to know what the magnetic field vector means. I like this source because it provides an image showing where magnetic vectors point as you move up and down earth. It points downward on the northern hemisphere and upward in the southern hemisphere. The text is otherwise not relevant as it more or less just explains how you can approximate laitudinal position from that data - the angle of the horizontal component is all that we need to be concerned with in order to obtain a heading.
The problem with Edgar's answer is that it assumes that your reference plane is flat against earth. As soon as you add any pitch or roll, that answer won't be right. To correct, you need to project the magnetic field vector onto your horizontal plane. Luckily, that sensor also provides you with an accelerometer which provides a way to tell you which way is down due to the force of gravity as long as you're not moving around too quickly.
What I did was project the magnetic field vector onto the acceleration vector then subtracted that from the magnetic field vector to get the horizontal component vector. Then I could retrieve the vector angle on the X/Y plane just as described by others.
First, a partial definition of a vector so I'm not depending on external libraries:
class Vector3D
{
public:
Vector3D() : mArr{} {}
Vector3D(double x, double y, double z)
{
mArr[0] = x;
mArr[1] = y;
mArr[2] = z;
}
double dot(const Vector3D& rhs) const
{
double out = 0;
for (int i = 0; i < NUM_DIMENSIONS; ++i)
{
out = out + mArr[i] * rhs.mArr[i];
}
return out;
}
Vector3D operator* (double x) const
{
Vector3D out;
for (int i = 0; i < NUM_DIMENSIONS; ++i)
{
out.mArr[i] = mArr[i] * x;
}
return out;
}
Vector3D operator- (const Vector3D& x) const
{
Vector3D out;
for (int i = 0; i < NUM_DIMENSIONS; ++i)
{
out.mArr[i] = mArr[i] - x.mArr[i];
}
return out;
}
double getX() const {return mArr[0];}
double getY() const {return mArr[1];}
double getZ() const {return mArr[2];}
private:
static const int NUM_DIMENSIONS = 3;
double mArr[NUM_DIMENSIONS];
};
Vector3D operator* (double x, const Vector3D& y)
{
return (y * x);
}
Here is how the compass angle can be computed:
// mag: magnetic vector
// accel: acceleration vector (due to gravity)
double computeYaw(double mag_x, double mag_y, double mag_z, double accel_x, double accel_y, double accel_z)
{
const Vector3D vector_mag(mag_x, mag_y, mag_z);
const Vector3D vector_down(accel_x, accel_y, accel_z);
const Vector3D vector_north = vector_mag - ((vector_mag.dot(vector_down) / vector_down.dot(vector_down)) * vector_down);
return atan2(vector_north.getX(), vector_north.getY()) * 180 / M_PI;
}
EDIT: I changed the code so needed elements of a Vector are defined here so there is no confusion.
EDIT2: Here is a good resource on this subject if you need a more accurate solution. As stated in this resource, the above is only really useful if you're not moving around too much and your magnetic environment is clean.